CobbleDB:低レイテンシとコスト削減のためのAI検索ストレージの再構築

分離された永続ステート、バッチ更新、クエリ時取得を備えたデカップリングされたアーキテクチャ。

著者Perplexity Engineering

AIネイティブ検索は、Webコンテンツの処理、保存、提供の方法に対して異なる要件を課します。大規模にそれらの要求を満たすために、パフォーマンスを向上させコストを削減するために、スタック全体を最初から再構築してきました。

効率的な処理と高速なサービングを実現するために、自社製のクロール、インデックス作成、ランキングインフラストラクチャ、埋め込みモデル、および 推論エンジンを構築しました。現在、そのアプローチを、処理が書き込み、サービングが読み取りを行う接続組織であるストレージへと拡張しています。

DynamoDBなどのマネージドデータベースは信頼性の高いストレージを提供していましたが、データの保存方法や読み取りの提供方法に対する制御は限られており、従量制課金はインデックスサイズとクエリボリュームの増加に伴って増加していました。個別に、私たちの以前のパイプラインはドキュメント処理をサービングデータベースに直接結合していたため、ライブ読み取りとは独立して更新を管理することが困難でした。

新しい社内コーディングエージェントの群れの助けを借りて、キーバリューホットストアであるCobbleDBを中心にこのレイヤーを再構築し、永続的なドキュメントステートと公開のためのPillar、およびバッチ更新のためのLorryによってサポートされています。この再設計により、ドキュメント処理がホットストアへの取り込みから分離され、ワークロードに合わせてサービングをチューニングできるようになりました。本番環境のビフォーアフターの測定値では、バッチ読み取りレイテンシが約5倍低下していることが示されており、社内のコストモデルではDynamoDBと比較して少なくとも20%の削減が見積もられています。

AIネイティブ検索のためのストレージインフラストラクチャ

従来の検索スタックは、言語モデルではなく人間の読者向けに構築されています。その違いにより、システムがWebコンテンツを準備、保存、および提供する必要がある粒度が変化します。

生のHTMLをモデル対応のコンテンツに変換するために、処理パイプラインは各ページをクリーンアップし、意味的に一貫性のあるパッセージに分割し、それらの埋め込みを計算し、パッセージと埋め込みをデータベースに一緒に保存します。クエリ時に、サービングパイプラインはクエリに最も関連するテキストを選択し、それをモデルに渡します。

2つの異なるワークフロー

したがって、ストレージレイヤーは、処理パイプラインからの更新の取り込みと低レイテンシの読み取りの提供という、異なる要求を持つ2つのワークフローをサポートする必要があります。

書き込み側では、新しいページが継続的に到着し、既存のページが変更され、チャンキング方法や埋め込みモデルの変更により、コーパスの大規模な部分の再処理が必要になる場合があります。ストレージレイヤーは、これらの更新を永続的なドキュメントステートに組み込み、現在のバージョンを追跡し、適切な表現を下流システムに公開し、生のクロールからの完全なパイプラインを繰り返すことなく大規模なバックフィルをサポートする必要があります。

対照的に、クエリ時の読み取りでは、ページキーのバッチがリクエストされ、ランキングとパッセージ選択のために対応するパッセージテキストと埋め込みが取得されます。これらの読み取りは最終的な回答へのパス上に位置するため、迅速に完了する必要があります。

これらのワークロードには異なる設計が必要です。処理には、耐久性のあるキューや、更新を効率的に配信し、障害を再試行し、既存のレコードを大規模に最新の状態にする機能が有益です。サービングには、事前計算されたレコード、ウォームキャッシュ、および最小限のクエリ時作業による高速なバッチ読み取りが有益です。両者の間の密結合により、リカバリ作業や大規模な更新が、レイテンシに敏感な読み取りと競合する可能性があります。

マネージドデータベースの限界

DynamoDBを使用することで、分散データベースを運用する負担なしに、可用性の高いストレージを実現できました。専用の準備済みページテーブルにはクエリ時の読み取り用のパッセージと埋め込みが保持され、ページの内容全体とメタデータは個別に保存されていました。しかし、ワークロードの成長に伴い、3つの制約が重要になってきました。

第2に、従量制課金では、読み取られた、または書き込まれたすべてのバイトに対して課金されます。 単一のSearch APIリクエストには、取得する必要のあるページに対応する100〜120個のキーが含まれています。データ取得プロセスは、リクエストを10〜20個のページキーの小さなバッチに分割し、アイテムの平均サイズは約50 KBです。これらのレコードを繰り返し読み取ることで多大なキャパシティが消費される一方、継続的なクロールやチャンキングまたは埋め込みモデルの変更により、より多くの書き込みが発生しました。経済性は、API呼び出しの数だけでなく、レコードのサイズと頻度にも依存していました。

第2に、マネージドサービスにはパフォーマンスチューニングに必要な柔軟性が欠けています。 私たちが必要とした操作は限定的でした:ページ識別子のバッチが与えられたとき、その準備済みコンテンツを迅速に返すことです。どのマシンがパーティションを保持し、キャッシュにどれだけのメモリが割り当てられ、どのレプリカがリクエストを処理するかを制御したいと考えていました。キャッシュされていない読み取り、クロスゾーンのホップ、または低速なレプリカがバッチ全体を遅らせる可能性があるため、これらのコントロールは重要です。DynamoDBはAPIの背後で独自のストレージとレプリカの動作を管理します。サービスの設定はできましたが、ワークロードに合わせて内部の配置、ストレージエンジンのキャッシュ、またはレプリカ選択ポリシーをチューニングすることはできませんでした。

第3に、処理がホットストアへの書き込みに直接結びついていました。 以前の処理パイプラインでは、準備された各ページがDynamoDBに直接書き込まれていました。そのため、大規模な再処理ジョブは個別のホットストア更新の波となり、処理はホットストアのスループット制限とリトライ動作に対応する必要がありました。新しいチャンキング方法を試したり、埋め込みモデルを変更したり、新しいフィールドを追加したりするために、データベース全体に対してMapReduceを1〜2日で実行することはできませんでした。別のサービスが独自のペースでバッチ処理してホットストアに配信できる、保存されたページ更新の独立したストリームはありませんでした。

これらの制約に対処するには、データベースを置き換えるだけでは不十分でした。永続的なドキュメントステートおよび更新の配信をクエリ時のストレージから分離し、その後、高速なバッチ読み取り専用にホットストアを最適化する必要がありました。

ドキュメントステート、更新の配信、およびサービングの分離

新しいアーキテクチャでは、各責任が異なるコンポーネントに割り当てられます。Pillarは永続的なドキュメントステートを維持し、何を公開するかを決定します。Lorryはそれらのエクスポートをパーティション分割されたバッチに変換します。CobbleDBはバッチを取り込み、ページキーごとに準備済みレコードを提供します。

ストレージ移行の前後。Pillar と Lorry は、永続的なドキュメント状態と更新配信を、CobbleDB が提供するクエリ時読み取りから分離します。

CobbleDB:チューニング可能な読み取りパス

CobbleDBは、クエリ時に準備済みレコードを提供するように最適化されたホットストアです。これは分散キーバリューストレージであり、キーはページ識別子(ハッシュ化されたURL)であり、値はページの処理済み表現(事前にチャンク化されたパッセージとチャンクごとのベクトル埋め込み)です。

重要な変更は、ストレージ設定に対する私たちの制御です。

データはパーティション分割され、データノードに分散されます。この分散形式により、データベースは水平方向にスケールして、処理されたページの膨大なコーパスに対応できるようになります。各パーティションには3つの異なるノードに3つのレプリカがあり、1つのコピーが利用できなくなっても、他のコピーは読み取りの提供を継続できます。

各データノードには、パーティションのサブセットが割り当てられます。ノードは、バッチ取り込みによって供給される読み取り負荷の高いワークロードに適したオープンソースの埋め込み型キーバリューストレージエンジンであるRocksDBを使用してデータを保存します。

キャッシュされたデータはメモリから提供できますが、キャッシュされていない読み取りではローカルのNVMeストレージを使用します。これにより、マネージドサービスの内部キャッシュポリシーを受け入れるのではなく、観測されたワークロードに対するメモリとディスクのバランスをチューニングする直接的な方法が得られます。

ステートレスなクエリールーターはキーをパーティションにハッシュ化し、リクエストを並列でノードにルーティングします。アーキテクチャには、PostgreSQLをバックエンドとするメタデータサービスと永続オブジェクトストレージも含まれています。

この設計は、ワークロードが要求する読み取りパターンをサポートします。クエリが検索とランキングを経て関連ページの候補セットを取得した後、ホットストアに対してそれらのページの内容が一括でクエリされます。これらの読み取りリクエストはルーターに到着し、対応するデータノードにルーティングされます。ルーターは、クロスゾーンのレイテンシを最小限に抑えるために、可能であれば同じアベイラビリティーゾーン内のノードへのリクエスト送信を優先します。各データノードは、低レイテンシで複数のキーを同時に取得するように設計されたRocksDBのバッチ読み取りAPIであるMultiGetを使用してページコンテンツを取得できます。ノードの応答が遅い場合、システムは別のノード上の別のパーティションレプリカにクエリを実行することもでき、テールのレイテンシを改善します。

ワークフローで必要としない一般的な機能の実装を避けることもできます。たとえば、ほとんどのデータベースは一貫性の保証とトランザクションサポートを提供していますが、私たちのホットストアではトランザクションや同期されたレプリカは必要ありません。ドキュメントが書き込まれてから読み取り可能になるまでに短いギャップがあっても問題なく、1つのレプリカが別のレプリカよりも速い速度でデータを取り込んでも問題ありません。不要な機能を省略することで、システムのオーバーヘッドとコストが削減されます。

ステートレスなルーターがページキーをパーティションごとにグループ化し、レプリカを並列に読み取ります。遅い読み取りは別のレプリカへ送ることができ、各パーティションの最初の応答が結果にマージされます。

PillarとLorry:永続的な書き込みパス

独立したストレージレイヤーがクロールおよび処理したすべてのデータを追跡し、更新をバージョニングされたドキュメントステートにマージして、必要な表現をCobbleDBおよび検索インデックスに公開します。この目的のために、ドキュメントステートおよび公開レイヤーであるPillarを構築しました。

PillarはYTsaurusによってバックアップされています。ページコンポーネント(メタデータ、チャンク、埋め込みなど)を個別のYTsaurusテーブルファミリに保存します。また、チャンクと埋め込みのバージョンを追跡し、下層のモデルが変更できるように、お互いを上書きすることなく複数の表現を共存させることができます。PillarとYTsaurusは安価なHDDストレージを使用しているため、以前にDynamoDBに保存していたよりもはるかに多くのドキュメントを保存できます。

Pillarは、サブセット、つまりフレッシュページや高価値ページなどのポリシーで定義されたページのグループを追跡します。サブセットのメンバーシップによって、どの更新をCobbleDBなどの下流コンポーネントに渡す必要があるかが決まります。CobbleDBはより高価なNVMeストレージを使用しているため、検索結果で実際に使用したいドキュメントのみをエクスポートします。ページの内容が更新された場合、またはサブセットに追加または削除された場合、Pillarは対応するエクスポート作業をキューに入れる責任を負います。

YTsaurusのアトミックトランザクションは、正確性を確保するために、ステート、エクスポートの意図、および消費された入力をカバーしています。トランザクションが正常に完了するためには、システムが永続ステートを更新し、入力が処理されたことをマークし、必要なエクスポートをすべてキューに追加する必要があります。トランザクションがアボートした場合、どのレイヤーでも変更は行われません。

CobbleDBは、パーティションに応じて更新がバッチ処理されることを期待しています。Pillarのエクスポートは、私たちがLorryと呼ぶステートレスサービスによってこの形式に変換されます。その唯一の仕事は、永続的なパーティションに揃えられたキューからエクスポートレコードを読み取り、パーティションごとのバッチファイルに累積し、それらをCobbleDBに渡すことです。

LorryとCobbleの間では、追加のプロトコルによりデータプレーン(S3)とコントロールプレーンが分離されます。Lorryは一意の識別子の下でバッチをS3に書き込み、Cobble内にバッチを登録します。Cobbleのパーティションレプリカは、取り込む次のS3バッチのためにバッチまたはコントロールプレーンAPIに独立してクエリを実行し、時系列順にバッチ更新を非同期で適用します。これは、低速なノードや回復中のノードが、システムの残りの部分を遅くするのではなく、独自のペースで追いつくことができることを意味します。

Pillar は耐久性のあるエクスポートを公開し、Lorry はパーティション化されたバッチを S3 に書き込み、制御プレーンに登録します。各 CobbleDB レプリカは、それぞれ独立してバッチを検出し、時系列順に取り込みます。

新しいパスでは、即時処理トランザクションとホットストアへの取り込みが分離され、インクリメンタルな更新と一括リビルドのための再実行可能なパスが提供されます。この役割分担により、永続的なドキュメントステートへの更新が読み取りパスのレイテンシに干渉しないようになっています。

検証

5倍高速

ホットストアの移行後、バッチ読み取りレイテンシの中央値は31.4 msから5.60 msに低下しました。p90は56.7 msから9.77 msに低下し、p99は123 msから24.2 msに低下しました。比率は5.08×から5.80×の範囲であり、通常のリクエストとテールの両方で改善が見られます。

移行前後の本番バッチ読み取りレイテンシ:P50 は 31.4 ms から 5.60 ms に、P90 は 56.7 ms から 9.77 ms に、P99 は 123 ms から 24.2 ms に低下しました。これらは、マッチしたリクエストやエンドツーエンドの応答レイテンシではなく、異なる時点での実トラフィックにおけるホットストアの読み取りレイテンシです。

これらの数値は、各クエリが約10〜15個のキーをリクエストするバッチ読み取りから導出されています。ペイロードアイテムの平均サイズは50KBです。劣化なしで最大500k rpsの負荷テストも実行しましたが、両方のシステムは約200k rpsでクエリされました。

CobbleDBの貢献度を切り分けるため、セットアップのその他の側面はすべて一定に保たれました。ただし、これは依然として観察に基づくビフォーアフター比較であり、システムは制御された条件下での同一のリクエストではなく、異なる時間にライブの本番トラフィックを処理しました。そのため、両システムのパフォーマンスを合成ベンチマークでも測定しました。本番環境で見られるリクエストを近似するために、10〜15個のキーと100B〜100 KiBの範囲の値を持つバッチを使用して両方にクエリを実行しました。

100 B から 100 KiB までの値サイズにわたる、10 キーおよび 15 キーのバッチ読み取りレイテンシのシンセティック測定。各ポイントは、各バックエンドと構成につき 2 回の実行から得られた実行レベルの P50、P90、または P99 レイテンシの平均です。

DynamoDBはこのコンテキストでは苦戦します。なぜなら、そのマネージド読み取りパスはこのサイズバッチの読み取りに向けてチューニングできないためです。対照的に、CobbleDBは配置、キャッシュ、およびレプリカコントロールを管理するため、まさにこの設定に最適化されています。キーをパーティションごとにグループ化し、パーティションレプリカに対して並列で読み取りを実行し、低速な読み取りを他のレプリカに対してヘッジできます。私たちの結果は、これらの設計上の選択が本番環境でのより高速な読み取りに直結することを示しています。

20%安価

CobbleDBはDynamoDBよりも大幅に安価です。ストレージに加えて、DynamoDBは読み取られた、または書き込まれたすべてのバイトに対しても課金します。私たちのコーパスとサービングトラフィックの規模では、これらの従量制課金が総コストの大部分を占めるようになります。継続的な更新は書き込みキャパシティを消費し、クエリのためにフェッチされた準備済みページの各バッチは読み取りキャパシティを消費します。

ストレージサイズ、書き込みキャパシティユニット、読み取りキャパシティユニットの内部見積もりに基づいてコスト削減を計算しました。どのコミットメントティアにおいても、CobbleDBはDynamoDBよりも少なくとも20%安価です。これらの結果には圧縮によるバックアップの節約が考慮されていないため、実際の節約はおそらくさらに高くなります。

AIエージェントの群れが私たちの構築をどのように支援するか

CobbleDBおよび周辺のインフラストラクチャの構築は、決して簡単なことではありませんでした。CobbleDBのコアデータベースだけでも約40,000行のRustであり、移行には、本番スケールでの正確性と障害動作の検証、CobbleDBと処理およびサービングの両方のパスの統合、およびライブトラフィックの安全な移行のための広範な作業も必要でした。

コーディングエージェントの群れを操作する社内システムを使用して、この取り組みを加速しました。このシステムは非常に持続性を持つように設計されており、セッションをまたいでプロジェクトの目標、アクティブなリスク、リポジトリの履歴、および以前の決定を保持します。

CobbleDBプロジェクトには、このようなプロactiveで常に稼働するエージェントの集合体を割り当てました。彼らの最初のタスクは、すでに進行中の作業の正確なモデルを構築することでした。プロジェクトのチャンネルとアクティブなスレッドを監査し、未解決のアイテムをそれらのpull requestと所有者に結びつけ、システムの健全性と次のアクションの現在の読み取り結果を作成しました。そこから、エンジニアリングループのいくつかの部分で作業を行いました:

  • コードとインフラストラクチャの変更をレビューしました。
  • 安全でないリストアの前提、古いビルド参照、およびランタイムで失敗する設定など、見落としやすいブロッカーを浮き彫りにしました。
  • 焦点を絞った修正、テスト、監視の変更、および運用ドキュメントを準備しました。
  • 本番アクションを明示的に人間が所有するように維持しながら、CI、レビュー、およびインフラストラクチャのゲートを追跡しました。
  • 監視付きのロールアウトとバックアップまたはリストアの演習を行い、観測されたシステムの状態をロールアウトの成功基準と比較しました。
  • エンジニアが何がリリースされ、何がブロックされたままであり、どこで人間の判断がまだ必要であるかを確認できるように、プロジェクトの読み取り結果を維持しました。

エンジニアは引き続きアーキテクチャの設定、重要度の高い変更のレビュー、および本番操作の承認を行いました。エージェントは、それらの決定の間にある継続的な検査とフォローアップの多くを処理しました。これにより、少人数チームがプロジェクトを効率的に推進することが可能になりました—CobbleDBのコアインフラストラクチャは、2人の人間のエンジニアと数百のAIエージェントによって2か月で構築されました。

結論

CobbleDBは汎用データベースではなく、非同期処理パイプラインによって供給される準備済みページレコードの繰り返しバッチ読み取りという特定のワークロード専用に構築されています。永続ステート、更新配信、およびクエリ時のサービングを分離することで、単一のデータベースインターフェイスにすべてを対応させたり、ライブクエリパスにメンテナンス作業を押し付けたりすることなく、各責任を個別に最適化できます。CobbleDBへの移行により、インフラストラクチャコストを削減しながら、中央値およびテールのレイテンシが大幅に改善されました。

インフラストラクチャを自社でAIネイティブに開発し続けることで、サードパーティの実装や価格モデルに縛られている場合には不可能な方法で、パフォーマンスとコストを最適化できます。強力なAIエージェントにより、購入するよりも構築する方が有利であるという計算がさらに加速します。

AIネイティブ検索システムを構築している他のチームが、製品内部で検索をより高速かつ安価にするのと同じストレージレイヤーの恩恵を受けられるよう、まもなくCobbleDBをオープンソース化する予定です。

引用

BibTeX
@online{perplexity2026Cobbledb,
  author = {{Perplexity} and Alexandr Yarats and Marco Montagna and Aleksei Bykov and Alexey Gusev and Ali Abdullaev and Amir Mokhammed-Ali and Andrey Zosimov and Denis Kremko and Gleb Vishnevsky and Micah Zirn and Phi Hoang and Claire Beamer and Jeremy Yang and Jerry Ma and Denis Yarats},
  title = {{CobbleDB: Rebuilding AI search storage for lower latency and cost}},
  date = {2026-09-14},
  url = {https://www.perplexity.ai/hub/blog/cobbledb}
}