HN 日本語サマリー

← 一覧へ戻る
インフラ・DevOps

PgBouncerのスループットを4倍にスケールさせた方法

We scaled PgBouncer to 4x throughput (clickhouse.com)

220 pointsby saisrirampur53 コメント

要約

PgBouncerはデフォルトではシングルスレッドでCPUコアを一つしか使用しないため、スループットのボトルネックになりがちです。この記事では、`so_reuseport`と複数のPgBouncerプロセスを連携させ、さらに「ピーリング」機能でクエリキャンセル問題を解決することで、スループットを4倍に向上させるスケーリング手法を解説しています。この手法はClickHouse Managed Postgresでデフォルトで採用されています。

全文翻訳

PgBouncerはシングルスレッドです。マシンにいくらCPUコアがあっても、単一のプロセスは1つのCPUコアしか使用しません。16 vCPUのボックスでは、1つのコアがコネクションプーリングの全てを担当し、他の15個はアイドル状態になります。そして、Postgresがリソース不足になるずっと前に、プーラーはスループットの上限に達し始めます。 ClickHouse Managed Postgresでは、利用可能なコア数に比例してサイズ設定されたPgBouncerプロセスのフリートを実行します。フリート内の各プロセスは、`so_reuseport`を有効にして同じポートにバインドします。カーネルは、クライアントが単一のエンドポイントに接続し、その背後に複数のPgBouncerがあることを知らないままでいられるように、受信接続をプロセス間でロードバランシングします。これは、PgBouncer自身のドキュメントが複数のコアを使用するために言及しているメカニズムです。各プロセスはシングルスレッドであり、`so_reuseport`は全てのコアを活用する方法です。 ただし、問題があります。クエリのキャンセルです。 Postgresのキャンセルリクエストは、クエリを実行している接続とは別に、キャンセルキーを運ぶ全く新しい接続で到着します。`so_reuseport`を使用すると、カーネルはその新しい接続を、セッションを保持しているプロセスとは異なるプロセスに自由に渡すことができます。キャンセルリクエストはクエリを全く知らないプロセスに着信し、何も起こりません。 これを解決するのがピーリングです。プロセスは互いを認識するため、間違ったプロセスに着信したキャンセルリクエストは、セッションを実際に所有しているプロセスに転送されます。これにより、任意の要求がどこに着信しても、フリート全体でキャンセルが機能するようになります。 プーリングはトランザクションモードで実行されるため、トランザクションがコミットされた瞬間にサーバー接続はプールに戻されます。そして、接続予算はフリート全体で分割されます。`max_client_conn`と`max_db_connections`はプロセスの数で割られるため、フリート全体でPostgresを過剰にサブスクライブすることはありません。 実際のハードウェアで確認する 両方の構成を同一のAWS EC2インスタンスで実行しました。プーラーには16 vCPUのc7i.4xlarge、Postgresには別のボックス、そしてpgbenchでselect-only、トランザクションプーリングモードで負荷をかけるために3番目のボックスを使用しました。1つのプーラーボックスは単一のPgBouncerプロセスを実行し、もう1つは16個のフリートを実行しました。インスタンスタイプ、Postgres、ワークロードは全て同じです。唯一の変数だけが、1プロセス対16プロセスでした。 クライアント接続を8から256まで増やし、スループットと、各プーラーが16コアのボックスのどれだけを使用しているかを測定しました。 単一プロセスは、約87kトランザクション/秒でピークに達し、それ以上の負荷ではパフォーマンスが悪化し、256クライアント時には77kに低下します。これは、全ての処理が1つのコアに集中するためです。フリートは、より多くのコアを利用できるため、約336kトランザクション/秒、つまり約4倍まで上昇し続けます。 単一プロセスは、1コア以上の処理能力に達することはありません。負荷がかかると、pidstatはPgBouncerプロセスが約97%のCPUで固定されていることを示します。これはフルコアですが、16 vCPUのボックス全体の使用率は10%未満のままです。フリートはマシン全体に分散し、約8コアがビジー状態になり、Postgresとロードジェネレーターが制限になる時点でもまだヘッドルームがありました。 各ボックスに対して256クライアントを安定して維持します。単一プロセスボックスは、実行全体で約9%のCPU使用率で動作しますが、フリートは約52%を維持します。インスタンスタイプ、Postgres、ワークロードは同じです。一方の構成はマシンをアイドル状態にし、もう一方はそれを活用します。 EC2自身のCloudWatchメトリックも、ゲストの外側から同様のことを示しています。負荷中、単一プロセスインスタンスは平均約16%のCPU使用率ですが、フリートは約60%です。CloudWatchの数値はゲスト内の数値より少し高いですが、同じギャップが維持されています。16 vCPUを支払っているボックスで、単一のPgBouncerはほとんどのコアを無駄にしています。 接続の上限も同様に振る舞います。単一プロセスは、`max_client_conn`を独自に強制し、それを超えると新しいクライアントは拒否されます。 ``` 1FATAL: no more connections allowed (max_client_conn) ``` コピーコマンド 予算をフリート全体で分割することが、各プロセスとPostgresを安全な制限内に保ちながら、集合的な上限を引き上げることを可能にします。 | クライアント数 | 単一TPSS | 単一ボックスCPU | フリートTPS | フリートボックスCPU | |---|---|---|---|---| | 8 | 88,910 | 0.8% | 6,450 | 2.9% | | 32 | 54,203 | 5.2% | 64,244 | 12.3% | | 64 | 48,6570 | 8.3% | 219,439 | 31.9% | | 128 | 83,463 | 8.1% | 320,547 | 45.9% | | 256 | 76,893 | 7.7% | 336,469 | 48.9% | 接続数が少ない場合、単一プロセスは実際には問題なく、並列化するものがなくフリートの接続が分散しているため、わずかに速いことさえあります。ギャップが開くのは、まさに重要な場所、つまり1つのコアが壁となる実際の同時実行性下です。 結論 プーラーがPostgresではなくスループットの上限を定めるようになるまで、単一のPgBouncerは問題ないデフォルトです。フリートをコア数に合わせてサイジングし、`so_reuseport`で単一ポートを共有し、ピーリングでプロセスを連携させることで、プーラーはボトルネックではなく配管に戻ります。 全てのClickHouse Managed Postgresサーバーは、このセットアップをデフォルトで出荷しています。Postgresをプロビジョニングして、実際に動作を確認してください。 ClickHouseによるPostgresを試す ClickHouse + Postgresは、スケーリングするアプリケーションのための統合データスタックになりました。ClickHouse CloudでManaged Postgresが利用可能になったことで、このスタックは初日から決定的な選択肢となります。 サインアップ