<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Lucene - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Lucene - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/jp/search-labs/blog/category/lucene</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/blog/category/lucene</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/category/lucene.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 11:54:59 GMT</lastBuildDate>
  <item>
    <title><![CDATA[ベクター検索フィルタリング: 関連性を保つ]]></title>
    <description><![CDATA[クエリに最も類似した結果を見つけるためにベクトル検索を実行するだけでは不十分です。検索結果を絞り込むには、フィルタリングが必要になることがよくあります。この記事では、Elasticsearch と Apache Lucene でのベクトル検索のフィルタリングの仕組みについて説明します。]]></description>
    <content:encoded><![CDATA[<p>関連する結果を見つけるには、ベクター検索だけでは不十分です。検索結果を絞り込み、無関係な結果を除外するのに役立つフィルタリング基準を使用することは非常に一般的です。</p><p>ベクトル検索でのフィルタリングの仕組みを理解すると、パフォーマンスとリコールのトレードオフのバランスをとるのに役立ちます。また、フィルタリングの使用時にベクトル検索のパフォーマンスを高めるために使用される最適化のいくつかを知ることもできます。</p><h2>なぜフィルタリングするのですか?</h2><p>ベクトル検索は、大規模なデータセット内で関連情報を検索する方法に革命をもたらし、クエリと意味的に類似する項目を発見できるようになりました。</p><p>ただし、類似アイテムを見つけるだけでは十分ではありません。多くの場合、特定の基準や属性に基づいて検索結果を絞り込む必要があります。</p><p>電子商取引ストアで商品を検索していると想像してください。純粋なベクター検索では視覚的に類似したアイテムが表示される場合がありますが、価格帯、ブランド、在庫状況、または顧客評価でフィルタリングすることもできます。フィルタリングがなければ、類似した製品が大量に表示され、探しているものを正確に見つけることが難しくなります。</p><p>フィルタリングにより、検索結果を正確に制御できるようになり、取得された項目が意味的に一致するだけでなく、必要な要件をすべて満たすことが保証されます。これにより、より正確で効率的、そしてユーザーフレンドリーな検索エクスペリエンスが実現します。</p><p>Elasticsearch と Apache Lucene が優れているのはこの点です。さまざまなデータ タイプにわたって効果的なフィルタリングを使用することが、他のベクター データベースとの主な違いの 1 つです。</p><h2>正確なベクトル検索のためのフィルタリング</h2><p>正確なベクトル検索を実行するには、主に 2 つの方法があります。</p><ul><li><p>dense_vector フィールドに<code>flat</code>インデックス タイプを使用します。これにより、 <code>knn</code>検索では近似検索ではなく正確な検索が使用されるようになります。</p></li><li><p>ベクトル関数を使用してスコアを計算する<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">script_score クエリ</a>を使用します。これはどのインデックス タイプでも使用できます。</p></li></ul><p>正確なベクトル検索を実行すると、すべてのベクトルがクエリと比較されます。このシナリオでは、フィルターを通過するベクトルのみを比較する必要があるため、フィルタリングによってパフォーマンスが向上します。</p><p>いずれにしてもすべてのベクトルが考慮されるため、結果の品質には影響しません。興味のない結果を事前にフィルタリングするだけで、操作の数を減らすことができます。</p><p>これは非常に重要です。適用したフィルターによって少数のドキュメントが生成される場合、近似検索ではなく正確な検索を実行するとパフォーマンスが向上する可能性があるためです。</p><p>経験則としては、フィルターを通過するドキュメントが 10,000 個未満の場合は完全一致検索を使用します。<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>インデックスは比較が非常に高速なので、ベース インデックスが 10 万未満の場合は、完全一致検索を使用するのが合理的です。詳細については、<a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">このブログ投稿</a>をご覧ください。</p><p>フィルターが常に非常に制限的である場合は、HNSW ベースのインデックス タイプではなく<code>flat</code>インデックス タイプを使用して、近似検索ではなく完全検索に重点を置いたインデックス作成を検討してください。詳細については、 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">index_options のプロパティ</a>を参照してください。</p><h2>近似ベクトル検索のためのフィルタリング</h2><p>近似ベクトル検索を実行する場合、結果の精度とパフォーマンスをトレードオフします。HNSW のようなベクトル検索データ構造は、数百万のベクトルのおおよその最近傍を効率的に検索します。計算コストのかかるベクトル比較を最小限に抑えて、最も類似したベクトルを取得することに重点を置いています。</p><p>つまり、他のフィルタリング属性はベクター データの一部ではないということです。さまざまなデータ タイプには、用語辞書、投稿リスト、ドキュメント値など、検索やフィルタリングに効率的な独自のインデックス構造があります。</p><p>これらのデータ構造はベクトル検索メカニズムとは別であるため、ベクトル検索にフィルタリングをどのように適用すればよいでしょうか?フィルターには、ベクター検索の後にフィルターを適用する (ポストフィルタリング) か、ベクター検索の前にフィルターを適用する (プレフィルタリング) という 2 つのオプションがあります。</p><p>それぞれの選択肢には長所と短所があります。詳しく見ていきましょう！</p><h3>ポストフィルタリング</h3><p>ポストフィルタリングは、ベクトル検索が完了した後にフィルターを適用します。これは、最も類似した上位 k 個のベクトル結果が見つかった後にフィルターが適用されることを意味します。</p><p>明らかに、結果にフィルターを適用した後、 k 件未満の結果が返される可能性があります。もちろん、ベクトル検索 (より高い k 値) からより多くの結果を取得できますが、フィルターを適用した後に k 以上の結果が得られるかどうかはわかりません。</p><p>ポストフィルタリングの利点は、ベクトル検索の実行時の動作を変更しないことです。つまり、ベクトル検索はフィルタリングを認識しません。ただし、取得される結果の最終的な数は変わります。</p><p>以下は、 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">knn クエリ</a>を使用したポストフィルタリングの例です。フィルタリング句が knn クエリとは別であることを確認します。</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">post-filter</a>を使用した knn 検索では、ポストフィルタリングも利用できます。</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>knn 検索では明示的なポストフィルター セクションを使用する必要があることに注意してください。ポストフィルターを使用しない場合、knn 検索はポストフィルターを実行する代わりに<a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">、最近傍の結果を他のクエリまたはフィルターと組み合わせます</a>。</p><h3>プレフィルタリング</h3><p>ベクトル検索の前にフィルターを適用すると、まずフィルターを満たすドキュメントが取得され、次にその情報がベクトル検索に渡されます。</p><p>Lucene は<a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a>を使用して、フィルター条件を満たすドキュメントを効率的に保存します。次に、ベクトル検索は条件を満たすドキュメントを考慮して、HNSW グラフを走査します。候補を結果に追加する前に、それが有効なドキュメントの BitSet に含まれているかどうかを確認します。</p><p>ただし、有効なドキュメントでない場合でも、候補を調査してクエリと比較する必要があります。HNSW の有効性は、グラフ内のベクトル間の接続に依存します。つまり、候補の探索を停止すると、その近傍もスキップされる可能性があることを意味します。</p><p>ガソリンスタンドに行くために車を運転するのを想像してください。ガソリンスタンドのない道路を無視すると、目的地にたどり着く可能性は低くなります。他の道はあなたにとって必要なものではないかもしれませんが、目的地まであなたを<em>繋いで</em>くれます。HNSW グラフ上のベクトルも同様です。</p><p>したがって、プレフィルタリングを適用すると、フィルタを適用しない場合よりもパフォーマンスが低下します。検索で訪れる<em>すべての</em>ベクトルに対して作業を実行し、フィルターに一致しないベクトルを破棄する必要があります。私たちは、トップ k の結果を得るために、より多くの作業とより多くの時間をかけています。</p><p>以下は、Elasticsearch クエリ DSL での事前フィルタリングの例です。フィルタリング句が knn セクションの一部になっていることを確認します。</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p>事前フィルタリングは、 <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">knn 検索</a>と<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">knn クエリの</a>両方で利用できます。</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>プレフィルタリングの最適化</h4><p>プレフィルタリングのパフォーマンスを確保するために適用できる最適化がいくつかあります。</p><p>フィルターの制限が厳しい場合は、完全一致検索に切り替えることができます。比較するベクトルが少ない場合は、フィルターを満たす少数のドキュメントに対して正確な検索を実行する方が高速です。</p><p>これは、 <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a>および Elasticsearch で自動的に適用される最適化です。</p><p>別の最適化方法では、フィルターの条件を満たさないベクトルを無視します。代わりに、このメソッドは、フィルターを通過するフィルター処理されたベクトルの近傍をチェックします。このアプローチでは、フィルタリングされたベクトルは考慮されず、現在のパスに接続されたベクトルの探索が継続されるため、比較の回数が効果的に削減されます。</p><p>このアルゴリズムは ACORN-1 であり、そのプロセスについては<a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">こちらのブログ記事</a>で詳しく説明されています。</p><h2>ドキュメントレベルのセキュリティを使用したフィルタリング</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">ドキュメント レベル セキュリティ (DLS)</a>は、ユーザー ロールが取得できるドキュメントを指定する Elasticsearch 機能です。</p><p>DLS はクエリを使用して実行されます。ロールにはインデックスに関連付けられたクエリを持たせることができ、これにより、そのロールに属するユーザーがインデックスから取得できるドキュメントを効果的に制限できます。</p><p>ロール クエリは、<a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">それに一致するドキュメントを取得する</a>ためのフィルターとして使用され、BitSet としてキャッシュされます。この BitSet は、基盤となる Lucene リーダーをラップするために使用されるため、クエリから返されたドキュメントのみが<em>ライブで</em>あると見なされます。つまり、それらのドキュメントはインデックス上に存在し、削除されていません。</p><p>knn クエリを実行するために<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">リーダーからライブ ドキュメントが取得される</a>ため、ユーザーが利用できるドキュメントのみが考慮されます。プレフィルターがある場合は、DLS ドキュメントが<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">それに追加され</a>ます。</p><p>つまり、DLS フィルタリングは、近似ベクトル検索のプレフィルタとして機能し、同じパフォーマンスへの影響と最適化を実現します。</p><p>完全一致検索を使用した DLS には、任意のフィルターを適用する場合と同じ利点があります。つまり、DLS から取得されるドキュメントが少ないほど、完全一致検索のパフォーマンスが向上します。DLS によって返されるドキュメントの数も考慮してください。DLS ロールの制限が非常に厳しい場合は、近似検索ではなく完全検索の使用を検討してください。</p><h2>ベンチマーク</h2><p>Elasticsearch では、ベクトル検索フィルタリングが効率的であることを確認したいと考えています。当社には、さまざまな<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">フィルタリングを使用して近似ベクトル検索を実行するベクトル フィルタリング用の特定のベンチマーク</a>があり、ベクトル検索で関連する結果を可能な限り高速に取得し続けることができるようにします。</p><p>ACORN-1 が導入されたときの<a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">改善点</a>を確認します。ベクトルの 2% のみがフィルターを通過するテストでは、クエリの待機時間は元の期間の 55% に短縮されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>まとめ</h2><p>フィルタリングは検索の不可欠な部分です。ベクトル検索でフィルタリングのパフォーマンスを確保し、トレードオフと最適化を理解することが、効率的で正確な検索の成否を左右します。</p><p>フィルタリングはベクトル検索のパフォーマンスに影響します。</p><ul><li><p>フィルタリングを使用すると、正確な検索が高速になります。フィルタリングが十分に制限されている場合は、近似検索ではなく完全検索の使用を検討する必要があります。これは Elasticsearch での自動最適化です。</p></li><li><p>事前フィルタリングを使用すると近似検索が遅くなります。事前フィルタリングを使用すると、検索速度は遅くなりますが、フィルターに一致する上位 k 件の結果を取得できます。</p></li><li><p>ポストフィルタリングでは、フィルターの適用時にフィルターによってフィルタリングされる可能性があるため、必ずしも上位 k 件の結果が取得されるわけではありません。</p></li></ul><p>フィルタリングをお楽しみください！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[Elastic内部の実情]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[HNSWグラフのマージを高速化する]]></title>
    <description><![CDATA[複数の HNSW グラフを構築する際のオーバーヘッドを削減するために、特にグラフのマージにかかるコストを削減するために私たちが行ってきた作業について説明します。]]></description>
    <content:encoded><![CDATA[<p>以前、複数の<a href="https://www.elastic.co/jp/search-labs/blog/hnsw-graph"> HNSW グラフを</a> 検索する必要がある場合に生じるいくつかの課題と、その課題をどのように軽減できるか<a href="https://www.elastic.co/jp/search-labs/blog/multi-graph-vector-search"> について説明しました 。</a>当時、私たちは計画していたさらなる改善点についても触れました。この投稿はその作業の集大成です。</p><p>なぜ複数のグラフを使用するのかと疑問に思うかもしれません。これは、Lucene のアーキテクチャ上の選択である不変セグメントによる副作用です。ほとんどのアーキテクチャ上の選択と同様に、長所と短所があります。たとえば、最近 Serverless Elasticsearch が GA になりました。この文脈において、私たちは不変セグメントから、効率的なインデックス レプリケーションや、インデックスとクエリの計算を分離して個別に自動スケーリングする機能など、非常に大きなメリットを得ています。ベクトル量子化の場合、セグメントのマージにより、パラメータを更新してデータ特性に適合させることができます。これに沿って、データ特性を測定し、インデックスの選択を再検討する機会を持つことで得られる他の利点もあると考えています。</p><p>この投稿では、複数の HNSW グラフを構築する際のオーバーヘッドを大幅に削減し、特にグラフのマージにかかるコストを削減するために私たちが行ってきた作業について説明します。</p><h3>背景</h3><p>管理可能な数のセグメントを維持するために、Lucene はセグメントをマージする必要があるかどうかを定期的に確認します。これは、現在のセグメント数が、基本セグメント サイズとマージ ポリシーによって決定されるターゲット セグメント数を超えているかどうかを確認することになります。カウントを超過すると、Lucene は制約に違反しながらセグメントのグループを結合します。このプロセスについては<a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">他の場所で</a>詳しく説明されています。</p><p>Lucene は、書き込み増幅の対数的増加を実現するため、同様のサイズのセグメントを結合することを選択します。ベクトル インデックスの場合、書き込み増幅はベクトルがグラフに挿入される回数です。Luceneはセグメントを約10個のグループにまとめ、マージしようとします。その結果、ベクトルはグラフに約挿入されます。ここで、 はインデックス ベクトル数、 予想される基本セグメント ベクトル数です。対数増加のため、巨大なインデックスの場合でも書き込み増幅は 1 桁になります。ただし、グラフのマージに費やされる合計時間は、書き込み増幅に直線的に比例します。</p><p>HNSW グラフをマージする際には、最大セグメントのグラフを保持し、他のセグメントのベクトルをそのグラフに挿入するという、小さな最適化をすでに行っています。これが上記の 9/10 要因の理由です。以下では、マージするすべてのグラフの情報を使用することで、どのように大幅に改善できるかを示します。</p><h3>HNSW グラフのマージ</h3><p>これまでは、最大のグラフを保持し、他のグラフからベクトルを挿入し、それらを含むグラフは無視していました。以下で利用する重要な洞察は、破棄する各 HNSW グラフには、それに含まれるベクトルに関する重要な近接情報が含まれているということです。この情報を使用して、少なくとも一部のベクトルの挿入を高速化したいと考えています。</p><p>これは任意のマージポリシーを構築するために使用できるアトミック操作であるため、小さなグラフを大きなグラフに挿入する問題に焦点を当てます。</p><p>戦略は、 の頂点のサブセットを見つけて、大きなグラフに挿入することです。次に、小さなグラフ内のこれらの頂点の接続性を使用して、残りの頂点の挿入を高速化します。以下では、小さいグラフと大きいグラフの頂点 の隣接点をそれぞれ と で表します。図式的にプロセスは次のとおりです。</p><p><code>MERGE-HNSW</code></p><p><code>Inputs </code><code> and </code></p><p><code>1</code><code>Find </code><code> to insert into </code><code> using COMPUTE-JOIN-SET</code>
<code>2</code><code>Insert each vertex </code><code> into </code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code><code>FAST-SEARCH-LAYER</code>
<code>7</code><code>SELECT-NEIGHBORS-HEURISTIC</code>
<code>8</code></p><p>以下で説明する手順 (1 行目) を使用して、セットを計算します。次に、標準の HNSW 挿入手順 (2 行目) を使用して、 内のすべての頂点を大きなグラフに挿入します。挿入していない各頂点については、挿入した隣接頂点と、大きなグラフ内でのその隣接頂点を見つけます (4 行目と 5 行目)。このセットでシードされた<code>FAST-SEARCH-LAYER</code>手順 (6 行目) を使用して、HNSW<a href="https://arxiv.org/pdf/1603.09320">論文</a>から<code>SELECT-NEIGHBORS-HEURISTIC</code>の候補を検索します (7 行目)。実際には、 <code>SEARCH-LAYER</code> <code>INSERT</code>メソッド (論文のアルゴリズム 1) の候補セットを見つけるために置き換えていますが、それ以外は変更されていません。最後に、先ほど挿入した頂点を (行 8) に追加します。</p><p>これが機能するためには、 内のすべての頂点が内に少なくとも 1 つの隣接頂点を持つ必要があることは明らかです。実際、 内のすべての頂点に対して、  (あるに対して) が最大のレイヤー接続性であることが必要です。実際の HNSW グラフでは、頂点次数がかなり広がっていることがわかります。下の図は、Lucene HNSW グラフの最下層の頂点次数の典型的な累積密度関数を示しています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="HNSWグラフ: 頂点次数分布の例" /><p>に固定値を使用することと、それを頂点次数の関数にすることを検討しました。この2番目の選択肢は、グラフの品質への影響を最小限に抑えながら大幅なスピードアップにつながるため、次の方法を採用しました。</p><p>定義により、  | は小さなグラフの頂点の次数に等しいことに注意してください。下限が 2 の場合、次数が 2 未満のすべての頂点が挿入されます。</p><p>単純な計算の議論によれば、 慎重に選択すれば、約に直接挿入するだけでよいことがわかります。具体的には、グラフの端の頂点の 1 つをに挿入すると、グラフのエッジに色を付けます。すると、 内に少なくとも 個の 隣接頂点を持つためには、少なくとも 個の 辺を色付けする必要があることがわかります。さらに、我々は</p><p>ここで、 小さなグラフ内の平均頂点次数です。J内の各頂点に対して、最大での辺を色付けします。したがって、色付けすると予想されるエッジの総数は最大でとなります。慎重に選択することで、この辺の数に近い色を塗ることができると期待しており、すべての頂点をカバーするには満たす必要がある。</p><p>これは、 を意味します。</p><p><code>SEARCH-LAYER</code>が実行時間を支配すると、マージ時間を最大高速化できる可能性があります。書き込み増幅の対数的増加を考慮すると、非常に大きなインデックスの場合でも、通常は 1 つのグラフを構築する場合と比べて構築時間が 2 倍になるだけです。</p><p>この戦略のリスクは、グラフの品質が損なわれることです。最初は何も実行しない<code>FAST-SEARCH-LAYER</code>を試しました。これにより、特に単一のセグメントにマージするときに、レイテンシの関数としてのリコールが影響を受ける程度にグラフの品質が低下することがわかりました。次に、グラフの限定的な検索を使用して、さまざまな代替案を検討しました。結局、最も効果的な選択は最も単純なものでした。<code>SEARCH-LAYER</code>を使用しますが、 <code>ef_construction</code>は低くします。このパラメータ化により、優れた品質のグラフを実現しながらも、マージ時間を平均で 30% 強短縮することができました。</p><h3>結合セットの計算</h3><p>適切な結合セットを見つけることは、HNSW グラフ カバー問題として定式化できます。貪欲ヒューリスティックは、最適なグラフカバーを近似するためのシンプルで効果的なヒューリスティックです。私たちが採用するアプローチでは、ゲインが減少する順に頂点を 1 つずつ選択してに追加します。ゲインは次のように定義されます。</p><p>ここで、 におけるベクトル の近傍の数を表し、 は 指示関数である。ゲインには、 に追加した頂点の数の変化、つまりが含まれます。これは、カバーされていない頂点を追加することで目標に近づくためです。中央のオレンジ色の頂点のゲイン計算を下の図に示します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="HNSWグラフの結合集合Jに追加する頂点ゲイン" /><p>各頂点に対して次の状態を維持します。</p><ol><li><p>古くなっても、</p></li><li><p>そのゲイン 、</p></li><li><p>内の隣接頂点の数はと表され、</p></li><li><p>タイブレークに使用される範囲 [0,1] 内の乱数。</p></li></ol><p>結合セットを計算するための疑似コードは次のとおりです。</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs </code></p><p><code>1</code>
<code>2</code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code>
<code>7</code><code>while </code><code> do
8</code><code> maximum gain vertex in </code>
<code>9</code><code>Remove the state for </code><code> from </code>
<code>10</code><code>if </code><code> is not stale then</code>
<code>11</code>
<code>12</code>
<code>13</code><code>for </code><code> do</code>
<code>14</code><code>mark </code><code> as stale if </code>
<code>15</code><code>mark neighbors of </code><code> stale if </code><code>
16</code><code>
17</code><code>else
18</code><code>
19</code><code>if </code><code> then
20</code><code>
21</code><code>return </code></p><p>まず 1 行目から 5 行目で状態を初期化します。</p><p>メイン ループの各反復では、最初に最大ゲイン頂点 (行 8) を抽出し、同点の場合はランダムに決定します。変更を加える前に、頂点のゲインが古くなっているかどうかを確認する必要があります。特に、 に頂点を追加するたびに、他の頂点のゲインに影響を与えます。</p><ol><li><p>すべての隣接ノードはに追加の隣接ノードを持つため、ゲインは変化する可能性がある（14行目）</p></li><li><p>いずれかの隣接セルが完全にカバーされている場合、その隣接セルのゲインはすべて変化する可能性があります（14～16行目）</p></li></ol><p>ゲインは遅延方式で再計算されるため、頂点をに挿入する場合にのみ、その頂点のゲインを再計算します (18 ～ 20 行目)。ゲインは常に減少するだけなので、挿入すべき頂点を見逃すことは決してありません。</p><p>いつ終了するかを決定するには、 に追加した頂点の合計増加を追跡するだけでよいことに注意してください。さらに、 の場合、少なくとも 1 つの頂点のゲインはゼロではないため、常に進歩します。</p><h3>成果</h3><p>私たちは、サポートされている 3 つの距離メトリック (ユークリッド、コサイン、内積) をカバーする 4 つのデータセットで実験を実行しました。</p><ol><li><p>quora-E5-small: 522931文書、384次元、コサイン類似度を使用、</p></li><li><p>cohere-wikipedia-v2: 100万文書、768次元、コサイン類似度を使用。</p></li><li><p>要点: 100万文書、960次元、ユークリッド距離を使用、</p></li><li><p>cohere-wikipedia-v3: 100 万件のドキュメント、1024 次元、最大内積を使用します。</p></li></ol><p>各データセットに対して、2 つの量子化レベルを評価します。</p><ol><li><p>int8 – 次元ごとに1バイトの整数を使用し、</p></li><li><p>BBQ – 次元ごとに 1 ビットを使用します。</p></li></ol><p>最後に、各実験について、2 つの検索深度で検索品質を評価し、インデックスを構築した後と、単一のセグメントに強制的にマージした後を調べます。</p><p>要約すると、グラフの品質を維持しながら、インデックス作成とマージの速度を一貫して大幅に向上させ、すべてのケースで検索パフォーマンスを実現できます。</p><h4>実験1: int8量子化</h4><p>ベースラインから候補、つまり提案された変更までの平均的な高速化は次のとおりです。</p><p>インデックス時間の高速化: <strong>1.28</strong> </p><p>強制マージの高速化: <strong>1.72</strong></p><p>これは実行時間の次の内訳に対応する。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="ベースラインと候補のマージ戦略のインデックスとマージ時間" /><p>完全性のために正確な時間は</p><p></p><p>索引</p><p></p><p>マージ</p><p></p><p>データセット</p><p>ベースライン</p><p>候補者</p><p>構築</p><p>候補者</p><p>quora-E5-small</p><p>112.41秒</p><p>81.55秒</p><p>113.81秒</p><p>70.87秒</p><p>ウィキコヒアv2</p><p>158.1秒</p><p>122.95秒</p><p>425.20秒</p><p>239.28秒</p><p>要旨</p><p>141.82秒</p><p>119.26秒</p><p>536.07秒</p><p>279.05秒</p><p>ウィキコヒアv3</p><p>211.86秒</p><p>168.22秒</p><p>654.97秒</p><p>414.12秒</p><p>以下に、複数のセグメントを持つインデックス（すべてのベクトルをインデックスした後のデフォルトのマージ戦略の最終結果）と単一セグメントへの強制マージ後のインデックスの、2 つの検索深度（recall@10 と recall@100）と、単一セグメントへの強制マージ後の、候補（破線）とベースラインを比較したリコール対レイテンシのグラフを示します。曲線が高く、左に寄っているほど良好であり、これは、より低いレイテンシでより高い再現性を意味します。</p><p>ご覧のとおり、複数のセグメント インデックスの場合、候補は Cohere v3 データセットの方が優れており、他のすべてのデータセットではわずかに劣りますが、ほぼ同等です。単一セグメントに統合した後、リコール曲線はすべてのケースでほぼ同じになります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="インデックス構築後のレイテンシと@10および@100の関係を思い出してください" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="単一セグメントにマージした後のレイテンシと@10および@100のリコール" /><h4>実験2：BBQ量子化</h4><p>ベースラインから候補までの平均的な高速化は次のとおりです。</p><p>インデックス時間の高速化: <strong>1.33</strong> </p><p>強制マージの高速化: <strong>1.34</strong></p><p>これは実行時間の次の内訳に対応する。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="ベースラインと候補のマージ戦略のインデックスとマージ時間" /><p>完全性のために正確な時間は</p><p></p><p>索引</p><p></p><p>マージ</p><p></p><p>データセット</p><p>ベースライン</p><p>候補者</p><p>構築</p><p>候補者</p><p>quora-E5-small</p><p>70.71秒</p><p>58.25秒</p><p>59.38秒</p><p>40.15秒</p><p>ウィキコヒアv2</p><p>203.08秒</p><p>142.27秒</p><p>107.27秒</p><p>85.68秒</p><p>要旨</p><p>110.35秒</p><p>105.52秒</p><p>323.66秒</p><p>202.2秒</p><p>ウィキコヒアv3</p><p>313.43秒</p><p>190.63秒</p><p>165.98秒</p><p>159.95秒</p><p>複数のセグメント インデックスの場合、ベースラインがわずかに優れている cohere v2 を除き、ほぼすべてのデータセットで候補の方が優れています。単一セグメントインデックスの場合、リコール曲線はすべてのケースでほぼ同じです。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="インデックス構築後のレイテンシと@10および@100の関係を思い出してください" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="@10と@100を1つのセグメントに統合した場合のレイテンシを思い出してください" /><h3>まとめ</h3><p>このブログで説明したアルゴリズムは、今後リリースされる Lucene 10.2 およびそれに基づく Elasticsearch リリースで利用できるようになります。ユーザーは、これらの新しいバージョンで改善されたマージ パフォーマンスと短縮されたインデックス構築時間を活用できるようになります。この変更は、Lucene と Elasticsearch をベクター検索とハイブリッド検索に高速かつ効率的にするための継続的な取り組みの一環です。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Mayya Sharipova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fca6a01d0541cfb/6a17e187033c8d46486bb0e1/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Lucene の同時実行バグ: 楽観的同時実行の失敗を修正する方法]]></title>
    <description><![CDATA[CMUのPASTAラボの決定論的並行性テストフレームワークであるFrayのおかげで、私たちはLuceneの厄介なバグを追跡し、それを潰すことができました。]]></description>
    <content:encoded><![CDATA[<p>はい、またバグ修正のブログです。しかし、この事件には意外な展開があり、オープンソースのヒーローが飛び込んできて事態を収拾するのです。 </p><p>並行性のバグをデバッグするのは簡単ではありませんが、ここで取り上げます。CMU の PASTA ラボによる決定論的同時実行テスト フレームワークである Fray は、不安定な障害を確実に再現可能な障害に変えます。Fray の巧妙なシャドウ ロック設計と正確なスレッド制御のおかげで、私たちは厄介な Lucene のバグを追跡し、ついにそれを撲滅することができました。この投稿では、オープンソースのヒーローとツールが、並行処理のデバッグの負担を軽減し、ソフトウェアの世界を大きく改善している仕組みについて説明します。</p><h2>並行性バグ：ソフトウェアエンジニアの悩みの種</h2><p>同時実行バグは最悪です。修正が難しいだけでなく、確実に故障させること自体が最も難しい部分です。このテストの失敗<a href="https://github.com/apache/lucene/issues/13127"><code>TestIDVersionPostingsFormat#testGlobalVersions</code></a>を例に挙げます。これは複数のドキュメント書き込みおよび更新スレッドを生成し、Lucene の楽観的同時実行モデルに挑戦します。このテストでは、楽観的同時実行制御における競合状態が明らかになりました。つまり、ドキュメント操作は、一連の操作の中で最新のものであると誤って主張する可能性があります😱。つまり、特定の状況では、楽観的同時実行制約により失敗するはずの更新または削除操作が実際には成功する可能性があります。</p><p></p>org.apache.lucene.sandbox.codecs.idversion.TestIDVersionPostingsFormat &gt; testGlobalVersions FAILED
    java.lang.AssertionError: maxSeqNo must be greater or equal to 7442 but was 7441
        at __randomizedtesting.SeedInfo.seed([B97A2BDBC7E40BF6:B4D76006D5101E6]:0)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DocumentsWriterDeleteQueue.close(DocumentsWriterDeleteQueue.java:325)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DocumentsWriter.flushAllThreads(DocumentsWriter.java:659)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.IndexWriter.getReader(IndexWriter.java:576)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenFromWriter(StandardDirectoryReader.java:381)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged(StandardDirectoryReader.java:355)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.StandardDirectoryReader.doOpenIfChanged(StandardDirectoryReader.java:345)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.index.DirectoryReader.openIfChanged(DirectoryReader.java:170)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.SearcherManager.refreshIfNeeded(SearcherManager.java:144)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.SearcherManager.refreshIfNeeded(SearcherManager.java:52)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.ReferenceManager.doMaybeRefresh(ReferenceManager.java:167)
        at org.apache.lucene.core@10.0.0-SNAPSHOT/org.apache.lucene.search.ReferenceManager.maybeRefresh(ReferenceManager.java:213)<p>Java スタック トレースが嫌いな方には申し訳ありません。注意: 削除は必ずしも「削除」を意味するわけではありません。Lucene のセグメントは読み取り専用であるため、ドキュメントの「更新」を示すこともできます。
</p><p>Apache Lucene は、 <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java"><code>DocumentsWriter</code></a>クラスを通じてドキュメントを書き込む各スレッドを管理します。このクラスはドキュメント書き込み用のスレッドを作成または再利用し、各書き込みアクションは<a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java"><code>DocumentsWriterPerThread</code></a> (DWPT) クラス内でその情報を制御します。さらに、ライターは<a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a> (DWDQ) で削除されたドキュメントを追跡します。これらの構造は、すべてのドキュメント変更アクションをメモリ内に保持し、定期的にフラッシュしてメモリ内のリソースを解放し、構造をディスクに保持します。</p><p></p><p><a href="https://en.wikipedia.org/wiki/Blocking_(computing)">スレッドのブロックを</a>防ぎ、並行システムでの高いスループットを確保するために、Apache Lucene は非常に重要なセクションのみで<a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">同期</a>を試みます。これは実際には良いことですが、他の並行システムと同様に、問題が存在します。</p><h2>
偽りの希望</h2><p>最初の調査で、適切に同期されていない重要なセクションがいくつか見つかりました。特定の<a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a>に対するすべてのやり取りは、それを囲む<a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java"><code>DocumentsWriter</code></a>によって制御されます。したがって、個々のメソッドは<a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a>内で適切に同期されない可能性がありますが、そのメソッドの世界へのアクセスは適切に同期されます (または同期される必要があります)。(所有権とアクセスがどのように混乱するかについては深く考えないことにします。これは、多くの貢献者によって書かれた長期にわたるプロジェクトです。少しは余裕を持ってください。</p><p></p><p>しかし、<a href="https://github.com/apache/lucene/blob/40060f8b7080d06a218518445a0a1dfc520c812a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterFlushControl.java#L572-L576">フラッシュ中に同期されていない場所が 1 か所</a>見つかりました。</p>// Advance the queue, meaning create a new one to keep track of deletes
// Since we have been flushed, let's starting tracking again
DocumentsWriterDeleteQueue newQueue = documentsWriter.deleteQueue.advanceQueue(perThreadPool.size());
// OK, get the current new maximum sequence number for optimistic concurrency
seqNo = documentsWriter.deleteQueue.getMaxSeqNo();
// Reset to the new queue
documentsWriter.resetDeleteQueue(newQueue);<p>これらのアクションは、単一のアトミック操作に同期されません。つまり、 <code>newQueue</code>が作成されてから<code>getMaxSeqNo</code>が呼び出されるまでの間に、 <code>documentsWriter</code>クラス内のシーケンス番号を増分する他のコードが実行された可能性があります。バグを見つけました!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc55033280b45913/6a1706b4dc55dec27fe00d30/3f2617a102d28735e755a7539b047c28beda41be-1600x453.jpg" alt="" /><p>
しかし、ほとんどの複雑なバグと同様に、根本原因を見つけるのは簡単ではありませんでした。その時、英雄が登場した。</p><h2>戦いの英雄</h2><p>私たちのヒーロー、PASTA ラボの<a href="https://aoli.al/">Ao Li</a>と彼の同僚の登場です。フレイでどうやって彼らが危機を救ったのか、彼に説明してもらいましょう。</p><p><a href="https://github.com/cmu-pasta/fray">Fray は</a>、カーネギーメロン大学の<a href="https://pastalab.org/">PASTA ラボ</a>の研究者によって開発された決定論的並行性テスト フレームワークです。Fray 構築の背後にある動機は、学界と業界の間にある顕著なギャップに起因しています。決定論的並行性テストは学術研究において 20 年以上にわたって広範に研究されてきましたが、実践者は並行プログラムをテストするために、信頼性が低く不安定であると広く認識されている方法であるストレス テストに依然頼っています。したがって、私たちは、一般性と実用性を主な目標として、決定論的な同時実行テスト フレームワークを設計および実装したいと考えました。</p><p></p><h2>核となるアイデア</h2><p>Fray は、本質的に、シンプルでありながら強力な原則である「順次実行」を活用します。Java の並行性モデルは重要な<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.3">特性</a>を提供します。プログラムにデータ競合がない場合、すべての実行は順次一貫しているように見えます。これは、プログラムの動作を一連のプログラム ステートメントとして表現できることを意味します。</p><p>Fray は、ターゲット プログラムを順次実行することで動作します。各ステップで、1 つを除くすべてのスレッドを一時停止し、スレッドのスケジュールを正確に制御できるようにします。同時実行をシミュレートするためにスレッドはランダムに選択されますが、選択内容は後続の確定的な再生のために記録されます。実行を最適化するために、Fray は、スレッドがロックやアトミック/揮発性アクセスなどの同期命令を実行しようとしているときにのみコンテキストスイッチを実行します。データ競合の自由に関する優れた特性は、この限定されたコンテキスト切り替えが、あらゆるスレッドインターリーブによるすべての観察可能な動作を調査するのに十分であるということです (<a href="https://arxiv.org/abs/2501.12618">私たちの論文に</a>は証明の概要があります)。</p><p></p><h2>課題: スレッドのスケジュール制御</h2><p>中心となるアイデアはシンプルに見えますが、Fray の実装には大きな課題がありました。スレッドのスケジュールを制御するには、Fray は各アプリケーション スレッドの実行を管理する必要があります。一見すると、同時実行プリミティブをカスタマイズされた実装に置き換えるという単純なことのように思えるかもしれません。ただし、JVM での同時実行制御は複雑であり、<a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html">バイトコード命令</a>、<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html">高レベルライブラリ</a>、<a href="https://github.com/openjdk/jdk/blob/b720517cb33c2119ec6ed85504bce321de748228/src/java.base/share/classes/java/lang/Object.java#L394">ネイティブメソッド</a>が混在します。</p><p></p><p>これはウサギの穴であることが判明しました:</p><p></p><ul><li><p>たとえば、すべての<code>MONITORENTER</code>命令には、同じメソッド内に対応する<code>MONITOREXIT</code>が必要です。Fray が<code>MONITORENTER</code>スタブ/モックへのメソッド呼び出しに置き換える場合は、 <code>MONITOREXIT</code>置き換える必要があります。</p></li><li><p><code>object.wait/notify</code>を使用するコードでは、 <code>MONITORENTER</code>が置き換えられる場合、対応する<code>object.wait</code>も置き換えられる必要があります。この置換チェーンは<code>object.notify</code>以降まで拡張されます。</p></li><li><p>JVM は、ネイティブ コード内で特定の並行性関連のメソッド (たとえば、スレッドが終了すると<code>object.notify</code> ) を呼び出します。これらの操作を置き換えるには、JVM 自体を変更する必要があります。</p></li><li><p>クラス ローダーやガベージ コレクション (GC) スレッドなどの JVM 機能でも、同時実行プリミティブが使用されます。これらのプリミティブを変更すると、JVM 機能との不一致が生じる可能性があります。</p></li><li><p>JDK で並行性プリミティブを置き換えると、初期化フェーズで JVM がクラッシュすることがよくあります。</p></li></ul><p></p><p>これらの課題により、並行性プリミティブを包括的に置き換えることは不可能であることが明らかになりました。</p><h2>
私たちの解決策：シャドウロック設計</h2><p>これらの課題に対処するために、Fray は、同時実行プリミティブを置き換えることなくスレッド実行を調整するための新しいシャドウ ロック メカニズムを使用します。シャドウ ロックは、スレッドの実行をガイドする仲介者として機能します。たとえば、ロックを取得する前に、アプリケーション スレッドは対応するシャドウ ロックと対話する必要があります。シャドウ ロックは、スレッドがロックを取得できるかどうかを決定します。スレッドが続行できない場合、シャドウ ロックによってそのスレッドがブロックされ、他のスレッドの実行が許可されるため、デッドロックが回避され、同時実行が制御されます。この設計により、Fray は並行性セマンティクスの正確性を維持しながら、スレッドインターリーブを透過的に制御できます。各同時実行プリミティブは、健全性と完全性を確保するために、シャドウ ロック フレームワーク内で慎重にモデル化されます。より詳しい技術的な詳細については、当社の論文をご覧ください。</p><p></p><p>さらに、この設計は将来を見据えたものとなっています。同時実行プリミティブの周囲にシャドウ ロックのインストルメンテーションのみを必要とするため、新しいバージョンの JVM との互換性が確保されます。これは、JVM の並行性プリミティブのインターフェースが比較的安定しており、長年にわたって変更されていないため実現可能です。</p><h2>
テストの乱闘</h2><p>Fray を構築した後、次のステップは評価でした。幸いなことに、Apache Lucene などの多くのアプリケーションには、すでに同時実行テストが含まれています。このような同時実行テストは、複数のスレッドを生成し、何らかの作業を実行し、その後 (通常は) それらのスレッドが終了するまで待機し、何らかのプロパティをアサートする通常の JUnit テストです。ほとんどの場合、これらのテストは 1 つのインターリーブのみを実行するため合格します。さらに悪いことに、前述のように、一部のテストは CI/CD 環境でたまにしか失敗しないため、これらの失敗をデバッグするのは非常に困難です。同じテストを Fray で実行したところ、多数のバグが発見されました。特に、Fray は、このブログの焦点である<a href="https://github.com/apache/lucene/issues/13127"><code>TestIDVersionPostingsFormat.testGlobalVersions</code></a>を含め、信頼できる再現がないため未修正のまま残っていた、以前に報告されたバグを再発見しました。幸いなことに、Fray を使用すると、それらを確定的に再生して開発者に詳細な情報を提供できるため、問題を確実に再現して修正することができます。</p><p></p><h2>フレイの次のステップ</h2><p>Elastic の開発者から、Fray が並行性バグのデバッグに役立ったという話を聞き、大変嬉しく思っています。私たちは、より多くの開発者が Fray を利用できるようにするために、引き続き取り組んでいきます。</p><p>私たちの短期的な目標には、乱数ジェネレータや<code>object.hashcode</code>の使用など、他の非決定論的な操作が存在する場合でも、スケジュールを決定論的に再生する Fray の機能を強化することが含まれます。また、Fray の使いやすさを向上させ、開発者が手動介入なしに既存の同時実行テストを分析およびデバッグできるようにすることを目指しています。最も重要なことは、プログラム内の同時実行の問題のデバッグやテストで課題に直面している場合は、ぜひご連絡ください。遠慮なく、 <a href="https://github.com/cmu-pasta/fray">Fray Github リポジトリ</a>に問題を投稿してください。</p><p></p><h2>同時実行バグを修正する時間</h2><p>Ao Li と PASTA ラボのおかげで、このテストが確実に失敗するインスタンスができました。ついにこの問題を解決できます。重要な問題は、 <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThreadPool.java"><code>DocumentsWriterPerThreadPool</code></a>スレッドとリソースの再利用をどのように許可するかにありました。</p>1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t0 20 getNextSequenceNumber 1 called from stack:
&lt;snip&gt;...&lt;/snip&gt;
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_1, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_5, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_2, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_6, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; new Writer: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_4, aborted=false, numDocsInRAM=0, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]<p>ここでは、世代 0 の初期削除キューを参照して各スレッドが作成されていることがわかります。</p><p>その後、フラッシュ時にキューの前進が発生し、キュー内の前の 7 つのアクションが正しく確認されます。</p>1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t020 advanceQueue called from stack with maxSeq 9 lastSeqNo: 1 maxNumPendingOps: 7:
&lt;snip&gt;...&lt;/snip&gt;
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t525 getNextSequenceNumber 2 called from stack:
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t828 getNextSequenceNumber 3 called from stack:
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t727 getNextSequenceNumber 4 called from stack:
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t626 getNextSequenceNumber 5 called from stack:
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t424 getNextSequenceNumber 6 called from stack:
1&gt; DWDQ: [ generation: 0 ] id: 245403753 tid: t323 getNextSequenceNumber 7 called from stack:<p>しかし、すべてのスレッドがフラッシュを完了する前に、2 つのスレッドが追加のドキュメントに再利用されます。</p>1&gt; getAndLock: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]
1&gt; getAndLock: DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds]<p>これにより、 <code>seqNo</code>はフラッシュ時に7と計算された想定最大値を超えて増加します。セグメント<code>_3</code>と<code>numDocsInRAM</code>が追加されていることに注意してください。 <code>_0</code></p>1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_2, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_3, aborted=false, numDocsInRAM=2, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_6, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_4, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_5, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_1, aborted=false, numDocsInRAM=1, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove
1&gt; DocumentsWriterPerThread [pendingDeletes=gen=0, segment=_0, aborted=false, numDocsInRAM=2, deleteQueue=DWDQ: [ generation: 0 ], 0 deleted docIds] checkout to remove<p>その結果、Lucene はフラッシュ中のドキュメント アクションのシーケンスを誤って考慮し、このテストの失敗が発生します。</p><p>すべての適切なバグ修正と同様に、実際の修正は約<a href="https://github.com/apache/lucene/pull/13627/files">10 行のコード</a>です。しかし、実際に理解するまでに 2 人のエンジニアが数日かかりました。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71d78f22ce6ec215/6a1706b61949f7bcd0e7a94a/7915e5ba2feba0fb8e846da0af11bfdca470c467-1600x622.jpg" alt="" /><h2>すべてのヒーローがマントを着ているわけではない</h2><p>はい、決まり文句ですが、それは真実です。</p><p></p><p>並行プログラムのデバッグは非常に重要です。これらの扱いにくい同時実行バグをデバッグして解決するには、膨大な時間がかかります。Rust のような新しい言語には、このような競合状態を防ぐためのメカニズムが組み込まれていますが、世の中のソフトウェアの大部分はすでに作成されており、しかも<a href="https://www.rust-lang.org/">Rust</a>以外の言語で書かれています。Java は、何年も経った今でも、最も使用されている言語の 1 つです。JVM ベースの言語でのデバッグを改善することで、ソフトウェア エンジニアリングの世界がより良くなります。また、一部の人々がコードは大規模言語モデルによって記述されると考えていることを考えると、エンジニアとしての私たちの仕事は、最終的には自分自身の悪いコードだけでなく、悪い LLM コードをデバッグすることだけになるかもしれません。しかし、ソフトウェア エンジニアリングの将来がどうであろうと、並行プログラムのデバッグはソフトウェアの保守と構築にとって重要なままです。</p><p></p><p>これをさらに素晴らしいものにしてくれた PASTA ラボの Ao Li 氏と同僚の皆さんに感謝します。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/concurrency-bugs-lucene-debugging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/concurrency-bugs-lucene-debugging</guid>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Benjamin Trent,Ao Li]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9fcbf843affc566/6a1706b8964ceaea3208bab9/7884e35f40c15fe349379ef7ae4648b21708962f-1070x712.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 07 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ルーシーンラップド 2024]]></title>
    <description><![CDATA[2024 年は Apache Lucene にとってまたしても重要な年となりました。このブログでは、主なハイライトを紹介します。]]></description>
    <content:encoded><![CDATA[<p>Apache Lucene は 2024 年に大幅な活動が行われ、3 年ぶりのメジャー アップデートを含む多数のリリースが行われ、魅力的な改善点や新機能が満載されています。いくつかの重要なハイライトを見てみましょう。</p><h2>Luceneとコミュニティ</h2><p>プロジェクトの強さは、それをサポートするコミュニティの強さによって決まります。20 年以上の開発期間を経ても、Lucene プロジェクトは、熱心で活動的な貢献者のおかげで、活気に満ち、成長し続けています。</p><p>2024 年、Lucene プロジェクトでは 98 人の貢献者から 2,000 件を超えるコミットと、約 800 件のプル リクエストが行われました。新しいコミッターや PMC メンバーがプロジェクトに参加し、プロジェクトの成功に貢献しているため、貢献者の数は増え続けています。</p><h2>ルーシーン10</h2><p>2024 年には、ほぼ 3 年ぶりのメジャー リリースである Lucene 10 がリリースされ、185 人の貢献者から 2,000 件を超えるコミットが行われました。Lucene が採用している開発モデルでは、マイナー リリースで多くの改善や機能を提供できますが、メジャー リリースではより大きな機能や最新化を導入する機会が与えられます。たとえば、Lucene 10 には少なくとも Java 21 が必要です。最小 Java バージョンを上げると、Lucene は最新の Java が提供する改善点を引き続き活用できるようになります。</p><p>Lucene 10 の主な焦点は、それが実行されるハードウェアをより有効に活用することです。主なハイライトのいくつかを簡単に見てみましょう。</p><ul><li><p><strong>検索の並列化の強化</strong>- 検索実行は既にセグメント間で並列化されていますが、セグメント内での並列化がさらに進みました。これにより、ディスク上の表現と実行パフォーマンスが分離され、単一のセグメントでも最新システムのコア数のメリットを享受できるようになります。</p></li><li><p><strong>より優れた I/O 並列処理</strong>- Lucene が使用する単純な同期 I/O モデルが、プリフェッチ ステージによって強化されました。これにより、呼び出しスレッドをブロックせずに、インデックス ファイルの領域が近い将来必要になることを OS に通知します。</p></li><li><p><strong>スパース インデックスによる CPU とストレージの効率向上</strong>- Lucene 10 では、スパース インデックス (他のデータ ストアでは主キー インデックスまたはゾーン インデックスと呼ばれることもあります) のサポートが導入されています。</p></li></ul><p>Lucene 10 の詳細については、Lucene 10 に関する専用<a href="https://www.elastic.co/search-labs/blog/apache-lucene-10-release-highlights">記事</a>をご覧ください。</p><h2>ルーシーンの研究と革新</h2><p>2024 年、Lucene では、特に機械学習の統合、ベクトル検索、大規模データセットの最適化の分野で研究とイノベーションが急増し、10 件の個別の<a href="https://scholar.google.com/scholar?as_ylo=2024&amp;q=lucene&amp;hl=en&amp;as_sdt=0,5">研究論文と出版物</a>が参照されています。主要な研究分野と開発には次のようなものがあります。</p><ul><li><p><strong>ベクター検索と埋め込みのサポート</strong>- Lucene は、ベクターベースの検索のための強力でスケーラブルなソリューションを提供し、大規模なセマンティック検索を可能にします。Lucene の堅牢なインデックス作成および検索インフラストラクチャを活用することで、ユーザーは従来のテキスト検索の長所と最新のベクター検索の高度な機能を組み合わせることができ、Lucene は幅広い検索および情報取得タスクに対応する包括的なソリューションになります。</p></li><li><p><strong>ハイブリッド検索モデル</strong>- 研究では、従来のキーワードベースの検索と最新のベクターベースの検索を組み合わせた、ハイブリッド検索技術についても詳しく調べられています。Lucene は、用語ベースのインデックスと高密度のベクトル表現を統合することで、従来の検索エンジンの精度とセマンティック検索の柔軟性の間のギャップを埋め、より正確で文脈的に関連性の高い検索結果を提供できます。</p></li></ul><p>2024 年に進行中の研究活動は、特に AI、セマンティック検索、ビッグデータ アプリケーションの分野における、最新の検索テクノロジーの進化するニーズに対する Lucene の適応性を実証しています。このプロジェクトは、従来の検索ユースケースと最先端の検索ユースケースの両方に対応する強力で柔軟性が高く効率的なプラットフォームとして成長を続けています。</p><h2>2024年のLuceneリリース</h2><p>正確な反映ではありませんが、リリースの膨大な量は、コミュニティの継続的な献身とエネルギーを浮き彫りにしています。これらのアップデートには、ベクトル検索のパフォーマンスと効率の大幅な強化、madvise のサポート、ポスティング リストのデコードの最適化、SIMD によるさらなる速度向上などが含まれています。</p><p>リリースの全リストは次のとおりです。</p><ul><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-1010-available">10.1.0</a>（2024年12月20日）</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-9121-available">9.12.1</a> (2024年12月13日)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-1000-available">10.0.0</a> (2024年10月14日)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-9120-available">9.12.0</a> (2024年9月28日)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-8114-available">8.11.4</a> (2024年9月24日)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-9111-available">9.11.1</a> (2024-06-27)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-9110-available">9.11.0</a> (2024-06-06)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-9100-available">9.10.0</a>（2024年2月20日）</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-8113-available">8.11.3</a> (2024年2月8日)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-992-available">9.9.2</a> (2024年1月29日)</p></li></ul><p>詳細情報とリリース ノートについては、 <a href="https://projects.apache.org/project.html?lucene-core">Lucene Core</a>ページをご覧ください。さらに、同等の<a href="https://projects.apache.org/project.html?lucene-pylucene">PyLucene</a>リリースもあります。</p><h2>まとめ</h2><p>Lucene は成熟するにつれ、熱心で活気のあるコミュニティのおかげで繁栄し続けています。これまで見てきたように、2024 年は信じられないほど生産性の高い年であり、私たちは 2025 年にもたらされる刺激的な発展に期待を寄せています。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/apache-lucene-wrapped-2024</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/apache-lucene-wrapped-2024</guid>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Chris Hegarty]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt901211870015335c/6a17ddf6a292998b08d02b93/29a02c89b3c5adb37a5f900de634ff09cb63fdd9-1792x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Lucene のバグアドベンチャー: 破損したインデックス例外の修正]]></title>
    <description><![CDATA[場合によっては、1 行のコードを書くのに数日かかることがあります。ここでは、Apache Lucene インデックスの潜在的な破損を修正するためにエンジニアが数日間にわたって苦労してデバッグする様子を垣間見ることができます。]]></description>
    <content:encoded><![CDATA[<h2>準備しておきましょう: </h2><p>このブログはいつもと違います。新しい機能の説明やチュートリアルではありません。これは、記述に 3 日かかった 1 行のコードです。Apache Lucene インデックスの潜在的な破損を修正します。皆さんが理解して頂けるよう、いくつかのポイントを挙げておきます。</p><ul><li><p>十分な時間と適切なツールがあれば、すべての不安定なテストは再現可能である</p></li><li><p>堅牢なシステムには、多層的なテストが重要です。ただし、テストのレベルが上がるにつれて、デバッグと再現がますます難しくなります。</p></li><li><p>Sleepは優れたデバッガーです</p></li></ul><h2>Elasticsearchのテスト方法</h2><p>Elastic では、Elasticsearch コードベースに対して実行されるテストが多数あります。シンプルで集中的な機能テストもあれば、単一ノードの「ハッピーパス」統合テスト、さらには障害シナリオですべてが正しく動作することを確認するためにクラスターを破壊しようとするテストもあります。テストが継続的に失敗する場合は、エンジニアまたはツール自動化によって github の問題が作成され、特定のチームが調査できるようにフラグが付けられます。この<a href="https://github.com/elastic/elasticsearch/issues/105122">特定のバグは</a>、最後の種類のテストによって発見されました。これらのテストは扱いが難しいため、何度も実行しないと再現できないこともあります。</p><h2>このテストは実際に何をテストしているのでしょうか?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58aab468587a7d35/6a17dd8e3e03d74b8d4f2b5f/63268f3b5714ebf9df8070ec2e3c4f0486ed822e-1600x1088.jpg" alt="githubの問題: https://github.com/elastic/elasticsearch/issues/105122" /><p>この特定のテストは興味深いものです。特定のマッピングを作成し、それをプライマリ シャードに適用します。次にレプリカを作成しようとします。主な違いは、レプリカがドキュメントを解析しようとすると、テストによって例外が挿入され、その結果、予期しない (しかし予想どおりの) 方法で回復が失敗するという点です。</p><p></p><p>すべては期待通りに機能していましたが、1つ大きな問題がありました。テストのクリーンアップ中に一貫性を検証したところ、このテストで問題が発生しました。</p><p>
このテストは予想どおりに失敗しました。整合性チェック中に、複製された Lucene セグメント ファイルとプライマリ Lucene セグメント ファイルがすべて整合性があることを確認します。つまり、破損しておらず、完全に複製されています。部分的なデータや破損したデータが存在することは、何かが完全に機能しなくなるよりもはるかに悪い状況です。以下は、失敗の恐ろしい短縮版スタック トレースです。</p><p></p>Caused by: org.apache.lucene.index.CorruptIndexException: Problem reading index from store(ByteSizeCachingDirectory(ElasticsearchMockDirectoryWrapper(HybridDirectory@/opt/buildkite-agent/builds/bk-agent-prod-gcp-1707109485745743789/elastic/elasticsearch-periodic/server/build/testrun/internalClusterTest/temp/org.elasticsearch.indices.recovery.IndexRecoveryIT_40853F21F419B395-001/tempDir-005/node_t0/indices/ZNwxG7VvShuwYV78RTjknA/0/index lockFactory=org.apache.lucene.store.NativeFSLockFactory@2c169f59))) (resource=store(ByteSizeCachingDirectory(ElasticsearchMockDirectoryWrapper(HybridDirectory@/opt/buildkite-agent/builds/bk-agent-prod-gcp-1707109485745743789/elastic/elasticsearch-periodic/server/build/testrun/internalClusterTest/temp/org.elasticsearch.indices.recovery.IndexRecoveryIT_40853F21F419B395-001/tempDir-005/node_t0/indices/ZNwxG7VvShuwYV78RTjknA/0/index lockFactory=org.apache.lucene.store.NativeFSLockFactory@2c169f59))))

    at org.apache.lucene.index.SegmentCoreReaders.&lt;init&gt;(SegmentCoreReaders.java:165)
    at org.apache.lucene.index.SegmentReader.&lt;init&gt;(SegmentReader.java:96)
    at org.apache.lucene.index.ReadersAndUpdates.getReader(ReadersAndUpdates.java:178)
    at org.apache.lucene.index.ReadersAndUpdates.getLatestReader(ReadersAndUpdates.java:243)
    at org.apache.lucene.index.SoftDeletesRetentionMergePolicy.keepFullyDeletedSegment(SoftDeletesRetentionMergePolicy.java:82)
    at org.apache.lucene.index.FilterMergePolicy.keepFullyDeletedSegment(FilterMergePolicy.java:118)
    at org.apache.lucene.index.FilterMergePolicy.keepFullyDeletedSegment(FilterMergePolicy.java:118)
    at org.apache.lucene.index.ReadersAndUpdates.keepFullyDeletedSegment(ReadersAndUpdates.java:822)
    at org.apache.lucene.index.IndexWriter.isFullyDeleted(IndexWriter.java:6078)
    &lt;snip&gt;

    Caused by: java.io.FileNotFoundException: No sub-file with id .kdi found in compound file "_0.cfs" (fileName=_0.kdi files: [_0.pos, .nvm, .fnm, _0.tip, _Lucene90_0.dvd, _0.doc, _0.tim, _Lucene90_0.dvm, _ES87BloomFilter_0.bfm, .fdm, .nvd, _ES87BloomFilter_0.bfi, _0.tmd, .fdx, .fdt])

      at org.apache.lucene.codecs.lucene90.Lucene90CompoundReader.openInput(Lucene90CompoundReader.java:170)
      at org.apache.lucene.codecs.lucene90.Lucene90PointsReader.&lt;init&gt;(Lucene90PointsReader.java:63)
      at org.apache.lucene.codecs.lucene90.Lucene90PointsFormat.fieldsReader(Lucene90PointsFormat.java:74)
      at org.apache.lucene.index.SegmentCoreReaders.&lt;init&gt;(SegmentCoreReaders.java:152)
      &lt;snip&gt;
<p>何らかの理由で、強制レプリケーションの失敗中に、レプリケートされたシャードが破損してしまいました。エラーの重要な部分を平易な英語で説明しましょう。</p><p></p><p>Lucene はセグメント ベースのアーキテクチャです。つまり、各セグメントは独自の読み取り専用ファイルを認識して管理します。この特定のセグメントは、すべてが正常であることを確認するために、 <a href="https://github.com/apache/lucene/blob/add9c09c84ee66d4522c566c9f679035a0dfec13/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java">SegmentCoreReaders</a>を介して検証されていました。各コア リーダーには、特定のセグメントに存在するフィールド タイプとファイルを示すメタデータが保存されています。ただし、 <a href="https://github.com/apache/lucene/blob/add9c09c84ee66d4522c566c9f679035a0dfec13/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90PointsFormat.java">Lucene90PointsFormat</a>を検証するときに、特定の予期されたファイルが見つかりませんでした。セグメント<code>_0.cfs</code>ファイルでは、 <code>kdi</code>と呼ばれるポイント形式ファイルが予期されていました。<code>cfs</code> 「複合ファイル システム」を表します。Lucene は、より効率的なレプリケーションとリソース利用のために、すべてのフィールド タイプとすべての小さなファイルを 1 つの大きなファイルに結合することがあります。実際、ポイント ファイル拡張子<code>kdd</code> 、 <code>kdi</code> 、 <code>kdm</code>の 3 つすべてが欠落していました。Lucene セグメントがポイント ファイルを見つけることを期待しているのに、それが見つからないという状況に陥るのはなぜでしょうか。恐ろしい破損バグのようです。</p><p></p><h2>あらゆるバグ修正の最初のステップは、それを再現すること</h2><p></p><p>この特定のバグの障害を再現するのは非常に困難でした。Elasticsearch の<a href="https://en.wikipedia.org/wiki/Random_testing">ランダム値テスト</a>を活用しながら、すべての障害を調査できるように、すべての障害に (できれば) 再現可能なランダム シードを提供するようにしています。そうですね、これは<a href="https://en.wikipedia.org/wiki/Race_condition">競合状態</a>によって引き起こされる障害を除くすべての障害に対してうまく機能します。</p>./gradlew ':server:internalClusterTest' --tests "org.elasticsearch.indices.recovery.IndexRecoveryIT.testDoNotInfinitelyWaitForMapping" -Dtests.seed=40853F21F419B395 -Dtests.jvm.argline="-Des.concurrent_search=true" -Dtests.locale=id-ID -Dtests.timezone=Asia/Jerusalem -Druntime.java=21<p>何度試しても、特定のシードはローカルで失敗を繰り返すことはありませんでした。しかし、テストを実行して、より再現性の高い失敗へと導く方法はあります。</p><p></p><p>私たちの特定のテスト スイートでは、 <code>-Dtests.iters</code>パラメータを使用して、同じコマンドで特定のテストを複数回実行できます。しかし、これだけでは十分ではなく、実行スレッドが切り替わっていることを確認し、競合状態が発生する可能性を高める必要がありました。システムのもう一つの問題は、テストの実行に非常に長い時間がかかり、テスト ランナーがタイムアウトになることでした。最終的に、私は次の悪夢のような bash を使用してテストを繰り返し実行しました。</p>for run in {1..10}; do ./gradlew ':server:internalClusterTest' --tests "org.elasticsearch.indices.recovery.IndexRecoveryIT.testDoNotInfinitelyWaitForMapping" -Dtests.jvm.argline="-Des.concurrent_search=true" -Dtests.iters=10 ; done || exit 1<p><a href="https://github.com/ColinIanKing/stress-ng">ストレス</a>が溜まります。これにより、CPU コアを大量に消費するプロセスをすばやく開始できます。失敗するテストを何度も繰り返し実行しながら、ランダムに stress-ng をスパムすることで、最終的に失敗を再現することができました。一歩近づく。システムに負荷をかけるには、別のターミナル ウィンドウを開いて次のコマンドを実行します。</p>stress-ng --cpu 16<h2>
バグの発見</h2><p>

バグを明らかにするテストの失敗がほぼ再現可能になったので、今度は原因を探してみましょう。この特定のテストが奇妙である理由は、Lucene がポイント値を期待しているにもかかわらず、テストによってポイント値が直接追加されないためにエラーがスローされる点です。テキスト値のみ。このため<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html">、楽観的同時実行制御</a>フィールド<code>_seq_no</code>と<code>_primary_term</code>の最近の変更点を確認することを検討することにしました。これらは両方ともポイントとしてインデックス化され、すべての Elasticsearch ドキュメントに存在します。</p><p></p><p>確かに<a href="https://github.com/elastic/elasticsearch/pull/105036">コミットによって</a><code>_seq_no</code>マッパーが変更されました。はい！原因はきっとこれだ！しかし、私の興奮は長くは続かなかった。これにより、ドキュメントにフィールドが追加される順序のみが変更されました。この変更の前は、 <code>_seq_no</code>フィールドがドキュメントの最後に追加されていました。その後、最初に追加されました。Lucene ドキュメントにフィールドを追加する順序がこの失敗の原因となるはずはありません...</p><p></p><p>はい、フィールドの追加順序を変更すると、エラーが発生しました。これは驚くべきことで、Lucene 自体のバグであることが判明しました。解析されるフィールドの順序を変更しても、ドキュメントの解析動作は変更されません。</p><p></p><h2>Luceneのバグ</h2><p>実際、Lucene のバグは次の条件に焦点を当てていました。</p><ul><li><p>ポイント値フィールドのインデックス作成（例：<code>_seq_no</code> ）</p></li><li><p>分析中にテキストフィールドのインデックスを作成しようとしています</p></li><li><p>この奇妙な状態では、テキストインデックス分析例外を経験したライターから<a href="https://blog.mikemccandless.com/2011/06/lucenes-near-real-time-search-is-fast.html">ニアリアルタイムリーダー</a>を開きます。</p></li></ul><p>しかし、どんなに方法を試しても、完全に再現することはできませんでした。Lucene コードベース全体にデバッグ用の一時停止ポイントを直接追加しました。例外パス中にランダムにリーダーを開こうとしました。この障害が発生した正確なパスを見つけようとして、何メガバイトものログを印刷することさえしました。どうしてもできなかったんです。私は一日中戦って負け続けました。</p><p></p><p>それから私は眠りました。</p><p></p><p>翌日、元のスタック トレースを再度読み直して、次の行を発見しました。</p><p>
</p>    at org.apache.lucene.index.SoftDeletesRetentionMergePolicy.keepFullyDeletedSegment(SoftDeletesRetentionMergePolicy.java:82)<p>これまでの再現の試みにおいて、私は保持マージポリシーを具体的に設定したことはありません。<a href="https://github.com/apache/lucene/blob/5f0fa2b291ff9e7d878642f025a70c15b788a470/lucene/core/src/java/org/apache/lucene/index/SoftDeletesRetentionMergePolicy.java">SoftDeletesRetentionMergePolicy</a>は Elasticsearch によって使用され、レプリカ内の削除を正確に複製し、ドキュメントが実際に削除されるタイミングをすべての同時実行制御が管理できるようにします。それ以外の場合、Lucene は完全な制御権を持ち、マージ時にそれらを削除します。</p><p></p><p>このポリシーを追加し、上記の最も基本的な手順を再現すると、障害はすぐに再現されました。</p><p>
<a href="https://github.com/apache/lucene/issues/13353">Lucene でバグを</a>発見してこれほどうれしかったことはありません。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89d37c3454ec298b/6a17dd90ec0f8993c75a6511/c7d9ce3de5278923a8454097e2bdf487c861168a-1600x920.jpg" alt="Github の問題 https://github.com/apache/lucene/issues/13353" /><p>
Elasticsearch では競合状態として現れましたが、すべての条件が満たされると、Lucene で繰り返し失敗するテストを書くのは簡単でした。</p><p></p><p>結局、すべてのバグと同様に、たった 1 行のコードで修正されました。たった 1 行のコードのために、数日間の作業が必要でした。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab62db3402b27f7/6a17dd92dbb4ff23e7fb55ad/2c10a02eb41da181b1dceee47186c076c9b14a90-1600x539.jpg" alt="1行のコード修正" /><p>しかし、それは価値がありました。</p><h2>
終わりではない</h2><p>私と一緒にこのワイルドな旅を楽しんでいただけたら嬉しいです！ソフトウェア、特にElasticsearchやApache Luceneのように広く使用され、複雑なソフトウェアを書くことは、やりがいのあることです。しかし、時には、非常にイライラすることもあります。私はソフトウェアが好きであると同時に嫌いでもあります。バグ修正は決して終わりません!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lucene-corrupted-index-exception</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lucene-corrupted-index-exception</guid>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc25119c4add97ba/6a17dd94420229633929f4f7/2c918bad62530ff6fbe419092b2ca44bfe9408c4-944x612.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 27 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenSearchとElasticsearchの違い：ベクトル検索のパフォーマンス比較]]></title>
    <description><![CDATA[Elasticsearchは、ベクトル検索においてOpenSearchよりもデフォルトのままで2倍から12倍高速です]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison#up-to-12x-faster-out-of-the-box">まとめ：Elasticsearchは最大12倍高速</a> - Elasticでは、特にセマンティック検索/ベクトル検索のレルムにおけるElasticsearchとOpenSearch のパフォーマンスの違いを明確にしてほしいというリクエストをコミュニティから多数受けていました。そこで、このパフォーマンステストを実施して、明確でデータに基づく比較を提供しました。曖昧さはなく、ユーザーに伝えるための単純な事実です。結論としては、<strong>Elasticsearchはベクトル検索においてOpenSearchより最大12倍高速</strong>であるため、必要な計算リソースが少なくなります。これは、ElasticがLuceneを検索および取得ユースケースに最適なベクトルデータベースとして統合することに重点を置いていることを反映しています。</p><p>ベクトル検索は、特にAIや機械学習のフィールドで、類似性検索の方法に革命をもたらしています。ベクトル埋め込みモデルの採用が増えるにつれ、何百万もの高次元ベクトルを効率的に検索する機能が重要になっています。</p><p>ベクトルデータベースの実現方法に関して、ElasticとOpenSearchは著しく異なるアプローチを採用しています。Elasticは、Apache LuceneとElasticsearchがベクトル検索アプリケーションにおけるトップクラスの選択肢となるべく、これらの最適化に多額の投資を行ってきました。対照的に、OpenSearchは焦点を広げ、他のベクトル検索実装を統合し、Luceneの対象範囲を超えて模索を行っています。Elasticは戦略的にLuceneへと焦点を合わせており、弊社版Elasticsearchにおいて高度に統合されたサポートを提供できるため、各コンポーネントが他のコンポーネントの機能を補完および増幅できるという特徴が強化されています。</p><p>このブログでは、Elasticsearch 8.14とOpenSearch 2.14の詳細な比較を、異なる構成とベクトルエンジンを考慮して紹介します。このパフォーマンス分析では、Elasticsearch がベクトル検索操作に優れたプラットフォームであることが証明されました。今後追加予定の<a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">機能により</a>その差は<a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">さらに</a>拡大するでしょう。OpenSearchと比較すると、すべてのベンチマークトラックで優れた結果を示し、<strong>平均で2倍から12倍高速なパフォーマンスを実現しました</strong>。これは、<code>so_vector</code>（2Mベクトル、768D）、<code>openai_vector</code>（2.5Mベクトル、1536D）、および<code>dense_vector</code>（10Mベクトル、96D）を含む、さまざまなベクトル量と次元を使用したシナリオ全体で行われました。これらはすべて、Google Cloudで必要なインフラストラクチャーをプロビジョニングするためのTerraformスクリプトと、テストを実行するためのKubernetesマニフェストと共に、<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">このリポジトリ</a>で利用可能です。</p><p>このブログで詳述されている結果は、<a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">以前に公開され、サードパーティによって検証された調査</a>の結果を補完するものであり、最も一般的な検索分析操作（テキストクエリ、並べ替え、範囲、日付ヒストグラム、用語フィルタリング）では、ElasticsearchがOpenSearchよりも40%–140%高速であることが示されています。これに、別の差別化要因であるベクトル検索を追加します。</p><h2>デフォルトの設定で最大12倍高速</h2><p>4つのベクトルデータセットにわたる重点的なベンチマークには、さまざまなサイズ、次元、構成を考慮した近似KNN検索と正確なKNN検索の両方が含まれ、合計<code>40.189.820</code>のキャッシュされていない検索要求がありました。結果：<strong>Elasticsearchはベクトル検索においてOpenSearchより最大12倍高速である</strong>ため、必要な計算リソースが少なくなります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="p90平均" /><p>図1：ElasticsearchおよびOpenSearchにおけるさまざまなタスクについて、ANNと厳密なKNNをグループ化した図。</p><p><code>knn-10-100</code>のようなグループは、およびのKNN検索を意味します。HNSWベクトル検索では、はクエリベクトルから取得する最近傍の数を決定します。結果として検索する類似ベクトルの数を指定します。は各セグメントで取得する候補ベクトルの数を設定します。候補の数を増やすと精度は向上しますが、より多くの計算リソースが必要になります。</p><p>また、さまざまな量子化手法とエンジン固有の最適化を活用してテストしました。各トラック、タスク、ベクトルエンジンの詳細な結果を以下に示します。</p><h2>厳密なKNNと近似KNN</h2><p>さまざまなデータセットとユースケースを扱う場合、ベクトル検索に適したアプローチは異なります。このブログでは、<code>knn-10-100</code>のように<code>knn-*</code>と記載されているすべてのタスクは<strong>近似KNN</strong>を使用し、<code>script-score-*</code>は<strong>正確なKNN</strong>を参照していますが、それらの違いは何で、なぜ重要なのでしょうか。</p><p>基本的に、より大規模なデータセットを扱う場合、優れた拡張性のある近似K-最近傍法（ANN）が推奨されます。フィルタリング処理が必要な場合のある、より小規模なデータセットの場合、厳密なKNN法が最適です。</p><p>正確なKNNでは、ブルートフォース法を使用して、データセット内の1つのベクトルと他のすべてのベクトルとの間の距離を計算します。次に、これらの距離を順位付けして、の最も近い近傍を見つけます。この方法では正確な一致が保証されますが、大規模で高次元のデータセットでは拡張性の課題があります。ただし、正確なKNNが必要なケースは数多くあります。</p><ul><li><p><strong>再スコアリング</strong>：語彙検索または意味検索の後にベクトルベースの再スコアリングを行うシナリオでは、正確なKNNが不可欠です。例えば、製品検索エンジンでは、テキストクエリ（キーワードやカテゴリなど）に基づいて最初の検索結果をフィルタリングし、フィルタリングされたアイテムに関連付けられたベクトルを使用して、より正確な類似性評価を行うことができます。</p></li><li><p><strong>パーソナライゼーション</strong>：多数のユーザーを扱う際、それぞれが比較的少数（100万個など）の個別のベクトルで表される場合、ユーザー固有のメタデータ（例：user_id）でインデックスをソートし、ベクトルを使用したブルートフォーススコアリングが効率的になります。このアプローチにより、個々のユーザーの好みに合わせた正確なベクトル比較に基づいて、パーソナライズされたレコメンデーションやコンテンツ配信が可能になります。</p></li></ul><p>したがって、厳密なKNNでは、ベクトルの類似性に基づく最終的な順位付けと推奨が正確なものとなり、ユーザーの好みに合わせて調整されるようになります。</p><p>一方、近似KNN（またはANN）は、特に大規模で高次元のデータセットにおいて、厳密なKNNよりも高速かつ効率的にデータ検索を行う方法を採用しています。ANNは、クエリとすべての点の間の正確な最も近い距離を測定することにより計算やスケーリングの問題につながるブルートフォース的なアプローチではなく、特定の手法を使用してデータセットにおける検索可能なベクトルのインデックスと次元を効率的に再構築します。これにより若干の不正確さが生じる可能性がありますが、検索処理速度が大幅に向上するため、大規模なデータセットを処理するための効果的な代替手段となります。</p><p>このブログでは、<code>knn-*</code>として記載されているすべてのタスクは<code>knn-10-100</code>のように<strong>近似KNN</strong>を使用し、<code>script-score-*</code>は<strong>正確なKNN</strong>を参照します。</p><h2>テスト手法</h2><p>ElasticsearchとOpenSearchはBM25検索操作のAPIにおいては似ていますが、後者が前者のフォークであるため、フォーク後に導入されたベクトル検索には当てはまりません。OpenSearchはアルゴリズムに関してElasticsearchとは異なるアプローチを取り、<code>lucene</code>とは別に<code>nmslib</code>と<code>faiss</code>の2つのエンジンを導入しました。それぞれに特定の構成と制限があります（例：OpenSearchの<code>nmslib</code>では、多くのユースケースで不可欠な機能であるフィルターが許可されていません）。</p><p>3つのエンジンはいずれも、階層型ナビゲート可能スモールワールド（HNSW）アルゴリズムを使用しています。このアルゴリズムは近似最近傍を検索するのに効率的で、特に高次元データを扱う際に強力です。<code>faiss</code>は第2のアルゴリズム<code>ivf</code>もサポートしていますが、データセットの事前トレーニングが必要なため、ここではHNSWのみに焦点を当てます。HNSWの中心的な考え方は、データを複数の接続されたグラフのレイヤーに整理し、各レイヤーがデータセットの異なる粒度を表すことです。検索は最も粗いビューの最上位レイヤーから始まり、ベースレベルに到達するまで、より細かいレイヤーへと進みます。</p><p>公平なテストの場を確保するために、両方の検索エンジンは制御された環境内で同一の条件下でテストされました。適用された方法は、Elasticsearch、OpenSearch、Rally専用のノードプールを使用した<a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">以前に公開されたパフォーマンス比較</a>と似ています。<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">terraformスクリプト</a>は（すべてのソースとともに）Kubernetesクラスターを提供するために利用可能です。</p><ul><li><p>Elasticsearch用の3台の<code>e2-standard-32</code>マシン（128GB RAM、32CPU）を持つ1つのNodeプール</p></li><li><p>OpenSearch用の3台の<code>e2-standard-32</code>マシン（128GB RAM、32CPU）を持つ1つのNodeプール</p></li><li><p>Rally用の2台の<code>t2a-standard-16</code>マシン（64GB RAM、16CPU）を持つ1つのNodeプール</p></li></ul><p>各「トラック」（またはテスト）を、異なるエンジン、異なる設定、異なるベクトルタイプを含む各設定に対して10回実行しました。各トラックには、トラックにより1000回から10000回まで繰り返すタスクがあります。ネットワークのタイムアウトなどでトラック内のタスクの1つが失敗した場合はすべてのタスクを無視したため、すべての結果は問題なく開始して終了したトラックを表します。すべてのテスト結果は統計的に検証されており、改善が偶然ではないことが保証されています。</p><h2>詳細な調査結果</h2><p>平均レイテンシではなく99パーセンタイルを使用して比較するのはなぜでしょうか。特定の地域の平均住宅価格の例を想像してみましょう。平均価格によれば高価な地域となっていても、よく調べてみると、ほとんどの住宅の価値ははるかに低く、わずかな高級物件が平均値を膨らませていることがわかるかもしれません。これは、平均価格がその地域の住宅価値の全範囲を正確に表現できない可能性があることを示しています。同じようなことが、応答時間についても言えます。平均値が重要な問題を隠してしまうことがあるのです。</p><h4>タスク</h4><ul><li><p>近似KNN（k:10 n:50）</p></li><li><p>近似KNN（k:10 n:100）</p></li><li><p>近似KNN（k:100 n:1000）</p></li><li><p>近似KNN（k:10 n:50、キーワードフィルター併用）</p></li><li><p>近似KNN（k:10 n:100、キーワードフィルター併用）</p></li><li><p>近似KNN（k:100 n:1000、キーワードフィルター併用）</p></li><li><p>近似KNN（k:10 n:100、インデックス作成と同時実行）</p></li><li><p>厳密なKNN（スクリプトスコア）</p></li></ul><h4>ベクトルエンジン</h4><ul><li><p><code>lucene</code> （ElasticsearchとOpenSearchにおいて、両方ともバージョン9.10）</p></li><li><p><code>faiss</code> （OpenSearchにおいて）</p></li><li><p><code>nmslib</code> （OpenSearchにおいて）</p></li></ul><h4>ベクトルの種類</h4><ul><li><p><code>hnsw</code> （ElasticsearchとOpenSearchにおいて）</p></li><li><p><code>int8_hnsw</code> Elasticsearch内（自動8ビット量子化を備えたHNSW：<a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">リンク</a>）</p></li><li><p><code>sq_fp16 hnsw </code>OpenSearch内（自動16ビット量子化を備えたHNSW：<a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">リンク</a>）</p></li></ul><h4>すぐに使える同時セグメント検索</h4><p>ご存知かと思いますが、LuceneはJavaで記述された高性能なテキスト検索エンジンライブラリであり、Elasticsearch、OpenSearch、Solrなどの多くの検索プラットフォームのバックボーンとして機能します。Luceneは本質的に、データをセグメントに整理します。セグメントは本質的には自己完結型のインデックスであり、Luceneがより効率的に検索を実行できるようにします。そのため、Luceneベースの検索エンジンに検索を発行すると、検索はそれらのセグメントで順次または並行して実行されます。</p><p>OpenSearchは同時セグメント検索をオプションのフラグとして導入しましたが、デフォルトでは使用されません。特別なインデックス設定<code>index.search.concurrent_segment_search.enabled</code>を使用して有効にする必要があります。詳細は<a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">こちら</a>をご覧ください。ただし、いくつかの<a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">制限</a>があります。</p><p>一方、Elasticsearchは<a href="https://github.com/elastic/elasticsearch/pull/101230">すぐに使用できる状態</a>でセグメントを同時に検索するため、このブログでの比較では、さまざまなベクトルエンジンとベクトルタイプに加えて、さまざまな構成も考慮に入れます。</p><ul><li><p>Elasticsearch ootb：同時セグメント検索検索を備えたデフォルト設定のElasticsearch</p></li><li><p>OpenSearch ootb：同時セグメント検索が有効でない状態</p></li><li><p>OpenSearch css：同時セグメント検索が有効な状態</p></li></ul><p>それでは、テストした各ベクトルデータセットの詳細な結果を見てみましょう。</p><h2>250万ベクトル、1536次元（openai_vector）</h2><p>最も単純なトラックで、かつ次元の点でも最大の<a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>から始めます。これは、OpenAIの<a href="https://openai.com/blog/new-and-improved-embedding-model">text-embedding-ada-002モデル</a>を使用して生成された埋め込みで強化された<a href="https://huggingface.co/datasets/BeIR/nq">NQデータセット</a>を使用します。これは、近似KNNのみをテストし、タスクが5つしかないため、最も単純です。スタンドアロン（インデキシングなし）でのテストと、インデキシングと並行してのテストが可能で、単一クライアントと8つの同時クライアントを使用します。</p><h3>タスク</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong>：8クライアントで250万ベクトルを同時に検索、k: 10およびn:100</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong>：8クライアントで250万ベクトルを同時に検索、k: 100およびn:1000</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>：単一クライアントで250万ベクトルを検索、k: 10およびn:100</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>：単一クライアントで250万ベクトルを検索、k: 100およびn:1000</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>：250万ベクトルを検索しながら追加の10万ドキュメントをインデキシング、k:10およびn:100</p></li></ul><p>p99の平均パフォーマンスの概要は次のとおりです。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="openai_vectorテーブル" /><p>ここで、 :10、:100でベクトル検索とインデックス作成（つまり読み取り+書き込み）を実行した場合、ElasticsearchはOpenSearchよりも<strong>3～8倍高速</strong>であり、kとnが同一のインデックス作成なしの場合には<strong>2～3倍高速</strong>であることがわかりました。:100 および:1000（<em>standalone-search-knn-100-1000-single-client</em>および<em>standalone-search-knn-100-1000-multiple-clients</em>）の場合、Elasticsearchは平均して OpenSearch より<strong>2倍から7倍</strong>高速です。</p><p>詳細な結果には、比較された具体的なケースとベクトルエンジンが示されています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>リコール</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969485</p><p>0.995138</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.781445</p><p>0.784817</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.96519</p><p>0.995422</p><p>OpenSearch-2.14.0@faiss</p><p>0.984154</p><p>0.98049</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.980012</p><p>0.97721</p><p>OpenSearch-2.14.0@nmslib</p><p>0.982532</p><p>0.99832</p><h2>1,000万ベクトル、96次元（dense_vector）</h2><p><a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a>には、1,000万ベクトルと96次元があります。これは<a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a>画像データセットに基づいています。データセットは、「sample data」ファイル<code>learn.350M.fbin</code>の最初の1,000万ベクトルから作成されます。検索操作では、「query data」ファイルクエリのベクトルを使用します。<code>public.10K.fbin</code>。</p><p>ElasticsearchとOpenSearchの両方がこのデータセットで非常に優れたパフォーマンスを発揮します。特に、通常読み取り専用のインデックスで実行される<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">force merge</a>の後では顕著です。これは、インデックスをデフラグして検索用の単一の「テーブル」を作成するのと似ています。</p><h3>タスク</h3><p>各タスクは100リクエストでウォームアップされ、その後1000リクエストが測定されます。</p><ul><li><p><strong>knn-search-10-100</strong>: 1000万ベクトルを検索、k: 10およびn:100</p></li><li><p><strong>knn-search-100-1000</strong>：1000万ベクトルを検索、k: 100およびn:1000</p></li><li><p><strong>knn-search-10-100-force-merge</strong>：強制マージ後の1000万ベクトルの検索、k: 10およびn:100</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>：強制マージ後の1000万ベクトルの検索、k: 100およびn:1000</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>：1000万ベクトルを検索しながら<a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">データセットの5%を</a>更新、k: 100およびn:1000</p></li><li><p><strong>script-score-query</strong>：<a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">特定の2000ベクトル</a>の正確なKNN検索。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>ElasticsearchとOpenSearchはどちらも近似KNNに対して良好なパフォーマンスを示しました。<em>knn-search-100-1000-force-merge</em>および<em>knn-search-10-100-force-merge</em>でインデックスが結合されている場合（つまり、セグメントが1つだけの場合）、<code>nmslib</code>および<code>faiss</code>を使用すると、いずれも約15ミリ秒で非常に近い値であるにもかかわらず、OpenSearchのパフォーマンスは他よりも優れています。</p><p>ただし、インデックスに複数のセグメントがある場合（インデックスがドキュメントの更新を受け取る典型的な状況）において、<em>knn-search-10-100</em>と<em>knn-search-100-1000</em>では、Elasticsearchは遅延を約7ミリ秒と16ミリ秒に保ちますが、他のすべてのOpenSearchエンジンはより遅くなります。</p><p>また、インデックスが検索され、同時に書き込まれる場合（<em>knn-search-100-1000-concurrent-with-indexing</em>）、Elasticsearchはレイテンシを15ミリ秒以下に保守し（13.8ミリ秒）、OpenSearchの出荷時（49.3ミリ秒）よりも<strong>4倍近く高速</strong>であり、同時セグメント検索が有効な場合でも（17.9ミリ秒）、依然として高速ですが、有意差があるほどではありません。</p><p>正確なKNNに関しては、その差はさらに大きくなります。Elasticsearch は OpenSearch よりも<strong>6 倍高速です</strong>（約 260 ミリ秒対約 1600 ミリ秒）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>リコール</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969843</p><p>0.996577</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.775458</p><p>0.840254</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.971333</p><p>0.996747</p><p>OpenSearch-2.14.0@faiss</p><p>0.9704</p><p>0.914755</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.968025</p><p>0.913862</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><h2>200万ベクトル、768次元（so_vector）</h2><p>この<a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">トラック</a>、<code>so_vector</code>、は2022年4月21日にダウンロードされた<a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">StackOverflowの投稿のダンプ</a>から派生しています。質問文書のみが含まれており、回答を表す文書はすべて削除されています。各質問タイトルは、文変換モデル<a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a>を使用してベクトルにエンコードされました。このデータセットには最初の200万件の質問が含まれています。</p><p>前のトラックとは異なり、ここでの各ドキュメントには、フィルタリングとハイブリッド検索を備えた近似KNNなどのテスト機能をサポートするためのベクトル以外のフィールドも含まれています。OpenSearchの<code>nmslib</code>は<a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">フィルターをサポートしていない</a>ため、このテストには含まれていません。</p><h3>タスク</h3><p>各タスクは100件のリクエストに対してウォームアップされ、その後100件のリクエストが測定されます。テストには16種類の検索タイプ*2つの異なるk値*3つの異なるn値が含まれているため、簡潔にするためにタスクがグループ化されていることに注意してください。</p><ul><li><p><strong>knn-10-50</strong>：フィルターなしで200万ベクトルを検索、k:10およびn:50</p></li><li><p><strong>knn-10-50-filtered</strong>：200万ベクトルを<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">フィルターを使用して</a>検索、k:10およびn:50</p></li><li><p><strong>knn-10-50-after-force-merge</strong>：200万ベクトルを強制マージ後にフィルターを使用して検索、k:10およびn:50</p></li><li><p><strong>knn-10-100</strong>：フィルターなしで200万ベクトルを検索、k:10およびn:100</p></li><li><p><strong>knn-10-100-filtered</strong>：200万ベクトルを<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">フィルターを使用して</a>検索、k:10およびn:100</p></li><li><p><strong>knn-10-100-after-force-merge</strong>：200万ベクトルを強制マージ後にフィルターを使用して検索、k:10およびn:100</p></li><li><p><strong>knn-100-1000</strong>：フィルターなしで200万ベクトルを検索、k:100およびn:1000</p></li><li><p><strong>knn-100-1000-filtered</strong>：200万ベクトルを<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">フィルターを使用して</a>検索、k:100およびn:1000</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>：200万ベクトルを強制マージ後にフィルターを使用して検索、k:100およびn:1000</p></li><li><p><strong>exact-knn</strong>：<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">フィルターあり、フィルターなしの</a>正確なKNN検索。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="so_vectorテーブル" /><p>このテストでは、ElasticsearchはOpenSearchよりも<strong>一貫して高速で</strong>あり、OpenSearchの方が高速なのは2つのケースのみで、その差はそれほど大きくありません（<em>knn-10-100</em>と<em>knn-100-1000</em>）。<em>knn-10-50</em>、<em>knn-10-100</em>、<em>knn-100-1000</em>をフィルターと組み合わせて使用するタスクでは、最大<strong>7倍</strong>（112ミリ秒対803ミリ秒）の差が見られます。</p><p>当然ながら、<em>knn-10-50-after-force-merge</em>、<em>knn-10-100-after-force-merge</em>、knn-100-1000-after-force-mergeで証明されているように、両方のソリューションのパフォーマンスは「<em>強制マージ」後に均等になるようです。</em>これらのタスクでは、 <code>faiss</code>の方が高速です。</p><p>Exact KNNのパフォーマンスは今回も大きく異なり、ElasticsearchはOpenSearchより<strong>13倍高速</strong>です（約385ミリ秒対約5262ミリ秒）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>リコール</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>knn-recall-10-50</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>1</p><p>0.986667</p><p>1</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><p>0.976394</p><h2>ElasticsearchとLuceneが明確な勝者に</h2><p>Elasticでは、Apache LuceneとElasticsearchを絶え間なく革新し、RAG（Retrieval-Augmented Generation）を含む検索および取得のユースケースに最適なベクトルデータベースを提供できるようにしています。最近の進歩により、パフォーマンスが大幅に改善され、ベクトル検索が<a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">以前よりも高速かつスペース効率的</a>になりました。これはLucene 9.10からの成果を基に構築されています。このブログでは、最新バージョンを比較すると、ElasticsearchがOpenSearchよりも最大12倍高速であることを示す調査を紹介しました。</p><p>両方の製品が同じバージョンのLucene（<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Elasticsearch 8.14リリースノート</a>および<a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">OpenSearch2.14 リリースノート</a>）を使用していることは注目に値します。</p><p>Elasticのイノベーションのペースは、オンプレミスおよびElastic Cloudのお客様だけでなく、<a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">ステートレスプラットフォーム</a>を使用しているお客様にもさらに多くの成果をもたらします。<a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">int4へのスカラー量子化</a>のサポートなどの機能は、<a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">int8のテスト</a>と同様に、顧客が再現率を大幅に低下させることなくこれらの手法を利用できるように、厳格なテストを経て提供されます。</p><p>AIおよび機械学習アプリケーションの急増により、ベクトル検索の効率は現代の検索エンジンでは交渉の余地のない機能になりつつあります。大量で複雑なベクトルデータの要求に対応できる強力な検索エンジンを探している組織にとって、Elasticsearchは決定的な答えです。</p><p>確立されたプラットフォームを拡張する場合でも、新しいプロジェクトを開始する場合でも、ベクトル検索のニーズに合わせてElasticsearchを統合することは、具体的かつ長期的なメリットをもたらす戦略的な動きです。実証済みのパフォーマンス上の優位性を備えたElasticsearchは、検索における次のイノベーションの波を支える態勢が整っています。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d70b25967c2194e/6a17d732b1e11383f879f0ca/13c3c0053e2968fb835ba2f90f34bec3a011b5c0-880x592.webp" length="0" type="image/webp"/>
    <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Luceneにおけるスカラー量子化の理解]]></title>
    <description><![CDATA[自動バイト量子化、セグメントごとの量子化、パフォーマンス分析など、Elastic が Lucene にスカラー量子化をどのように導入したかをご覧ください。]]></description>
    <content:encoded><![CDATA[<h2>Luceneにおける自動バイト量子化</h2><p>HNSW はベクトルを保存および検索するための強力かつ柔軟な方法ですが、高速に実行するには大量のメモリが必要です。たとえば、768 次元の 1MM float32 ベクトルをクエリするには、およその RAM が必要です。大量のベクトルを検索し始めると、コストが高くなります。メモリ使用量を約削減する方法の 1 つは、バイト量子化を使用することです。Lucene と Elasticsearch は以前からベクトルのインデックス作成をサポートしてきましたが、これらのベクトルの構築はユーザーの責任でした。Lucene にスカラー量子化が導入されたため、この状況は変わりつつあります。</p><h2>スカラー量子化101</h2><p>すべての量子化手法は、生データの非可逆変換であると見なされます。つまり、スペースの都合上、一部の情報が失われます。スカラー量子化の詳細な説明については、 <a href="https://www.elastic.co/search-labs/scalar-quantization-101">「スカラー量子化 101」</a>を参照してください。大まかに言えば、スカラー量子化は非可逆圧縮技術です。簡単な計算により、リコールにほとんど影響を与えずに、大幅にスペースを節約できます。</p><h2>建築を探る</h2><p>Elasticsearch の使用に慣れている方は、これらの概念にすでに馴染みがあるかもしれませんが、ここでは検索対象ドキュメントの配布について簡単に概要を説明します。</p><p>各 Elasticsearch インデックスは<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/size-your-shards.html#size-your-shards">複数のシャード</a>で構成されます。各シャードは 1 つのノードにのみ割り当てることができますが、インデックスごとに複数のシャードを割り当てることで、ノード間で並列計算が可能になります。</p><p>各シャードは単一の<a href="https://lucene.apache.org/core/9_8_0/core/org/apache/lucene/index/package-summary.html">Lucene インデックス</a>として構成されます。Lucene インデックスは複数の読み取り専用セグメントで構成されます。インデックス作成中、ドキュメントはバッファリングされ、定期的に読み取り専用セグメントにフラッシュされます。特定の条件が満たされると、これらのセグメントをバックグラウンドでより大きなセグメントにマージできます。これらはすべて構成可能であり、独自の複雑さを伴います。ただし、セグメントとマージについて話すときは、読み取り専用の Lucene セグメントと、これらのセグメントの定期的な自動マージについて話しています。<a href="https://www.elastic.co/search-labs/vector-search-elasticsearch-rationale">ここでは、セグメントのマージと設計上の決定について詳しく説明します</a>。</p><h2>Luceneにおけるセグメントごとの量子化</h2><p>Lucene のすべてのセグメントには、個々のベクトル、HNSW グラフ インデックス、量子化されたベクトル、および計算された分位数が保存されます。簡潔にするために、Lucene が量子化されたベクトルと生のベクトルを保存する方法に焦点を当てます。すべてのセグメントについて、 ファイル内の生のベクトル、 内の量子化されたベクトルと単一の補正乗数浮動小数点数、およびファイル内の量子化に関するメタデータを追跡します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1861d0af970fd096/6a17d9887f6f15a45dc099c0/507a4d578f8a5d2225b11d16ac1fc0b7dd39eaa1-1207x228.png" alt=".vecファイル" /><p>図 1: 生のベクター保存ファイルの簡略化されたレイアウト。 値は 4 バイトなので、 dimension量子化しているため、これらは HNSW 検索中に読み込まれません。これらは、特に要求された場合にのみ使用されます（例：<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#query-rescorer">再スコアリング</a>によるブルートフォースセカンダリ、またはセグメントマージ中の再量子化に使用します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c52b51939357280/6a17d9897b54f9130f8b3761/ffc6e8bffefc5cd36f14aae315184c89e04a6587-1440x207.png" alt=".veqファイル" /><p>図2: の簡略化されたレイアウトファイル。のスペースを占有し、検索中にメモリにロードされます。バイトは、スコアリングを調整して精度と再現性を向上させるために使用される補正乗数浮動小数点数を表します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28e007988f81e5c0/6a17d98b25daabe23208a0d9/555cc92d7d179d8c9711cefbaaac3e0b815d2e42-1440x182.png" alt=".vemqファイル" /><p>図 3: メタデータ ファイルの簡略化されたレイアウト。ここで、このセグメントの計算された分位数とともに、量子化とベクトル構成を追跡します。</p><p>したがって、各セグメントについて、量子化されたベクトルだけでなく、これらの量子化されたベクトルの作成に使用された分位数と元の生のベクトルも保存します。しかし、なぜ生のベクトルを保存しておくのでしょうか?</p><h2>あなたとともに成長する量子化</h2><p>Lucene は定期的にセグメントを読み取り専用にフラッシュするため、各セグメントにはすべてのデータの部分的なビューのみが表示されます。つまり、計算された四分位数は、データ全体のそのサンプル セットにのみ直接適用されます。さて、サンプルがコーパス全体を適切に代表しているのであれば、これは大した問題ではありません。しかし、Lucene ではさまざまな方法でインデックスを並べ替えることができます。したがって、セグメントごとの分位数計算にバイアスを追加する方法でソートされたデータをインデックス化することができます。また、いつでも好きなときにデータをフラッシュできます。サンプル セットは、たとえ 1 つのベクトルだけでも非常に小さい可能性があります。さらにもう一つの問題は、マージがいつ発生するかを制御できることです。Elasticsearch ではデフォルトと定期的なマージが設定されていますが、 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.10/indices-forcemerge.html">_force_merge</a> API を介していつでもマージを要求できます。では、どうすれば、優れた再現性をもたらす適切な量子化を実現しながら、こうした柔軟性をすべて実現できるのでしょうか?</p><p>Lucene のベクトル量子化は時間の経過とともに自動的に調整されます。Lucene は読み取り専用セグメント アーキテクチャで設計されているため、各セグメントのデータが変更されていないことが保証され、コード内で更新できるタイミングが明確に区別されます。つまり、セグメントのマージ中に、必要に応じて分位数を調整し、ベクトルを再量子化できる可能性があります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d9a2390958c3e82/6a17d98cbe6086e4fd0045d5/646baa8279719d06c05c1a4fdb80a0c060e0dd94-1440x446.png" alt="複数セグメントの変位値" /><p>図 4: 異なる分位数を持つ 3 つのセグメントの例。</p><p>しかし、再量子化はコストがかかるのではないですか?多少のオーバーヘッドはありますが、Lucene は分位数をインテリジェントに処理し、必要な場合にのみ完全に再量子化します。図 4 のセグメントを例に挙げてみましょう。セグメントとにそれぞれドキュメントを割り当て、セグメントにはドキュメントのみを割り当てます。Lucene は、四分位数の加重平均を取得し、その結果として得られる結合された四分位数がセグメントの元の四分位数に十分近い場合、そのセグメントを再量子化する必要がなく、新しく結合された四分位数を利用します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2596c10ee616cf3/6a17d98eec0f8903b85a6497/58fa20d60dc337ca7108eb01e84a07c330e87ee6-1440x447.png" alt="統合された四分位数" /><p>図 5: セグメントとにドキュメントがあり、セグメントにはドキュメントしかない場合の結合された分位数の例。</p><p>図5に示されている状況では、結果として得られる統合された分位数は、 との元の分位数と非常に類似していることがわかります。したがって、ベクトルを量子化する必要性は認められません。セグメントは、逸脱しすぎているようです。その結果、 のベクトルは、新しく結合された分位値で再量子化されます。</p><p>実際、結合された四分位数が元の四分位数と大幅に異なる極端なケースもあります。この場合、各セグメントからサンプルを取得し、四分位数を完全に再計算します。</p><h2>量子化性能と数値</h2><p>それで、それは高速であり、良好なリコールを提供し続けるのでしょうか?<code>c3-standard-8</code> GCP インスタンスで実験を実行すると、次の数値が収集されました。との公平な比較を確実にするために、生のベクトルをメモリ内に保持するのに十分な大きさのインスタンスを使用しました。最大内積を使用して<a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-simple-embeddings">Cohere Wiki</a>ベクトルをインデックスしました。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa48e3724ad97fed/6a17d98f445de96aca4cff88/81958f99b8a1397f669db48de00f264cacc6b49f-576x455.png" alt="量子化リコール" /><p>図 6: 量子化ベクトルと生のベクトルの Recall@10。量子化ベクトルの検索パフォーマンスは生のベクトルよりも大幅に高速で、さらに 5 つのベクトルを集めるだけでリコールをすぐに回復できます。これはで確認できます。</p><p>図6にそのストーリーを示します。予想通り、再現率の違いはありますが、それは大きな差ではありません。そして、さらに 5 つのベクトルを集めるだけで、再現率の差は消えます。これらすべてを、セグメントのマージが高速化し、 ベクトルの 1/4 のメモリで実現します。</p><h2>まとめ</h2><p>Lucene は、難しい問題に対する独自のソリューションを提供します。量子化には「トレーニング」や「最適化」のステップは必要ありません。Lucene では、問題なく動作します。データがシフトした場合にベクトル インデックスを「再トレーニング」する必要があることを心配する必要はありません。Lucene は重要な変更を検出し、データの存続期間中これを自動的に処理します。この機能が Elasticsearch に導入されるのを楽しみにしてください。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/scalar-quantization-in-lucene</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/scalar-quantization-in-lucene</guid>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[MLの調査]]></category>
    <dc:creator><![CDATA[Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb382d99ffac9992c/6a17d991b1e113640179f12c/dc07f16d347a68476bded5df9adb831452f61278-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Sat, 11 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[学術論文の実装: ElasticsearchとLuceneから学んだ教訓]]></title>
    <description><![CDATA[Elasticsearch と Lucene の経験を活かして、研究論文をソフトウェア アプリケーションに組み込む戦略を学びます。]]></description>
    <content:encoded><![CDATA[<p>この投稿では、学術論文をソフトウェア アプリケーションに実装するための戦略を紹介します。他のエンジニアが私たちの経験から学べるよう、Elasticsearch と Lucene の例を参考にしています。これらの戦略を読んで、「でもこれは単なるソフトウェア開発だ！」と思うかもしれません。そしてそれは確かに真実です。エンジニアとして私たちはすでに適切な方法とツールを持っており、それらを新たな課題に適応させる必要があるだけです。</p><h2>背景</h2><p>Elasticsearch を開発しているときに、解決するための単純なアプローチや確立されたアプローチがない重要な問題に遭遇することがあります。「うーん、これについて言及している学術論文はあるのだろうか？」と疑問に思うのは当然です。時には、学術的な仕事がインスピレーションの源となることもあります。新しいアルゴリズムやデータ構造を提案する論文に出会うと、「これはとても便利だろう！」と思うでしょう。Elasticsearch と Apache Lucene が学術研究にどのように組み込まれているか、いくつかの例を次に示します。</p><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.15/search-aggregations-metrics-cardinality-aggregation.html">カーディナリティ集計 のための</a> <a href="https://research.google/pubs/pub40671/">HyperLogLog++</a></p></li><li><p><a href="https://www.elastic.co/blog/improving-response-latency-in-elasticsearch-with-adaptive-replica-selection">適応型レプリカ選択</a> <a href="https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-suresh.pdf">のための C3アルゴリズム</a></p></li><li><p>Lucene における最近傍ベクトル検索のため<a href="https://arxiv.org/abs/1603.09320">の階層的ナビゲート可能スモールワールドグラフ (HNSW)</a></p></li><li><p><a href="https://github.com/elastic/ml-cpp/pull/488">機械学習の分類を改善する</a> <a href="https://jmlr.csail.mit.edu/papers/volume17/15-308/15-308.pdf">ための MIC統計</a></p></li><li><p><a href="https://www.elastic.co/blog/faster-retrieval-of-top-hits-in-elasticsearch-with-block-max-wand">Lucene でトップヒット検索を高速化</a> する<a href="http://engineering.nyu.edu/~suel/papers/bmw.pdf"> ブロック最大 WAND</a></p></li><li><p>...<a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-combined-fields-query.html"> その他 多数</a></p></li></ul><p>学術論文は、データ集約型システムを開発するエンジニアにとって貴重なリソースです。しかし、それらを実装するのは困難で、エラーが発生しやすくなります。アルゴリズムの説明は複雑であることが多く、重要な実用的な詳細が省略されています。そして、テストは本当に難しいです。たとえば、出力がデータセットに大きく依存する機械学習アルゴリズムを徹底的にテストするにはどうすればよいでしょうか。</p><h2>ソフトウェアの依存関係と同じように論文を評価する</h2><p>新しいソフトウェア依存関係を追加するには、慎重な評価が必要です。他のパッケージが正しくなかったり、速度が遅かったり、安全でなかったりする場合は、プロジェクトも同様になる可能性があります。依存関係を導入する前に、開発者は必ずその品質を評価します。</p><p>実装を検討している学術論文にも同じことが当てはまります。アルゴリズムが論文で発表されているので、そのアルゴリズムは正しく、パフォーマンスも優れているに違いないと思われるかもしれません。しかし、査読プロセスに合格したとしても、学術論文には問題がある可能性があります。おそらく、正しさの証明は現実的ではない仮定に依存しているのでしょう。あるいは、「実験」セクションではベースラインよりもはるかに優れたパフォーマンスが示されていますが、これは特定のデータセットにのみ当てはまります。たとえ論文の質が優れていたとしても、そのアプローチがプロジェクトに適していない可能性があります。</p><p>学術論文に「依存」するかどうかを考えるときは、ソフトウェア パッケージの場合と同じ質問をすると役立ちます。</p><ul><li><p>ライブラリは広く使用され、「実戦テスト済み」ですか?→ 他のパッケージでもこの論文は実装されていますか? また、うまく機能しましたか?</p></li><li><p>パフォーマンスベンチマークは利用可能ですか?これらは正確かつ公平と思われますか?→論文には現実的な実験が含まれていますか？それらはうまく設計されていますか?</p></li><li><p>パフォーマンスの改善は複雑さを正当化するほど大きいですか?→ この論文は強力なベースラインアプローチに匹敵しますか?このベースラインをどれだけ上回るのでしょうか?</p></li><li><p>このアプローチは当社のシステムとうまく統合されるでしょうか?→ アルゴリズムの前提とトレードオフは、私たちのユースケースに適合していますか?</p></li></ul><p>どういうわけか、ソフトウェア パッケージが競合製品とのパフォーマンス比較を公開すると、そのパッケージが常に最速であると出てきます。第三者がベンチマークを設計した場合、よりバランスが取れる可能性があります。同じ現象が学術論文にも当てはまります。アルゴリズムが元の論文だけでなく、他の論文でも強力なベースラインとして優れたパフォーマンスを示している場合、そのアルゴリズムは信頼できる可能性が非常に高くなります。</p><h2>テストを創造的に行う</h2><p>学術論文のアルゴリズムは、私たちが日常的に目にするタイプのアルゴリズムよりも動作が洗練されていることがよくあります。おそらく、これは精度と引き換えに速度を向上させる近似アルゴリズムです。あるいは、大規模なデータセットを取り込み、（場合によっては予期しない）出力を生成する機械学習の手法である可能性もあります。アルゴリズムの動作を単純な方法で特徴付けることができない場合、これらのアルゴリズムのテストをどのように記述できるでしょうか?</p><h3>不変量に焦点を当てる</h3><p>ユニット テストを設計するときは、例に基づいて考えるのがよくあります。つまり、アルゴリズムにこの例の入力を与えると、その出力が得られるはずです。残念ながら、ほとんどの数学アルゴリズムでは、例に基づくテストではその動作を十分にカバーできません。</p><p>Elasticsearch が検索リクエストをどのノードが処理すべきかを判断するために使用する C3 アルゴリズムを考えてみましょう。ノードの以前のサービスと応答時間、およびキュー サイズを組み込んだ微妙な計算式を使用して各ノードをランク付けします。いくつかの例をテストしても、式を正しく理解できたかどうかは実際には確認できません。一歩下がって不変条件のテストについて考えてみると役に立ちます。サービス時間が長くなると、ノードのランクは下がりますか?キューのサイズが 0 の場合、論文で主張されているように、ランクは応答時間によって決定されますか?</p><p>不変条件に焦点を当てると、次のような多くの一般的なケースで役立ちます。</p><ul><li><p>このメソッドは順序に依存しないものになるのでしょうか?もしそうなら、入力データを異なる順序で渡すと同じ出力が得られるはずです。</p></li><li><p>アルゴリズムの何らかのステップでクラス確率が生成されますか?もしそうなら、これらの確率の合計は 1 になるはずです。</p></li><li><p>関数は原点を中心に対称ですか?もしそうなら、入力の符号を反転すると、出力の符号も単に反転するはずです。</p></li></ul><p>C3 を初めて実装したとき、式にバグがあり、応答時間の代わりに応答時間の逆数を誤って使用していました。つまり、より遅いノードの方が上位にランク付けされる可能性があるということです。問題を修正する際には、将来のミスを防ぐために<a href="https://github.com/elastic/elasticsearch/pull/70283">不変チェックを追加するようにしました</a>。</p><h3>リファレンス実装と比較する</h3><p>論文とともに、著者らはアルゴリズムの実装も公開したようです。(論文に実験が含まれている場合は特にこの可能性が高くなります。多くのジャーナルでは、著者が結果を再現するためのコードを投稿することを求めているためです。)このリファレンス実装に対してアプローチをテストし、アルゴリズムの重要な詳細を見逃していないことを確認できます。</p><p>最近傍検索用の Lucene HNSW 実装を開発する際に、論文の著者による<a href="https://issues.apache.org/jira/browse/LUCENE-9937">参照ライブラリに対してテストを行いました</a>。同じデータセットに対して Lucene とライブラリの両方を実行し、結果の精度と実行された計算の数を比較しました。これらの数値がほぼ一致する場合、Lucene がアルゴリズムを忠実に実装していることがわかります。</p><p>アルゴリズムをシステムに組み込む場合、複数のコアに拡張したり、パフォーマンスを向上させるためにヒューリスティックを追加したりするなど、変更や拡張を行う必要があることがよくあります。最初に「バニラ」バージョンを実装し、リファレンスに対してテストしてから、段階的な変更を加えるのが最適です。そうすれば、カスタマイズを行う前に、すべての重要な部分を確実にキャプチャできます。</p><h3>既存のアルゴリズムとの決闘</h3><p>最後のセクションでは、テスト不変条件の別のアイデアとして、アルゴリズムの出力を、より単純で理解しやすいアルゴリズムの出力と比較する方法について説明します。例として、上位の結果に表示されないドキュメントをスキップすることでドキュメントの検索を高速化する、Lucene の block-max WAND アルゴリズムを考えてみましょう。block-max WAND があらゆるケースでどのように動作するかを正確に説明することは困難ですが、これを適用しても上位の結果は変わらないはずです。したがって、テストではランダムな検索クエリをいくつか生成し、<a href="https://github.com/apache/lucene/blob/main/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java#L669">それらを WAND 最適化ありとなしの両方で実行して</a>、結果が常に一致することを確認できます。</p><p>これらのテストの重要な側面は、比較を実行するための<a href="https://www.elastic.co/blog/elasticsearch-testing-qa-increasing-coverage-randomizing-test-runs">ランダムな入力を生成する</a>ことです。これにより、思いもよらなかったケースを練習したり、予期しない問題を明らかにしたりすることができます。たとえば、Lucene の BM25F スコアリングのランダム化比較テストは<a href="https://issues.apache.org/jira/browse/LUCENE-10039">、微妙なエッジ ケースのバグを検出するのに</a>役立ちました。アルゴリズムにランダムな入力を与えるという考え方には、コンピューター セキュリティの一般的なテスト手法である<a href="https://en.wikipedia.org/wiki/Fuzzing">ファジング</a>の概念との密接な関係があります。</p><p>Elasticsearch と Lucene では、このテスト手法が頻繁に使用されます。2 つのアルゴリズム (TestDuelingAnalyzers、testDuelTermsQuery...) 間の「決闘」について言及しているテストを見ると、この戦略が実行中であることがわかります。</p><h2>論文の用語を使用する</h2><p>他の開発者があなたのコードで作業する場合、詳細を理解するために論文を参照する必要があります。<a href="https://github.com/elastic/elasticsearch/blob/4f22f437ee50cacb94b37b457be1da0b8ba0e8ce/server/src/main/java/org/elasticsearch/search/aggregations/metrics/HyperLogLogPlusPlus.java#L24-L39">Elasticsearch の HyperLogLog++ 実装に関するコメントは、</a>次のようにうまく表現しています。「論文を読まずにこのクラスが何をするかを理解しようとするのは冒険的だと見なされます。」このメソッドのコメントも良い例を示しています。学術論文へのリンクが含まれており、元々説明されていたアルゴリズムにどのような変更が加えられたかが強調されています。</p><p>開発者は紙に基づいてコードを理解するので、まったく同じ用語を使用すると便利です。数学表記は簡潔なので、通常は「適切なスタイル」とは見なされない名前になることもありますが、論文の文脈では非常に明確になります。学術論文の数式は、Elasticsearch で<a href="https://github.com/elastic/elasticsearch/blob/4f22f437ee50cacb94b37b457be1da0b8ba0e8ce/server/src/main/java/org/elasticsearch/node/ResponseCollectorService.java#L151">rS や muBarSInverse</a>のような難解な変数名に遭遇する数少ない例の 1 つです。</p><p>
<em>著者が推奨する論文の読み方は、大きなコーヒーを飲みながら読むことです。</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12d81453c706f7cb/6a17d80e0b0bede6badd3424/d03a8e2a50e173b15a4ec3732810c63ff53321f6-1440x1081.jpg" alt="elastic-blog-academicpaper.jpg" /><h2>著者にメールを送ることができます</h2><p>難しい論文に取り組むとき、誤解しているのか、それとも単なるタイプミスなのかわからず、数式について頭を悩ませるのに何時間も費やすことがあります。これがオープンソース プロジェクトであれば、GitHub または StackOverflow で質問することができます。しかし、学術論文はどこで入手できるのでしょうか?著者は忙しそうなので、あなたのメールにイライラするかもしれません。</p><p>それどころか、多くの学者は自分のアイデアが実践されていると聞くことを喜び、メールで質問に喜んで答えてくれます。彼らが精通している製品に取り組んでいる場合は、そのアプリケーションが彼らの Web サイトに掲載される可能性もあります。</p><p>また、ソフトウェア開発で使用したのと同じツールの多くを使用して、研究者が論文を公開して議論する傾向も高まっています。論文にソフトウェア パッケージが付属している場合は、 <a href="https://github.com/facebookresearch/faiss/issues/1928">Github でよくある質問</a>への回答が見つかることがあります。「理論計算機科学」や「Cross Validated」などの Stack Exchange コミュニティにも、<a href="https://cstheory.stackexchange.com/questions/49296/problem-in-the-paper-stable-minimum-space-partitioning-in-linear-time">人気のある論文に関する詳細な議論</a>が含まれています。一部の会議では、すべての論文レビューをオンラインで公開し始めています。これらのレビューには著者との<a href="https://openreview.net/forum?id=H1eA7AEtvS">双方向の議論</a>が含まれており、アプローチに関する役立つ洞察が得られる可能性があります。</p><h2>つづく</h2><p>この投稿では、学術論文の選び方の基本に焦点を当て、 </p><p>正しく実装しますが、アルゴリズムを実際に展開するすべての側面をカバーしているわけではありません。たとえば、アルゴリズムが複雑なシステムの 1 つのコンポーネントにすぎない場合、そのコンポーネントへの変更がエンドツーエンドの改善につながることをどのように保証すればよいでしょうか。また、アルゴリズムを統合するために、元の論文でカバーされていない大幅な変更や拡張が必要な場合はどうなるでしょうか?これらは重要なトピックであり、今後の投稿でさらに詳しく共有したいと考えています。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/implementing-academic-papers-lessons-learned-from-elasticsearch-and-lucene</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/implementing-academic-papers-lessons-learned-from-elasticsearch-and-lucene</guid>
    <category><![CDATA[MLの調査]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Julie Tibshirani]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 29 Sep 2021 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>