1 ポイント 投稿者 GN⁺ 5 시간 전 | 1件のコメント | WhatsAppで共有
  • ClickHouse Managed Postgres では、シングルスレッドの PgBouncer が1つの CPU コアに縛られる制約を避けるため、コア数に比例したマルチプロセスのフリートを標準構成として運用している
  • すべてのプロセスが so_reuseport で同じポートにバインドされ、カーネルが新規接続を分散するため、クライアントからは単一のエンドポイントに見える
  • 別接続で入ってくる Postgres のキャンセルリクエストが、セッションを保持していないプロセスに到着する可能性があるため、プロセスピアリングで実際のセッション所有者へ転送する
  • 16-vCPU の c7i.4xlarge では、単一プロセスは約 87,000 TPS がピークだったが、16プロセスのフリートは約 336,000 TPS まで伸び、約4倍のスループットを記録した
  • 接続数が少ない場合は単一プロセスが同等または少し速いこともあるが、高い並行性では1つのコアがボトルネックになるため、接続予算をフリート全体に分け、Postgres を過剰接続にせずにスループットと接続上限を拡張する必要がある

単一プロセスが生む CPU ボトルネック

  • PgBouncer はシングルスレッドであるため、システムの CPU 数に関係なく、1つのプロセスは1つのコアしか使わない
  • 16-vCPU サーバーでも接続プーリング処理は1つのコアに集中し、残りの15コアはアイドル状態になる
    • そのため Postgres の処理余力が尽きる前に、プーラーが全体のスループットを制限する
  • ClickHouse Managed Postgres は、利用可能なコア数に比例して PgBouncer のプロセス数を決めるプロセスフリートを運用している

so_reuseport を使った接続分散

  • フリート内のすべてのプロセスは so_reuseport を有効化し、同じポートにバインドする
  • カーネルが受信接続を各プロセスに分散するため、クライアントは1つのエンドポイントに接続すればよく、背後で複数の PgBouncer が動作していることを知る必要はない
  • PgBouncer のドキュメントでも、複数のシングルスレッドプロセスで複数コアを活用する方法として so_reuseport を案内している

クエリキャンセルを保証するプロセスピアリング

  • Postgres のキャンセルリクエストは、実行中のクエリ接続とは別の新しい接続を通じ、キャンセルキーとともに入ってくる
  • so_reuseport を使うと、カーネルが新しいキャンセル接続を、そのセッションを保持しているプロセスではなく別のプロセスに渡す可能性がある
    • リクエストを受けたプロセスが該当クエリを知らなければ、キャンセルは実行されない
  • PgBouncer プロセス同士をピアリングすると、誤ったプロセスに到着したリクエストを実際にセッションを所有するプロセスへ転送できる
  • これにより、どのプロセスがリクエストを受けても、フリート全体でクエリキャンセルが正常に動作する

トランザクションプーリングと接続予算

  • プーリングはトランザクションモードで動作し、トランザクションがコミットされるとすぐにサーバー接続がプールへ返される
  • max_client_connmax_db_connections はプロセス数で割り、各プロセスに割り当てる
  • 接続予算を分割すると、フリート全体の接続数を Postgres の安全な上限内に保ちながら、集約した接続上限を引き上げられる
  • 単一プロセスは自身の max_client_conn を超える新規クライアントを、次のエラーとともに拒否する
    • FATAL: no more connections allowed (max_client_conn)

同一ハードウェアでのベンチマーク

  • 2つの構成は同一の AWS EC2 環境で測定した
    • プーラーサーバー: 16-vCPU c7i.4xlarge
    • Postgres: 別サーバー
    • 負荷生成器: 3台目のサーバーで pgbench を使用
    • ワークロード: 読み取り専用(select-only)、トランザクションプーリングモード
  • 比較対象は PgBouncer の単一プロセスと16プロセスのフリートで、インスタンスタイプ・Postgres・ワークロードは同一
  • クライアント接続を8から256まで増やしながら、スループットと16コアサーバーの CPU 使用率を測定した
クライアント 単一プロセス TPS 単一プロセス サーバー CPU フリート TPS フリート サーバー CPU
8 8,910 0.8% 6,450 2.9%
32 54,203 5.2% 64,244 12.3%
64 86,570 8.3% 219,439 31.9%
128 83,463 8.1% 320,547 45.9%
256 76,893 7.7% 336,469 48.9%

スループットと CPU 活用の結果

  • 単一プロセスは約 87,000 TPS でピークに達した後、負荷が増えるほど性能が低下し、クライアント256では約 77,000 TPS を記録した
  • 16プロセスのフリートはより多くのコアを活用しながら約 336,000 TPS まで増加し、単一プロセス比で約4倍のスループットを示した
  • pidstat の測定では、単一の PgBouncer プロセスは負荷中に CPU 約97%で1つのコアを使い切っていたが、16-vCPU サーバー全体の使用率は10%未満にとどまった
  • フリートはサーバー全体に処理を分散して約8コアを使用し、Postgres と負荷生成器が限界に達した時点でも追加の余力が残っていた
  • クライアント256を維持した測定では、単一プロセスのサーバーが約9%、フリートのサーバーが約 52% CPU を継続的に使用した
  • EC2 CloudWatch の外部測定では、単一プロセスインスタンスの平均 CPUUtilization は約16%、フリートは約60%だった
    • CloudWatch の値はゲスト内部の測定より少し高いが、単一の PgBouncer が 16-vCPU の大部分を活用できないという違いは同じである

並行性に応じた構成選択

  • 接続数が少ない場合は並列化する処理がなく、フリートの接続が複数プロセスに分散されるため、単一プロセスで十分、または少し速い場合がある
    • クライアント8では単一プロセスが 8,910 TPS、フリートが 6,450 TPS を記録した
  • 並行性が高まると、単一プロセスが占有する1つのコアがスループットの壁となり、フリートとの性能差が広がる
  • PgBouncer より先に Postgres が限界に達する環境では、単一プロセスも妥当なデフォルトである
  • プーラーがスループットを制限し始めたら、コア数に合わせたプロセスフリート、so_reuseport ベースのポート共有、プロセスピアリングを組み合わせて使うことでボトルネックを解消できる
  • すべての ClickHouse Managed Postgres サーバーは、この構成を標準で提供している

1件のコメント

 
GN⁺ 5 시간 전
Hacker Newsのコメント
  • https://github.com/yandex/odysseyを使えばよい。OdysseyはスケーラブルなPgBouncerである

    • 最も実績があり、PostgreSQLに自然に適合するPgBouncerをまず適切に調整して使っており、prepared statement対応のような長年の課題も解消されて、とてもよく動いている。
      PostgreSQL接続を1万超まで拡張した顧客も多く、今後はOdysseyやpgdogのような代替案も検討する予定である。ただ個人的には、PostgreSQLに1万を超える接続を置く方式は好まず、数百接続でも十分にスケールできると考えている
    • 関連する興味深い事実として、ClickHouseももともとYandexが開発した
    • Yandexはロシア企業なので、使わないほうがよいと思う
  • KubernetesでPgBouncerを運用すれば、1台のマシンで複数プロセスを立ち上げるのも簡単で、複数マシンに分散するのも容易である。
    AzureがVMメンテナンスの過程でサーバ群全体に順次障害を起こすことがあるため、マルチマシン構成は特に有用である

    • PgBouncerからPostgreSQLまでネットワークホップが1つ増えると、スループットにどのような影響があるのか気になる。差は小さいと予想するが、確認する価値はある
  • ここで言うピアリングの概念は理解できるが、PostgreSQLで使ったことはない。
    A) PostgreSQLにはこれを簡単に構成できるモードや設定があるのか。エラーが出なくなるまでキャンセル要求をピアへ順番に転送するのか、あるいは誤って受け取ったプロセスが正しいプロセスを識別できるメタデータがキャンセル要求に含まれているのか、という構造を想像している。
    B) すべてのPostgreSQLプロセスがso_reuseportでクライアント要求を受けるなら、ピア間通信には別の**プロセス間通信(IPC)**手段が必要に見えるが、実際にはどのような仕組みを使っているのか?

  • pgdogを使っているが、必要な用途に非常によく合っている

    • Pgdogは、以前Instacartで直面したPgBouncerの欠点を解消し、PostgreSQLを水平スケールさせるより堅牢な基盤、つまりシャーディングを提供するために作られた
    • 今後はpgdogの提供も検討する予定である。PgBouncerを選んだ理由は https://news.ycombinator.com/item?id=48873867 により詳しく書かれている
    • 最近はPostgreSQLのリバースプロキシプロジェクトが次々と紹介されている。Pgdogを作った理由はここで見られる: https://news.ycombinator.com/item?id=48819308
  • PgBouncerがなぜキャンセル要求まで気にする必要があるのか気になる。PostgreSQLへそのまま渡し、キャンセルされたクエリに対してPostgreSQLが通常応答の代わりにエラーを返せば、その接続を担当するPgBouncerが処理すればよいのではないか?

    • クエリのキャンセルは既存のクエリ接続とは別経路で送られるため、PgBouncerが要求を正しいサーバーと接続へ送らなければならない。
      PgBouncerはクライアントに偽ではあるが追跡可能なキャンセルPIDとシークレット値を渡し、キャンセル要求が戻ってくると実際のサーバー・プロセスのPIDとシークレット値を見つけて転送する。また、クライアントはクエリが実行中だと信じていても、PgBouncerはすでに終了して接続が再利用されたことを知っている場合があるため、その接続とPIDが本当にまだそのクエリを実行中かどうかも確認しなければならない
  • これはマイクロサービスで、接続プールを通じてPostgreSQLサーバーへのアクセスを制御する用途なのか? モノリシックなバックエンドなら必要なさそうに見える。
    たいていのまともなバックエンドフレームワークには接続プールが内蔵されており、マイクロサービスが必要でも推奨でもないユースケースの98%はこれで解決できる

    • ホストごとにバックエンドアプリケーションが1つの大きなバイナリとして動くならその通りだ。ただし、バックエンドが状態を共有しない多数のforkプロセスで構成されているなら、明らかに必要である
  • PgBouncerは素晴らしいソフトウェアである。広く活用しており、データベース運用をかなり簡単にしてくれる

  • so_reuseportを初めて知ったが興味深い。構成の要はこれとピアリングに見えるが、ピアリングはPgBouncerに組み込まれていて設定も簡単なのか?

  • Kubernetesでもピアリングを使えるのか? この環境ではポートを再利用する必要はなさそうだが、各Podが個別の接続プールを持って独立して動作することになるのかも気になる

  • 今46歳だが、23歳のときにPostgreSQLの重い接続モデルを見て衝撃を受けた記憶がある。その後も改善されていないのか?

    • かなり改善されており、今では数千接続もかなりうまく処理できる: https://techcommunity.microsoft.com/blog/adforpostgresql/imp...
      ただし接続プールを使わないと、PostgreSQLはプロセスをforkしなければならないため、新規接続の作成には常に数十ミリ秒以上のコストがかかる。接続プールなしで書かれたアプリケーションも理想的ではないが、よく存在する。
      アプリケーションフレームワークも変化しており、サーバーレスアーキテクチャではPostgreSQLが問題を抱え始める数万接続を生み出しうる。個人的には数百を超える接続は好まないが、今では十分あり得る状況である
    • 当時は、多くの企業がJavaの提示したスレッディングモデルに対して初めて実用的な答えを出しつつあった時期だった。これはWindowsのモデルに機能を追加したようなもので、SolarisでさえJava処理には苦労しており、HP-UXはさらに厳しかったし、SGIがHPより良かったかどうかは覚えていない。
      Java互換性を確保する過程で、多くの企業が並行性処理の方式を大きく変えなければならなかった。PostgreSQLとSQLiteも似た時期に設計されたが、このようなスレッド論争が起こる前から高負荷システムを展開してきた業界のベテランたちが、古いハードウェア利用者まで支援しながら作ったシステムだった