HN 日本語サマリー

← 一覧へ戻る
プログラミング

なぜ私たちはまた別のPostgres接続プーラーを構築したのか

Why we built yet another Postgres connection pooler (pgdog.dev)

228 pointsby levkk58 コメント

要約

PgDogはPostgresのスケーリングのためのプロキシであり、その機能の一つに接続プーリングがあります。既存の接続プーラー(PgBouncerなど)は、UNIX哲学に従い単機能で優れていますが、「リーキー抽象(leaky abstraction)」を生み出し、データベースの使用方法を変更したり、アプリケーションコードの修正を必要としたりすることがあります。PgDogは、SETコマンドやLISTEN/NOTIFYといったPostgresのセッション状態やPub/Sub機能を透過的に扱い、アプリケーションコードの変更なしに接続プーリングの恩恵を受けられるように設計されています。

全文翻訳

PgDogはPostgresをスケーリングするためのプロキシです。その機能の一つに接続プーリングがあり、多数のクライアントアプリケーションがデータベースの接続制限を超えずに同じデータベースを使用できるようになります。PgBouncer、RDS Proxy、Pgpool-II、Supavisorなど、多くの接続プーラーが存在します。では、なぜ私たちはもう一つ構築したのでしょうか? リーキー抽象 Postgresエコシステムの多くのツールは、UNIX哲学を非常に真剣に受け止めています。「一つのことを行い、それをうまく行う」という哲学です。一般的に、これは信頼性の高いソフトウェアを構築するための良い方法であり、PgBouncerが今日これほど人気がある理由です。それは機能しますが、それは私たちが業界で「リーキー抽象」と呼ぶものを導入することによって機能します。PgBouncerやRDS Proxyをデプロイすると、データベースの使用方法を変更するインフラストラクチャを追加したことにすぐに気づくでしょう。それはトレードオフを要求し、多くの場合、アプリケーションコードを変更する必要があります。もしあなたがアプリをしばらく構築していて、接続プーリングを追加する必要がある場合、それは通常、テストカバレッジが非常に少ない、数千行のプロダクションコードを変更することを意味するかもしれません。PgDogを使えば、それはもはや必要ありません。 接続状態 接続プーラーを追加すると最初に失われるのはセッション制御、つまりSETコマンドです。これらは、例えば遅いクエリを実行するためにデータベース設定を一時的に上書きするために使用されます。 SET statement_timeout TO '5m'; SELECT * FROM users WHERE banned IS true; 接続プーラーはクライアント間で接続を再利用するため、あるクライアントの接続状態が別のクライアントの接続状態に「漏れ」ます。本番環境では、これは非常に悪い結果を招く可能性があります。最良の場合、遅いクエリがしばらく実行され、データベースのインシデントを引き起こします。最悪の場合、セッション変数に依存する行レベルセキュリティ(RLS)ポリシーが機能しなくなり、行がサイレントに消えてしまいます。したがって、接続プーリングに移行する際の一般的なアドバイスは、SETを使わないことです。しかし、SETは実際のPostgresの機能です。データベース機能の使用が許可されない場合、トレードオフを行い、アプリの書き方を変える必要があります。そして、RLSのような重要な目的で使用している場合、接続プーリングを全く使用できません。 SETステートメントの処理 PgDogには、組み込みのSQLパーサーが付属しています。SETステートメントを検出し、変数名と値を抽出し、それらをプロキシ内の各クライアント接続に保存できます。クライアントがクエリを実行すると、PgDogはまずその状態がサーバーの状態と一致するかどうかを確認し、一致しない場合は、独自のSETステートメントのシリーズを実行して状態を更新します。SETステートメントを検出するために使用するアルゴリズムは非常に高速です。複数の変数が異なる場合、クエリパイプラインを使用して1回のラウンドトリップで更新します。これによりパフォーマンスへの影響は小さくなり、スケーリングする際に依存しているPostgresの機能を引き続き使用できます。 LISTEN/NOTIFY LISTENとNOTIFYは、Postgres内にパブリッシュ/サブスクライブキューを実装するPostgresコマンドです。これは便利な機能であり、別のデータベースをスタックに追加することなく pretty well機能します。また、プーラーを追加した場合、少なくともトランザクションモードを使用したい場合は、諦めなければならない機能でもあります。過去10年以内にアプリを構築した場合(PostgreSQL 10は2017年にリリースされました)、SQSやRedisに移行する前に、おそらくこれを使用したでしょう。PgDogはそれも機能させます。コマンドを内部で処理しながら、メッセージを複数のPgDogプロセス間で移動させます。クライアントにとっては、PgDogがブローカーであるように見えますが、実際には依然としてPostgresです。また、一部の友人がデータベースを停止させた原因となったトランザクションセマンティクス(ちなみに、私たちはそれを修正しました)もすべて保持するようにしています。内部実装は興味深いものです。同じPgDogプロセス内のクライアント間でメッセージを移動するためにTokioのブロードキャストチャネルを使用しています。複数のPgDogプロセス(例えば、本番環境では、いくつかのコンテナを実行するでしょう)をサポートするために、すべてのLISTENおよびNOTIFYコマンドを専用接続を通じてPostgresにも送信します。つまり、実質的にPgDogは、Postgresをブローカーとして使用し、他のPub/SubクライアントをプロキシするPub/Subクライアントとして機能します。私たちが使用する機能を「ただ機能させる」ために、少しクリエイティブなエンジニアリングを行ったのです。Postgresをスケーリングするために。 マルチスレッド PgDogは、マルチスレッドワーカーを備えたRustの非同期ランタイムであるTokio上に構築されています。各クライアントは独自の非同期タスクによって処理され、接続数に応じて線形にスケーリングします。Tokioを使用することで、複数のCPUを活用し、1つのPgDogプロセスからより多くのクライアントと1秒あたりのクエリ数を処理できます。しかし、PgBouncerがSO_REUSEPORTをサポートし、RDS Proxyが「サーバーレス」オートスケーリングを備えているのに、なぜこれが重要なのでしょうか?どちらのツールも「シャーディング」された接続プールを必要とします。各プロキシプロセスは独自の専用Postgres接続セットを持っています。クライアントが接続すると、インスタンスを変更できなくなるため、過負荷になると他のすべてのクライアントも立ち往生します。複数のCPUでマルチスレッド化することにより、PgDogプロセスははるかに多くのトラフィックを処理できます。これにより、より少ないサーバー接続でより多くのクライアントをプールでき、接続の利用率と効率が向上します。マルチスレッドプロセスは、オートスケーリングを待つ必要がないため、クエリの突然のバーストもより良く処理できます。アプリにレイテンシSLAがある場合、プロキシが邪魔になることを望まないでしょう。最後に、マルチスレッドプロセスを管理するためのランブックは短くなります。単一のメトリクスとヘルスチェックのソースであり、複雑さをLinuxカーネルやAWS RDSコントロールプレーンのようなデバッグが困難な場所に押し込む必要はありません。 締めくくり PgBouncerのような20年前のプロジェクトを置き換えるのは難しいかもしれませんが、何かが正しくないと思ったときは、それを修正します。PgDogは1年以上本番環境で使用されており、毎秒200万クエリで接続をプールしています。それは自由(使用と変更の自由という意味で)であり、オープンソースであり、どこにでもデプロイできます。