メディカルサークルには、ノート機能の周辺にチャット・DM・タイムライン・プッシュ通知といったリアルタイム機能が組み込まれています。
これらは「即時性」が体験そのものですが、同時に Firestore の読み取りコストに直結するため、設計の工夫が必要でした。
ここでは、その実装方針を整理します。
目次
チャット・DM は差分監視で通信コストを抑える
メッセージコレクションをチャットルーム ID で絞り込み、作成日時の昇順でリアルタイム監視するストリームを張っています。
Firestore のスナップショットリスナーは差分のみ通知するため、新着メッセージが追加されても全件再取得にはなりません。
メッセージ数が増えてもクライアント側の負荷が線形に増えない設計です。
リアルタイム性とコストのバランスを取るうえで、Firestore の差分配信は強い武器でした。
タイムラインは初期上限件数で実用十分にする
タイムラインは、Firestore のリアルタイムリスナーで上限件数(50 件)を一括取得してクライアント側で表示する構成です。
無限スクロールによる追加読み込みは現時点では未実装ですが、ユーザーの利用ログを見るかぎり、初期表示の上限件数で実用上はカバーできています。
「いつでも入れられるが、本当に必要になるまで入れない」スタンスを取り、機能の重さを増やさない判断をしました。
オープンチャットと DM を同一コレクションで管理する
オープンチャットと DM は、別のコレクションに分けずチャットルームの「種別」フィールドで切り替える構成にしました。
メッセージ取得のロジックや UI を共通化でき、新しい種別を追加するときも 1 フィールド増やすだけで済みます。
セキュリティルールでは「種別 = DM の場合は参加者のみ読み取り可」のように分岐させ、データ構造の共通性と権限制御の分離を両立しました。
Cloud Functions のトリガーで通知を発火する
プッシュ通知は、Firestore のドキュメント作成をトリガーにした Cloud Functions で実装しています。
気をつけたのは 2 点。
1 つは、自分自身の操作で自分に通知が飛ばないようにする送信者除外。
もう 1 つは、ブロック関係にあるユーザーには通知を送らないようブロックリストを参照するフィルタ処理です。
通知は「相手の生活時間に直接届く」性質があるため、ノイズを減らすことが信頼につながります。
通知が出るアクションを絞り、うるさくしない設計
「通知が多くてうるさい」を防ぐために、まとめ送信や個別 ON/OFF を作る前に、そもそも通知を出すアクション自体を絞り込みました。
タイムラインの閲覧や検索といった受動的操作では通知を飛ばさず、いいね・フレンド申請・コメントといった「相手に伝える価値があるアクション」だけを通知対象にしています。
UI で抑えるより、設計で抑える方が確実でした。
件数上限と差分配信で Firestore コストを管理する
リアルタイム機能は、Firestore の読み取り回数がそのままランニングコストに直結します。
メディカルサークルでは、すべてのクエリに必ず件数上限を設定し、必要以上のドキュメントを読まない方針を徹底しました。
差分配信と件数上限の組み合わせで、ユーザー数が伸びても比例して請求が伸びない構造を作っています。
まとめ
リアルタイム機能は「ただ動かす」だけなら Firestore のスナップショットで簡単に実現できます。
しかし、コミュニティ機能としてのうるささや、コストとしての持続性まで含めると、設計上の判断は山ほどあります。
メディカルサークルでは、件数上限・送信者除外・ブロック考慮・アクションの絞り込みを一通り組み込み、長く運用できる土台を作りました。

.webp%3Falt%3Dmedia%26token%3D6ca2c2ef-9413-4453-b992-55b66b11ed54&w=3840&q=75)


.webp%3Falt%3Dmedia%26token%3D900f385d-12a2-449b-8d1e-83a57cef0088&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D0e802fb0-2dda-44a7-bf80-5d39019635ba&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D3fb3dc66-ecca-402e-8fb8-fbec9407f7f5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Ddb21d760-e1ed-4ec2-af28-3462041e31b5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dcce7bd72-f11e-4292-86bf-e6ccf3e7bf32&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D457ff920-e0df-4ff5-95eb-e29f74b73823&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dc21fcc77-7404-458d-9eb5-85b8d84ae1bc&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D92052f12-5280-49df-877a-b514582e95db&w=3840&q=75)

.webp%3Falt%3Dmedia%26token%3Da7c14698-1b08-4fea-89c6-f77a9121f4c5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D899eeefd-f4c9-44a6-9ec2-3ced0b223ffd&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dca25fa6b-e233-43f7-90c3-e68e4c5b0bc5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D7f18e5f1-cfda-4148-ab86-b3d2e6547262&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D5f10e078-4d87-4c87-928c-21b719cbf1cb&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D957b18b6-9b01-4c94-9207-7b9fca22a787&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dd952e11d-4461-47ae-892d-622fc3f2a48a&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D532bb657-5670-49b4-9165-5f758062d8dd&w=3840&q=75)