
2026/05/01 3:45
Postgres は拡張性を保ち続けることができるのでしょうか?
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
AWS RDS PostgreSQL インスタンス(db.m7i.24xlarge)への最新のベンチマークでは、書き込み戦略を最適化することで、単一サーバーが持続的に 40 億回以上の耐久型日次ワークフローを取り扱えることが明らかになりました。非同期 Python クライアントを使用した結果、生書き込みスループットは秒間 144,000 回の書き込み(約 120 億回/日)に達する天井を確認しました。生負荷および最適化された両方のワークロードにおける主なボトルネックは、Write-Ahead Log(WAL)をディスクにフラッシュすることですが、比較的低い量においては、WAL の制限に復帰する以前に、キューの先頭行に対するロック競合などのキュー管理上の問題が制約因子となりました。単一の最適化されたサーバーが巨大なスケーリング(>40 億 ワークフロー/日)を実現できる一方で、それ以上の負荷を処理するには、複数のキューへの分散負荷またはシャーディングの実装を通じてロック非効率性を管理し、ディスク I/O ボトルネックが再び重大にならないようにする必要があります。ベンチマークのコードはオープンソースであり、耐久性には特定の書き込みパターン(開始時の入力/ステータス、完了時の結果/ステータス、およびチェックポイント)が関与しており、これら進化するパフォーマンス障壁を監視する限り、主要なアーキテクチャの再設計なしにチームが大きく容量を増やすことを可能にするという事実を示しています。
本文
PostgreSQL に基づき耐久性のあるワークフロー実行システムを構築する際、最も頻繁に寄せられる質問の一つは「PostgreSQL スケーラブルですか?」というものです。トップクラスのテック企業からの投稿では、PostgreSQL がスケーラブルであることが多くの場所で主張されていますが、いかに実際の運用環境においてパフォーマンスが発揮されるかは十分に示されていない場合もあります。本ブログ投稿では、単一の PostgreSQL サーバーのスケーラビリティについてベンチマークを行いました。特に注力したのは書き込み性能であり、ワークフロー実行におけるボトルネックとなる箇所だからです:耐久性のあるワークフローは、入力データをチェックポイントし、結果を記録し、各ステップの結果もまた記録するために、データベースに対して複数回の書き込みを行う必要があります。まず、真空状態(他の影響なし)において Postgres の生・書き込みスループットを測定しました。次に、以下の 2 つの耐久性のあるワークフローワークロードのパフォーマンスについて分析を行いました:ローカルでワークフローを開始するもの、および PostgreSQL をバックエンドとするキューを利用するものの 2 種類です。その結果、PostgreSQL は予想以上に優れたスケーラビリティを発揮していることが判明しました:単一のサーバーでも、継続的な書き込みスループットとして 14 万回(144K)/秒に対応可能であり、または 4 万 3,000 件のワークフロー(43K)/秒を処理できることを示唆しています。これは、換算すると 1 日で約 120 億回の書き込み、あるいは 40 億件のワークフローに相当し、ほとんどのユースケースでは十分な容量となります。全てのベンチマークコードはオープンソースとして公開されています。なお、すべての実験は AWS RDS db.m7i.24xlarge インスタンス(96 vCPU、384 GB RAM、io2 ボリューム上の 120K 規定 IOPS)において実施されました。
PostgreSQL ポイント・ライトパフォーマンス
まず、PostgreSQL が単一テーブルに対して維持できる最大書き込みスループットを測定しました。UUIDv7 をプライマリキーとする 3 カラムの単純なテーブル(UUIDv7 で主鍵、TEXT 型データフィールド、タイムスタンプ)を使用しました:
その後、多数の非同期 Python クライアントから毎秒何行 inser トできるかについてベンチマークを行いました。各行は独立したトランザクションとして挿入されます:
全体を通じて、PostgreSQL サーバーが最大で 14 万回(144K)/秒のこうした書き込みを処理できることが分かりました。これは非常に高い値であり、1 日あたり約 120 億回の書き込みに相当します。
PostgreSQL のスケーラビリティの限界に達していることを確実にするため、さらなるパフォーマンス向上を制約するボトルネックも分析しました。まず CPU や IOPS といった高水準のメトリクスを確認しましたが、これらが十分に利用されていないことが分かりました。真のボトルネックを発見するために、内蔵の
pg_stat_activity テーブルをクエリして、その時点で各 PostgreSQL バックエンドプロセスが何を行っているかを inspect しました:
その結果、ボトルネックはディスクへの PostgreSQL ライト・アヘッド・ログ(WAL)フラッシュ速度にあることが分かりました。書き込みを行う際、PostgreSQL は直接ディスク上のデータページを更新することはしません。代わりに、まずその書き込みの記述を WAL に追加し、その後 WAL をディスクにフラッシュする(
fsync システムコールを使用)、そしてクライアントに対してコミット確認を行います。実際のデータファイルの更新はバックグラウンドプロセスで行われます。この設計により、比較的低コストな WAL 書き込みだけを同期処理とし、より高コストなディスク更新をバックグラウンドで行うことで、パフォーマンスが最大化されています。
PostgreSQL プロセスの活動状況を分析すると、どのような時点でも正確に 1 つのプロセスだけが WAL をディスクにフラッシュしている(グループコミットのため、他のプロセスからのデータも含めてバッファ全体をフラッシュ)ことが分かりました。残りのプロセスのほとんどは、自分のデータがフラッシュされるのを待つために WAL ロックで待機していました。パフォーマンスのボトルネックは、PostgreSQL が WAL エントリをディスクにフラッシュすることで書き込みトランザクションをコミットする速度によって決定されました。これは極めて書き込み intensive なワークロードにおけるよく知られたボトルネックであり、PostgreSQL では WAL が 1 つしかなく、すべての書き込みがこの経路を経る必要があるためです。
デュアブル ワークフロー パフォーマンス
次に、PostgreSQL をバックエンドとするデュアブル(耐久性のある)ワークフローのパフォーマンスを測定しました。デュアブル ワークフローは正確に 2 回の PostgreSQL 書き込みを実行します:
- ワークフロー開始時:データベースへのエントリ作成と入力データおよび初期ステータスの記録
- ワークフロー完了時:結果と最終ステータスの記録
ワークフローにステップがある場合、各ステップのチェックポイントを作成するために追加の 1 回分の書き込みも実行します。今回のベンチマークでは、ステップのない単純な No-op ワークフローを評価しました:
多数の非同期 Python クライアントから多数のワークフローを並行して開始しました:
全体を通じて、単一の PostgreSQL サーバーが最大で毎秒 43,000 件のワークフローを処理できることが分かりました。言い換えれば、1 秒あたり 43K のワークフローを実行しているアプリケーションに PostgreSQL を通じた耐久性を付与しても、パフォーマンスのボトルネックにはなりません。前回のベンチマークと同様に、さらなるパフォーマンス向上を制約するボトルネックを探すために、WAL におけるコミット速度(INSERT や UPDATE の WAL エントリをディスクへフラッシュする速度)が再びボトルネックであったことが分かりました。これは予想されることではなく、両方のワークロードは完全に書き込み主体であるためです。
生・PostgreSQL INSERT パフォーマンスとワークフローパフォーマンスの差を説明する 2 つの要因があります:
- ワークフローでは 2 回の書き込みが必要であり、したがって「43K ワークフロー/秒」という数字は実際には「86K PostgreSQL 書き込み/秒」に相当します。
テーブルは単純な書き込みベンチマーク用テーブルよりも大幅に大きく(31 カラム vs 3 カラム、9 インデックス vs 1)、そのテーブルへの更新ではより多くのデータをディスクにフラッシュする必要があり、それがボトルネックとなります。workflow_status
デュアブルキュー パフォーマンス
次に、PostgreSQL をバックエンドとするキューのスケーラビリティを測定しました。これは前回のベンチマークと似ていますが、クライアントが直接ワークフローを実行するのではなく、PostgreSQL キュー上にそれらをキューイングします。ワーカーはその後からキューを読み出し、実行を行います。これには 1 ワークフローあたり 4 回の PostgreSQL 書き込みが必要になります:
- キューへの追加時:データベースへのエントリ作成と入力データおよび初期ステータスの記録
- キューからの削除時:ステータスの更新(これは同じ実行器によって同時にキューから削除された他のすべてのワークフローの書き込みとバッチ処理されます)
- 削除されたワークフローを開始する際:ステータスの更新
- ワークフロー完了時:結果および最終ステータスの記録
全体を通じて、単一の PostgreSQL サーバーが最大で毎秒 12,100 件のキュー化ワークフローを処理できることが分かりました。再びパフォーマンスのボトルネックを探りました。興味深いことに、この回におけるボトルネックは WAL がなく、
workflow_status テーブルにおけるロック競合でした。すべてのクライアントプロセスは、キューの先頭にある同じ少数の行に対して追加操作または削除操作を実行しており、それらの間の競合がパフォーマンスを制限していました(SKIP LOCKED などの最適化があるにもかかわらず)。この問題は Python が相対的に非効率な言語であるため、PostgreSQL を飽和させるには多くのクライアントが必要であり、その結果としてキューの削除操作中に競合が発生しやすいことが原因だと仮説を立てています。より高速な言語(例:Go)では必要なクライアント数が少なくなり、その分キュー削除時の競合も少なくなるでしょう。
ロック競合によるボトルネックを排除するために、複数のキュー(または等価に言えば同じキューの複数のパーティション)にワークロードを分散することもテストしました。最大可实现ワークフロースループットは使用するキュー数とともに増加することが分かりました(逓減法則に従います)。結局のところ、十分な数のキューまたはパーティションを用いれば、キュー化ワークフローは 30.6K ワークフロー/秒のスループットを達成します。これは、直接ワークフローを開始して得られた 43K ワークフロー/秒の約 2/3 に相当し、キュー化ワークフローの方がより多くの書き込み(非バッチ処理が 3 回とバッチ処理が 1 回 vs 非バッチ処理が 2 回)を必要とするため妥当な結果です。このスケールにおいて、データベースのボトルネックは再び WAL にシフトします。
結論
総じて、このベンチマークは PostgreSQL が驚くほど優れたスケーラビリティを持つことを示しています。1 秒間に単一の PostgreSQL サーバーで 14 万回(144K)の小規模な書き込みを実行するか、または 43,000 の耐久性のあるワークフローを処理できます。これは 1 日あたり約 120 億回の書き込み、あるいは 40 億件のワークフローに相当し、ほとんどのアプリケーションにとって十分です。さらに高性能を求める場合、ワーカーロードを複数の PostgreSQL サーバー間にシャードすることで、ほぼあらゆる負荷に対応可能です。
詳細情報を知りたい方へ
スケーラブルで信頼性のあるシステムを構築することに興味がある場合は、是非お気軽にご連絡ください。DBOS では、耐久性のあるワークフローを可能な限りシンプルかつ高性能にすることを目標としています。以下をご覧ください:
- クイックスタート: https://docs.dbos.dev/quickstart
- GitHub: https://github.com/dbos-inc
- Discord コミュニティ: https://discord.gg/eMUHrvbu67