- PostgreSQL拡張プロキシのPgDogが、SQLパース性能を高めるためProtobufシリアライズの代わりにRustの直接バインディングを導入
- 従来のProtobufベース構造を**C–Rust直接変換(bindgen + Claude生成ラッパー)**に置き換え、パース5.45倍、デパース9.64倍の高速化を実現
- 性能ボトルネックはpg_query_parse_protobuf関数で見つかり、キャッシュを試みた後も根本改善のため構造変更を実施
- Claude LLMを活用して6,000行のRust–C変換コードを自動生成し、
parse、deparse、fingerprint、scanなど主要関数に適用
- この最適化により、PgDogのCPU使用量とレイテンシが低下し、PostgreSQL水平スケーリングプロキシとしての効率が大幅に向上
PgDogとProtobufの限界
- PgDogはPostgreSQLをスケールさせるためのプロキシで、内部ではlibpg_queryを使ってSQLクエリをパースしている
- Rustで書かれており、従来はProtobufのシリアライズ/デシリアライズを通じてCライブラリと通信していた
- Protobufは高速だが、直接バインディング方式のほうがさらに速い
- PgDogチームは
pg_query.rsをフォークしてProtobufを取り除き、C–Rust直接バインディングを実装
- その結果、クエリパースは5.45倍、デパースは9.64倍高速化した
ベンチマーク結果
- ベンチマークはPgDogのフォークリポジトリで再現可能
pg_query::parse (Protobuf): 613 QPS
pg_query::parse_raw (直接C–Rust): 3357 QPS
pg_query::deparse (Protobuf): 759 QPS
pg_query::deparse_raw (直接Rust–C): 7319 QPS
性能ボトルネック分析とキャッシュの試み
- samplyプロファイラでCPU使用時間を分析した結果、pg_query_parse_protobuf関数がボトルネックであることが確認された
- キャッシュによる一部改善も試みた
- LRUアルゴリズムベースのハッシュマップキャッシュを使い、クエリテキストをキーにASTを保存
- プリペアドステートメントを使う場合は再利用が可能
- しかし一部のORMは数千のユニーククエリを生成し、古いPostgreSQLドライバはプリペアドステートメントをサポートしないため、キャッシュ効率は低かった
LLMを活用したProtobufの除去
- PgDogチームはClaude LLMを活用して、Protobufを除去したRustバインディングを生成
- 明確で検証可能な作業範囲では、AIは効果的に機能した
- Claudeは
libpg_queryのProtobuf仕様に基づいてC構造体をRust構造体へマッピング
- 2日間の反復作業の末、6,000行の再帰的なRustコードが完成
parse、deparse、fingerprint、scan関数に適用し、pgbench基準で25%の性能向上を確認
実装の詳細構造
- RustとCの間の変換はunsafe関数を使って構造体を直接マッピング
- C構造体をPostgres APIに渡してASTを生成し、Rustへ再帰変換する
- 各ASTノードはconvert_node関数で処理され、SQL文法の数百のトークンをマッピング
- SELECT、INSERTなど各ノードタイプごとに個別の変換関数が存在
- 変換結果には既存のProtobuf構造体(
protobuf::ParseResult)を再利用し、テスト時にバイト単位の比較検証が可能
- 再帰アルゴリズムはメモリ割り当てが少なくCPUキャッシュ効率が高いため、反復ベース実装より高速
- 反復ベース実装は不要なメモリ割り当てとハッシュマップ参照により、かえって遅くなる
結論
- Postgresパーサのオーバーヘッドを減らし、PgDogのレイテンシ、メモリ、CPU使用量をすべて削減
- この最適化により、PgDogはより高速かつ低コストで運用可能なPostgreSQLスケーリングプロキシへ進化
- PgDogはPostgreSQLの**水平スケーリング(next iteration)**をともに構築するエンジニアを募集中
3件のコメント
私が原文を曲解しているのかもしれませんが、ことさらに Rust 関連の記事は、本質から離れて、まるで「Rustだから」速くなったかのように書かれている気がします。
今回の記事の主なポイントは、不要なシリアライズのオーバーヘッドを減らしたことで性能が改善した、という点ですよね。
今あらためて見ると、そこまで Rust を称賛する記事でもない気もしますが、ほかの記事の影響で私にネガティブな印象ができてしまっているのでしょうか?
私もこれ、元記事のタイトルが実際の内容と違って Rust に寄りすぎていて、性能向上に焦点を当てているように見えたので、少し修正しました。
Rust の記事はそういう傾向がよく見られるので、少しフィルタリングしながら読む必要はある気がします。
Hacker News の反応
タイトルだけ見ると Rust が 5倍の性能向上 をもたらしたように見えるが、実際には遅くなっていたというのが皮肉
問題は、Rust で書かれたソフトウェアが C 製の libpg_query を使う必要があったものの、直接接続できず、Protobuf ベースの Rust–C バインディングを使っていたこと
この方式が遅かったため、最終的に LLM の助けを借りて、移植性は低いがはるかに最適化されたバインディングを書き直した
もし最初から C で書いていれば変換工程は不要だったはず。つまりタイトルは「Rust の利用による性能損失を減らした」のほうが正確だったはず
変換レイヤーは移植性と安全性をもたらすが、結局 コピー・変換・シリアライズ が繰り返され、アプリを遅くする原因の1つだと思う
Rust から C ライブラリを呼ぶのは非常に簡単で、安全なラッパーもすでに多く存在する
Protobuf を間に挟む構成はほとんど見たことがなく、それがボトルネックだった
タイトルは単にクリックを誘うための「Rust で書き直した」系のミームに近いと思う
元のライブラリが シリアライズ/デシリアライズ を繰り返す誤った設計になっていて、それを取り除いたのが本質
タイトルは「Protobuf を通常の API に置き換えたら 5倍速くなった」のほうが正確
Rust での C バインディングは最も簡単な部類で、大きな API でなければ単純
Protobuf はメモリ内データ交換には 不適切なツール だと思う
今後は LLM のおかげで、さまざまな言語への移植が爆発的に増えそう
タイトルはやや誤解を招く
実際には「Protobuf のシリアライズ段階を取り除いたら速くなった」という内容
クライアントとサーバーが独立して更新されても動作できるようにし、複数言語間の通信も容易にする
大規模システムではこうした柔軟性が非常に重要
memcpyやmmapのほうがはるかに速いが、Rust 界隈ではそうした unsafe な方式 を嫌うRust のせいではなく、Protobuf を 汎用保存フォーマット として使ったことが遅さの原因かもしれない
結局のところ、特定の目的に合わせて単純化したのが核心
Rust をタイトルに入れたのはクリック誘導のための選択に見える
pg_query の元の作者が背景を説明している
もともと pganalyze で Postgres クエリをパースしてテーブル参照を見つけ、クエリを書き換えたり整形したりする用途で使っていた
初期は JSON を使っていたが、その後 Protobuf に移行 し、複数言語(Ruby、Go、Rust、Python など)で 型安全なバインディング を容易に提供するためだった
Rust のような言語では FFI のほうがよいが、他の言語では保守負担が大きい
Lev の試みは支持しており、今後 libpg_query に直接 FFI でアクセスできる関数 を追加する予定
ただし性能が重要でない場合は、Protobuf が依然としてより 便利な選択肢 でもある
「5倍速い」という言葉で、Cap’n Proto の「無限に速い」という冗談を思い出した
タイトルは大げさだが、実際の作業は印象的
Protobuf を完全になくしたのではなく、使い方を最適化 したという話
「X に変えたら 5倍速くなった」という文句は、たいてい「ひどい実装を直した」という意味
核心となる教訓は
Rust FFI にもオーバーヘッドはあるため、本当の成果は言語ではなく データフローの再設計 と最適化の努力によるもの
FlatBuffers のほうが速いが、Protobuf が使われる理由は 大企業が保守している からだ
結局「Google 製だから安全」という認識には根拠がない
単に メモリ共有とバージョンフィールドだけを持つ zero-copy 構造 で十分なのに、あえて Protobuf を使う理由はないと思う
Protobuf の性能は 冗談レベル だと思う
シリアライズが事実上無料な zero-copy フォーマット を使うべき
たとえば自分が作った Lite³ は FlatBuffers より 242倍速い
Protobuf が使われるのは、エコシステム、スキーマ、言語ごとのツール群など、数多くの現実的な理由があるから
実際には Rust や Protobuf の問題ではなく、PostgreSQL 抽象化レイヤーの 非効率なシリアライズ実装 が原因だった
pgdog はそのレイヤーを取り除き、C API で直接データを渡した
不要な機能を削れば当然速くなる
ただし、人によっては依然として シリアライズが必要な状況 がある
そういう人たちに「Rust に変えろ」というタイトルは誤ったメッセージになる
結局、ほとんどの場合は JSON で十分 で、本当にもっと速さが必要ならシリアライズ自体を避けるべき
これは 不公平な比較 だ
IPC 通信にシリアライズプロトコルを使えば、当然オーバーヘッドがある
「20%速くなれば改善、10倍速くなれば最初から作り方が間違っていた」という言葉がぴったり当てはまる例