メインコンテンツへスキップ
友田 陽大
近日公開

落ちる前提で書かれた、SQLだけのプッシュ通知パイプライン

外部のワーカー基盤を使わず、デッドレターキューも再試行も回収もPostgresに持たせる。memofu の配信基盤とプライバシー設計の記録

ペットの写真と健康記録を非公開で家族と共有するアプリ memofu

iOS / Android とも近日公開です。現在公開しているのはランディングページのみです。

なぜポートフォリオにあるか

通知が届かない、二重に届く、深夜に全員へ届く — このクラスの障害は、機能追加より高くつきます。memofu では配信基盤を外部サービスに預けず Postgres の中で組み、失敗経路を先に設計しました。

なぜ作ったか

ペットの記録アプリは市場にありますが、多くが公開フィードを前提にしています。飼い主が本当に見せたい相手は、フォロワーではなく、その子を知っている数人です。

もう一つの動機は、記録を人質にしないことでした。写真とログを預けたあと、解約したら取り出せない — という設計にはしたくなかった。全データのエクスポートを有料機能ではなく独立したサブシステムとして無料で置くところから決めました。

いちばん難しかったこと

健康ログ(体重・通院・投薬)を続けてもらうには通知が要ります。ところが通知は、外部サービスに預けると障害の切り分けができず、自前で書くと失敗経路を書き忘れる領域です。

最初の実装は、正直に言えば「成功することしかできないキュー」でした。送信に失敗したときの再試行間隔がない。処理中にワーカーが落ちたら、そのレコードは永久に処理中のまま残る。同じ種類の通知が短時間に何度も来ても、まとめる仕組みがない。

もう一つの難所は写真です。ペットの写真には撮影場所が入ります。

どう決めたか(と、捨てたもの)

配信基盤をSQLの中に閉じることにしました。外部のワーカー基盤(SQS / Lambda / Sidekiq)を1つも使わず、キュー・再試行・デッドレター・回収を Postgres のテーブルと関数として持ち、pg_cron で駆動します。

理由は、障害時に見る場所が1つになることです。「通知が届かない」と言われたとき、キューの行を見ればどこで止まっているかが分かります。

写真については、端末上でEXIFを完全に落としてからアップロードする方針にしました。撮影日時だけはメモリ上で先に読み取り、正しい記録日として保存します。

捨てたものはスループットです。専用のワーカー基盤なら出せる並列度は出ません。1日数回の静かな通知にはこれで足ります。

実際に動いているもの

キューには送信予定時刻・試行回数・次回試行時刻・確保時刻・受信確認時刻を持たせ、バックオフ曲線を単一の関数に集約しました。処理中のまま取り残されたレコードは回収関数が拾い直します。再試行を使い切ったものは dead に落ちます。

通知の種類はカタログ表として持ち、種類ごとに「ユーザーが変更できるか」「サイレント時間を突破してよいか」を持たせました。サイレント時間を突破してよいのは投薬とリマインドの2種類だけです。

配信時刻は、アプリを開いた時刻のヒストグラムを平滑化して丸めた「その人の時間帯」に寄せます。機械学習ではなく、一文で説明できて夜間のクエリ1回で済む方法を選びました。

画像は端末で長辺2048pxのJPEGに変換し、**変換後のJPEGにはEXIFが一切残りません。**GPS座標が端末の外に出ません。動画は変換せず、端末で抜いたポスターフレームを写真のサムネイルと同じ命名規則で書き込むので、アプリでもアプリ不要の閲覧ビューアでも同じ経路で表示できます。

コードから数えられる値

2026-07-28 から 07-30 までの28コミット。データベースは RLSポリシー100本を定義し、58テーブルで force row level security を有効にしています。force を付けているのが要点で、テーブル所有者であってもポリシーを迂回できません。

RLSの検証は、実際のユーザーとJWTを発行して許可・拒否を1ポリシーずつ確認するテストで行い、CIでは Supabase をローカル起動してこれを実行します。テストは91ファイル。

pg_cron のジョブは14本。プッシュの生成・配信・受信確認・孤児レコードの回収・保持期間の掃除・パーティション保守・夜間集計を回しています。

アナリティクスはサイト用のルートグループの内側にだけ読み込まれるので、実際のペット写真や有効なトークンを表示する画面(共有ビューア・招待・ペアリング)には構造的に入りません。入れ忘れではなく、入らない構造です。

これは、あなたの案件の何を証明するか

予約リマインド、請求督促、在庫アラート、シフト通知 — 通知が事業の一部になっている案件は多く、そのすべてで「落ちたときにどうなるか」が後回しにされがちです。memofu の配信基盤は、その失敗経路(再試行間隔・デッドレター・取り残された処理の回収)を先に設計した実例です。外部サービスを増やさずに済む構成なので、運用の見る場所も増えません。

RLSを100本書き、force まで付けてテナント分離を担保した経験は、B2B SaaS の設計レビューでそのまま使えます。位置情報や個人情報の取り扱いが要件に入る案件(医療・子ども・不動産・防犯)についても、端末内で落としてから送る形を実装済みです。

同じ設計は、SaaS・業界DX開発のサービスとしてお請けしています。

コードから数えられる値

RLSポリシー
100本58テーブルで force row level security を有効化。テーブル所有者であっても迂回できない
テスト
91ファイル実際のJWTを発行してポリシーの許可/拒否を検証するRLSテストを含む
pg_cron ジョブ
14本プッシュ配信・受信確認・孤児クレームの回収・保持期間・パーティション保守を駆動

よくある質問

memofu はアプリストアからダウンロードできますか?
いいえ。iOS・Android とも近日公開の段階で、現在公開されているのはランディングページのみです。
ペットの写真から撮影場所が漏れることはありますか?
ありません。memofu は画像のEXIFを端末上で完全に除去してからアップロードするため、GPS座標が端末の外に出ません。撮影日時だけはメモリ上で先に読み取り、正しい記録日として保存します。
解約したら写真とデータは取り出せますか?
取り出せます。全データのエクスポートは有料機能ではなく、独立したサブシステムとして無料で提供しています。
プッシュ通知の配信基盤は何で作られていますか?
SQLのみで実装されています。キュー・指数バックオフ・デッドレターキュー・取り残されたレコードの回収をPostgres側に持ち、14本のpg_cronジョブが駆動します。外部のワーカー基盤は使っていません。
家族ごとのデータ分離はどう保証されていますか?
100本のRLSポリシーを定義し、58テーブルで force row level security を有効にしています。force を付けているため、テーブル所有者であってもポリシーを迂回できません。検証は実際のJWTを発行して許可・拒否を確認するテストで行っています。
memofu にAI機能はありますか?
ありません。写真と健康ログを扱う設計上、AIによる解析は入れていません。

同様の課題、抱えていませんか?

あなたのビジネス課題も、最新の技術で解決できます。 まずは30分の無料技術相談から、状況をお聞かせください。

自社の課題もSaaS化できるか相談する

プロジェクト単位(請負)・技術顧問、どちらにも対応可能です

自社プロダクト一覧を見る