Amazon Web Services ブログ

Amazon OpenSearch Service、FAISS エンジンでのベクトル検索トラブル対処の考え方:検索レイテンシの増加が問題になっている場合

はじめに

Amazon OpenSearch Service を使用したベクトル検索では exact k-NN もしくは Approximate k-NN が使用されます。exact k-NN では総当たり的に近傍を探索することにより最も正確な検索が可能ですが、ベクトルデータ数に対して線形に実行時間が増えるため、大規模なデータセットに対しては深刻にパフォーマンスが悪化する可能性があります。一方で Approximate k-NN は精度を一定落とす代わりに高速な検索を実現する手法です。Amazon OpenSearch Service において利用できる Approximate k-NN アルゴリズム用のベクトル検索エンジンは主に FAISS と Lucene があり、FAISS エンジンにはアルゴリズムとして HNSW と IVF があります(参考)。

本ブログでは、FAISSエンジンを使用したベクトル検索において、検索レイテンシが構築初期より増加していることが問題になっている場合の原因調査および対処法選択の考え方について説明します。

シナリオ:検索レイテンシーが構築初期より著しく増加している

OpenSearch Service において検索リクエストに対するパフォーマンスはユーザーの視点でもクラスター全体の健全な運用をする上でも重要な要素です。ベクトル検索ワークロード構築初期においてはデータ量が少ないため大抵の場合検索レイテンシーが問題になることはありませんが、検索対象となっているベクトルデータや新規に投入されてインデクシングされるベクトルが多くなるにつれて、さまざまな要因により検索レイテンシーが悪化する可能性があります。このような問題は単純な、検索パフォーマンスが悪化しているタイミングや CPU リソースやメモリの使用状況からどのようことが要因となっているか調査し、対処法を選択することが重要です。インスタンスタイプを増強したり、データノード数を増やすなどの対応によりクラスター全体のキャパシティを大きくすることでこのような問題に対処できることは多いですが、ここではその前段階として検討すべき最適化の手法について紹介します。
調査および対処法選択の大まかな流れは以下にようになります。

問題調査と対処のフローチャート

原因調査2.1:JVMMemoryPressure、CPUUtilizationの確認

検索パフォーマンスが悪化している場合などにおいて調査すべきメトリクスの一つが JVMMemoryPressure および CPUUtilization です。これらのメトリクスは AWS コンソールの OpenSearch Service ページにおける、ドメインの Cluster Health および CloudWatch Metrics から調査することができます。
JVMMemoryPressureCPUUtilization を確認することにより、検索およびインデクシングといった処理にどの程度の負荷がかかっているかが確認できます。JVMMemoryPressure は JVM ヒープメモリの使用率であり、CPUUtilization はデータノードにおける CPU 使用率です。k-NN グラフ構築などのデータ投入処理やセグメントのマージなどで上昇します。これらのメトリクスが高い値を示している場合は、クエリやデータ投入のパフォーマンスが悪化している可能性があります。一般的に JVMMemoryPressure は 75% 以上になると GC が発生しますが、インデクシングや検索リクエストの処理が追いつかない場合 GC が多発し、JVMMemoryPressure も下降しない状態になります。さらに 95% に到達するとサーキットブレーカーにより新規リクエストを拒否します。

原因調査2.2:シャードの偏り状況の調査

OpenSearch Service のドメインでは、シャード分布がノード間で偏りが生じることにより特定のノードに処理が集中する場合があります。このような場合、ドメイン全体のリソース状況に余裕があったとしてもノード単体のリソース不足によりパフォーマンスに支障をきたす可能性があります。
以下のコマンドにより、ノードごとにシャード数やデータ量の偏りがないか調査することができます。

GET _cat/allocation?v

また、以下のコマンドにより、シャードごとのデータ量に偏りがないか調査することができます。

GET _cat/shards?v

また、シャードごとの集計情報は Cluster Insights を確認することでも簡単に調査できます。OpenSearch UI から Cluster Insights を開き、対象となる OpenSearch ドメインを選択します。Shard view と記載されたタブを開くことで直近使用されている index の各シャードごとの集計情報が表示されます。

対処2.1:シャード最適化

各シャードが持つドキュメントのサイズを調整したり、ノードに存在するシャード数の偏りを解消することで検索パフォーマンスが改善する場合があります。ベクトル検索のインデックスにおいて、適切なシャードサイズは一般的に 10GB〜75GB とされています。検索クエリが、ベクトル検索に加えて様々な全文検索を加えたハイブリットなものである場合、シャードサイズを小さくすることで検索レイテンシを改善する効果があります。一方で、シャードサイズを大きくすることで純粋なベクトル検索クエリに対してはパフォーマンス改善につながる可能性があります。また、ノードごとにシャードの偏りが発生している場合は、プライマリシャード数をノード数の倍数にすることで偏りを解消でき、検索パフォーマンス改善につながる可能性があります。

インデックスのシャード数を変更する場合、Shrink API の実行や reindex による index 移行を行う必要がありますが、reindex 中は CPU 負荷が増大することや index 移行に際して実行中のワークロードに影響を与えないよう注意が必要です。

対処2.2:量子化

OpenSearchにおけるベクトルはknn_vector型の32ビット浮動小数点配列として保存されますが、これらをよりデータ量の小さなベクトルに圧縮するアプローチです。以下の4つの量子化手法をサポートしています。より圧縮度の高い量子化は検索の精度を落とす可能性がありますが、メモリ使用量の削減だけでなく、検索パフォーマンスの向上やディスク使用量の削減にもつながります。精度要件に余裕があり、検索速度を維持もしくは高速化した上でメモリ効率化も行いたい場合有用な手法です。
詳細に関してはこちらのブログも参照ください。

スカラー量子化 (Scaler Quantization)

  • バイナリ量子化
    ベクトルの各次元を 1 ビット(-1 または +1)で表現する最も圧縮率の高い量子化手法。最大 32 倍と大幅にメモリ、ストレージ使用量を低減できますが、精度の低下が最も大きくなります。
  • バイト量子化
    各次元を 8 ビット整数(int8)で表現し、元の浮動小数点数を 256 段階に量子化する手法。メモリを約 1/4 に削減しつつ、比較的高い精度を維持できるバランスの良い方式です。
  • FP16量子化
    32 ビット浮動小数点数(FP32)を 16 ビット浮動小数点数(FP16)に変換する手法。メモリを半分に削減し、精度の劣化が少ないため、高精度が求められる用途に適しています。

直積量子化(Product Quantization)

ベクトルを複数のサブベクトルに分割し、各サブベクトルを独立にクラスタリングしてコードブックで表現する手法。最大 64 倍と高い圧縮率と高速な検索を両立でき、大規模ベクトル検索でしばしば使用されますが、利用にあたり事前のトレーニングが必要になります。

対処2.3:force merge、warm upの事前実行

FAISS エンジンでは、初回の検索クエリを行う際にベクトルグラフをメモリにロードする必要があり、これに非常に時間がかかる場合があります。事前に Warm-up APIを使用してグラフをメモリにロードすることにより初回検索のレイテンシを低減することが可能です。

GET /_plugins/_knn/warmup/<index-name>

また OpenSearch において、投入されたデータは最初セグメントと呼ばれる単位で管理されます。このセグメント数が大きくなると検索パフォーマンスの低下につながります。Warm-up 実行の前に Force Merge API を実行することでセグメント数を小さくすることができます。ただし、これらの処理は CPU リソースを多く消費する可能性があるので、データ投入などのリクエストが少ないタイミングで行うべきです。

POST /<index-name>/_forcemerge

対処2.4:refresh_intervalの調整

OpenSearch では、refresh_interval で指定した時間ごとに、メモリバッファに保持していたデータをセグメント化し検索対象に登録する処理を行っています。Approximate k-NN では、このタイミングでグラフの構築が行われます。
refresh_interval が小さいと、グラフ構築の頻度が上がり CPU リソースを消費する他、セグメント数が多くなることで検索クエリパフォーマンスが低下する可能性があります。データ投入から検索対象になるまでにかかる時間が長くなるデメリットがありますが、refresh_interval を大きくすることでパフォーマンス改善につながります。

PUT /<index-name>/_settings
{
  "index.refresh_interval": "30s"  # デフォルトは1s
}

対処2.5:GPUアクセラレーションの活用(対象:HNSW、バージョン3.1+)

ベクトルデータ数が非常に大きくなると、データ投入のタイミングで発生するグラフ構築に長い時間がかかり、CPU リソースの消費も大きくなります。ベクトルデータのインデクシングにおける GPU 加速を使用すると、大規模ベクトルデータに対するグラフ構築をマネージドな GPU を使用して高速化しつつ、CPU への負荷をオフロードすることが可能です。もし、検索クエリなどのパフォーマンス低下がベクトルデータのインデクシングによる CPU リソースの枯渇が原因であった場合、この機能の活用により改善する可能性があります。

$ aws opensearch update-domain-config \
    --domain-name <domain-name> \
    --aiml-options '{"ServerlessVectorAcceleration": {"Enabled": true}}'

GPU アクセラレーションを使用するかどうかは index.knn.remote_index_build.enabled からインデックスごとに設定可能です。

PUT <index-name> 
{ 
    "settings": { 
        "index.knn": true, 
        "index.knn.remote_index_build.enabled": true 
    }, 
    "mappings": { 
        "properties": { 
            "vector_field": { 
                "type": "knn_vector", 
                "dimension": 768
            }, 
            "text": { 
                "type": "text" 
            } 
        } 
    } 
}

ベクトルグラフ構築の高速化は最大 10 倍にのぼり、ユーザーは GPU リソースの使用を全く意識する必要はありません。有効化にはまず、OpenSearch ドメインの設定をアップデートします。この設定変更によるダウンタイムの発生はありません。
詳細についてはこちらのブログを参照ください。

対処2.6:クエリの工夫

検索パフォーマンスに問題がある場合、しばしばクエリ設計を工夫するアプローチが有効です。一般に OpenSearch におけるクエリ設計の最適化は様々な要素があり、クエリの種類によって取ることのできるアプローチも異なります。
ここでは、knn_vector フィールドへのクエリに対して取ることのできるアプローチを紹介します。

ef_search(対象:HNSW、バージョン2.16+)

HNSW を使用している場合、クエリに対して ef_search として小さい値を指定することで、精度を落として検索速度を向上することができます。ef_search はインデックスマッピング作成時に指定することができるパラメータですが、デフォルトは 100 となっており、クエリごとに調整することで、精度と検索速度のトレードオフを調整することが可能です。

GET /<index-name>/_search
{
  "size": 2,
  "query": {
    "knn": {
      "my_vector": {
        "vector": [2, 3, 5, 6],
        "k": 2,
        "method_parameters": {
          "ef_search": 90
        }
      }
    }
  }
}

HNSW ではこれ以外にもパラメーターが存在し、精度、レイテンシー、メモリ使用量のトレードオフを調整することができます。クエリのタイミングで指定できるパラメーターは ef_search のみであり、他のパラメーターはインデックス作成時に指定することができます。詳細に関してはこちらの資料を参考にしてください。

_sourceフィルタリングでベクトルデータ転送量を削減

ベクトル検索の多くの場合、ヒットしたドキュメントが持つベクトルデータはアプリケーションから直接利用されません。検索クエリで事前にベクトルデータを返さないように設定することでネットワーク転送や json シリアライゼーションのオーバーヘッドが削減され、結果的に検索レイテンシを低減する効果があります。

GET /<index-name>/_search
{
  "_source": {
    "excludes": ["vector_field"]
  },
  "query": {
    "knn": {
      "vector_field": {
        "vector": [...],
        "k": 5
      }
    }
  }
}

まとめ

本ブログでは、OpenSearch Service において FAISS エンジンを使用したベクトル検索ワークロードを運用している際に発生しうるトラブルとして検索クエリに対するレイテンシーの増加に着目し、その原因究明と対処方法選択の考え方について紹介しました。一般的に OpenSearch を利用した検索のクエリパフォーマンスを改善する場合は、様々な要素が複雑に絡み合う場合があり、特に複数の検索条件、フィルター、集計などを併用している場合にクエリパフォーマンスに影響が出る場合が多いです。一方で今回取り上げたようにベクトル検索単体においてもそのパフォーマンスの改善の余地があり、適切にモニタリングをした上で改善施策を取ることが重要です。
OpenSearch Service の機能は継続的に拡充されているため、新機能を活用するためにもクラスターのバージョンアップグレードを定期的に検討することを推奨します。


著者

黒木 琢央 (Takuo Kuroki)

アマゾンウェブサービスジャパン合同会社ソリューションアーキテクト

2024年4月入社。現在toCのITサービス提供企業におけるクラウド全般の技術支援を行いつつ、OpenSearchのコミュニティ活動や機能改善に取り組んでいます。