KoderKoder.ai
料金エンタープライズ教育投資家向け
ログインはじめる

プロダクト

料金エンタープライズ投資家向け

リソース

お問い合わせサポート教育ブログ

リーガル

プライバシーポリシー利用規約セキュリティ利用ポリシー不正利用を報告

ソーシャル

LinkedInTwitter
Koder.ai
言語

© 2026 Koder.ai. All rights reserved.

ホーム›ブログ›PostgreSQL LISTEN/NOTIFY:ライブ更新に十分な場合
2025年8月21日·1 分

PostgreSQL LISTEN/NOTIFY:ライブ更新に十分な場合

PostgreSQLのLISTEN/NOTIFYは最小限の設定でライブダッシュボードやアラートを実現できます。適した用途、限界、ブローカー導入のタイミングを解説します。

PostgreSQL LISTEN/NOTIFY:ライブ更新に十分な場合

LISTEN/NOTIFYが解こうとしている問題

プロダクトのUIで「ライブ更新」と言うと、ユーザーがリフレッシュしなくても何かが起きてすぐ画面が変わることを意味します。ダッシュボードの数値が増える、受信箱に赤いバッジが付く、管理者が新しい注文を目にする、あるいは「ビルド完了」や「支払い失敗」のトーストが出る、といった具合です。肝心なのはタイミングで、実際には1〜2秒でも即時に感じられます。

多くのチームはまずポーリングから始めます:ブラウザが数秒ごとにサーバーに「新しいものは?」と聞く方法です。ポーリングは動きますが、二つの一般的な欠点があります。

まず、次のポーリングまで変化が見えないので遅く感じます。

次に、何も変わっていないときにも繰り返しチェックするためコストがかかります。これを何千人ものユーザーに掛け合わせるとノイズになります。

PostgreSQLのLISTEN/NOTIFYはもっとシンプルなケースのためにあります:「何か変わったら教えて」。繰り返し聞く代わりに、アプリは待機してデータベースが小さな合図を送ったときに反応できます。

UIでちょっとした合図が十分な場合に適しています。例えば:

  • 合計値が変わったのでダッシュボードタイルを更新したい
  • 新しいアイテム到着でバッジ数を更新したい
  • ジョブのステータスが変わったので内部アラートを出したい

トレードオフはシンプルさと保証の間にあります。LISTEN/NOTIFYはPostgresに組み込まれていて追加が簡単ですが、完全なメッセージシステムではありません。通知はヒントであり、永続化された記録ではないため、リスナーが切断されていると信号を見逃す可能性があります。

実用的な使い方の1つは:NOTIFYでアプリを目覚めさせ、アプリ側でテーブルから真の状態を読みに行く、というものです。

PostgreSQL LISTEN/NOTIFYの仕組み(平易に)

PostgreSQLのLISTEN/NOTIFYは、データベースに内蔵されたシンプルな呼び鈴のようなものです。アプリはベルが鳴るのを待てますし、システムの別の部分が何かが変わったときに鳴らせます。

通知はチャネル名と任意のペイロードの2つで構成されます。チャネルはトピックラベルのようなもので(例:orders_changed)、ペイロードは短いテキストメッセージ(例:注文ID)です。PostgreSQLは構造を強制しないので、小さなJSON文字列を送ることが一般的です。

呼び鈴を鳴らすのは誰か?

通知はアプリケーションコードから(あなたのAPIサーバーがNOTIFYを実行)でも、データベース内のトリガーからでも発行できます(INSERT/UPDATE/DELETE後にトリガーがNOTIFYを実行)。

  • アプリコードは追跡とテストが容易です。
  • トリガーは複数のライターが同じテーブルに触れる場合に一貫性を保つのに役立ちます。

受信側では、アプリサーバーがデータベース接続を開いてLISTEN channel_nameを実行します。その接続は開いたままになります。NOTIFY channel_name, 'payload'が発行されると、PostgreSQLはそのチャネルをリッスンしている全ての接続にメッセージをプッシュします。アプリはそれを受けて反応します(キャッシュ更新、変更行のフェッチ、ブラウザへのWebSocketイベント送信など)。

NOTIFYがすること(しないこと)

NOTIFYは配信サービスではなく合図として理解するのが最適です:

  • リスナーに「今何かが起きた」と教えます。
  • 全てのメッセージが受信される保証はありません(切断中のリスナーは見逃します)。
  • キューではありません(メッセージは後で保存されません)。
  • ペイロードにはサイズ制限があり、小さく保つべきです。
  • データを置き換えるのではなく、データを指し示すべきです(IDを送り、詳細は照会する)。

このように使えば、PostgreSQLのLISTEN/NOTIFYは追加インフラなしでライブUI更新を支えられます。

ライブUI更新においてLISTEN/NOTIFYが十分なとき

LISTEN/NOTIFYは、UIが必要とするのが「ちょっとした合図(何か変わった)」であり、完全なイベントストリームが不要な場合に強みを発揮します。"このウィジェットをリフレッシュ"や"新しいアイテムがある"のような用途を想像してください。クリックを順序通りに処理するといった使い方には向きません。

データベースがすでにソース・オブ・トゥルースであり、UIをそれに同期させたいときに最もうまく機能します。一般的なパターンは:行を書き、IDを含む小さな通知を送り、UI(またはAPI)が最新状態を取得する、という流れです。

LISTEN/NOTIFYが通常十分な条件は次の通りです:

  • メッセージは小さな「何か変わった」シグナルであり、フルペイロードではない。
  • イベント量は低〜中程度(バーストは許容されるが持続的な高スループットはNG)。
  • ユーザーが通知を見逃してもUIが再読み込みや短時間のポーリングで回復できる。
  • 完璧な配信保証よりもシンプルさを重視する(初期プロダクトや社内ツールで一般的)。
  • 単一のプライマリデータベースを持ち、構成要素を少なくしたい。

具体例:社内サポートダッシュボードで「未処理チケット」と「新しいノート」バッジを表示する場合。エージェントがノートを追加するとバックエンドがPostgresに書き込み、ticket_changedをチケットID付きでNOTIFYします。ブラウザはWebSocket経由で受け取り、そのチケットカードだけを再取得します。追加インフラ不要でUIはライブに感じられます。

LISTEN/NOTIFYが破綻し始める場面

LISTEN/NOTIFYは初めは素晴らしく感じられますが、限界があります。それらは通知をメッセージシステムのように扱ったときに顕在化します。

最大のギャップは耐久性です。NOTIFYはキュージョブではありません。誰もリッスンしていないときの通知は失われます。リスナーが接続中でもクラッシュ、デプロイ、ネットワークの瞬断、データベースの再起動で接続が切れると、その接続に向けられた通知は戻ってきません。

切断はユーザー向け機能では特に厄介です。ダッシュボードが新しい注文を表示する場合を想像してください。ブラウザタブがスリープし、WebSocketが再接続されると、数件のイベントを見逃してUIが"止まっている"ように見えることがあります。対策は可能ですが、それはもはや「ただLISTEN/NOTIFYするだけ」ではなく、NOTIFYをリフレッシュのヒントにして状態を再構築する設計になります。

ファンアウトも一般的な問題です。1つのイベントが何百、何千ものリスナーを起こす(多くのアプリサーバー、たくさんのユーザー)可能性があります。ordersのような騒がしい1つのチャネルを使うと、1人のユーザーしか気にしていないイベントでも全員が起きます。これがCPUや接続に一斉負荷を与えることがあります。

ペイロードサイズと頻度も罠です。NOTIFYのペイロードは小さく、高頻度のイベントはクライアントが処理するより早く積み重なります。

以下の兆候を監視してください:

  • 確実な配信、リトライ、順序保証が必要になった。
  • クライアントが頻繁に再接続し、更新を見逃してはいけない。
  • 1つのチャネルが多くのリスナーを起こし、ほとんどの人が無視する。
  • 大きなペイロードを送っている、または毎秒何度もイベントを発火している。

その段階では、NOTIFYを「ポーク(軽い合図)」として残し、信頼性をテーブルや専用のメッセージブローカーに移すことを検討してください。

ステップバイステップ:有効なシンプルパターン

ポーリングを通知に置き換える
NOTIFYヒントとターゲットリフェッチでポーリングを置き換える軽い更新ループを生成します。
今すぐ試す

信頼性の高いパターンは、NOTIFYを合図にし、本当のソースはデータベースの行であると扱うことです。通知は見るべきタイミングを伝え、データ自体はテーブルから取得します。

1) 書き込み→コミット→通知

書き込みはトランザクション内で行い、データ変更がコミットされた後に通知を送ります。早すぎる通知はクライアントが起きてデータを見つけられない原因になります。

一般的なセットアップは、INSERT/UPDATEでトリガーが発火して小さなメッセージを送るものです。

NOTIFY dashboard_updates, '{\\\"type\\\":\\\"order_changed\\\",\\\"order_id\\\":123}'::text;

2) シンプルなチャネル名と小さなペイロードを選ぶ

チャネル名は人がシステムをどう考えるかに合わせるとよいです。例:dashboard_updates、user_notifications、またはテナントごとにtenant_42_updatesのようにします。

ペイロードは小さく保ち、識別子とタイプだけ入れ、フルレコードは入れないでください。役立つデフォルトの形は:

  • type(何が起きたか)
  • id(何が変わったか)
  • 必要ならtenant_idやuser_id

この形にすると帯域を抑え、通知ログに機密データが漏れるリスクも避けられます。

3) 再接続とサブスクライブは各接続でやり直す

接続は切れます。計画してください。

接続時に必要なチャネルすべてでLISTENを実行します。切断時は短いバックオフで再接続し、再接続後に再度LISTENします(サブスクリプションは持ち越されません)。再接続後は最近の変更を素早く再取得して、見逃しをカバーしてください。

4) UIの更新:まず再取得、あとでパッチ

多くのライブUI更新では再取得が最も安全です:クライアントは{type, id}を受け取り、その後サーバーに最新状態を要求します。

インクリメンタルにパッチを当てると速くできますが、順序のずれや部分失敗で間違いやすいです。中間的な妥協案として、小さなスライス(1件の注文行、1つのチケットカード、1つのバッジ数)だけを再取得し、重い集計は短いタイマーに任せる、という方法があります。

ダッシュボードと通知のスケーリングパターン

管理ダッシュボードが1つから多数のユーザーが同じ数値を監視する形に移ると、賢いSQLよりも良い運用が重要になります。LISTEN/NOTIFYは引き続き有効ですが、データベースからブラウザへのイベントの流れを整形する必要があります。

一般的なベースラインは:各アプリインスタンスが1つの長寿命接続を開いてLISTENし、接続クライアントに更新をプッシュする構成です。この「インスタンスごとに1リスナー」はシンプルで、小規模なサーバー数であれば十分なことが多いです。

多くのアプリインスタンス(またはサーバーレスワーカー)がある場合は、共有のリスナーサービスを作る方が楽です。1つの小さなプロセスが1度だけリッスンし、その後スタックの他の部分にファンアウトします。ここでバッチ、メトリクス、バックプレッシャーを一ヶ所で扱えます。

ブラウザへのプッシュは通常WebSocket(双方向、インタラクティブUI向け)かSSE(一方向、ダッシュボード向けでシンプル)を使います。どちらでも「全部更新」ではなくorder 123 changedのようなコンパクトなシグナルを送って、UIが必要なものだけ再取得するようにしてください。

UIが動作不良を起こさないように、いくつかのガードレールを追加します:

  • バーストをデバウンス(例:100〜500ms)してクライアントにプッシュする
  • 重複をまとめる(同じレコードが10回変わっても1回の更新にする)
  • フルページ更新ではなくウィジェットごとのダーティフラグを使う

チャネル設計も重要です。1つのグローバルチャネルの代わりにテナント、チーム、機能で分割するとクライアントは関連するイベントだけ受け取れます。例:notify:tenant_42:billingやnotify:tenant_42:ops。

よくある失敗と回避法

LISTEN/NOTIFYは簡単に見えるため、チームは素早く導入して本番で驚くことが多いです。ほとんどの問題はそれを耐久的なメッセージキューのように扱うことに起因します。

1) 通知を永続メッセージのように扱う

アプリが再接続すると(デプロイ、ネットワーク切れ、DBのフェイルオーバーなど)、切断中に送られたNOTIFYは失われます。対処法は通知を信号として扱い、データベースを再チェックすることです。

実用的なパターン:実際のイベントをテーブルに保存し(idとcreated_atを付ける)、再接続時に最後に見たid以降のものをフェッチする。

2) ペイロードの過負荷

LISTEN/NOTIFYのペイロードは大きなJSON向けではありません。大きいペイロードは解析コストを増やし、制限に当たるリスクを高めます。

ペイロードは\"order:123\"のような小さなヒントにして、詳細はデータベースから読みます。

3) “信号”と“データ取得”の混同

ペイロードの内容をそのままソース・オブ・トゥルースとしてUI設計するのはよくないです。スキーマ変更やクライアントのバージョン差が問題になります。

クリーンな分離を保ってください:何かが変わったことを通知し、その後通常のクエリで現在のデータを取得します。

4) トリガーが頻繁に発火しすぎる

行単位の変更で常にNOTIFYするトリガーはシステムを洪水状態にします。特にアクセスの多いテーブルでは深刻です。

意味のある遷移(例:ステータス変更)だけで通知する、騒がしい更新はバッチ処理する(トランザクションごと、または時間窓ごとに1回)か、通知パスから外すことを検討してください。

5) UIでのバックプレッシャーを無視する

データベースが通知を送れても、UI側が耐えられないなら意味がありません。イベントごとに再レンダリングするダッシュボードは固まることがあります。

クライアントでデバウンスし、バーストをまとめ、"無効化して再取得"の方針を優先してください。例えば通知ベルは即時更新、ドロップダウンリストは数秒ごとに最大1回の更新に抑えるなどです。

チェックリスト:LISTEN/NOTIFYが適合するか判断する

ライブダッシュボードを素早く作る
UIの更新内容を説明すると、Koder.aiがReact、Go、Postgresのコードを生成します。
構築を始める

LISTEN/NOTIFYは「何か変わった」の小さな信号でアプリが新しいデータを取りに行く用途に適していますが、フルなメッセージシステムではありません。

構築前に次の質問に答えてください:

  • リスナーが1分オフラインでもイベントを見逃して問題ないか? 見逃しが許されないなら耐久的な配信とリプレイが必要です。
  • 「ほぼリアルタイム」で十分か? デプロイやネットワークの瞬断で短時間の遅延や手動リフレッシュが許容されればLISTEN/NOTIFYで十分なことが多いです。
  • ピーク時のバースト率はどれくらいか? 秒間数イベントや時折のスパイクはスイートスポット、持続的な高負荷は難しくします。
  • 何人のコンシューマーが同時にリッスンするか? バックエンドワーカーが1〜数台なら簡単ですが、数百〜数千の接続クライアントがいるならサーバー側のファンアウト層を作るのがよいです。
  • 多数のイベントタイプにまたがる厳密な順序保証が必要か? ダッシュボードが"Aの後にBを処理する必要がある"を前提にしているなら難易度が一気に上がります。

実用的なルール:NOTIFYをペイロードそのものではなく「行を再読み込みして」とする合図として扱えるなら、安全圏にいます。

例:管理ダッシュボードが新しい注文を示す場合、通知を見逃しても次のポーリングやページリロードで正しい数が表示されるなら適合します。しかし"カード請求"や"出荷指示"のような、見逃すと重大な事故になるイベントはNOTIFYだけに頼るべきではありません。

例:ライブダッシュボードとユーザー通知

小さな営業アプリを想像してください:ダッシュボードが本日の売上、合計注文数、"最近の注文"リストを表示する一方で、各営業担当者は自分の受注が支払済みや出荷済みになったときに素早い通知を受け取りたいとします。

シンプルなアプローチはPostgreSQLをソース・オブ・トゥルースとして扱い、LISTEN/NOTIFYは「変化があったよ」という軽い合図だけに使うことです。

注文が作成されるかステータスが変わると、バックエンドは同一リクエストで二つのことを行います:行を書き込み(または更新し)、小さなペイロード(通常は注文IDとイベントタイプ)でNOTIFYします。UIはNOTIFYペイロードをフルデータの代わりにはせず、必要なデータを再取得します。

実用的なフローは次の通りです:

  • トランザクション内で注文変更を書き込む。
  • コミット後にorders_eventsへ{\\\"type\\\":\\\"status_changed\\\",\\\"order_id\\\":123}のようなNOTIFYを出す。
  • バックエンドリスナーがイベントを受け取り、接続済みのブラウザにプッシュする(WebSocketやSSE)。
  • ダッシュボードは必要なものを再取得する:IDで最近の注文行を取得し、合計は重い集計を避けるため短いタイマー(例:2〜5秒)で更新する。
  • ユーザー通知はターゲット化する:その注文に紐づく営業担当者だけが"支払い済み"や"出荷済み"のトーストを受け取る。

こうすることでNOTIFYを軽量に保ち、高コストなクエリを制限できます。

トラフィックが増えると限界が見えてきます:イベントのスパイクが単一リスナーを圧倒したり、再接続で通知が見逃されたり、配信と再生が必要になります。そのときはアウトボックステーブル+ワーカー、あるいはブローカーを追加しつつPostgresをソース・オブ・トゥルースとして残すのが一般的です。

専用ブローカーに移行すべきとき

構築中にクレジットを稼ぐ
作ったものを共有したり友人を紹介してKoder.aiのクレジットを獲得しましょう。
クレジットを獲得

LISTEN/NOTIFYは簡単に"何か変わった"と知らせるのに良いですが、フルなメッセージシステムではありません。イベントを配信のソースとして頼り始めたらブローカーを追加すべきです。

LISTEN/NOTIFYが手狭になる明確な兆候

次のいずれかが出てきたらブローカーを導入すべきです:

  • 耐久性が必要:アプリ再起動やDBフェイルオーバー時にイベントを失えない。
  • リトライとデッドレター処理が必要:消費者が失敗したら再試行しトラッキングしたい。
  • コンシューマーグループが必要:複数ワーカーで負荷分散し二重処理を避けたい。
  • 監査と再生が必要:「過去1時間のすべてを見たい」や「イベントからビューを再構築したい」。
  • 制御されたバックプレッシャーが必要:プロデューサーが遅いコンシューマーを圧倒しないようにしたい。

LISTEN/NOTIFYはメッセージを後で保存しません。プッシュ信号であって永続ログではありません。ダッシュボードのリフレッシュ用途には完璧ですが、"請求を実行"や"パッケージを出荷"のような不可欠なワークフローにはリスクがあります。

ブローカーが追加するもの(平易に)

ブローカーは実際のメッセージフローのモデルを提供します:キュー(作業)、トピック(多対多のブロードキャスト)、保持(数分〜数日のメッセージ保持)、および確認(コンシューマーが処理を確認)。これにより「データベースが変わった」ことと「それによって起きる全てのこと」を切り離せます。

最も複雑なツールを選ぶ必要はありません。一般的に評価される選択肢はRedis(pub/subやstreams)、NATS、RabbitMQ、Kafkaなどです。どれを選ぶかは、単純なワークキューが欲しいのか、多くのサービスへのファンアウトが必要か、履歴をリプレイしたいかに依存します。

段階的な移行プラン

大きな書き換えなしに移行できます。実用的なパターンはNOTIFYを目覚まし信号として残しつつ、ブローカーを配信のソースにすることです。

同じトランザクション内で"イベント行"をテーブルに書き込み、そのイベントをワーカーがブローカーに公開するようにします。移行期間中はNOTIFYがUI層に「新しいイベントを確認して」と伝え、バックグラウンドワーカーがブローカーからリトライや監査付きで消費します。

こうするとダッシュボードはスナッピーに保たれ、重要なワークフローはベストエフォート通知に依存しなくなります。

次のステップ:小さく出して安全に反復する

1つの画面(ダッシュボードタイル、バッジ数、"新着通知"のトーストなど)を選んでエンドツーエンドで配線してください。LISTEN/NOTIFYを使えば小さなスコープで役立つ結果を素早く得られますが、範囲を狭く保ち実際のトラフィックで何が起きるかを計測してください。

まずは最もシンプルで信頼できるパターンを採用しましょう:行を書き、コミットし、その後に何かが変わったという小さな信号を出す。UIはその信号を受けて最新状態(または必要なスライス)を取りに行きます。こうするとペイロードを小さく保ち、メッセージの順序が乱れたときの微妙なバグを避けられます。

早めに基本的な可観測性を追加してください。高級ツールは不要ですが、システムが騒がしくなったときに答えが必要です:

  • 再接続とサブスクリプション開始を(理由付きで)ログに残す
  • NOTIFYレートとピークバーストを追跡する
  • 通知によって引き起こされたフェッチ率を追跡する
  • "見逃し"症状(ユーザーがリフレッシュを必要とする)の兆候を監視する

契約はシンプルに保ち、文書化してください。チャネル名、イベント名、ペイロードの形(たとえIDだけでも)をリポジトリ内の短い「イベントカタログ」にまとめることでドリフトを防げます。

もし素早く作りたいなら、Koder.ai (koder.ai)のようなプラットフォームを使えば、React UI、Goバックエンド、PostgreSQLを使った最初のバージョンを迅速に出し、要件が明確になってから反復できます。

よくある質問

PostgreSQL LISTEN/NOTIFYは何に向いていますか?

LISTEN/NOTIFYは、バッジ数やダッシュボードタイルの更新など「何かが変わった」という短い合図があれば十分な場面で使います。通知は再取得のきっかけと考え、実際のデータはテーブルから読み直してください。

なぜポーリングの代わりにLISTEN/NOTIFYを使うべきですか?

ポーリングは定期的にサーバーに「新しいものは?」と聞くため、更新が遅れて見えたり、何も変わっていないのに多くのリクエストを発生させます。LISTEN/NOTIFYは変更時に小さな信号をプッシュするので、通常はより速く感じ、無駄なリクエストを減らせます。

LISTEN/NOTIFYは配信を保証しますか?

いいえ。ベストエフォートです。リスナーがNOTIFYが発行されたときに切断されていると、その信号を見逃す可能性があります。通知は後で再生されるように保存されません。

NOTIFYのペイロードには何を入れるべきですか?

小さくしてヒントとして使ってください。一般的なデフォルトはtypeとidを含む小さなJSONです。その後アプリがPostgresに問い合わせて最新状態を取得します。

トランザクションをコミットする前に通知すべきですか?

一般的なパターンは、書き込みがコミットされた後に通知を送ることです。早すぎる通知は、クライアントが起きてデータを見つけられない原因になります。

NOTIFYはアプリコードから送るべきですか、それともトリガーから送るべきですか?

アプリ側コードは明示的でテストしやすく、理解もしやすいです。トリガーは複数のライターが同じテーブルに書き込む場合や、誰が変更しても一貫した動作を望むときに便利です。

再接続を扱って更新を見逃さないようにするには?

再接続は普通のこととして計画してください。再接続時には必要なチャネルで再度LISTENを実行し、オフライン中に見逃した可能性のある最近の状態を素早く再取得してください。

データベースの通知をブラウザに届けるには?

すべてのブラウザを直接Postgresに接続させないでください。典型的にはバックエンドインスタンスごとに1つの長寿命接続を開き、そこからWebSocketやSSEでブラウザに転送します。UIは必要なデータを再取得します。

多くのリスナーを起こしたりUIを氾濫させないにはどうする?

適切な消費者だけが起きるようにチャネルを狭くし、騒がしいバーストはバッチ処理してください。クライアント側で数百ミリ秒のデバウンスや重複の集約を行えば、UIやバックエンドの過負荷を避けられます。

いつLISTEN/NOTIFYをやめてブローカーに移行すべきですか?

配信の耐久性やリトライ、コンシューマーグループ、順序保証、監査や再生が必要になったら切り替えどきです。通知を見逃すと課金や出荷など重大な問題になる場合は、アウトボックス+ワーカーや専用ブローカーを使ってください。

目次
LISTEN/NOTIFYが解こうとしている問題PostgreSQL LISTEN/NOTIFYの仕組み(平易に)ライブUI更新においてLISTEN/NOTIFYが十分なときLISTEN/NOTIFYが破綻し始める場面ステップバイステップ:有効なシンプルパターンダッシュボードと通知のスケーリングパターンよくある失敗と回避法チェックリスト:LISTEN/NOTIFYが適合するか判断する例:ライブダッシュボードとユーザー通知専用ブローカーに移行すべきとき次のステップ:小さく出して安全に反復するよくある質問
共有
Koder.ai
Koderで自分のアプリを作ろう 今すぐ!

Koderの力を理解する最良の方法は、自分で体験することです。

無料で始めるデモを予約