【ITニュース解説】Supabase realtime keeps mobile presence honest without polling your database dead
2026年09月08日に「Dev.to」が公開したITニュース「Supabase realtime keeps mobile presence honest without polling your database dead」について初心者にもわかりやすく解説しています。
ITニュース概要
Supabase Realtimeは、モバイルアプリのデータ最新化で問題となる定期問い合わせ(ポーリング)を解消する。サーバーから変更をリアルタイムにプッシュし、バッテリーやサーバー負荷を削減。ブロードキャスト、オンライン状況、DB変更の3機能で、OS切断への対応やセキュリティ設定が重要だ。
ITニュース解説
モバイルアプリでリアルタイムな情報をユーザーに届けることは、現代のアプリ開発において非常に重要だ。しかし、このリアルタイム通信を実現する方法として伝統的に使われてきた「ポーリング」という手法は、特にモバイル環境では多くの問題を引き起こす。ポーリングとは、アプリがサーバーに対し、一定の間隔で「何か新しい情報がありますか?」と繰り返し問い合わせる仕組みを指す。この問い合わせは、何も新しい情報がない場合でも行われるため、デバイスのバッテリーを無駄に消費し、データ通信量を圧迫する。また、サーバー側では、膨大な数のリクエストを処理する必要が生じ、結果としてサーバーの負荷が大幅に増加する。例えば、1万人のユーザーが5秒ごとにポーリングを行うと、1日に1億7千万回以上のリクエストが発生することもある。
さらに、モバイルOSはバッテリー節約のため、アプリがバックグラウンドに移行するとJavaScriptのタイマーを一時停止させることがある。これにより、定期的だったはずのポーリング間隔が不正確になり、アプリがフォアグラウンドに戻った際に古い情報が表示されるという問題も発生する。一方、WebSocketのような技術を用いるリアルタイム通信は、サーバーとクライアント間で一度接続を確立すれば、必要な時にサーバーからクライアントへ直接情報がプッシュされるため、上記のような問題を解決できる。Supabase Realtimeは、この効率的なWebSocketベースのリアルタイム通信を簡単に導入できるように設計されている。
Supabase Realtimeは、主に三つのプリミティブ(基本的な機能)を提供し、これらを組み合わせて様々なリアルタイム機能を構築できる。一つ目は「Broadcast(ブロードキャスト)」だ。これは、低遅延で一時的なメッセージを複数のクライアント間でやり取りするための機能である。チャットアプリの「入力中…」表示や、オンラインゲームでのプレイヤーの動き、ライブ配信中のリアルタイムリアクションなど、履歴が不要で現在の状態が重要な情報に適している。メッセージはサーバーに保存されないため、送信時にオフラインだったクライアントはその情報を受け取ることはできない。
二つ目は「Presence(プレゼンス)」だ。これは、「誰が現在オンライン状態か」を追跡し、そのユーザーに関する付加情報(ユーザー名、アバター、現在見ている画面など)を他のオンラインユーザーと同期させる機能である。例えば、「このドキュメントを3人が閲覧中」といった表示や、ドライバーがオンラインかどうかのステータス表示などに利用される。サーバーがオンラインユーザーのリストを管理し、誰かが参加したり離脱したりするたびに、その変更を購読しているすべてのクライアントに通知する。
三つ目は「Postgres Changes(Postgresの変更通知)」だ。これは、Supabaseが利用するデータベースであるPostgreSQLのデータが、挿入、更新、削除といった形で変更された際に、その変更内容をリアルタイムで購読しているクライアントに通知する機能である。これにより、チャットアプリで新しいメッセージがデータベースに保存された瞬間に、その会話に参加しているすべてのユーザーにメッセージが届くといったことが可能になる。クライアントはデータベースをポーリングする必要がなくなり、常に最新の情報を表示できる。通常、プロダクション環境のモバイルアプリでは、Presence機能で「誰がここにいるか」を把握し、Postgres Changesで「何が変わったか」を通知し、Broadcastで一時的な情報をやり取りするという形で、これら三つの機能を組み合わせて使用することが多い。
モバイルアプリでPresence機能を正しく実装するには、注意が必要だ。アプリが起動した時にオンラインになり、終了時にオフラインになるという単純な実装では、モバイルOSの特性上、正確な情報が表示されないことが多い。モバイルアプリは、ユーザーがアプリを閉じなくても、バックグラウンドに移行したり、ネットワークが一時的に切断されたりすることが頻繁にある。このような状況でも正確なプレゼンスを維持するためには、アプリがフォアグラウンド(アクティブな状態)に戻るたびに、自身のプレゼンス情報を明示的に更新することが重要だ。この際、ユーザーが最後に活動した時刻を示す「last_seen」のようなタイムスタンプを含めることで、UI側では、このタイムスタンプが一定時間(例えば60秒)以上古いユーザーを「離席中」と表示し、一時的なネットワーク問題によるオンラインリストのちらつきを防ぐことができる。
また、リアルタイム機能は接続中に発生する変更を通知するが、アプリがオフラインになっていた間に発生した変更は教えてくれない。そのため、アプリがフォアグラウンドに戻った際には、「最後に確認した時刻以降に発生したすべての変更」を改めてサーバーに問い合わせて取得し、ローカルのデータと同期させる処理が不可欠である。これにより、ユーザーはアプリを開いた際に常に最新の状態を見ることができる。
リアルタイム通信の購読も、認証されたクライアントからのものであっても、セキュリティ面で特別な扱いをしてはならない。Supabase RealtimeのPostgres Changesは、データベースに設定された行レベルセキュリティ(RLS)ポリシーを尊重する。これは、ユーザーが購読しているテーブルのデータであっても、RLSポリシーによってそのユーザーに閲覧が許可されていない行は通知されないことを意味する。モバイルアプリのバイナリには匿名キーなどの認証情報が埋め込まれるため、攻撃者がテーブル名を推測してデータを購読しようとする可能性がある。そのため、RLSポリシーは、誰がそのデータを見る権限を持つかを厳密に制限するよう設計し、たとえばデータの所有者、特定のグループのメンバー、会話の参加者のみが閲覧できるように設定する必要がある。また、複雑なRLSポリシーは、データベースの変更通知がクライアントに届くまでの遅延を引き起こす可能性があるため、リアルタイムで通知したいデータに関するポリシーはシンプルに保ち、重い認証ロジックはデータの書き込み時(例:サーバーレス関数やデータベーストリガー)に処理することが推奨される。
モバイルOSは、バッテリー節約のために、バックグラウンドにあるアプリのアイドル状態のネットワークソケット接続を積極的に切断する。iOSでは数秒以内に停止させることがあり、AndroidのDozeモードもネットワークアクセスを制限する。したがって、リアルタイム通信を扱うレイヤーは、接続が常に切断される可能性があることを前提とし、ユーザーの介入なしに自動的に回復する仕組みを持つ必要がある。この回復プロトコルには四つの要素がある。
第一に、購読状態を明示的に監視することだ。購読が成功した(SUBSCRIBED)だけでなく、タイムアウト(TIMED_OUT)やチャンネルエラー(CHANNEL_ERROR)も適切に処理し、ログすることが重要だ。第二に、再接続時には「指数バックオフとジッター」という手法を適用すること。これは、多くのクライアントが同時にネットワークから復帰した際に、サーバーへ一斉に再接続リクエストが殺到して過負荷を引き起こすのを防ぐためのものだ。再試行の間隔を徐々に長くし、さらにランダムな遅延を加えることで、サーバーへの負荷を分散させる。第三に、再接続が成功した際には、前述したように「最後に確認した時刻以降のデータ」を再同期し、接続が途切れていた間の変更を取りこぼさないようにすること。第四に、ユーザーインターフェースに現在の接続状態(例:「ライブ」か「再接続中」か)を表示すること。これにより、ユーザーが古い情報を最新のものだと誤解してしまうという、リアルタイム機能における最悪のバグを防ぐことができる。また、一つの画面で多数のチャンネルを開くのではなく、複数の購読設定(バインディング)を一つのチャンネルにまとめることで、管理する接続の状態を減らし、再接続時の負荷を軽減することが推奨される。
リアルタイム機能は非常に強力だが、常に最適な選択肢とは限らない。リアルタイム接続を維持するクライアントはSupabaseのインフラ上でソケットを保持し、Postgres Changesはデータベースに書き込み負荷(レプリケーションスロット)をかけるため、コストが発生する。そのため、リアルタイム機能を使用しない方が良いケースも存在する。例えば、アプリの設定情報やユーザープロフィールのように、めったに更新されないデータは、アプリがフォアグラウンドになった際に一度だけ取得する、またはREST APIで取得する方が効率的だ。また、ユーザーが現在見ていない画面のデータ、例えばバックグラウンドで受信したチャットメッセージの更新などは、リアルタイムソケットで常時同期するのではなく、プッシュ通知を利用する方がバッテリーとデータ消費の観点から優れている。さらに、GPSの位置情報のように非常に高頻度で発生するセンサーデータは、個別の変更イベントとしてデータベースにリアルタイムで送るよりも、ある程度のデータをまとめてから一括で送信する方がコストを抑えられる場合が多い。基本的な指針は、「ユーザーが現在画面で見ているものだけをリアルタイムで購読し、画面から離れたら購読を解除する。それ以外の情報はプッシュ通知とアプリのフォアグラウンド復帰時の再同期でカバーする」ことだ。これにより、接続数と関連する費用を、実際のユーザーの利用状況に合わせたものにできる。