
2026/07/31 3:39
Postgres キューのスケールアップ
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Postgres は、高スループットキューの拡張性に不向きであるという従来の常識を打破し、最適化されたアーキテクチャにより、1 秒あたりの実行数 30,000 を達成しました。歴史的に、コンテンションとボトルネックの影響でデータベースは約 100 ワークフロー/秒が限界でした。しかし、特定の技術的調整を実施することでこの限界を突破し、特別キューウェアウェアを必要とせずに大規模なスケーラビリティを実現しています。
この画期的成果は、3 つの主要な最適化に依存しています。まず、
FOR UPDATE SKIP LOCKED クローゼの使用により、ワーカスレッドがロックされた行を待機することが防され、システムの混雑が大幅に削減されます。次に、トランザクションの分離レベルを READ COMMITTED に切り替えることで、高い並行性下で通常発生するシリアライゼーション失敗を排除します。さらに、インデックスを部分構造へと再構築し、その内部でのデータをソートすることで、手動ソートや重い自動バキューム処理といった高コストな CPU 操作を除去します。
このアプローチにより、組織は標準的なリレーショナルデータベースを使用して信頼性の高い分散システムを構築できるようになります。適切に構成された既存の技術が 1 ヶ月あたりの 800 億回の実行を効率的に処理できることを示すことで、インフラコストと複雑性を劇的に低下させます。
本文
Postgres によるスケーラブルなキューシステムの実現と最適化
一般的な見解では、Postgres はキュー処理に不向きでスケーリングしないと考えられています。そのため、大規模ワークロードには RabbitMQ や Redis などの専用システムが推奨されがちです。しかし、適切な最適化を行えば Postgres でも数万 QPS のスケーリングが可能であり、実際に秒間 3 万件のワークフロー実行を達成しました。
教訓 1: 「SKIP LOCKED」による競合解決
複数のプロセッサが同一のワークフローをデキューする際、以下の競合が発生します。
従来のアプローチと問題点
クライアントは最も古いワークフローを以下のように取得しようとしますが、これにより**高い競合(チーニング)**が発生します。
SELECT * FROM workflows ORDER BY timestamp ASC LIMIT N FOR UPDATE;
- 問題: 多数のプロセッサが同時に最古の行を選択しようとし、ロック争奪戦に陥ります。
- 結果: システム全体のデキュー速度がボトルネックとなり、スケーリングに限界が生じます(約 100 ワークフロー/秒)。
解決策:SKIP LOCKED
Postgres は
FOR UPDATE SKIP LOCKED を使用することで、この競合を解消します。
SELECT * FROM workflows ORDER BY timestamp ASC LIMIT N FOR UPDATE SKIP LOCKED;
このクエリの効果:
- 行ロック: 選択された行は他のプロセッサにロックされます。
- ロック済み行のスキップ: すでにロックされている行はスルーされ、未ロックの次にくるワークフローが取得されます。
これにより、多数のプロセッサが競合なく同時に新しいタスクを引き出すことが可能になります。
教訓 2: トランザクション隔離レベルの見直し
SKIP LOCKED で競合を解決したものの、次に直面したのは「シリアル化エラー」でした。
シリアル化エラーの発生原因
大規模環境(1,000 ワークフロー/秒以上)では以下の現象が発生しました。
- デキュー操作が頻繁に失敗し、再試行が必要になる。
- 多くの時間がトランザクションの再試行に費やされ、パフォーマンス低下を招く。
根本原因:REPEATABLE READ
デキューは当初
REPEATABLE READ レベルで実行されていました。これはグローバルな一貫性を保証するためのものでしたが、高並行環境では以下のようなデメリットがありました。
- トランザクション開始時のスナップショットに基づくため、同時更新を検知できず、シリアル化エラーを発生させやすい。
対策:隔離レベルの条件付き変更
キューがグローバルなフロー制御を持たない場合でも
REPEATABLE READ を使う必要はありません。
| キュータイプ | 隔離レベル | 理由 |
|---|---|---|
| グローバル制御が必要 | | 一貫性を維持するため |
| ローカル制限のみ(グローバルなし) | | シリアル化エラーを排除しスループット向上のため |
この変更により、不必要なロック競合を減らし、スケーリング性能を劇的に向上させることができました。
教訓 3: インデックスの最適化
低隔離レベルを導入したことで競合は解消しましたが、高負荷時(約 8,000 ワークフロー/秒以上)には高い CPU 使用率という新たなボトルネックに直面しました。
非効率なインデックスの問題
ワークフローステータステーブルには、クエリ加速や観測可能性のための複数の二次インデックスが存在していました。
-- デキュー用インデックス(ソート順を保証していない) CREATE INDEX idx_workflows_enqueue ON workflows (queue_name, status); -- 階層クエリ用インデックス CREATE INDEX idx_workflows_parent ON workflows (parent_workflow_id);
課題:
- 不要なソート: インデックスが返す順序は保証されず、Postgres が取得したデータをメモリ上でソートする必要があります。
- 維持コスト: 全インデックスの更新と自動真空(Autovacuum)処理により、CPU リソースを大量に消費します。
対策:高選別性な部分インデックスへ
インデックスを「特定の順序で返す」かつ「必要なデータのみを保持」するように変更しました。
-- 部分インデックス:親 ID が NULL の行は除外 CREATE INDEX idx_workflows_parent ON workflows (parent_workflow_id) WHERE parent_workflow_id IS NOT NULL;
改善点:
- ソート不要: インデックス自体がソート済みデータを返すため、クエリ時の CPU 負荷が軽減。
- 維持コスト削減: デキューされたワークフローのインデックスエントリを即時削除でき、自動真空処理の負担が減る。
これらの最適化により、CPU 使用率は劇的に低下し、秒間 3 万件(月間 800 億件)のスケールを実現しました。
さらに学ぶ
スケーラブルで信頼性の高い Postgres ベースシステムの構築に興味がある方は、以下のリソースをご参照ください。
- クイックスタート: https://docs.dbos.dev/quickstart
- GitHub リポジトリ: https://github.com/dbos-inc
- Discord コミュニティ: https://discord.gg/eMUHrvbu67