
2026/08/07 20:00
Postgres の分析処理を 300 倍高速化:バッチ処理、演算子融合、SIMD を活用したアプローチ
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
バージョン 0.2 の先週リリースにより、pgrust は重大なパフォーマンスの飛躍を達成し、Clickbench では 30 倍の高速化を実現するとともに、分析ワークロードにおいて PostgreSQL および Clickhouse を凌駕しました。ベンチマーク結果では、前バージョン比で 10 倍の改善が見られ、OLTP タスクにおいては Postgres に対して 30% の性能向上を示しています。これらの成果は、CPU およびメモリのボトルネックに対処するためにクエリエンジンの内部アーキテクチャを最適化し、NVMe ストレージに特有なディスク I/O の制約から焦点を移すことで実現されています。従来の PostgreSQL で採用されていた行単位(Volcano モデル)による処理手法を放棄し、代わりにデータを 1024 行分のバッチにバッファリングすることでオペレーターを融合化してコピーオーバーヘッドを排除し、AArch64 チップ上で SIMD 命令を活用するといった主要な最適化を実施しました。これらの変更により、Postgres で約 20 秒かかっていた 5 億個の数値の総和処理時間は、135ms に短縮され(約 9.6 倍の高速化)、高性能な Rust アルティネーティブとして業界 гигアンを強く挑戦しています。JIT コンパイルによる動的オペレーター融合は将来の機能向上として議論されていますが、現在の能力だけでも AWS Graviton4 インスタンスなどの現行ハードウェア上で実用的な高速化を実現しています。
本文
pgrust v0.2 リリース:パフォーマンス 10 倍化と ClickHouse を凌ぐ速度を実現した改修
先週、pgrust バージョン 0.2 がリリースされました。今回のリリースでは、すべてのリソースをパフォーマンスの向上に注力しました。
🚀 パフォーマンス結果
以前のバージョンと比較して、pgrust は全体的に 10 倍も高速化されました。主なベンチマーク結果は以下の通りです。
- OLTP(トランザクション処理)ベンチマーク: PostgreSQL より 30% 高速。
- Clickbench(分析用データベースベンチマーク): PostgreSQL より驚くべき 300 倍の高速化 を達成。
- さらに、この性能は ClickHouse を上回る ものとしています。
🔧 パフォーマンス向上のための核心改修:クエリエンジンの再設計
パフォーマンスを劇的に向上させる最大の要因は、クエリエンジンの完全な再設計です。単独で約 300 倍という性能向上の 1/3(〜1/10) をこの改修が担っています。
なぜ PostgreSQL よりも改善余地が大きいか?
PostgreSQL は 1980 年代から開発されており、当時の中核的なボトルネックは「ディスク入出力(I/O)」でした。しかし、現在は以下の 3 つの傾向により状況が変わっています:
- RAM の拡大: 多くのデータセットが RAM に収まり、大半のディスク I/O が消失しました。
- ワークロードの変化: データ分析ではデータを全スキャンするため、ボトルネックは CPU スループットまたはメモリスループットにシフトしています。
- ストレージの高速化: NVMe はハードドライブの数百倍も高速です。
これにより、CPU とメモリの速度が歴史的に重要になりました。pgrust のクエリエンジンは、PostgreSQL が同等の処理を行う際よりも少ない CPU リソースとメモリ帯域幅で動作するように最適化されています。
⚡ 単なる比較:PostgreSQL vs Rust(単純な加算)
クエリエンジンの効率を実感するため、「最初の 5 億個の数値を加算する」という単純な処理を比較します。
PostgreSQL での実行
CREATE TABLE my_table AS select col::float8 from generate_series(1.0, 500000000.0) g(col); SELECT SUM(col) FROM my_table;
- 所要時間: 約 20 秒
- (条件: AWS c8g.4xl インスタンス、並列クエリ無効化)
Rust での実行
let table: Vec<f64> = (1..=500_000_000usize).map(|i| i as f64).collect(); let mut sum = 0.0; for &value in &table { sum += value; }
- 所要時間: 約 358ms
- 倍率: PostgreSQL の約 55 倍 高速。
重要な違い:オーバーヘッドの排除
上記の結果は「リンゴとリンゴ」の比較ではありません。PostgreSQL 内部ではロック機構やストレージ形式のパースなど、多くのオーバーヘッドが発生します。データベース最適化の本質は、可能な限りこの余計なコストを排除することにあります。
🛠️ クエリエンジン(Volcano モデル)の実装と最適化プロセス
PostgreSQL 内部では SQL クエリが「クエリプラン」に変換され、Volcano モデルと呼ばれる形式で実行されます。このエンジンの小型版を実装し、段階的に最適化した結果です。
1. Volcano モデルの基礎実装
プラン内の各ノードは
next() メソッドを持つだけですべてを実行します(単一行処理)。
use std::hint::black_box; trait Node { fn next(&mut self) -> Option<f64>; } // 簡素なシーケンススキャンと集計の実装 struct SeqScan<'a> { /* ... */ } struct SumAggregate<'a> { /* ... */ } let mut plan = SumAggregate { child: black_box(Box::new(SeqScan { table: &table, pos: 0 })), total: 0.0, done: false, }; let sum = plan.next().unwrap();
- 所要時間: 約 1.3 秒
- 状態: Volcano モデル特有のオーバーヘッドにより、単純な for ループより遅い。
2. バッチ処理の実装
next() が 1 行ずつ呼び出されるのがボトルネックです。これをバッチ処理に変えることで、パイプライン化などの最適化を有効にします。
const BATCH: usize = 1024; trait BatchNode { fn next_batch(&mut self, out: &mut [f64; BATCH]) -> usize; } // バッチ処理版のシーケンススキャンと集計 struct BatchSeqScan<'a>; struct BatchSumAggregate<'a> { /* ... */ } let mut plan = BatchSumAggregate { child: black_box(Box::new(BatchSeqScan { table: &table, pos: 0 })), total: 0.0, }; let sum = plan.run();
- 所要時間: 約 480ms(1.3 秒 → 2.7 倍 高速化)
- 工夫: バッチバッファをスタック上へ割り当て、メモリアロケーション数を最小限に。
3. オペレーターフュージョン (Operator Fusion)
バッチ処理版でも
copy_from_slice でデータをコピーするオーバーヘッドが残ります。事前にわかっている操作(スキャン + 加算)を 1 つのノードへ統合します。
struct SumAggregateSequentialScan<'a> { table: &'a [f64], done: bool, } impl Node for SumAggregateSequentialScan<'_> { fn next(&mut self) -> Option<f64> { // スキャンと加算のロジックを単一の for ループで統合 if self.done { return None; } self.done = true; let mut total = 0.0; for &value in self.table { total += value; } Some(total) } }
- 所要時間: 約 358ms(480ms → 3.6 倍 高速化)
- 効果: コピーオーバーヘッドを完全に排除し、単純な for ループと同等の性能に。
4. JIT コンパイルによる汎用最適化
ハードコーディングは特定のクエリに限られます。JIT(Just-In-Time)コンパイルを使用することで、あらゆるクエリに対して自動的に最適なコード(オペレーターフュージョン相当)を生成できます。(詳細は別記事に譲ります。)
5. SIMD(単一命令・複数データ)の利用
複数のデータを同時に処理する CPU 命令を活用します。
#[cfg(target_arch = "aarch64")] impl Node for SumAggregateSequentialScanSimd<'_> { fn next(&mut self) -> Option<f64> { // SIMD 命令を使用:並列に読み込み、加算 use std::arch::aarch64::*; let mut acc = unsafe { [vdupq_n_f64(0.0); 4] }; let (chunks, rest) = self.table.as_chunks::<8>(); for chunk in chunks { for lane in 0..4 { // ベクタ命令で並列加算 unsafe { let v = vld1q_f64(chunk.as_ptr().add(2 * lane)); acc[lane] = vaddq_f64(acc[lane], v); } } } // 結果の統合とテール処理 Some(unsafe { let s01 = vaddq_f64(acc[0], acc[1]); let s23 = vaddq_f64(acc[2], acc[3]); vaddvq_f64(vaddq_f64(s01, s23)) + tail }) } }
- 所要時間: 約 135ms(358ms → 9.6 倍 高速化)
- 注意点: 浮動小数点演算の結合性のため、コンパイラが自動で SIMD に置き換えることを意図的に抑制。
📊 まとめ:段階的な最適化による成果
以上の 3 つ(バッチ処理、オペレーターフュージョン、SIMD)を適用した結果、クエリ処理は 10 倍も高速化されました。
| 実装 | 時間 | スピードアップ |
|---|---|---|
| PostgreSQL (基準) | ~20 s | — |
| Volcano モデル | 1.3 s | 1× |
| + バッチ処理 | 480 ms | 2.7× |
| + オペレーターフュージョン | 358 ms | 3.6× |
| + SIMD | 135 ms | 9.6× |
これらの最適化により、分析クエリにおいて PostgreSQL の数百倍もの性能を引き出せるようになります。
📢 クレジットと情報
- ベンチマーク環境: AWS c8g.4xlarge (Graviton4), PostgreSQL 18.4, Rust (
)。cargo build --release - 支援方法: プロジェクトを支持するには、GitHub で Star を押すのが一番です。
詳細や更新情報は以下の場所で:
- GitHub
- Discord
- Mailing List: pgrust の週次アップデートと JIT コンパイルに関する追報が含まれます。
- pgrust.com