Lance クエリのベストプラクティス
このガイドでは、Lance クエリについて、BE ごとのキャッシュ容量とインデックスセグメントの構成を決める方法を説明します。ワークロードを起点に、キャッシュに保持する必要がある内容を見積もり、想定する同時実行数で検証してください。以下の例は計画の出発点であり、ベンチマークで確認された最適値や BE のメモリ上限ではありません。
Lance Catalog は実験的な機能であり、Apache Doris 4.2 からサポートされています。まず Lance Catalog を使用してアクセスを設定し、リーダーの互換性を確認してください。Doris は既存の Lance インデックスを読み取ります。インデックスの構築や保守には、互換性のある Lance ツールを使用してください。
スカラーフィルターとベクトル検索または全文検索を組み合わせる場合は、フィルターの配置、整合したインデックスの構築、Profile による診断について Lance ハイブリッド検索のパフォーマンスに関するベストプラクティス を参照してください。
1. チューニング方針を選ぶ
| ワークロード | 最初に行うこと | 検証すること |
|---|---|---|
| 単一クエリの低レイテンシーを重視するベクトル検索 | 頻繁に探索するベクトルパーティションを保持する。セグメントガイドを使い、対象 BE 数に対してバランスの取れたセグメント数を比較する | 同等の再現率での P95 レイテンシー、最も遅いスキャナー、ピークメモリ |
| 同時実行数の多いベクトル検索 | 異なるクエリベクトルがアクセスするホットパーティションの和集合を見積もる。各クエリの Split 数を最大化するより、同時実行する検索のためのメモリと CPU を残す | 目標同時実行数でのスループットと P95 レイテンシー、キャッシュの再読み込み、BE ごとのメモリ |
| BTree の範囲/ポイントフィルター、または Bitmap/LabelList フィルター | 検索用構造を含むホットページまたはビットマップエントリを見積もり、述語とインデックスの使用状況を確認する | ページの読み込み、選択率、一時的な行 ID 集合、出力列の読み取り |
| 全文検索またはフレーズ検索 | 辞書、文書メタデータ、ポスティング、位置情報(有効な場合)を見積もり、ウォームアップにさまざまな検索語を含める | 頻出語のクエリ、フレーズクエリのメモリ、候補のマージ |
| ベクトル、スカラー、全文検索クエリが BE を共有する場合 | 各クエリの共有ワーキングセットを合計し、同時にテストする | あるクエリ群が別のクエリ群のエントリを追い出すか |
| 出力列の取得またはスキャンが支配的な場合 | データファイルの読み取りと、独立したディスクデータキャッシュを調べる | 有効に使われたバイト数と読み取り増幅の比率、デコードと取得のどちらが支配的か |
スカラーフィルターはベクトルクエリの Prefilter に含まれる場合もあるため、単一のクエリでスカラーとベクトルの両方のインデックス内容が必要になることがあります。各シナリオを排他的なものとして扱わず、該当する行をすべて選んでください。
2. 入力情報を集め、メモリ予算を決める
各 BE、または実際に近い配置の代表的な BE について、次の情報を集めます。
- ワークロード: 目標 QPS/同時実行数、レイテンシー目標、必要な再現率、
top_k、フィルター、フレーズクエリ、出力列。 - リソース: クエリを実行できる BE、CPU 容量、他の Doris ワークロードとプロセス/OS の必要量を差し引いた利用可能メモリ。
- インデックス: 種類、バージョン、物理セグメント数、カバレッジ、ベクトルの次元数/エンコーディング、IVF パーティション数、スカラー値の幅または文字列長。
- 再利用: 代表的なクエリ期間にアクセスされる、異なるパーティション、ページ、検索語、ビットマップエントリの数。単一クエリの返却行ではなく、複数クエリにわたるワーキングセットを使用します。
- クエリのピーク: 想定する同時実行数での、実行中のパーティション読み込み、Prefilter/結果の行 ID 集合、候補ヒープ、デコード、精緻化、第二段階の取得に必要なメモリ。
インデックスの計算式でペイロードを見積もり、合計予算が収まることを確認します。
available_Lance_cache_budget_on_BE
= BE memory budget
- other BE memory needs
- peak concurrent query working memory
- safety headroom
index_cache_target + metadata_cache_target
<= available_Lance_cache_budget_on_BE
これは計画上の確認であり、強制されるメモリ予約ではありません。キャッシュ容量はクエリメモリやプロセス RSS の上限にはなりません。再利用できるキャッシュ内ワーキングセットに同時実行クエリ数を掛けないでください。一方、クエリごとに追加で必要なワーキングメモリは確保し、同時実行クエリが異なるデータにアクセスする場合は、キャッシュする内容の和集合が大きくなることも考慮してください。
全パラメータの定義とデフォルト値は BE の設定 を参照してください。各 BE の lance_* BE パラメータを be.conf に設定し、その BE を再起動します。これらは Catalog プロパティや SQL セッション変数ではありません。インデックスセグメント数と IVF 構築パラメータは、これらのキャッシュパラメータではなく Lance のインデックス構築ツールで設定します。
3. インデックスのワーキングセットを見積もる
各 BE が再利用する必要のあるインデックスパーティションを基に、インデックスキャッシュの容量を決めます。ストレージ上のインデックス全体、キャッシュ内のワーキングセット、クエリのピークメモリは、それぞれ異なる量です。以下の見積もりは、Doris がサポートする六種類のベクトルインデックスを対象とします。計画用の見積もりであり、正確なキャッシュ使用量やメモリ上限ではありません。
インデックスタイプ別のベクトルペイロード
以下の記号を使用します。計算式の結果はすべてバイト単位です。1 GiB = 1073741824 bytes。
| 記号 | 意味 |
|---|---|
N | 見積もるパーティション内のインデックス登録済みベクトル数。通常の単一ベクトル列では、インデックス登録済み行数です。マルチベクトル列では、インデックス登録済みのサブベクトルを数え、行のマッピングと精緻化は別途見積もります。 |
D | ベクトルの次元数。 |
w | 保存された量子化前のベクトル要素あたりのバイト数。Float16 は 2、Float32 は 4、Float64 は 8。バイナリ uint8 ベクトルでは、実際のバイト長を D、w=1 とします。 |
m、b | PQ のサブベクトル数と、サブベクトルコードあたりのビット数。これらはクエリパラメータではなく、インデックス構築時の設定です。 |
G | 読み込まれた HNSW グラフが占めるメモリ。すべてのレベルと補助構造を含みます。 |
ベクトルコードだけでなく、該当するすべての構成要素を加算してください。 最初の表ではベクトルごとのペイロードを分けて示します。後続の表には共有構造と HNSW グラフの構成要素を示します。すべての計算式は読み込み済みバッファを見積もります。実測と記された行にも予算が必要です。費用がゼロという意味ではありません。共有される割り当ては一度だけ数えます。
| インデックスタイプ | ベクトル値/コード | UInt64 行 ID | HNSW グラフ | その他に必要な予算 |
|---|---|---|---|---|
IVF_FLAT | N * D * w | 8 * N | なし | IVF 構造 + 以下の共通オーバーヘッド |
IVF_SQ | SQ8 では N * D | 8 * N | なし | IVF 構造 + SQ メタデータ + 共通オーバーヘッド |
IVF_PQ | N * ceil(m * b / 8) | 8 * N | なし | IVF 構造 + PQ コードブック + 共通オーバーヘッド |
IVF_HNSW_FLAT | N * D * w | 8 * N | G を加算 | IVF 構造 + 共通オーバーヘッド |
IVF_HNSW_SQ | SQ8 では N * D | 8 * N | G を加算 | IVF 構造 + SQ メタデータ + 共通オーバーヘッド |
IVF_HNSW_PQ | N * ceil(m * b / 8) | 8 * N | G を加算 | IVF 構造 + PQ コードブック + 共通オーバーヘッド |
b=8 の PQ では、コードはベクトルあたり m バイトを占めます。b=4 では m/2 バイトを占めます(サポートされる偶数の m の場合)。例えば、1 million(百万)ベクトルで m=96、b=8 の場合、コードが 96000000 バイト、行 ID が 8000000 バイトで、合計 104000000 バイト(約 99.2 MiB)です。強く圧縮されたインデックスを見積もる場合でも、行 ID を省略してはいけません。
| 追加の構成要素 | 対象 | 読み込み済みペイロード/予算の立て方 |
|---|---|---|
| IVF セントロイド | 全六種類 | FP32:保持するセントロイド集合ごとに num_partitions * D * 4。他のレイアウトでは実際の要素幅を使用 |
| IVF パーティションディレクトリ/サブインデックスメタデータ | 全六種類 | 保持するパーティションのオフセット、長さ、リーダー構造を実測。ベクトル値やセントロイドとは別 |
| PQ コードブック | IVF_PQ、IVF_HNSW_PQ | 標準的な FP32 コードブック:保持するコピーごとに 2^b * D * 4 |
| SQ 量子化器のメタデータ | IVF_SQ、IVF_HNSW_SQ | 保持する量子化器の境界値/パラメータを実測。バージョン間で同一の固定レイアウトを想定しない |
| ノルム、行カバレッジ構造、追加マッピング | リーダーが保持する場合 | 実際の配列/集合を実測。マルチベクトルの行マッピングには独立した予算が必要 |
| インデックスオブジェクト、Arrow の validity バッファ、余剰容量とコピー | 全六種類 | 論理ペイロード以外の割り当てを実測。共有バッファは一度だけ数える |
独立したインデックスセグメントは、それぞれ別のセントロイド集合とコードブックを保持する場合があります。共有割り当てをすべてのパーティションに掛けたり、すべてのコピーが共有されると想定したりしないでください。追加の構造は、組み込まれている Lance のバージョンに依存します。
HNSW グラフの構成要素
グラフはベクトルコードと行 ID に加えて必要です。 UInt32 のノード/近傍 ID と、保持される場合がある Float32 のエッジ距離を持つ Arrow ベースの Lance グラフについて、E を読み込まれた全レベルにわたる有向の近傍エントリ数、V をそれらのレベルにわたるノード出現数、A を独立して保持されるグラフバッチ数とします。複数レベルに現れるノードは、V に複数回加算します。
| グラフの構成要素 | 主なペイロード(バイト) | 数え方 |
|---|---|---|
| 近傍 ID | 4 * E | 有向近傍エントリごとに UInt32。全レベルを数える |
| 保存されたエッジ距離 | 4 * E | 近傍エントリごとに Float32。リーダーが距離列を読み飛ばす場合はゼロ |
| グラフノード ID | 4 * V | ノード出現ごとに UInt32。データセットの UInt64 行 ID とは別 |
| 近傍リストのオフセット | 4 * (V + A) | Int32 のリストオフセット。バッチごとに一つの終端オフセットを含む |
| 距離リストのオフセット | 4 * (V + A) | 二つ目の Int32 リストオフセット配列。リーダーが距離列を読み飛ばす場合はゼロ |
| 距離を含む小計 | 8 * E + 12 * V + 8 * A | バッチが大きい場合は約 8 * E + 12 * V。G 全体ではない |
| 距離を含まない小計 | 4 * E + 8 * V + 4 * A | ノード ID と近傍リストだけを保持する場合に使用。両方の小計を加算せず、片方を選ぶ |
| レベルオフセット、エントリポイント、ノード検索構造 | 実測 | 読み込まれたグラフごとに、保持するメタデータと検索用の割り当てを含める |
| Validity バッファ、グラフオブジェクト、割り当ての余剰容量 | 実測 | 上記のバッファ以外の実際の割り当てを加算し、共有バッファを二重計上しない |
グラフ予算の合計 G | 主な小計 + 実測したグラフの追加分 | ベクトル/行 ID ペイロードおよびその他のインデックス構造に加算する |
これは特定のレイアウトに対する見積もりであり、ノードあたりの固定バイト数を保証するものではありません。HNSW の m はグラフの接続度を制御し、PQ の m とは無関係です。N * m * 4 を G の代わりに使わないでください。実際の次数、上位レベル、保持される距離、リーダーのバージョンが影響します。代表的なパーティションを読み込み、保持された割り当てを測定して補正してください。
例えば、キャッシュ内の IVF_HNSW_PQ パーティションに 1 million(百万)ベクトルがあり、PQ の m=96、b=8、E=32000000、V=1100000、A=1 とします。このリーダーは距離列を保持すると仮定します。これらのグラフ数値は説明用の入力値であり、HNSW の m から導かれたものではありません。
| 構成要素 | ペイロードの例 |
|---|---|
| PQ コード | 96000000 バイト |
| データセットの行 ID | 8000000 バイト |
| 主なグラフバッファ | 8 * 32000000 + 12 * 1100000 + 8 = 269200008 バイト、約 256.7 MiB |
| コード + 行 ID + 主なグラフバッファ | 373200008 バイト、約 355.9 MiB |
| 別のケース:距離を読み飛ばす場合の主なグラフバッファ | 4 * 32000000 + 8 * 1100000 + 4 = 136800004 バイト、約 130.5 MiB |
| 別のケースの合計:コード + 行 ID + 距離を含まないグラフバッファ | 240800004 バイト、約 229.6 MiB |
| どちらの場合も追加が必要なもの | グラフの追加分、IVF セントロイド/ディレクトリ、PQ コードブック、上記の表に示した共通オーバーヘッド |
リーダーが保持する列に合った例を選んでください。どちらの小計も、完全なキャッシュ容量の推奨値ではありません。
アップストリームの Lance パフォーマンスガイド では、ベクトルペイロードとインデックス構築時のメモリ見積もりを説明しています。Lance のベクトルインデックス形式 では、IVF パーティション、サブインデックス、量子化されたストレージの分離を説明しています。これらの文書の最新版には、Doris に組み込まれた Lance のバージョンより新しい機能が記載されている場合があります。互換性は Lance Catalog のサポートマトリックスで確認してください。
スカラーインデックスと全文インデックスのペイロード
スカラーインデックスも見積もれますが、必要な入力値はインデックスファミリーによって異なります。クエリが返す行だけでなく、一つの BE で実際に保持するエントリを数えてください。以下の方法は Lance のストレージレイアウトを説明するものであり、Doris の述語プッシュダウンやインデックス互換性の保証を広げるものではありません。組み込まれた Lance のバージョンがそのインデックスをサポートし、クエリが使用していることを確認してください。
BTree。 読み込まれたリーフページには、値と UInt64 行 ID が含まれます。R をキャッシュ内のリーフページに含まれるレコード数、H をそれらのページ数、w を固定長値のサイズ、L をそれらのページ内の平均文字列長(バイト)とします。
| リーフページの構成要素/表現 | 値バッファ | オフセット | UInt64 行 ID |
|---|---|---|---|
| Int32/Float32 | 4 * R | なし | 8 * R |
| Int64/Float64/64-bit timestamp | 8 * R | なし | 8 * R |
| その他の固定長値 | w * R。ビットパックされた Boolean には実際のビットマップバッファを使用 | なし | 8 * R |
| Arrow Utf8/Binary | R * L | Int32 オフセットでは 4 * (R + H) | 8 * R |
| Arrow LargeUtf8/LargeBinary | R * L | Int64 オフセットでは 8 * (R + H) | 8 * R |
リーフページの小計は、三つのペイロード列の合計です。 開かれた各セグメントについて、次の構成要素を加えます。以下の P はそのセグメント内の全リーフページ数を表し、キャッシュ内のページだけではありません。
| BTree の追加構成要素 | 主なペイロード/予算の立て方 |
|---|---|
| 検索用の最小値 | 固定長値では P * w。可変長値では実際の値/オフセットバッファ |
| 検索用の最大値 | 固定長値では P * w。可変長値では実際の値/オフセットバッファ |
| 検索用の NULL カウント | UInt32 レイアウトでは 4 * P |
| 検索用のページ番号 | UInt32 レイアウトでは 4 * P |
| 固定長の検索用バッファの小計 | P * (2 * w + 8)。以下の構造は含まない |
| NULL ページリストと検索オブジェクト | 保持するリスト、オブジェクト、実装固有のコピーを実測 |
| リーフ/検索用の validity バッファ、Arrow オブジェクト、余剰容量 | 論理的な値/オフセット/行 ID 以外の割り当てを実測 |
文字列の計算式は表に示した Arrow レイアウトを前提とします。辞書表現や view 表現には、実際のバッファを使用してください。ディスク圧縮率からデコード後のページサイズは決まりません。デフォルトの BTree ページサイズは 4096 レコードであり、バイト数ではありません。インデックスの実際のページサイズを使用してください。検索用構造はページを記述するものですが、BE のメタデータキャッシュ予算ではなく、インデックスのワーキングセットに含めます。
例えば、一億(100 million)個の Int64 値では、リーフの値/行 ID ペイロードは 100000000 * 16 = 1600000000 バイト(約 1.49 GiB)です。キャッシュ内のページが一千万(10 million)レコードをカバーする場合、このペイロードは約 152.6 MiB です。ページあたり 4096 レコードであれば、インデックス全体に約 24415 ページがあり、追加の構造を除く検索用バッチの最小値/最大値/カウント/ページ番号のペイロードは約 572 KiB です。十行が一致して返されたとしても、キャッシュするレコードが十件だけとは限りません。リーダーはページを読み込みます。
Bitmap、LabelList、NGram。 以下の該当する構成要素の行を合計します。メンバーシップビットマップに、一致行ごとに自動的に八バイトが必要になるわけではありません。LabelList では異なるラベルごとにメンバーシップを数えます。一行が複数のラベルビットマップに現れる場合があります。NGram では、異なる gram/行の組を数えます。一行内で同じものが繰り返し現れても、メンバーシップは増えません。この NGram インデックスは、n-gram トークナイザーを使用する FTS インデックスとは異なります。
| インデックスファミリー | 構成要素 | 読み込み済みペイロード/予算の立て方 |
|---|---|---|
| Bitmap | キー検索 | 保持するキー値、エントリ参照、検索オブジェクトを実測 |
| LabelList | ラベル検索/ラッパー | 異なるラベルキー、エントリ参照、ラッパーオブジェクトを実測 |
| NGram | gram 検索 | 保持する gram、エントリ参照、検索オブジェクトを実測 |
| 三種類すべて | メンバーシップビットマップ:配列コンテナ | 通常の Roaring 配列コンテナでは約 2 * c バイト。c は 65536 値の範囲内にある UInt16 エントリ数 |
| 三種類すべて | メンバーシップビットマップ:密なコンテナ | 通常の密なコンテナでは 8192 バイト。65536 値の範囲をカバー |
| 三種類すべて | メンバーシップビットマップ:ラン/フルフラグメント表現 | 実際の表現を実測。各コンテナに選択された表現を使用し、代替表現をすべて加算しない |
| 三種類すべて | ビットマップディレクトリ/容量 | フラグメント/上位ビットのディレクトリ、コンテナオブジェクト、余剰容量を実測 |
| 存在する場合 | NULL/NULL リストの追跡 | 保持する NULL メンバーシップ構造を別途実測 |
| 各ファミリーの合計 | 検索用構造 + 読み込まれたメンバーシップ表現 + ディレクトリ + NULL 追跡 | 共有割り当ては一回だけ数える。シリアライズされたビットマップのサイズは、常駐メモリ量を保証しない |
例えば、読み込まれた密なコンテナが 256 個ある場合、ビット集合のペイロードは 2 MiB です。これにはキー、ディレクトリ、NULL 追跡、オブジェクトのオーバーヘッドは含まれません。
その他のスカラーインデックスと全文インデックス。 各行は別の構成要素を示します。小計の行はその前の構成要素をまとめたものであり、再度加算してはいけません。BE に保持される内容だけを数えます。実測とは、リーダーによる実際の割り当てが必要であり、その構成要素を省略してよいという意味ではありません。
| インデックスファミリー | 構成要素 | 読み込み済みペイロード/予算の立て方 |
|---|---|---|
| FTS/Inverted | 検索語の辞書 | 読み込まれた検索語、辞書構造、検索用の割り当てを実測 |
| FTS/Inverted | 文書メタデータ | 保持する文書マッピング、長さ、その他のリーダーメタデータを実測 |
| FTS/Inverted | 非圧縮ポスティングの行 ID | 8 * T バイト。T は UInt64 の検索語/文書エントリ数 |
| FTS/Inverted | 非圧縮ポスティングの頻度 | Float32 頻度には 4 * T。ID + 頻度の合計は 12 * T |
| FTS/Inverted | 位置情報(有効な場合) | 4 * O バイト。O は保存済みの Int32 出現数 |
| FTS/Inverted | 位置情報のオフセット | 実際に保持するオフセット配列のバイト数。4 * O とは別 |
| FTS/Inverted | 圧縮ポスティングのレイアウト | 対応する非圧縮レイアウトの計算式の代わりに、実際に保持する圧縮ブロックを使用 |
| FTS/Inverted | スコアリング/ブロックメタデータと割り当てのオーバーヘッド | 保持するブロック記述子、スコアリングデータ、オブジェクト、余剰容量を実測 |
| ZoneMap | 最小値 | Z * w 論理バイト。Z は読み込み済みゾーン数、w は固定長値の幅 |
| ZoneMap | 最大値 | 論理バイト数 Z * w。最小値 + 最大値の合計は 2 * Z * w |
| ZoneMap | カウントとゾーン境界 | 保持するカウントと境界を実測。部分ゾーンも含め、Fragment ごとにゾーンを数える |
| ZoneMap | NULL 追跡と、ボックス化された値/オブジェクトのオーバーヘッド | オプションの NULL 行集合と実際のスカラー割り当てを実測。これらは論理的な最小値/最大値のバイト数を大きく上回る場合がある |
| BloomFilter | フィルタービット配列 | 実際に割り当てられたフィルターのバイト数を合計。同じサイズのフィルターでは Z * F。Z はフィルター数、F は各フィルターのバイト数 |
| BloomFilter | ゾーン記述子/フィルターオブジェクト | 保持する記述子、フィルターパラメータ、オブジェクトのオーバーヘッドを実測 |
| BloomFilter | NULL 追跡 | オプションの NULL 行集合を実測 |
| RTree | 境界ボックスの座標 | 2D エントリあたり四つの Float64 座標で 32 * Q。キャッシュ内の全ツリーレベルにわたる Q 個のエントリを数える |
| RTree | 行/子ページ ID | UInt64 ID について 8 * Q。座標 + ID の合計は 40 * Q |
| RTree | ツリーメタデータとページオフセット | 保持するツリー記述子とページオフセットバッファを実測 |
| RTree | NULL 追跡、Arrow/オブジェクト、余剰容量 | 実際の追加割り当てを実測 |
| JSON-path ラッパー | 基になるインデックス | 選択したスカラーインデックスファミリーの構成要素をすべて含む予算を使用 |
| JSON-path ラッパー | パスとラッパーの構造 | 基になるインデックスに加えて、保持するパスとラッパーの割り当てを実測 |
Bloom フィルターには、構築ツールによる実際のブロック境界に丸められた割り当てが必要です。行数と偽陽性率だけでは、すべてのレイアウトを記述できません。なじみのないレイアウトや実験的なレイアウトには、別のファミリーの計算式を適用せず、読み込み済みの割り当てを実測してください。一致する行が少なくても、辞書、ツリー、文書のメタデータが大量に必要になる場合があります。
クエリのワーキングメモリは、すべてのキャッシュ表とは分けてください。 一時的な積集合/和集合、結果の行 ID 集合、近似フィルターの再確認、正確な RTree 判定のための元のジオメトリの読み取り、出力列の読み取りには、それぞれ予算が必要です。
基になる構造については、アップストリームの BTree 形式、Bitmap 形式、FTS 形式を参照してください。上記の計算式は読み込み済みペイロードの見積もりであり、圧縮ファイルのサイズやクエリ RSS 全体ではありません。
インデックスサイズから BE ごとのワーキングセットを求める
セグメント s について、N_s をインデックス登録済みベクトル数、P_s を IVF パーティション数、H_s を特定の BE で保持する異なるパーティション数とします。パーティションが概ね均等な場合は、次のように計算します。
cached_vectors_on_BE ≈ sum over segments (N_s * H_s / P_s)
index_cache_target ≈ cached vector/row-ID payload
+ cached HNSW graphs
+ centroids, codebooks and other index allocations
+ measured headroom
パーティションに偏りがある場合は、実際のパーティション行数を使用してください。H_s は単一クエリの nprobes だけでなく、複数クエリにわたるワーキングセットを表します。多様なクエリは最終的にすべてのパーティションにアクセスする可能性があります。一方、繰り返されるクエリや同時実行クエリは、同じキャッシュ内パーティションを共有できます。異なる (dataset, index segment, partition) エントリを数え、その BE がアクセスするすべてのテーブル、インデックス、バージョンを含めてください。ある BE のキャッシュが温まっても、別の BE のキャッシュは温まりません。また、スケジューリングによって各インデックスセグメントが 一つの BE に固定される保証もありません。観測した配置と再利用の状況で正当化できない限り、インデックス全体のサイズを BE 数で割らないでください。
実行中のパーティション読み込み、キャッシュから追い出された後もクエリが保持するオブジェクト、Prefilter の状態、距離テーブル、候補ヒープ、デコード済みバッチ、精緻化、第二段階の取得には、クエリ実行時の追加メモリが必要です。top_k、ef、refine_factor、スキャンの同時実行数、nprobes は、このメモリまたはアクセスされるワーキングセットに影響しますが、保存されたベクトルあたりのバイト数は変えません。設定したキャッシュ容量は、Lance や BE の RSS の厳密な上限ではありません。また、キャッシュを増やしても、距離計算やクエリごとのフィルタリング処理はなくなりません。
設定例とチューニング
FP32、768 次元の IVF_PQ インデックスに一億(100 million)ベクトルがあり、m=96、b=8、一つのインデックスセグメント内に均等な 4096 パーティションがあるとします。ある BE の代表的なワークロードが、異なる 1024 パーティションに繰り返しアクセスします。
Full code/row-ID payload = 100000000 * (96 + 8) = 10400000000 bytes ≈ 9.69 GiB
Cached payload = 10400000000 * 1024 / 4096 = 2600000000 bytes ≈ 2.42 GiB
IVF centroids = 4096 * 768 * 4 = 12582912 bytes = 12 MiB
PQ codebook = 256 * 768 * 4 = 786432 bytes = 0.75 MiB
これが BE 上で唯一の大きなインデックスワーキングセットであり、メモリ予算が許すなら、この例では 4 GiB がインデックスキャッシュの妥当な初期割り当てです。計算したペイロードより多くの領域を追加のインデックス割り当て用に残せます。これは検証すべき初期値であり、どのケースにも通用するオーバーヘッド率ではありません。ワークロードでインデックス全体を常駐させる必要がある場合は、4 GiB では足りません。デフォルトの 10 GiB でも、他の割り当てを加える前の 9.69 GiB のペイロードに近い値です。
BE 上で、4 GiB のインデックスキャッシュと 1 GiB のメタデータキャッシュに加え、ピーク時のクエリメモリ、他の Doris ワークロード、プロセス/OS 用の余裕を確保できる場合、次のように設定します。
lance_index_cache_size_bytes = 4294967296
lance_metadata_cache_size_bytes = 1073741824
設定を有効にするには、その BE を再起動します。予算は BE ごとに個別に適用してください。これらは BE の設定パラメータであり、Catalog プロパティや SQL セッション変数ではありません。
- 異なるクエリベクトル、フィルター、テーブルを含む代表的なクエリと同時実行数でウォームアップします。一つのベクトルだけを繰り返すと、多様なワークロードのワーキングセットを過小評価します。
doris_be_lance_session_index_cache_usage_bytesとdoris_be_lance_session_index_cache_capacity_bytesを比較し、hits_total/misses_totalの区間内の増分を観察します。完了したクエリごとに、LanceIndexCacheHits、LanceIndexCacheMisses、LanceIndexObjectLoads、LanceIndexComponentsLoadedもLanceIndex内で確認します。クエリ単位のキャッシュ検索とコンポーネントの読み込みは対象範囲が異なり、BE 全体のメトリクス差分と同じものとして扱えません。使用量が容量に近い状態でミスが続く場合は、キャッシュ内でエントリの入れ替わりが頻発している可能性があります。ただし、新しいパーティション、インデックスのバージョン、異なる BE、内容を保持しないストリーミング経路もミスの原因になります。再利用の状況と利用可能メモリから妥当と判断できる場合にのみ容量を増やします。- BE のメモリ予算が許す場合、メタデータキャッシュはデフォルトの 1 GiB を起点に容量を決めます。
usage_bytesとヒット/ミスのメトリクスを使って調整します。需要はファイル/Fragment 数、スキーマ/ページのメタデータ、削除情報、行 ID マッピングによって決まります。ベクトルインデックスサイズの固定割合ではありません。BE のメタデータキャッシュは、FE のテーブルアクセスキャッシュとは別です。 - いずれかのキャッシュを増やす前に、想定する同時実行数で BE プロセスのメモリとクエリのピークを確認します。キャッシュヒット率が高いのにレイテンシーが高い場合は、スコアリング、Prefilter、デコード、取得のコストが原因かもしれません。キャッシュの追加が有効とは限りません。
- 精緻化や出力列の取得など、繰り返されるデータファイルの読み取りに対して、
lance_data_cache_disk_capacity_bytesの容量を独立して決めます。増やしてもインデックスキャッシュは拡大せず、インデックスキャッシュのミスも補えません。インデックスファイルはこのディスクデータキャッシュを経由しないためです。lance_data_cache_read_block_size_bytesは最初はデフォルトの 1 MiB のままにし、有効な読み取りと読み取り増幅を測定してから変更してください。
4. 共有キャッシュの予算を立てる
各 BE では、Lance は共有セッションと 一つのインデックスキャッシュ容量を使用します。ベクトルパーティション、BTree ページ、Bitmap/LabelList エントリ、全文インデックスの内容は、同じ BE でアクセスされる異なるテーブルや Catalog のインデックスを含め、この領域を奪い合う可能性があります。キャッシュキーはそれぞれを識別しますが、容量は予約しません。Lance の BE キャッシュ設定には、テーブル別またはインデックスタイプ別の割り当て枠や、固定保持の制御はありません。
キャッシュ領域を消費するのは、読み込まれた内容だけです。複数のインデックスを作成しただけでは、そのすべては読み込まれません。クエリで使用するようになると、あるインデックスの読み込みによって別のインデックスのエントリが追い出されることがあります。例えば、広範囲の BTree レンジスキャンや多様な FTS クエリが、温まっていたベクトルパーティションを置き換える可能性があります。その後のベクトルクエリは、それらを再読み込みします。インデックスのバージョンが変わると、古いエントリが残っている間に新しいエントリが加わる場合もあります。追い出されても、実行中のクエリが参照しているオブジェクトはすぐには解放されません。
共有割り当てを二重計上せず、混合ワーキングセットを計画します。
index_cache_target_on_BE ≈ vector working set
+ BTree working set
+ Bitmap / LabelList / NGram working sets
+ FTS and other index working sets
+ measured headroom
例えば、測定値とペイロード見積もりから、インデックス構造を含めてベクトルに 2.5 GiB、BTree に 0.25 GiB、Bitmap に 0.5 GiB、FTS に 0.75 GiB が必要だとします。ワーキングセットの合計は 4 GiB です。初期インデックスキャッシュを 5 GiB にすると、増加や見積もり誤差のために 1 GiB を残せますが、すべてのワークロードが収まる保証はありません。さらに BE に、1 GiB のメタデータキャッシュとピーク時のクエリメモリ、他の Doris ワークロード、プロセス/OS 用の余裕を確保できる場合は、次のように設定します。
lance_index_cache_size_bytes = 5368709120
lance_metadata_cache_size_bytes = 1073741824
インデックスキャッシュとメタデータキャッシュには、それぞれ独立した容量があります。メタデータエントリが直接インデックスエントリを追い出すことはなく、一方を増やしても他方は拡大しません。ただし、両方とも同じ BE プロセスのメモリを消費します。ディスクデータキャッシュは 三つ目の予算であり、インデックスファイルはキャッシュしません。同じ BE 上で Catalog を分けても、インデックスキャッシュは分離されません。ワークロードを分離するには、別の BE リソースと、デプロイメントでサポートされるルーティングが必要です。
競合を診断するには、まず一方のクエリ群をウォームアップして、レイテンシーとキャッシュメトリクスの差分を記録します。次にもう一方のクエリ群を実行し、最初のクエリ群を再実行します。インデックスのバージョンと BE の配置が同等であり、キャッシュ使用量が容量に近い状態でミスや再読み込みが再び発生するなら、追い出しの圧力が示唆されます。セッションのヒット/ミスメトリクスは複数ワークロードを集計するため、クエリ単位の LanceIndexCacheHits/LanceIndexCacheMisses、LanceIndexComponentsLoaded、LanceIOReadBytes と組み合わせてください。コンポーネントの読み込み数はミス数ではなく、実行時の読み取りバイト数は実測されたリモートトラフィックではありません。すべてのインデックスを事前に温める行為自体が、保持したいワーキングセットを追い出す可能性があります。代表的な混合ワークロードでウォームアップし、再利用と利用可能メモリから妥当と判断できる場合にのみ容量を増やしてください。
5. インデックスセグメントの粒度を選ぶ
Index Segment、データ Fragment、IVF パーティション、BTree リーフページは、それぞれ異なる単位です。現在の Doris のベクトル検索経路では、選択された各物理 Index Segment が 一つのスキャン Split になります。インデックスでカバーされない Fragment は Flat Search の Split を追加します。FTS も物理インデックスセグメントを使用します。一つのベクトルセグメント内の IVF パーティションが、BE 間に分散される個別の Doris Split になるわけではありません。Lance は内部で処理を並列実行する場合があります。通常のスカラースキャンでは、セグメント単位のグループ化は選択されたインデックスと実行計画に依存します。すべてのスカラーインデックスがベクトル検索の Split 方針に従うと想定しないでください。
B をそのクエリで実際に利用できる BE 数、S を選択されたベクトルセグメント数とします。S が B より小さい場合、そのクエリではすべての BE を使うだけのインデックス付きスキャン Split がありません。S を増やすとスケジューリングの機会は増えますが、すべての Split が同時に実行される保証はありません。スキャナーの制限、Lance 内部の同時実行、CPU、I/O、キャッシュの常駐状態、同時実行クエリも影響します。Doris のパイプラインインスタンスだけを増やしても、一つのインデックスセグメントは分割されません。
大きなデータセットでは、適度にバランスの取れたベクトルセグメント数 B、2 * B、4 * B を、最初の実験として比較します。これは公式な最適値でも、小さなデータセットへの要件でもありません。説明用に、100 百万ベクトルのデータセットと、利用できる 八台の BE を考えます。
| セグメント数 | セグメントあたりの平均ベクトル数 | 評価すること |
|---|---|---|
| 8 | 12.5 百万 | BE を利用するのに十分な Split があり、セグメントごとの処理は少ない |
| 16 | 6.25 百万 | スケジューリングの柔軟性が高まり、一つの大きなセグメントの影響を受けにくい |
| 32 | 3.125 百万 | タスクが増える一方、検索、読み込み、マージのオーバーヘッドが増える可能性がある |
Fragment 数だけでなく、検索処理の見積もりと読み込むインデックスのバイト数を均等にしてください。次元数とエンコーディングが同じベクトルでは、行数を揃えることが有用な出発点です。ただし、HNSW グラフのサイズ、IVF の偏り、スカラー選択率、文書長によってバランスが変わる場合があります。普遍的なセグメントあたりの行数やファイルサイズのしきい値はありません。高スループットのワークロードでは、同時実行クエリのために容量を残せるよう、クエリあたりのタスク数を少なくした方がよい場合があります。単一クエリのレイテンシーは、Split を増やすことで改善する場合があります。
セグメント数を増やすと、セグメントごとのセットアップ、メタデータ/量子化器オブジェクト、ローカルな候補生成も増えます。各ベクトル/FTS Split は最大で top_k + offset 個の候補を保持し、Doris はこの上限を S で割りません。したがって、利用可能な一致数の範囲内で、スキャン側の候補数の上限は S * (top_k + offset) です。top_k=1000、offset なし、32 Split の場合、Doris による絞り込み前に最大 32000 個の候補が生じます。ローカル TopN はネットワークトラフィックを減らす場合があるため、この値はネットワーク行数の保証ではありません。top_k が大きい場合、候補の処理が特に重要になります。
セグメント数は、各セグメントの IVF num_partitions およびクエリの nprobes と合わせて調整します。パーティションが均等な場合、探索されるベクトル数の概算は sum(N_s * nprobes_s / P_s) で、各セグメントでは N_s が上限です。これは HNSW の比較回数を求める計算式ではありません。同じ nprobes でも、セグメントの再構築やパーティション数の変更によって再現率が変わる場合があります。同等の再現率でレイテンシーを比較してください。HNSW の ef と精緻化の設定も比較に影響します。
Lance ツールでセグメントを構築・保守し、意図した論理インデックスとしてコミットします。テーブル全体を対象とする別々の名前のインデックスを複数作っても、複数の物理セグメントを持つ 一つのインデックスの代わりにはなりません。Fragment 全体をバランスよくグループ化し、小さな追加によって極小セグメントが多数蓄積するのを避け、組み込まれた Lance バージョンでサポートされる保守 API を使用してください。Doris の EXPLAIN/Profile で、結果の物理セグメント数と、カバーされない Fragment 数を確認します。SHOW INDEX の行数から推測しないでください。すべてのセグメントを 一つにマージすると、Doris のスキャン並列性が低下する場合があります。セグメントの構築とコミットの考え方については、Lance の分散インデックス構築 を参照してください。
BTree のポイント検索や選択率の高い Bitmap クエリでは、セグメントを多数に分けると検索やオープン処理が増える一方、セグメントごとに削減できる処理は少ない場合があります。FTS ではセグメントごとに辞書/ポスティングの検索と候補のマージが増えます。ベクトル用のセグメント数をすべてのインデックスファミリーに適用せず、これらのワークロードは別々に評価してください。分割しても不要な行ごとの Prefilter 構築はなくなりません。リーダーのフィルタリング動作を独立して確認してください。
6. 混合ワークロードを検証する
- インデックスの選択、物理 Split、インデックスのカバレッジを確認します。フォールバックスキャンと第二段階の取得も、レイテンシー分析に含めます。
- 各インデックスが保持するペイロードを見積もり、共有ワーキングセットを合計します。並列読み込み、行 ID 集合、候補ヒープ、デコードを含むクエリメモリを別途確保します。
- 実際に近い同時実行数で、キャッシュが冷えた状態と温めた混合ワークロードを比較します。P95 レイテンシー、スループット、該当する場合の再現率、BE ごとのピークメモリ、キャッシュ使用量とヒット/ミスの差分、インデックス読み込み I/O、スキャン時間の偏りを追跡します。
- キャッシュ容量、セグメント数、検索パラメータのいずれか一つだけを変更します。セグメントの再構築によってモデルが変わる場合は、再現率を再確認します。Split やキャッシュを増やしてもスループット、レイテンシー、メモリの改善が止まったら、増加を止めます。
- 大規模な追加、インデックスの再構築、BE のスケーリング、クエリ構成の変更後に再検証します。一つのベクトルやテーブル向けに調整した設定は、すべてのワークロードに共通する安定した予算ではありません。
7. リソースを増やす前に診断する
| 観測結果 | 最初に確認すること | 検討する対処 |
|---|---|---|
| キャッシュ使用量が容量に近く、インデックスのミスが繰り返される | インデックス/バージョンの変更、BE の配置、ワーキングセットの合計 | メモリが許す場合は lance_index_cache_size_bytes を増やす。不要なウォームアップを減らすか、別の BE リソースにワークロードを分離する |
| スカラーまたは FTS クエリの後にベクトルクエリが遅くなる | 共有キャッシュの予算にある、ワークロード間の追い出し確認を繰り返す | 両方のクエリ群の予算を合わせて立てる。同じ BE 上で Catalog を分けてもキャッシュは分離されない |
| 大きなインデックス付きデータセットを少数の BE しかスキャンしない | 物理セグメントの Split 数、利用できる BE、スキャナーのスケジューリング | より多くのバランスの取れたセグメントを比較する。パイプラインインスタンスだけを増やしてもセグメントは分割できない |
| セグメントを増やすと CPU、メモリ、またはマージ時間が増える | Split ごとの候補数、top_k + offset、クエリの同時実行数 | セグメント数を減らすか、アプリケーションが必要とする数を超える候補の要求を避ける。検索設定を変更したら再現率を再確認する |
| キャッシュヒット率は高いがレイテンシーも高い | スコアリング、Prefilter の処理、デコード、第二段階の取得時間 | 支配的な段階を最適化する。インデックスキャッシュを増やしても、計算や取得はなくならない |
| メタデータの再読み込みが続く | メタデータキャッシュの使用量とヒット/ミスの差分、Fragment/ファイル数、バージョンの入れ替わり | 再利用とメモリが許す場合は、lance_metadata_cache_size_bytes を独立して調整する |
| キャッシュ使用量が容量未満なのにプロセスメモリが高い | 同時実行クエリの割り当て、実行中の読み込み、保持されるオブジェクト、他の BE ワークロード | 測定したピークに基づいて同時実行数またはキャッシュ予算を下げる。キャッシュ容量はプロセスメモリの上限ではない |
| データファイルの I/O が高いまま | ディスクキャッシュのカウンター、再利用、読み取りブロックサイズ、クエリの出力列 | データキャッシュの予算または読み取り粒度を調整する。インデックスファイルはこのディスクデータキャッシュを使用しない |
クエリ Profile リファレンスで、FE のメタデータ/プランニング、BE のインデックス/スキャン処理、第二段階の行 ID 取得を切り分けます。キャッシュカウンターと BE 全体のメトリクスについては、キャッシュの有効性を確認するを参照してください。集計されたヒット率だけでは、どのインデックスが追い出されたか、どの段階がレイテンシーを支配するかは分かりません。設定は一度に一つだけ変更し、比較ごとにワークロード、インデックスバージョン、BE の配置、レイテンシー、スループット、再現率、ピークメモリを記録してください。