拡張機能不要!Postgres肥大化を防ぐ新キューPgQue
「データベースをメッセージキューとして使うな」。私が駆け出しのエンジニアだった頃、インフラの先輩から耳にタコができるほど聞かされた言葉だ。RDBは永続的な状態を保存するものであって、激しく入れ替わる一時的なデータを扱うようには設計されていない。当時はそれが絶対の常識だった。
しかし時代は変わり、PostgreSQL 9.5でSKIP LOCKED構文が登場して以降、Postgresをキューとして扱うアプローチはすっかり市民権を得た。RabbitMQやKafkaといった外部の分散システムを管理する面倒から解放されるメリットは、現場にとって何にも代えがたいからだ。だが、その「手軽さ」の裏で、多くの開発チームが静かに「死の螺旋(Death spiral)」に足を踏み入れている。数カ月運用を続けるうちに処理済みの不要な行(Dead tuples)が蓄積し、VACUUMが追いつかず、インデックスが肥大化(bloat)し、最終的にシステム全体がスローダウンするあの恐ろしい現象だ。
この長年のジレンマに対して、極めて美しく、かつ歴史的な裏打ちを持つアプローチで終止符を打とうとするプロジェクトが登場した。それが「PgQue」である。
Skypeの伝説的アーキテクチャをマネージド環境に蘇らせる
このPgQue、実はポッと出の奇抜なアイデアではない。バックボーンにあるのは、かつてSkypeで数億人規模のメッセージングを支えた「PgQ」という伝説的なキューアーキテクチャだ。10年以上にわたって大規模なオンプレミス環境で稼働した実績を持つPgQだが、現代においては致命的な弱点があった。C言語の拡張モジュール(pgq)と、外部の常駐デーモン(pgqd)に強く依存していたのだ。
RDSやAurora、SupabaseといったマネージドPostgreSQL環境が当たり前になった今日において、プロバイダの許可やデータベースの再起動を要求するC拡張は、それだけで採用の俎上から外れてしまう。
PgQueの作者は、この枯れた堅牢なアーキテクチャを「純粋なPL/pgSQL」だけで再実装するという離れ業をやってのけた。自らを「アンチ拡張機能(Anti-Extension)」と名乗る通り、C拡張も、共有ライブラリのロードも、インフラの再起動も一切不要だ。マネージド環境の厳しい制約を完全にすり抜けながら、エンタープライズ級のキューイングシステムをPostgresの内部に構築できるようになったのである。
なぜ肥大化しないのか?「TRUNCATE」という逆転の発想
では、なぜPgQueは「絶対に肥大化しない(Zero-bloat)」と断言できるのか。それは、ジョブのライフサイクル管理が既存のアプローチと根本的に異なるからだ。
| 従来のPostgresキュー | PgQue | |
|---|---|---|
| ジョブの取得 | 行単位(SKIP LOCKED) |
バッチ単位(スナップショットベース) |
| 処理後の破棄 | 行単位(DELETE / UPDATE) |
テーブルごと(TRUNCATEによるローテーション) |
| VACUUMの負荷 | トラフィックに比例して増大(死の螺旋) | 皆無(ホットパスにゴミが残らない) |
従来のキュー実装は、ジョブを1行ずつ取り出し、終わったらDELETEやフラグのUPDATEで処理済みマークをつける。これが大量のDead tuplesを生み、VACUUMプロセスを過労死させる元凶だった。
一方のPgQueは「スナップショットベースのバッチ処理」と「TRUNCATEによるテーブルローテーション」を採用している。数千のジョブを一つのバッチとして切り出し、処理が終わったテーブルは行ごとに消すのではなく、TRUNCATEで丸ごと破棄するのだ。TRUNCATEはMVCCの仕組みを迂回してディスク上のファイルを直接解放するため、VACUUMの出る幕すらない。システムがどれだけ高負荷に晒されようと、性能劣化が起きないように設計(By design)されている。
レイテンシという代償との向き合い方
もちろん、技術の世界に魔法はない。PgQueがこの驚異的な安定性を手に入れるために支払った代償は「レイテンシ」だ。
行単位の即時ロックを避けてバッチで処理をまとめる特性上、ミリ秒単位での即時実行が求められるユースケースには適していない。「ユーザーがボタンを押した瞬間、1ミリ秒でも早くワーカーを叩き起こしたい」のであれば、おとなしくRedisや専用のインメモリブローカーを立てるべきだろう。
しかし、メール送信、レポート生成、外部API連携など、大半のバックグラウンドジョブにおいて、数秒の遅延は十分に許容されるはずだ。持続的で高負荷なトラフィックを、性能劣化なく何カ月も安定して捌き続ける「スループットと運用安定性」こそが、PgQueの真価である。
導入は拍子抜けするほど簡単だ。手元のPostgresに1つのSQLファイルを流し込み、定期実行のためのpg_cronをセットするだけで終わる。
-- 巨大なミドルウェアは不要。導入はこれだけで完結する
\i pgque.sql
SELECT pgque.create_queue('my_heavy_queue');
「もう一つ分散システムをインフラに追加するのか、それとも手元のPostgresを限界までしゃぶり尽くすのか」。システムの複雑さに疲弊している開発チームにとって、このレトロモダンなソリューションは、極めて魅力的な選択肢として映るはずだ。
参考リポジトリ: NikolayS/pgque
Photo by Shubham Dhage on Unsplash