Skip to content

ClickHouse Managed PostgresでPgBouncerをスケールさせる方法

image 512x512 1
2026年7月1日 · 7分で読む

PgBouncerはシングルスレッドです。マシンにコアがいくつあっても、1つのプロセスが使うCPUコアは1つだけです。16 vCPUのマシンでは、1つのコアがコネクションプーリングをすべて担い、残りの15コアは遊んだままになります。そしてPostgresの余力が尽きるはるか前に、プーラーがスループットの上限になります。

ClickHouse Managed Postgresでは、利用可能なコア数に比例した数のPgBouncerプロセスをフリートとして動かしています。

フリート内のすべてのプロセスは、so_reuseportを有効にして同じポートにバインドします。カーネルが着信接続をプロセス間で負荷分散するため、クライアントは従来どおり単一のエンドポイントに接続し、その裏に複数のPgBouncerがいることを意識しません。複数コアを使う方法としてPgBouncerの公式ドキュメントが示しているのが、まさにこの仕組みです。PgBouncerはプロセス単位ではシングルスレッドであり、すべてのコアを働かせる手段がso_reuseportです。

pgbouncer_jul2026_image5.png

落とし穴: クエリのキャンセル

Postgresのキャンセル要求は、クエリを実行している接続とは別の新しい接続で、キャンセルキーを携えて届きます。so_reuseportを使うと、カーネルはその新しい接続を、セッションを保持しているプロセスとは別のプロセスに渡すことがあります。キャンセルはそのクエリを知らないプロセスに届き、何も起こりません。

これを解決するのがピアリングです。プロセス同士が互いを認識しているため、担当外のプロセスに届いたキャンセルは、実際にセッションを持つプロセスへ転送されます。要求がどのプロセスに届いても、フリート全体でキャンセルが機能します。

プーリングはトランザクションモードで動作するため、サーバー接続はトランザクションのコミットと同時にプールへ返却されます。また、接続数の枠はフリート全体で分け合います。max_client_connmax_db_connectionsをプロセス数で割るため、フリート全体としてPostgresに過剰な接続を張ることはありません。

実機で確かめる

両方の構成を、同じタイプのAWS EC2インスタンスで動かしました。プーラーには16 vCPUのc7i.4xlarge、Postgresには別のマシン、負荷生成には3台目のマシンを使い、pgbenchをselect-onlyかつトランザクションプーリングのモードで実行しました。プーラー用マシンの一方は単一のPgBouncerプロセスを、もう一方は16プロセスのフリートを動かしています。インスタンスタイプもPostgresもワークロードも同じで、違いはプロセスが1つか16かだけです。

クライアント接続数を8から256まで段階的に増やし、スループットと、各プーラーが16コアのマシンを実際にどれだけ使ったかを測定しました。

単一プロセスは約87kトランザクション/秒でピークに達し、その後は負荷を増やすほど悪化します。すべてが1つのコアを取り合うため、256クライアントでは77kまで落ち込みます。フリートは使えるコアが多いため伸び続け、およそ336kトランザクション/秒、約4倍に達します。

単一プロセスは、およそ1コア分の仕事量を超えられません。負荷をかけると、pidstatではPgBouncerプロセスがCPU約97%、つまりまるまる1コアに張り付いているのに対し、16 vCPUのマシン全体の使用率は10%未満にとどまります。フリートはマシン全体に広がり、およそ8コアが稼働する状態に達します。しかもPostgresと負荷生成側が限界になった時点でも、まだ余力を残しています。

各マシンのクライアント数を256に固定すると、単一プロセスのマシンは実行中ずっとCPU 9%前後で推移し、フリートは52%前後を維持します。インスタンスタイプもPostgresもワークロードも同じです。一方の構成はマシンを遊ばせ、もう一方はマシンを働かせています。

EC2側のCloudWatchメトリクスも、ゲストの外側から同じことを示しています。負荷をかけている間、単一プロセスのインスタンスのCPUUtilizationは平均約16%、フリートは約60%です。CloudWatchの値はゲスト内の数値よりやや高めに出ますが、両者の開きは変わりません。16 vCPU分の料金を払っているマシンで、単一のPgBouncerはそのほとんどを使わずに捨てています。

接続数の上限も同じ挙動です。単一プロセスはmax_client_connを単独で適用し、これを超えると新しいクライアントは拒否されます。

FATAL:  no more connections allowed (max_client_conn)

接続数の枠をフリートで分割することで、各プロセスとPostgresを安全な範囲に保ちながら、全体の上限を引き上げられます。

クライアント数単一 TPS単一 マシンCPUフリート TPSフリート マシンCPU
88,9100.8%6,4502.9%
3254,2035.2%64,24412.3%
6486,5708.3%219,43931.9%
12883,4638.1%320,54745.9%
25676,8937.7%336,46948.9%

接続数がごく少ないなら、単一プロセスでも十分で、むしろわずかに速いほどです。並列化する余地がなく、フリート側では接続が各プロセスにまばらに散るためです。差が開くのは、1つのコアが壁になる実際の高並列の状況、つまり本当に重要な場面です。

まとめ

スループットの上限がPostgresではなくプーラーになるまでは、単一のPgBouncerが既定構成として十分に機能します。コア数に合わせたフリートを用意し、so_reuseportで1つのポートを共有し、ピアリングでプロセス同士をつなげば、プーラーはボトルネックではなく、再び単なる配管に戻ります。

ClickHouse Managed Postgresのすべてのサーバーには、この構成が既定で組み込まれています。Postgresをプロビジョニングして、実際の動きをお確かめください。

ClickHouse Managed Postgresを試す

ClickHouse + Postgresは、スケールするアプリケーションのための統合データスタックになりました。Managed PostgresがClickHouse Cloudで利用できるようになった今、このスタックは開発初日から採用できる選択肢です。

サインアップ

この記事をシェア

  • Y Combinator icon
  • X icon
  • Bluesky icon
  • Facebook icon
  • LinkedIn icon

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!

Follow us

XBlueskySlackGithubTelegramMeetupRSS