2026/10/31 22:39
Jepsen:ParticleDB 1.1.4
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
ParticleDB は、トランザクショナル SQL、キーバリューストア、ベクター検索を備えた AI ワークロード向けの多モデルデータベースであり、Orkes から提供されています。しかし、同製品は重大なセキュリティおよび整合性の欠如があることが判明しました。Jepsen 倫理ポリシーに基づき Orkes が資金提供した独立したテストにより、バージョン 1.1.4 から 1.1.18.11.rc-22 までの製品で 96 の深刻な問題が発見されました。これにはクラッシュ、デッドロック、データ損失、および「WHERE 4 = 5」といった論理バグが含まれます。バージョン 1.1.18.11.rc-18 では多くの問題が解決されたものの、既定の
occ トランザクションモードではコミット済みの書き込みを失い続けたほか、serializable 分離レベル下でも内部の異常とロストアップデートエラーが持続しました。ParticleDB は TLA+ を通じて完全な ACID 準拠と形式的正当性を主張していますが、コラボレーション中に TLA+ の仕様は利用できませんでした。また、標準的な SQL 分離レベルコマンド(SET TRANSACTION ISOLATION LEVEL)も無視し、代わりに --txn-mode セッティングに依存しており、そのドキュメンテーションでは耐久性モードに関する記述が曖昧かつ矛盾していました。Postgres を比して桁違いの性能向上を謳していますが、頻発するデッドロック、クラッシュ、および緩慢な動作の実証によりその主張は揺らぎます。さらに、gRPC/HTTP2 API を通じて多数の Postgres 機能(例:CASE 内のアグリゲート、プリペアドステートメント)が欠落しているか壊れています。Orkes の Conductor ワークフローオーケストレーションプラットフォームを支えるために設計された ParticleDB は、エンタープライズクラウドデプロイメントのみで単一ノードレプリケーションを提供していました。Orkes が以前の問題の解決を表明するバージョン 1.1.19-rc.13 をリリースしましたが、コア論理エラーの持続性と研究が資金提供されていたという事実により、修正の有効性と耐久性保証について重大な疑問が生じています。このシステムを重要任務ワークロードに依存するユーザーは、データ破損および不安定さの大きなリスクに直面しており、理論的な主張を超えた実際の耐性と実証された耐久性が示されるまで ParticleDB に依存を見直す必要があることを示唆しています。本文
ParticleDB の脆弱性情報報告:Jepsen テストでの 96 の問題発見と対応状況
概要
ParticleDB は、AI ワークロード向けに設計されたマルチモデルデータベースです。当レポートでは、バージョン 1.1.4 から 1.1.18.11.rc-22 までのテスト結果を提示します。開発元である Orkes, Inc. は後述する問題のうち多くの解決済みであることを主張していますが、特にデフォルト設定下での深刻な不具合が残存していた期間があります。
主要な発見
- 「フル ACID」の主張と現実は乖離: 開発元が主張する「フル ACID」特性は実際には満たされておらず、平凡なクラッシュから刺激的なデータ破損まで多岐にわたる問題が発生していました。
- 再現性の高さ: 問題のうちほとんどすべては、単一のサーバー・単一のクライアント・障害なしという単純な条件下で再現できました。
- 解決の進捗: バージョン 1.1.18.11.rc-22 では 96 件内の 10 件を除く問題は修復されたようです。しかし、同バージョンでもデフォルト設定(
トランザクションモード /occ
WAL)ではコミット済みデータの欠落や Lost Update が発生していました。groupsync - 最終的な対応: Orkes は 1.1.19-rc.13 のリリースで残りの問題も解決したと主張しています。
本研究は Orkes, Inc. の資金提供のもと、Jepsen の倫理規定に基づき実施されました。
背景 (Background)
現代のデータシステムにおける課題
- アプリケーションは状態をデータベース、キャッシュ、キューなど多様なシステムに分散させる傾向があります。
- 調整の難しさ: キャッシュのスナップショット、分析プラットフォームの遅延、検索インデックスの不一致、異種システム間のトランザクション的アップデートなど、一貫性を保つことは困難です。
ParticleDB の位置づけ
- 統合目的: AI エージェント向けにトランザショナル SQL、キー値ストア、ベクトル検索、ライブ分析などを単一のエンジンで統合し、分散システムの問題を回避します。
- 技術スタック: Rust でゼロから構築されており、Postgres と同等の安全性を持ちつつ高速化を謳っています。
- プロトコル対応: Postgres プロトコル、Redis プロトコルに加え、独自の gRPC および HTTP2 API を提供します。
開発経緯とリリース戦略
- Conductor のバックエンド: Orkes が開発し、ワークフローオーケストレーションプラットフォーム「Conductor」のデータストアとして作られました。
- 進化の軌跡:
- Ver. 1.0.0: Conductor クエリに特化した限定的な機能セット。
- リコンストラクション後:
でソフトローンチされ、カスタムワイヤプロトコルや OLAP クエリなどを追加。particledb.ai
- Jepsen テスト: Orkes は初回テスト対象として 1.1.4 を Jepsen に提出しました。
レプリケーションについて
- Raft コンセンサスプロトコルを使用したレプリケーションクラスタも提供されていますが、これはエンタープライズクラウド展開でのみ利用可能です。
- 本レポートは、Orkes が提供する(レプリケーション非対応の)ビルドおよび単一ノードの挙動のみを調査しています。
安全性 (Safety) の問題
TLA+ 形式検証の虚偽性アピール
- ホームページでは「形式検証済み(formally proven)」を謳いましたが、テスト期間中に TLA+ の仕様書が入手できませんでした。
- 何が証明されているのか、実装を記述しているかは不明瞭です。
トランザクションモードと WAL 同期の矛盾
- WAL シンクモード:
: コミット毎に fsync 呼び出し。fsync
: 複数のトランザクションを単一 fsync にバッチ化(ドキュメントでは「より強い耐久性」と謳われているが、groupsync
では「COMMIT がフラッシュを待機する」のみと記述)。start --help
: fsync をスキップ。nosync
- 隔離レベルの混乱:
- ホームページは「フル ACID. 159K TPS」を謳していますが、直下のテキストには「スナップショット隔離(snapshot isolation)」とあり、完全な隔離ではないため矛盾しています。
- SQL の
ステートメントを無視または拒否することがあります。SET TRANSACTION ISOLATION LEVEL
- 起動時の設定依存:
- トランザクションの隔離は
引数で決まります(fast, occ, zero-copy-occ, table-2pl など)。--txn-mode - ドキュメントと実際の動作が一致せず、ベストプラクティス説明が曖昧なままです。
- トランザクションの隔離は
Jepsen 安全性の用語の不一致
- CLI 引用ページやドキュメント間では、「Jepsen-safe」という用語が使われており、その解釈が不明確です(occ vs table-2pl の安全性特性の説明に矛盾があったため)。
テスト設計 (Test Design)
環境設定
- ツール: Jepsen テスティングライブラリを使用。
- バージョン: ParticleDB 1.1.4 〜 1.1.18.11.rc-22。
- インフラ: コンテナおよび EC2 VM を用いた Debian 13 ノード上での個別実行。
- 故障シミュレーション:
- プロセス終了によるクラッシュ。
- LazyFS を用いた fsync なし書き込みの損失。
- ディスクファイルの切断・破損。
クライアント SDK の問題
- 公式 Java SDK は Maven 依存性として存在しましたが、実際には JAR やソースコードがありませんでした。
- 代わりに Postgres JDBC (v42.7.11) および ParticleDB Java SDK (v1.0.0〜1.0.2) を使用しました。
ワークロード (Workloads)
複数の手法で内部一貫性と安全性を検証しました。
- rw ワークロード: 一意の整数キーによるランダムな読み書き。プライマリキーおよび未インデックス化セカンダリキーからのアクセスをテスト。依存グラフ上の制約違反を検出(Elle チェッカー)。
- append ワークロード: リストへの追加・読み取り。SQL concat を使用した結合動作の検証。
- internal ワークロード: 単純なキー値テーブル上で INSERT/UPDATE/DELETE/SELECT の一貫性を開発する内部処理を検証。
- compare ワークロード: Clojure
でランダム SQL を生成し、ParticleDB と PostgreSQL を比較して発散(Divergence)を検出。test.check - internal-sim ワークロード: ランダムなテーブルセット上でランダムトランザクションを実行し、各トランザクションの先行結果との一貫性を独立にチェック。
結果 (Results):96 の問題カテゴリー
A. 利用可能性とパフォーマンス (Availability & Performance)
述語処理の不具合
- WHERE 句の無視 (#40): Ver. 1.1.4〜18.9 では
条件を無視し、空の結果でも全行返すなどした(例:WHERE
が 2 行返す)。Ver. 18.11.rc-1 で改善。SELECT 5 WHERE FALSE - 複雑な述語の評価失敗 (#47): Ver. 18.9〜では浮動小数点演算を含む述語(例:
)を静かに一致させない(0 行返す)。Ver. 18.11.rc-1 で改善。WHERE 1 < (a + 2) - 常に TRUE な述語の失敗 (#18): Ver. 1.1.4〜17 では
をプライマリキーのルックアップとして誤解釈。例:WHERE something = x
が非対応列の行を返す、WHERE b=4
が 0 行ではなく全行返す。Ver. 18.18.4 で改善。WHERE 4=5 - トランザクション内の SELECT の混乱 (#35): Ver. 1.1.4〜18.8 ではトランザクション内での SELECT 結果が不定(述語に関係なく常に同じ行を返す、または
で 0 行)。Ver. 18.18.9 で改善。WHERE TRUE
制約違反の許容
- NOT NULL 制約の無視 (#8): Ver. 1.1.4〜17 では
を挿入し、SELECT すると非表示になる(COUNT は含む)。Ver. 18.18.4 で強制開始。NULL - UNIQUE 制約の無視 (#9): Ver. 1.1.4〜18.4 では重複データを静かに許容。Ver. 18.18.6 で部分的に強制。
- プライマリキーの二重化 (#29): Ver. 1.1.4〜18.8 では UPDATE により同一 PK を持つ複数の行を生成。SELECT は片方のみ返すなど挙動不審。Ver. 18.18.9 で改善。
- NULL プライマリキーの許容 (#57): Ver. 1.1.4〜18.11.rc-1 では UPDATE により PK を NULL にできる(標準違反)。Ver. 18.11.rc-3 で禁止化。
衝突検出と削除の問題
- 削除後の再挿入失敗 (#82): Ver. 18.4〜18.11.rc-11 では、行を DELETE 後に同じ PK の INSERT を試みるとエラーになるが、Wire Error (Postgres インタフェースのみ) と内部一貫性の問題が混在。Ver. 18.11.rc-9 で部分的解決。
- 既存行との PK 衝突無視 (#90): Ver. 18.11.rc-9 では、他のトランザクションから読んだ PK を持つ新しい INSERT が成功し、古い行が消失(内部一貫性違反)。Ver. 18.11.rc-19 で改善。
ジョイン処理の欠陥
- 整数と浮動小数点の結合拒否 (#58): Ver. 1.1.4〜18.9 では
とint
の結合をエラーとする(Postgres と違い)。Ver. 18.11.rc-1 で改善。real - カラム名の誤変換 (#54): Ver. 1.1.4〜18.11.rc-1 では、2 テーブル結合時テーブル名を省略し、カラム名を
などの非標準命名に変換。Ver. 18.11.rc-3 でカラム混同改善(ただしテーブル名省略継続)。Orkes は Ver. 19-rc.13 で解決と発表。right.a - 文字列への強制変換 (#76): Ver. 1.1.4〜18.11.rc-19 では、Postgres インタフェースで結合結果が文字列として返る(例:
→3.2
)。Ver. 18.11.rc-21 で改善。"3.2" - 行の欠落 (#59): Ver. 1.1.4〜18.11.rc-1 では LEFT JOIN において空テーブルが左に存在するにもかかわらず全行消失。Ver. 18.11.rc-3 で改善。
- カラムの混乱 (#77): Ver. 1.1.4〜18.11.rc-6 では同名カラムを持つテーブル結合で出力カラムが入れ替わり、型不一致時の挙動も奇妙。Ver. 18.11.rc-7 で改善。
ゾンビ行 (Ghost Rows)
- TOTAL RECALL (#26): Ver. 1.1.7〜18.8 では DELETE が無効(
)。Ver. 18.18.9 で解決。DELETE 0 - Memento Mori (#45): Ver. 18.9 でも削除された行は集計クエリ(COUNT)で観察可能。Ver. 18.11.rc-1 で影の排除。
- ナイト・オブ・ザ・リビング・イントegers (#23): Ver. 1.1.4〜17 では整数カラムの UPDATE で行を削除不能に(
が失敗)。Ver. 18.18.4 で改善。UPDATE ... DELETE - チーム・ロケット (#84): Ver. 18.11.rc-1〜8 では DOUBLE PRECISION カラム更新でゾンビ行が発生。Ver. 18.11.rc-9 で解決。
- ゴーストは技術的に数字ではありません (#78): Ver. 18.11.rc-1〜6 では NaN 値の UPDATE/DELETE が失敗。Ver. 18.11.rc-7 で改善。
- 着氷した文字列 (#74): Ver. 18.11.rc-1〜6 では固定幅文字列 (
) の更新・削除が不可能、あるいは型強制エラー。Ver. 18.11.rc-7 で改善。CHAR(3) - ペット・セマタリー (#46): Ver. 18.9〜18.11.rc-1 では
後でも行が再出現(スキーマ変更あり)。Ver. 18.11.rc-3 で改善。DROP TABLE
状態破損 (State Corruption)
- 読み取り不可能の無限大 (#15): Ver. 1.1.4〜17 では
を INSERT できても SELECT でエラー(Bad value for type double)。Ver. 18.18.6 で改善。Infinity - NaN が時々 0.0 に変換される (#21): Ver. 1.1.4〜18.4 では INSERT 順序に依存して NaN が 0.0 に化ける。Ver. 18.18.6 で改善。
- 不適合型の割り当て (#7): Ver. 1.1.4〜18.4 では異なる型(Int vs Boolean)の比較で NULL を返すなど、エラーではなく破損結果を生成。Ver. 18.18.6 で改善。
- INSERT カラム順序テーブル破損 (#16): Ver. 1.1.4〜17 では INSERT のカラム順序が無視され、スキーマ定義の順序に従う。これによりデータ配置が崩壊(SELECT 不可)。Ver. 18.18.4 で改善。
- Updates は破損した行を挿入できる (#24, #25): Ver. 1.1.4〜18.8 では UPDATE が失敗しても新しい破損行を作成。プライマリキー衝突の挙動も不整合。Ver. 18.18.9 で改善。
- 時には広範な文字列…は呪われている (#37): Ver. 1.15〜17 では TEXT カラムが時折 8 ビット整数に変容(シリアライズ破損)。
コードブロックとコマンド例
以下に、主な問題を示す代表的なコマンドを記載します。
WHERE 句の無視 (Issue #40)
root=> SELECT 5 WHERE FALSE; 5 --- 5 (1 row)
プライマリキー NULL の許容 (Issue #57)
root=> CREATE TABLE t (a int PRIMARY KEY); root=> INSERT INTO t (a) VALUES (3), (4); root=> UPDATE t SET a = NULL; -- 失敗しない! root=> SELECT * FROM t; a --- (2 rows)
データ破損によるカラム順序の無視 (Issue #16)
root=> CREATE TABLE t (a TEXT NOT NULL PRIMARY KEY, b boolean); root=> INSERT INTO t (a, b) VALUES ('x', TRUE); root=> INSERT INTO t (b, a) VALUES (FALSE, 'y'); -- 順序が逆! root=> SELECT * FROM t; -- 結果は空! a | b ---+--- (0 rows)
ゾンビ行の発生 (Issue #23)
root=> CREATE TABLE t (i int); root=> INSERT INTO t VALUES (0); root=> UPDATE t SET i = 1.5; -- 更新は成功 root=> DELETE FROM t; -- 削除は失敗(DELETE 0) root=> SELECT * FROM t; i --- 1 <-- 実際には 1.5 のゾンビ行