
PostgreSQL LISTEN/NOTIFY のスケーラビリティを実現
TL;DR: DBOS は PostgreSQL の LISTEN/NOTIFY 機能をバッファリングとバッチ処理で最適化し、初期実装の 2.9K から 60K 書き込み/秒へ 20倍の性能向上を達成した。グローバルロックがボトルネックだったが、バッチ フラッシュ時にのみロック取得することで解決した。
DBOS は PostgreSQL の LISTEN/NOTIFY 機能を対象にスケーラビリティの最適化を行った。初期実装では、ストリームテーブルのトリガーが通知を送信するたびに NOTIFY を実行していたが、単一の PostgreSQL サーバー上でも 2.9K 書き込み/秒を超える処理ができず、ボトルネックに直面していた。
最適化の背景
LISTEN/NOTIFY は従来からスケーラビリティの課題を抱えていた。その根本原因は、PostgreSQL がトランザクションのコミット時に取得するグローバルな排他ロック(global exclusive lock)である。このロックはトランザクションがコミットを開始してから、ディスクへの fsync() によるフラッシュまで解放されず、通知を含むトランザクションのコミットをシリアライズする。そのため、CPU、メモリ、IOPS といった PostgreSQL リソースに顕著な消費がないにもかかわらず、スループットが制限されていた。
最適化ソリューション
DBOS の最適化は、NOTIFY をメモリ内でバッファリングおよびバッチ処理し、一定周期でこれらを単一のバッチ トランザクションで一括フラッシュするアプローチを採用した。この手法により、グローバルロックは個別の書き込みのたびではなく、バッファをフラッシュするときだけ取得される。

最適化版の実装は、並行リーダーの存在下で最大 60K 書き込み/秒を達成し、遅延は 15–100 ミリ秒である。これは初期実装比で 20 倍の改善である。最大スループット時には PostgreSQL の CPU が完全に使用される。

PostgreSQL 19 のパッチとの関係
PostgreSQL 19 のリリースに向けた本体パッチは、多数の通知チャネルと特定チャネル購読リスナーのシナリオを最適化するが、グローバルロック自体は除去していない。通知が未配信となった場合には、リーダーが定期的にデータベースをポーリングする仕組みが機能する。PostgreSQL は通知をトランザクション コミット順で送信することを保証している。
筆者の見立て
- LISTEN/NOTIFY がスケールしない場合、それは残念なことである。なぜなら、これは強力なツールだからである。(意見)
- グローバルロックは観測されたスケーラビリティの低さと PostgreSQL リソース消費の乏しさを説明している。(解釈)
- 通知自体はストリームの信頼できる情報源ではなく、多くの LISTEN/NOTIFY アプリケーションでは、リーダーがデータベース テーブルをチェックするためのシグナルに過ぎない。(解釈)
- NOTIFY をバッファリングおよびバッチ処理することで、個別の書き込みのたびではなく、バッファをフラッシュするときだけグローバルロックを取得する必要があるため、ボトルネックを回避できる。(解釈)
この記事は元記事の事実のみに基づいて自動生成されました。
出典
DBOS「Postgres LISTEN/NOTIFY Actually Scales」https://www.dbos.dev/blog/postgres-listen-notify-scalability (PostgreSQL パッチについてのオンライン議論による)