<?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/fr/search-labs/blog/category/lucene</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/lucene</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/lucene.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 09:34:29 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Filtrage de la recherche vectorielle : Garder la pertinence]]></title>
    <description><![CDATA[Il ne suffit pas d'effectuer une recherche vectorielle pour trouver les résultats les plus similaires à une requête. Le filtrage est souvent nécessaire pour réduire les résultats de la recherche. Cet article explique comment fonctionne le filtrage pour la recherche vectorielle dans Elasticsearch et Apache Lucene.]]></description>
    <content:encoded><![CDATA[<p>La recherche vectorielle ne suffit pas pour trouver des résultats pertinents. Il est très courant d'utiliser des critères de filtrage qui permettent de réduire les résultats de la recherche et d'éliminer les résultats non pertinents.</p><p>Comprendre le fonctionnement du filtrage dans la recherche vectorielle vous aidera à équilibrer les compromis entre performance et rappel, et à découvrir certaines des optimisations utilisées pour rendre la recherche vectorielle plus performante lorsque le filtrage est utilisé.</p><h2>Pourquoi le filtrage ?</h2><p>La recherche vectorielle a révolutionné la manière dont nous trouvons des informations pertinentes dans de grands ensembles de données, en nous permettant de découvrir des éléments sémantiquement similaires à une requête.</p><p>Toutefois, il ne suffit pas de trouver des articles similaires. Nous devons souvent réduire les résultats de la recherche en fonction de critères ou d'attributs spécifiques.</p><p>Imaginez que vous recherchiez un produit dans un magasin de commerce électronique. Une recherche purement vectorielle peut vous montrer des articles visuellement similaires, mais vous pouvez aussi vouloir filtrer par fourchette de prix, marque, disponibilité ou évaluations des clients. Sans filtrage, vous seriez confronté à un vaste éventail de produits similaires, ce qui rendrait difficile de trouver exactement ce que vous cherchez.</p><p>Le filtrage permet un contrôle précis des résultats de la recherche, garantissant que les éléments récupérés ne sont pas seulement alignés sur le plan sémantique, mais qu'ils répondent également à toutes les exigences nécessaires. L'expérience de recherche est ainsi beaucoup plus précise, efficace et conviviale.</p><p>C'est là qu'Elasticsearch et Apache Lucene excellent - l'utilisation d'un filtrage efficace sur différents types de données est l'une des principales différences avec les autres bases de données vectorielles.</p><h2>Filtrage pour la recherche vectorielle exacte</h2><p>Il existe deux manières principales d'effectuer des recherches de vecteurs exacts :</p><ul><li><p>Utilisation d'un type d'index <code>flat</code> pour votre champ dense_vector. Ainsi, les recherches sur <code>knn</code> utilisent la recherche exacte au lieu de la recherche approximative.</p></li><li><p>Utilisation d'une <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">requête script_score</a> qui utilise des fonctions vectorielles pour calculer le score. Ceci peut être utilisé avec n'importe quel type d'index.</p></li></ul><p>Lors de l'exécution d'une recherche vectorielle exacte, tous les vecteurs sont comparés à la requête. Dans ce cas, le filtrage améliore les performances, car seuls les vecteurs qui passent le filtre doivent être comparés.</p><p>Cela n'a pas d'incidence sur la qualité du résultat, car tous les vecteurs sont pris en compte de toute façon. Nous filtrons simplement à l'avance les résultats qui ne sont pas intéressants, afin de réduire le nombre d'opérations.</p><p>C'est très important, car il peut être plus performant d'exécuter une recherche exacte plutôt qu'une recherche approximative lorsque les filtres appliqués donnent un petit nombre de documents.</p><p>La règle de base est d'utiliser la recherche exacte lorsque moins de 10 000 documents passent le filtre. Les index <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a> sont beaucoup plus rapides pour les comparaisons, il est donc logique d'utiliser la recherche exacte lorsque les index basés sont inférieurs à 100k. Consultez <a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">cet article de blog</a> pour plus de détails.</p><p>Si vos filtres sont toujours très restrictifs, vous pouvez envisager une indexation axée sur la recherche exacte plutôt que sur la recherche approximative en utilisant un type d'index <code>flat</code> plutôt qu'un index basé sur HNSW. Pour plus de détails, voir <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">les propriétés de index_options</a>.</p><h2>Filtrage pour la recherche vectorielle approximative</h2><p>Lors de l'exécution d'une recherche vectorielle approximative, nous échangeons la précision des résultats contre la performance. Les structures de données de recherche vectorielle telles que HNSW recherchent efficacement les voisins les plus proches sur des millions de vecteurs. Ils se concentrent sur la récupération des vecteurs les plus similaires en effectuant le moins possible de comparaisons de vecteurs, qui sont coûteuses à calculer.</p><p>Cela signifie que les autres attributs de filtrage ne font pas partie des données vectorielles. Les différents types de données ont leurs propres structures d'indexation qui sont efficaces pour les trouver et les filtrer, comme les dictionnaires de termes, les listes d'écritures et les valeurs doc.</p><p>Étant donné que ces structures de données sont distinctes du mécanisme de recherche vectorielle, comment appliquer le filtrage à la recherche vectorielle ? Il existe deux options : appliquer les filtres après la recherche vectorielle (post-filtrage) ou avant la recherche vectorielle (préfiltrage).</p><p>Chacune de ces options présente des avantages et des inconvénients. Voyons cela de plus près !</p><h3>Post-filtrage</h3><p>Le post-filtrage applique des filtres après que la recherche vectorielle a été effectuée. Cela signifie que les filtres sont appliqués après que les k résultats vectoriels les plus similaires ont été trouvés.</p><p>Il est évident que nous pouvons potentiellement obtenir moins de k résultats après avoir appliqué les filtres aux résultats. Nous pourrions bien sûr obtenir plus de résultats à partir de la recherche vectorielle (valeur k plus élevée), mais nous ne serons pas sûrs d'obtenir k ou plus après avoir appliqué les filtres.</p><p>L'avantage du post-filtrage est qu'il ne modifie pas le comportement de la recherche vectorielle lors de l'exécution - la recherche vectorielle n'est pas consciente du filtrage. En revanche, il modifie le nombre final de résultats obtenus.</p><p>Voici un exemple de post-filtrage à l'aide de la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">requête knn</a>. Vérifier que la clause de filtrage est distincte de la requête 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>Le post-filtrage est également disponible pour la recherche knn en utilisant le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">post-filtre</a>:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>Gardez à l'esprit que vous devez utiliser une section de post-filtrage explicite avec la recherche knn. Si vous n'utilisez pas de post-filtre, la recherche knn <a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">combinera les résultats des plus proches voisins</a> avec d'autres requêtes ou filtres au lieu d'effectuer un post-filtre.</p><h3>Préfiltrage</h3><p>L'application de filtres avant la recherche vectorielle permet d'abord d'extraire les documents qui satisfont aux filtres, puis de transmettre ces informations à la recherche vectorielle.</p><p>Lucene utilise les <a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a> pour stocker efficacement les documents qui satisfont aux conditions du filtre. La recherche vectorielle parcourt ensuite le graphe HNSW en tenant compte des documents qui satisfont à la condition. Avant d'ajouter un candidat aux résultats, il vérifie qu'il est contenu dans le BitSet des documents valides.</p><p>Cependant, le candidat doit être exploré et comparé à la requête, même s'il ne s'agit pas d'un document valide. L'efficacité de HNSW repose sur la connexion entre les vecteurs du graphe : si nous cessons d'explorer un candidat, cela signifie que nous risquons d'ignorer également ses voisins.</p><p>Imaginez que vous conduisiez pour vous rendre à une station-service. Si vous écartez les routes qui ne comportent pas de station-service, il est peu probable que vous arriviez à destination. Les autres routes ne sont peut-être pas celles dont vous avez besoin, mais elles vous <em>relient à</em> votre destination. Idem pour les vecteurs sur un graphique HNSW !</p><p>Il s'ensuit que l'application d'un préfiltrage est moins performante que la non-application de filtres. Nous devons effectuer le travail sur <em>tous les</em> vecteurs que nous visitons dans notre recherche, et nous devons rejeter ceux qui ne correspondent pas au filtre. Nous travaillons davantage et prenons plus de temps pour obtenir nos meilleurs résultats.</p><p>Voici un exemple de préfiltrage dans le DSL de requête Elasticsearch. Vérifiez que la clause de filtrage fait désormais partie de la section knn :</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p>Le préfiltrage est disponible à la fois pour la <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">recherche knn</a> et la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">requête knn</a>:</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>Optimisation du préfiltrage</h4><p>Il existe quelques optimisations que nous pouvons appliquer pour garantir la performance du préfiltrage.</p><p>Nous pouvons passer à la recherche exacte si le filtre est très restrictif. Lorsqu'il y a peu de vecteurs à comparer, il est plus rapide d'effectuer une recherche exacte sur les quelques documents qui satisfont le filtre.</p><p>Il s'agit d'une optimisation appliquée automatiquement dans <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a> et Elasticsearch.</p><p>Une autre méthode d'optimisation consiste à ignorer les vecteurs qui ne satisfont pas au filtre. Au lieu de cela, cette méthode vérifie les voisins des vecteurs filtrés qui passent le filtre. Cette approche réduit effectivement le nombre de comparaisons puisque les vecteurs filtrés ne sont pas pris en compte, et continue d'explorer les vecteurs connectés au chemin actuel.</p><p>Cet algorithme est ACORN-1, et le processus est décrit en détail dans <a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">ce billet de blog.</a></p><h2>Filtrage à l'aide de la sécurité au niveau du document</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">Document Level Security (DLS)</a> est une fonctionnalité d'Elasticsearch qui spécifie les documents que les rôles d'utilisateurs peuvent récupérer.</p><p>La DLS est réalisée à l'aide de requêtes. Une requête peut être associée aux index pour un rôle, ce qui limite effectivement les documents qu'un utilisateur appartenant à ce rôle peut extraire des index.</p><p>L'interrogation sur le rôle est utilisée comme filtre pour <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">extraire les documents qui y correspondent</a> et qui sont mis en cache sous la forme d'un ensemble de bits. Ce BitSet est ensuite utilisé pour envelopper le lecteur Lucene sous-jacent, de sorte que seuls les documents renvoyés par la requête sont considérés comme <em>vivants, c'est-à-dire</em>qu'ils existent dans l'index et n'ont pas été supprimés.</p><p>Étant donné que les documents sont <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">extraits du lecteur</a> pour effectuer la requête knn, seuls les documents disponibles pour l'utilisateur seront pris en compte. S'il existe un préfiltre, les documents DLS <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">y seront ajoutés</a>.</p><p>Cela signifie que le filtrage DLS fonctionne comme un préfiltre pour la recherche vectorielle approximative, avec les mêmes implications en termes de performances et d'optimisations.</p><p>Le DLS avec recherche exacte présente les mêmes avantages que l'application de n'importe quel filtre - moins il y a de documents extraits du DLS, plus la recherche exacte est performante. Tenez également compte du nombre de documents renvoyés par le DLS - si les rôles du DLS sont très restrictifs, vous pouvez envisager d'utiliser la recherche exacte au lieu de la recherche approximative.</p><h2>Analyse comparative</h2><p>Chez Elasticsearch, nous voulons nous assurer que le filtrage de la recherche vectorielle est efficace. Nous disposons d'<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">un benchmark spécifique pour le filtrage vectoriel</a> qui effectue des recherches vectorielles approximatives avec différents filtrages afin de s'assurer que la recherche vectorielle continue à récupérer des résultats pertinents aussi rapidement que possible.</p><p>Vérifiez les <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=()">améliorations apportées</a> lors de l'introduction d'ACORN-1. Pour les tests où seuls 2% des vecteurs passent le filtre, le temps de latence des requêtes est réduit à 55% de la durée initiale :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>Conclusion</h2><p>Le filtrage fait partie intégrante de la recherche. S'assurer que le filtrage est performant dans la recherche vectorielle, et comprendre les compromis et les optimisations, c'est ce qui fait l'efficacité et la précision d'une recherche.</p><p>Le filtrage a un impact sur les performances de la recherche vectorielle :</p><ul><li><p>La recherche exacte est plus rapide lorsque l'on utilise le filtrage. Vous pouvez envisager d'utiliser la recherche exacte au lieu de la recherche approximative si votre filtrage est suffisamment restrictif. Il s'agit d'une optimisation automatique dans Elasticsearch.</p></li><li><p>La recherche approximative est plus lente lorsque l'on utilise le préfiltrage. Le préfiltrage nous permet d'obtenir les k premiers résultats correspondant au filtre, au prix d'une recherche plus lente.</p></li><li><p>Le post-filtrage ne permet pas nécessairement de retrouver les k premiers résultats, car ils peuvent être filtrés par le filtre lorsqu'il est appliqué.</p></li></ul><p>Bon filtrage !</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[Base vectorielle]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[À l'intérieur d'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[Accélérer la fusion des graphiques de HNSW]]></title>
    <description><![CDATA[Explorez le travail que nous avons effectué pour réduire la charge de travail liée à la construction de plusieurs graphes HNSW, en particulier en réduisant le coût de la fusion des graphes.]]></description>
    <content:encoded><![CDATA[<p>Dans le passé, <a href="https://www.elastic.co/fr/search-labs/blog/multi-graph-vector-search">nous avons abordé</a> certains des défis posés par la recherche dans plusieurs <a href="https://www.elastic.co/fr/search-labs/blog/hnsw-graph">graphes de HNSW</a> et la manière dont nous avons pu les atténuer. À cette occasion, nous avons fait allusion à d'autres améliorations que nous avions prévues. Ce billet est l'aboutissement de ce travail.</p><p>Vous vous demandez peut-être pourquoi utiliser des graphiques multiples ? Il s'agit d'un effet secondaire d'un choix architectural de Lucene : les segments immuables. Comme pour la plupart des choix architecturaux, il y a des avantages et des inconvénients. Par exemple, nous avons récemment obtenu la certification Serverless Elasticsearch. Dans ce contexte, nous avons tiré des avantages très importants des segments immuables, notamment une réplication efficace de l'index et la possibilité de découpler le calcul de l'index et de la requête et de les faire évoluer indépendamment. Pour la quantification vectorielle, les fusions de segments nous donnent la possibilité de mettre à jour les paramètres pour les adapter aux caractéristiques des données. Dans le même ordre d'idées, nous pensons que la possibilité de mesurer les caractéristiques des données et de revoir les choix d'indexation présente d'autres avantages.</p><p>Dans ce billet, nous discuterons du travail que nous avons effectué pour réduire de manière significative la charge de travail liée à la construction de plusieurs graphes HNSW et, en particulier, pour réduire le coût de la fusion des graphes.</p><h3>Arrière-plan</h3><p>Afin de maintenir un nombre raisonnable de segments, Lucene vérifie périodiquement s'il doit fusionner des segments. Cela revient à vérifier si le nombre de segments actuel dépasse un nombre de segments cible, qui est déterminé par la taille du segment de base et la politique de fusion. Si le nombre est dépassé, Lucene fusionne les groupes de segments alors que la contrainte n'est pas respectée. Ce processus a été décrit en détail <a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">dans d'autres documents.</a></p><p>Lucene choisit de fusionner des segments de taille similaire car cela permet d'obtenir une croissance logarithmique de l'amplification de l'écriture. Dans le cas d'un index vectoriel, l'amplification de l'écriture est le nombre de fois qu'un vecteur sera inséré dans un graphique. Lucene essaiera de fusionner les segments par groupes d'environ 10. Par conséquent, les vecteurs sont insérés dans un graphe environ {10}\left (\frac{n}{n_0}\right )fois, où  le nombre de vecteurs de l'index et  est le nombre de vecteurs du segment de base attendu. En raison de la croissance logarithmique, l'amplification de l'écriture est à un chiffre, même pour les indices les plus importants. Cependant, le temps total passé à fusionner les graphes est linéairement proportionnel à l'amplification de l'écriture.</p><p>Lors de la fusion des graphes HNSW, nous procédons déjà à une petite optimisation : nous conservons le graphe du plus grand segment et y insérons les vecteurs des autres segments. C'est la raison pour laquelle le facteur 9/10 est mentionné ci-dessus. Nous montrons ci-dessous comment nous pouvons faire beaucoup mieux en utilisant les informations de tous les graphiques que nous fusionnons.</p><h3>Fusion du graphique HNSW</h3><p>Auparavant, nous retenions le plus grand graphe et insérions des vecteurs provenant des autres en ignorant les graphes qui les contiennent. L'idée clé que nous utilisons ci-dessous est que chaque graphe HNSW que nous écartons contient des informations de proximité importantes sur les vecteurs qu'il contient. Nous aimerions utiliser cette information pour accélérer l'insertion d'au moins une partie des vecteurs.</p><p>Nous nous concentrons sur le problème de l'insertion d'un petit graphe  dans un graphe plus grand , puisqu'il s'agit d'une opération atomique que nous pouvons utiliser pour construire n'importe quelle politique de fusion.</p><p>La stratégie consiste à trouver un sous-ensemble de sommets de l' à insérer dans le grand graphe. Nous utilisons ensuite la connectivité de ces sommets dans le petit graphe pour accélérer l'insertion des sommets restants  Dans ce qui suit, nous utilisons  et  pour désigner les voisins d'un sommet  dans le petit et le grand graphe, respectivement. Schématiquement, le processus est le suivant.</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>Nous calculons l'ensemble  à l'aide d'une procédure que nous décrivons ci-dessous (ligne 1). Ensuite, nous insérons chaque sommet de  dans le grand graphe à l'aide de la procédure d'insertion HNSW standard (ligne 2). Pour chaque sommet que nous n'avons pas inséré, nous trouvons ses voisins que nous avons insérés et leurs voisins dans le grand graphe (lignes 4 et 5). Nous utilisons une procédure <code>FAST-SEARCH-LAYER</code> avec cet ensemble (ligne 6) pour trouver les candidats pour le site <code>SELECT-NEIGHBORS-HEURISTIC</code> de l'<a href="https://arxiv.org/pdf/1603.09320">article</a> HNSW (ligne 7). En fait, nous remplaçons <code>SEARCH-LAYER</code> pour trouver l'ensemble de candidats dans la méthode <code>INSERT</code> (Algorithme 1 de l'article), qui est par ailleurs inchangée. Enfin, nous ajoutons le sommet que nous venons d'insérer dans  (ligne 8).</p><p>Il est clair que pour que cela fonctionne, chaque sommet dans  doit avoir au moins un voisin dans  En fait, nous exigeons que pour chaque sommet dans  que  pour quelque  M, la connectivité maximale de la couche. Nous observons que dans les graphes HNSW réels, les degrés des sommets sont assez variés. La figure ci-dessous montre une fonction de densité cumulative typique du degré de sommet pour la couche inférieure d'un graphique Lucene HNSW.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="Graphique HNSW : Exemple de distribution des degrés des sommets" /><p>Nous avons étudié la possibilité d'utiliser une valeur fixe pour  et de la faire dépendre du degré du sommet. Ce deuxième choix conduit à des accélérations plus importantes avec un impact minimal sur la qualité du graphe, nous avons donc opté pour ce qui suit</p><p>Notez que | est égal au degré du sommet  dans le petit graphe par définition. Le fait d'avoir une limite inférieure de deux signifie que nous insérerons tous les sommets dont le degré est inférieur à deux.</p><p>Un simple argument de comptage suggère que si nous choisissons  avec soin, il nous suffit d'insérer directement dans   Plus précisément, nous colorons une arête du graphe si nous insérons exactement l'un de ses sommets d'extrémité dans  Nous savons alors que pour que chaque sommet de  ait au moins  voisins dans , nous devons colorer au moins \sum_  arêtes. En outre, nous nous attendons à ce que</p><p>Ici, \mathbb  [N_s(U)|\right] est le degré moyen des sommets dans le petit graphe. Pour chaque sommet , nous colorons au plus  arêtes. Par conséquent, le nombre total d'arêtes que nous prévoyons de colorer est au maximum de _U\left [|N_s(U)|\right]. Nous espérons qu'en choisissant  avec soin, nous colorerons un nombre d'arêtes proche de ce nombre et donc, pour couvrir tous les sommets,  doit satisfaire à</p><p>Ceci implique que  {1}{4}|V_s|=\frac{1}{5}|V_s|.</p><p>Si <code>SEARCH-LAYER</code> domine le temps d'exécution, cela signifie que nous pourrions accélérer le temps de fusion jusqu'à  Compte tenu de la croissance logarithmique de l'amplification de l'écriture, cela signifie que même pour de très grands indices, nous ne ferions que doubler le temps de construction par rapport à la construction d'un seul graphique.</p><p>Le risque de cette stratégie est de nuire à la qualité du graphique. Nous avons d'abord essayé avec une version sans option <code>FAST-SEARCH-LAYER</code>. Nous avons constaté que cela dégradait la qualité des graphes dans la mesure où le rappel en fonction de la latence était affecté, en particulier lors de la fusion en un seul segment. Nous avons ensuite exploré diverses alternatives en effectuant une recherche limitée dans le graphique. Finalement, le choix le plus efficace a été le plus simple. Utilisez <code>SEARCH-LAYER</code> mais avec une faible <code>ef_construction</code>. Ce paramétrage nous a permis d'obtenir des graphes d'excellente qualité tout en réduisant le temps de fusion d'un peu plus de 30% en moyenne.</p><h3>Calcul de l'ensemble de jonction</h3><p>La recherche d'un bon ensemble de jointures peut être formulée comme un problème de couverture de graphe HNSW. Une heuristique gourmande est une heuristique simple et efficace pour approximer les couvertures optimales des graphes. L'approche que nous adoptons consiste à sélectionner les sommets un par un pour les ajouter à  dans l'ordre décroissant des gains. Le gain est défini comme suit :</p><p>Ici,  représente le nombre de voisins d'un vecteur  dans  et  est la fonction indicatrice. Le gain comprend la variation du nombre de sommets que nous avons ajoutés à , c'est-à-dire \max , puisque nous nous rapprochons de notre objectif en ajoutant un sommet moins couvert. Le calcul du gain est illustré dans la figure ci-dessous pour le sommet central orange.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="Gain de sommet à ajouter à l'ensemble de jointure J dans le graphe HNSW" /><p>Nous conservons l'état suivant pour chaque sommet </p><ol><li><p>S'il est périmé,</p></li><li><p>Son gain ,</p></li><li><p>Le nombre de sommets adjacents dans  est noté </p></li><li><p>Un nombre aléatoire dans l'intervalle [0,1] qui est utilisé pour départager les ex-aequo.</p></li></ol><p>Le pseudo-code pour le calcul de l'ensemble de jonction est le suivant.</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>Nous commençons par initialiser l'état dans les lignes 1 à 5.</p><p>À chaque itération de la boucle principale, nous extrayons d'abord le sommet de gain maximal (ligne 8), en brisant les égalités de manière aléatoire. Avant de procéder à toute modification, nous devons vérifier si le gain du sommet est périmé. En particulier, chaque fois que nous ajoutons un sommet à , nous affectons le gain d'autres sommets :</p><ol><li><p>Puisque tous ses voisins ont un voisin supplémentaire en , leurs gains peuvent changer (ligne 14)</p></li><li><p>Si l'un de ses voisins est maintenant entièrement couvert, tous les gains de ses voisins peuvent changer (lignes 14-16).</p></li></ol><p>Nous recalculons les gains paresseusement, c'est-à-dire que nous ne recalculons le gain d'un sommet que si nous voulons l'insérer dans  (lignes 18-20). Puisque les gains ne font que diminuer, nous ne pouvons jamais manquer un sommet que nous devrions insérer.</p><p>Notez que nous avons simplement besoin de suivre le gain total de sommets que nous avons ajoutés à  pour déterminer quand sortir. En outre, pendant que  {exit}au moins un sommet aura un gain non nul, de sorte que nous progressons toujours.</p><h3>Résultats</h3><p>Nous avons mené des expériences sur quatre ensembles de données qui, ensemble, couvrent les trois mesures de distance prises en charge (Euclidean, cosinus et produit intérieur) :</p><ol><li><p>quora-E5-small : 522931 documents, 384 dimensions et utilise la similarité cosinus,</p></li><li><p>cohere-wikipedia-v2 : 1M documents, 768 dimensions et utilise la similarité cosinus,</p></li><li><p>gist : 1M documents, 960 dimensions et utilise la distance euclidienne, et</p></li><li><p>cohere-wikipedia-v3 : 1M documents, 1024 dimensions et utilise le produit intérieur maximum.</p></li></ol><p>Pour chaque ensemble de données, nous évaluons deux niveaux de quantification :</p><ol><li><p>int8 - qui utilise un entier de 1 octet par dimension et</p></li><li><p>BBQ - qui utilise un seul bit par dimension.</p></li></ol><p>Enfin, pour chaque expérience, nous avons évalué la qualité de la recherche à deux profondeurs d'extraction et nous l'avons examinée après la construction de l'index, puis après la fusion forcée en un seul segment.</p><p>En résumé, nous obtenons des accélérations substantielles et constantes dans l'indexation et la fusion tout en maintenant la qualité du graphe et donc les performances de recherche dans tous les cas.</p><h4>Expérience 1 : quantification int8</h4><p>Les gains de vitesse moyens entre la ligne de base et le candidat, les changements proposés, sont les suivants :</p><p>Accélération du temps d'indexation : <strong>1,</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,</strong>fois</p><p>Cela correspond à la répartition suivante des durées d'exécution</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="Temps d'indexation et de fusion pour la stratégie de fusion de base et la stratégie de fusion candidate" /><p>Par souci d'exhaustivité, les horaires exacts sont les suivants</p><p></p><p>Index</p><p></p><p>Fusionner</p><p></p><p>Ensemble de données</p><p>ligne de base</p><p>candidat</p><p>Développer</p><p>candidat</p><p>quora-E5-petit</p><p>112.41s</p><p>81.55s</p><p>113.81s</p><p>70.87s</p><p>wiki-cohere-v2</p><p>158.1s</p><p>122.95s</p><p>425.20s</p><p>239.28s</p><p>liste</p><p>141.82s</p><p>119.26s</p><p>536.07s</p><p>279.05s</p><p>wiki-cohere-v3</p><p>211.86s</p><p>168.22s</p><p>654.97s</p><p>414.12s</p><p>Nous présentons ci-dessous les graphiques de rappel et de latence qui comparent le candidat (lignes pointillées) à la ligne de base à deux profondeurs de recherche : rappel@10 et rappel@100 pour les index avec plusieurs segments (le résultat final de notre stratégie de fusion par défaut après l'indexation de tous les vecteurs) et après la fusion forcée à un seul segment. Une courbe plus haute et plus à gauche est meilleure, ce qui signifie une meilleure mémorisation avec une latence plus faible.</p><p>Comme vous pouvez le constater, pour les indices de segments multiples, le candidat est meilleur pour l'ensemble de données Cohere v3 et légèrement moins bon, mais presque comparable, pour tous les autres ensembles de données. Après la fusion en un seul segment, les courbes de rappel sont presque identiques dans tous les cas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="Rappel @10 et @100 vs latence après construction de l'index" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="Rappel @10 et @100 vs latence après fusion en un seul segment" /><h4>Expérience 2 : Quantification BBQ</h4><p>Les gains de vitesse moyens entre la ligne de base et le candidat sont les suivants :</p><p>Accélération du temps d'indexation : <strong>1,</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,</strong>fois</p><p>Cela correspond à la répartition suivante des durées d'exécution</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="Temps d'indexation et de fusion pour la stratégie de fusion de base et la stratégie de fusion candidate" /><p>Par souci d'exhaustivité, les horaires exacts sont les suivants</p><p></p><p>Index</p><p></p><p>Fusionner</p><p></p><p>Ensemble de données</p><p>ligne de base</p><p>candidat</p><p>Développer</p><p>candidat</p><p>quora-E5-petit</p><p>70.71s</p><p>58.25s</p><p>59.38s</p><p>40.15s</p><p>wiki-cohere-v2</p><p>203.08s</p><p>142.27s</p><p>107.27s</p><p>85.68s</p><p>liste</p><p>110.35s</p><p>105.52s</p><p>323.66s</p><p>202.2s</p><p>wiki-cohere-v3</p><p>313.43s</p><p>190.63s</p><p>165.98s</p><p>159.95s</p><p>Pour les indices de segments multiples, le candidat est meilleur pour presque tous les ensembles de données, à l'exception de cohere v2 où la ligne de base est légèrement meilleure. Pour les indices de segment unique, les courbes de rappel sont presque identiques dans tous les cas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="Rappel @10 et @100 vs latence après construction de l'index" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="Rappel @10 et @100 vs latence après fusion en un seul segment" /><h3>Conclusion</h3><p>L'algorithme présenté dans ce blog sera disponible dans la prochaine version de Lucene 10.2, ainsi que dans la version d'Elasticsearch qui est basée sur cette dernière. Les utilisateurs pourront profiter de l'amélioration des performances de fusion et de la réduction du temps de construction de l'index dans ces nouvelles versions. Ce changement fait partie de nos efforts continus pour rendre Lucene et Elasticsearch rapides et efficaces pour la recherche vectorielle et hybride.</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[Base vectorielle]]></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[Bugs de concurrence dans Lucene : Comment corriger les échecs de concurrence optimiste]]></title>
    <description><![CDATA[Grâce à Fray, un cadre de test de concurrence déterministe du PASTA Lab de CMU, nous avons découvert un bogue Lucene délicat et l'avons éliminé.]]></description>
    <content:encoded><![CDATA[<p>Yep, un autre blog sur la correction des bogues. Mais cette fois-ci, c'est un héros de l'open source qui intervient et sauve la situation. </p><p>Le débogage des bogues de concurrence n'est pas une sinécure, mais nous allons nous y atteler. C'est là qu'intervient Fray, un cadre de test de concurrence déterministe du laboratoire PASTA de l'Université de Californie du Sud, qui transforme les défaillances en défaillances reproductibles de manière fiable. Grâce à la conception astucieuse du shadow lock de Fray et au contrôle précis des threads, nous avons traqué un bug Lucene délicat et l'avons finalement éliminé. Ce billet explore la façon dont les héros et les outils open-source rendent le débogage de la concurrence moins pénible et le monde du logiciel bien meilleur.</p><h2>Les bogues de simultanéité : le fléau des ingénieurs en informatique</h2><p>Les bogues liés à la concomitance sont les pires. Non seulement ils sont difficiles à réparer, mais le plus difficile est de les faire tomber en panne de manière fiable. Prenons l'exemple de l'échec de ce test, <a href="https://github.com/apache/lucene/issues/13127"><code>TestIDVersionPostingsFormat#testGlobalVersions</code></a>. Il génère plusieurs fils d'écriture et de mise à jour de documents, ce qui remet en cause le modèle de concurrence optimiste de Lucene. Ce test a mis en évidence une condition de course dans le contrôle optimiste de la concurrence. En d'autres termes, une opération de document peut faussement prétendre être la dernière d'une séquence d'opérations 😱. Cela signifie que, dans certaines conditions, une opération de mise à jour ou de suppression peut réussir alors qu'elle aurait dû échouer compte tenu des contraintes de concurrence optimistes.</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>Toutes nos excuses à ceux qui détestent les traces de pile Java. Remarque : supprimer ne signifie pas nécessairement "effacer". Il peut également indiquer une "mise à jour" du document, car les segments de Lucene sont en lecture seule.
</p><p>Apache Lucene gère chaque thread qui écrit des documents par l'intermédiaire de la classe <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java"><code>DocumentsWriter</code></a>. Cette classe crée ou réutilise des fils de discussion pour l'écriture de documents et chaque action d'écriture contrôle ses informations dans la classe <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java"><code>DocumentsWriterPerThread</code></a> (DWPT). En outre, le rédacteur garde la trace des documents supprimés dans le site <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a> (DWDQ). Ces structures conservent en mémoire toutes les actions de mutation des documents et se vident périodiquement, libérant ainsi les ressources en mémoire et transférant les structures sur le disque.</p><p></p><p>Afin d'éviter le <a href="https://en.wikipedia.org/wiki/Blocking_(computing)">blocage des threads</a> et d'assurer un débit élevé dans les systèmes concurrents, Apache Lucene tente de ne <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">se synchroniser</a> que dans les sections très critiques. Bien que cela puisse être une bonne chose dans la pratique, comme dans tous les systèmes concurrents, il y a des dragons.</p><h2>
Un faux espoir</h2><p>Mon enquête initiale m'a permis d'identifier quelques sections critiques qui n'étaient pas correctement synchronisées. Toutes les interactions avec un site <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a> donné sont contrôlées par le site <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java"><code>DocumentsWriter</code></a> qui l'entoure. Ainsi, même si les méthodes individuelles ne sont pas synchronisées de manière appropriée sur le site <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterDeleteQueue.java"><code>DocumentsWriterDeleteQueue</code></a>, leur accès au monde l'est (ou devrait l'être). (Ne nous attardons pas sur la confusion entre propriété et accès - il s'agit d'un projet de longue date rédigé par de nombreux contributeurs. Laissez-lui un peu de répit.)</p><p></p><p>Cependant, j'ai trouvé <a href="https://github.com/apache/lucene/blob/40060f8b7080d06a218518445a0a1dfc520c812a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterFlushControl.java#L572-L576">un endroit</a> qui n'était pas synchronisé lors d'un rinçage.</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>Ces actions ne sont pas synchronisées en une seule opération atomique. Cela signifie qu'entre la création de <code>newQueue</code> et l'appel de <code>getMaxSeqNo</code>, un autre code a pu être exécuté, incrémentant le numéro de séquence dans la classe <code>documentsWriter</code>. J'ai trouvé l'insecte !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc55033280b45913/6a1706b4dc55dec27fe00d30/3f2617a102d28735e755a7539b047c28beda41be-1600x453.jpg" alt="" /><p>
Mais, comme pour la plupart des bogues complexes, il n'a pas été facile de trouver la cause première. C'est alors qu'un héros est intervenu.</p><h2>Un héros dans la mêlée</h2><p>Voici notre héros : <a href="https://aoli.al/">Ao Li</a> et ses collègues du laboratoire PASTA. Je le laisserai expliquer comment ils ont sauvé la situation avec Fray.</p><p><a href="https://github.com/cmu-pasta/fray">Fray</a> est un cadre de test de concurrence déterministe développé par des chercheurs du <a href="https://pastalab.org/">PASTA Lab de</a> l'Université Carnegie Mellon. La motivation derrière la création de Fray provient d'un écart notable entre le monde universitaire et l'industrie : alors que les tests déterministes de concurrence ont été largement étudiés dans la recherche universitaire depuis plus de 20 ans, les praticiens continuent de s'appuyer sur les tests de stress - une méthode largement reconnue comme peu fiable et peu fiable - pour tester leurs programmes simultanés. C'est pourquoi nous avons voulu concevoir et mettre en œuvre un cadre de test de concurrence déterministe dont l'objectif principal est la généralité et l'applicabilité pratique.</p><p></p><h2>L'idée maîtresse</h2><p>Fray s'appuie sur un principe simple mais puissant : l'exécution séquentielle. Le modèle de concurrence de Java offre une <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.3">propriété</a>essentielle: si un programme est exempt de course aux données, toutes les exécutions sembleront cohérentes d'un point de vue séquentiel. Cela signifie que le comportement du programme peut être représenté comme une séquence d'instructions.</p><p>Fray exécute le programme cible de manière séquentielle : à chaque étape, il met en pause tous les threads sauf un, ce qui permet à Fray de contrôler précisément l'ordonnancement des threads. Les fils sont sélectionnés au hasard pour simuler la concurrence, mais les choix sont enregistrés en vue d'une relecture déterministe ultérieure. Pour optimiser l'exécution, Fray n'effectue des changements de contexte que lorsqu'un thread est sur le point d'exécuter une instruction de synchronisation telle que le verrouillage ou l'accès atomique/volatile. Une propriété intéressante de la liberté de la course aux données est que ce changement de contexte limité est suffisant pour explorer tous les comportements observables dus à l'entrelacement des threads<a href="https://arxiv.org/abs/2501.12618">(notre article</a> contient une esquisse de preuve).</p><p></p><h2>Le défi : contrôler l'ordonnancement des threads</h2><p>Bien que l'idée de base semble simple, la mise en œuvre de Fray a présenté des défis importants. Pour contrôler l'ordonnancement des threads, Fray doit gérer l'exécution de chaque thread d'application. À première vue, cela peut sembler simple : remplacer les primitives de concurrence par des implémentations personnalisées. Cependant, le contrôle de la concurrence dans la JVM est complexe et implique un mélange d'<a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html">instructions de bytecode</a>, de <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html">bibliothèques de haut niveau</a> et de <a href="https://github.com/openjdk/jdk/blob/b720517cb33c2119ec6ed85504bce321de748228/src/java.base/share/classes/java/lang/Object.java#L394">méthodes natives.</a></p><p></p><p>Il s'est avéré qu'il s'agissait d'un trou de lapin :</p><p></p><ul><li><p>Par exemple, chaque instruction <code>MONITORENTER</code> doit avoir un correspondant <code>MONITOREXIT</code> dans la même méthode. Si Fray remplace <code>MONITORENTER</code> par un appel de méthode à un stub/mock, il doit également remplacer <code>MONITOREXIT</code>.</p></li><li><p>Dans le code qui utilise <code>object.wait/notify</code>, si <code>MONITORENTER</code> est remplacé, le <code>object.wait</code> correspondant doit également être remplacé. Cette chaîne de remplacement s'étend jusqu'à <code>object.notify</code> et au-delà.</p></li><li><p>La JVM invoque certaines méthodes liées à la concurrence (par exemple, <code>object.notify</code> lorsqu'un thread se termine) dans le code natif. Le remplacement de ces opérations nécessiterait de modifier la JVM elle-même.</p></li><li><p>Les fonctions de la JVM, telles que les chargeurs de classes et les threads de collecte de déchets (GC), utilisent également des primitives de concurrence. La modification de ces primitives peut créer des incohérences avec ces fonctions de la JVM.</p></li><li><p>Le remplacement des primitives de concurrence dans le JDK entraîne souvent des plantages de la JVM pendant sa phase d'initialisation.</p></li></ul><p></p><p>Ces défis ont clairement montré qu'un remplacement complet des primitives de concurrence n'était pas réalisable.</p><h2>
Notre solution : la conception d'une serrure à l'abri des regards</h2><p>Pour relever ces défis, Fray utilise un nouveau mécanisme de verrouillage fictif pour orchestrer l'exécution des threads sans remplacer les primitives de concurrence. Les Shadow locks agissent comme des intermédiaires qui guident l'exécution des threads. Par exemple, avant d'acquérir un verrou, un thread d'application doit interagir avec le verrou fantôme correspondant. Le verrou fantôme détermine si le thread peut acquérir le verrou. Si le thread ne peut pas continuer, le verrou fantôme le bloque et permet à d'autres threads de s'exécuter, ce qui évite les blocages et permet une concurrence contrôlée. Cette conception permet à Fray de contrôler l'entrelacement des threads de manière transparente tout en préservant l'exactitude de la sémantique de la concurrence. Chaque primitive de concurrence est soigneusement modélisée dans le cadre du verrouillage fictif afin d'en garantir la solidité et l'exhaustivité. Plus de détails techniques peuvent être trouvés dans notre document.</p><p></p><p>En outre, cette conception est conçue pour être à l'épreuve du temps. En n'exigeant que l'instrumentation des verrous fantômes autour des primitives de concurrence, il garantit la compatibilité avec les versions plus récentes de la JVM. Cela est possible parce que les interfaces des primitives de concurrence dans la JVM sont relativement stables et sont restées inchangées depuis des années.</p><h2>
Test Fray</h2><p>Après la construction de Fray, l'étape suivante a été l'évaluation. Heureusement, de nombreuses applications, comme Apache Lucene, intègrent déjà des tests de concurrence. Ces tests de concurrence sont des tests JUnit classiques qui génèrent plusieurs threads, effectuent un certain travail, attendent (généralement) que ces threads se terminent, puis affirment une certaine propriété. La plupart du temps, ces tests réussissent parce qu'ils n'exercent qu'un seul entrelacement. Pire encore, certains tests n'échouent qu'occasionnellement dans l'environnement CI/CD, comme décrit précédemment, ce qui rend ces échecs extrêmement difficiles à déboguer. Lorsque nous avons exécuté les mêmes tests avec Fray, nous avons découvert de nombreux bogues. Notamment, Fray a redécouvert des bogues précédemment signalés qui n'avaient pas été corrigés en raison de l'absence d'une reproduction fiable, y compris l'objet de ce blog : <a href="https://github.com/apache/lucene/issues/13127"><code>TestIDVersionPostingsFormat.testGlobalVersions</code></a>. Heureusement, avec Fray, nous pouvons les rejouer de manière déterministe et fournir aux développeurs des informations détaillées, ce qui leur permet de reproduire et de résoudre le problème de manière fiable.</p><p></p><h2>Prochaines étapes pour Fray</h2><p>Nous sommes ravis d'entendre les développeurs d'Elastic dire que Fray a été utile pour déboguer les bogues de concurrence. Nous continuerons à travailler sur Fray afin de le rendre accessible à un plus grand nombre de développeurs.</p><p>Nos objectifs à court terme sont d'améliorer la capacité de Fray à rejouer le programme de manière déterministe, même en présence d'autres opérations non déterministes telles qu'un générateur de valeurs aléatoires ou l'utilisation de <code>object.hashcode</code>. Nous visons également à améliorer la facilité d'utilisation de Fray, en permettant aux développeurs d'analyser et de déboguer les tests de concurrence existants sans aucune intervention manuelle. Plus important encore, si vous rencontrez des difficultés pour déboguer ou tester les problèmes de concurrence dans votre programme, nous aimerions avoir de vos nouvelles. N'hésitez pas à créer un problème dans le <a href="https://github.com/cmu-pasta/fray">dépôt Github de Fray</a>.</p><p></p><h2>Il est temps de corriger le bogue de la concurrence</h2><p>Grâce à Ao Li et au laboratoire PASTA, nous disposons désormais d'une instance de ce test qui échoue de manière fiable ! Nous pouvons enfin réparer cette chose. Le principal problème réside dans la manière dont <a href="https://github.com/apache/lucene/blob/f2e7ae40af0b28b1d5f2edc31f8858229a8523f4/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThreadPool.java"><code>DocumentsWriterPerThreadPool</code></a> permet la réutilisation des fils et des ressources.</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>Ici, nous pouvons voir la création de chaque thread, qui fait référence à la file d'attente de suppression initiale de la génération 0.</p><p>Ensuite, l'avance dans la file d'attente se produira au moment de l'effacement, en voyant correctement les 7 actions précédentes dans la file d'attente.</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>Mais avant que tous les fils ne puissent finir de se vider, deux d'entre eux sont réutilisés pour un document supplémentaire :</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>Ces derniers incrémenteront alors le site <code>seqNo</code> au-delà du maximum supposé, qui a été calculé lors de la chasse d'eau comme étant 7. Notez le site supplémentaire <code>numDocsInRAM</code> pour les segments <code>_3</code> et <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 tient donc compte de manière incorrecte de la séquence d'actions des documents au cours d'une vidange et déclenche l'échec de ce test.</p><p>Comme toutes les bonnes corrections de bogues, la correction proprement dite se résume à <a href="https://github.com/apache/lucene/pull/13627/files">une dizaine de lignes de code</a>. Mais il a fallu plusieurs jours à deux ingénieurs pour trouver la solution :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71d78f22ce6ec215/6a1706b61949f7bcd0e7a94a/7915e5ba2feba0fb8e846da0af11bfdca470c467-1600x622.jpg" alt="" /><h2>Tous les héros ne portent pas de capes</h2><p>Oui, c'est un cliché, mais c'est vrai.</p><p></p><p>Le débogage de programmes simultanés est extrêmement important. Ces bogues de concurrence délicats prennent un temps fou à déboguer et à résoudre. Bien que les nouveaux langages comme Rust intègrent des mécanismes permettant d'éviter de telles conditions de course, la majorité des logiciels dans le monde sont déjà écrits, et écrits dans un autre langage que <a href="https://www.rust-lang.org/">Rust</a>. Java, même après toutes ces années, reste l'un des langages les plus utilisés. L'amélioration du débogage sur les langages basés sur la JVM permet d'améliorer le monde du génie logiciel. Et étant donné que certains pensent que le code sera écrit par de grands modèles de langage, peut-être que notre travail d'ingénieur consistera finalement à déboguer du mauvais code LLM au lieu de notre propre mauvais code. Mais quel que soit l'avenir du génie logiciel, le débogage simultané des programmes restera essentiel pour la maintenance et la création de logiciels.</p><p></p><p>Merci à Ao Li et à ses collègues du laboratoire PASTA de l'avoir rendu encore meilleur.</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[Lucene Wrapped 2024]]></title>
    <description><![CDATA[2024 a été une autre année importante pour Apache Lucene. Dans ce blog, nous examinerons les points essentiels.]]></description>
    <content:encoded><![CDATA[<p>Apache Lucene a connu une activité importante en 2024, avec de nombreuses versions, dont la première mise à jour majeure depuis trois ans, riche en améliorations et en nouvelles fonctionnalités. Examinons quelques-uns de ses principaux points forts.</p><h2>Lucene &amp; la communauté</h2><p>La force d'un projet dépend de la communauté qui le soutient. Malgré plus de 20 ans de développement, le projet Lucene reste dynamique et prospère grâce à ses contributeurs passionnés et actifs.</p><p>En 2024, le projet Lucene a fait l'objet de plus de 2 000 modifications de la part de 98 contributeurs uniques, et de près de 800 demandes de modification. Le nombre de contributeurs continue de croître, avec de nouveaux committers et membres du PMC qui rejoignent le projet et contribuent à son succès.</p><h2>Lucene 10</h2><p>2024 a vu la première version majeure depuis près de 3 ans - Lucene 10, avec plus de 2 000 commits de 185 contributeurs uniques. Si le modèle de développement suivi par Lucene permet d'apporter de nombreuses améliorations et fonctionnalités dans des versions mineures, une version majeure offre la possibilité d'apporter des fonctionnalités plus importantes et des modernisations. Par exemple, Lucene 10 nécessite au minimum Java 21. L'augmentation de la version minimale de Java permet à Lucene de continuer à bénéficier des améliorations apportées par la version moderne de Java.</p><p>L'objectif principal de Lucene 10 est de mieux utiliser le matériel sur lequel il fonctionne. Jetons un coup d'œil rapide sur les principaux faits marquants :</p><ul><li><p><strong>Plus de parallélisme</strong> dans les recherches - alors que l'exécution des recherches est déjà parallélisée entre les segments, nous allons maintenant plus loin, en parallélisant à l'intérieur des segments. Cela permet de dissocier la représentation sur disque des performances d'exécution, ce qui permet même à des segments uniques de bénéficier du nombre de cœurs des systèmes modernes.</p></li><li><p><strong>Meilleur parallélisme des E/S</strong> - le modèle d'E/S synchrone simple utilisé par Lucene a été amélioré grâce à une étape de préfixation. Cela permet d'informer le système d'exploitation qu'une région d'un fichier d'index sera nécessaire dans un avenir très proche, sans pour autant bloquer le thread appelant.</p></li><li><p><strong>Meilleure efficacité de l'unité centrale et du stockage grâce à l'indexation épar</strong> se - Lucene 10 introduit la prise en charge de l'indexation éparse, parfois appelée indexation par clé primaire ou indexation par zone dans d'autres magasins de données.</p></li></ul><p>Pour plus d'informations sur Lucene 10, consultez l'<a href="https://www.elastic.co/search-labs/blog/apache-lucene-10-release-highlights">article</a> dédié à Lucene 10.</p><h2>Recherche et innovation dans le domaine de Lucene</h2><p>En 2024, Lucene a connu un essor de la recherche et de l'innovation, en particulier dans les domaines de l'intégration de l'apprentissage automatique, de la recherche vectorielle et de l'optimisation pour les ensembles de données à grande échelle, avec des références à 10 <a href="https://scholar.google.com/scholar?as_ylo=2024&amp;q=lucene&amp;hl=en&amp;as_sdt=0,5">articles et publications de recherche</a> distincts. Voici quelques-uns des principaux domaines de recherche et développements :</p><ul><li><p><strong>Recherche vectorielle et prise en charge de l'intégration</strong> - Lucene offre une solution puissante et évolutive pour la recherche vectorielle, permettant la recherche sémantique à grande échelle. En tirant parti de la solide infrastructure d'indexation et de recherche de Lucene, les utilisateurs peuvent combiner le meilleur de la recherche textuelle traditionnelle avec les capacités avancées de la recherche vectorielle moderne, ce qui fait de Lucene une solution complète pour un large éventail de tâches de recherche et d'extraction d'informations.</p></li><li><p><strong>Modèles de recherche hybrides</strong> - La recherche s'est également penchée sur les techniques de recherche hybrides, Lucene combinant la recherche traditionnelle par mot-clé et la recherche moderne par vecteur. En fusionnant des index basés sur des termes avec des représentations vectorielles denses, Lucene peut fournir des résultats de recherche plus précis et plus pertinents sur le plan contextuel, comblant ainsi le fossé entre la précision des moteurs de recherche traditionnels et la flexibilité de la recherche sémantique.</p></li></ul><p>Les efforts de recherche en cours en 2024 démontrent la capacité d'adaptation de Lucene à l'évolution des besoins des technologies de recherche modernes, en particulier dans le contexte de l'IA, de la recherche sémantique et des applications de big data. Le projet continue de se développer en tant que plateforme puissante, flexible et efficace pour les cas d'utilisation de la recherche traditionnelle et de pointe.</p><h2>2024 versions de Lucene</h2><p>Bien qu'il ne s'agisse pas d'un reflet exact, le simple volume des publications met en évidence le dévouement et l'énergie constants de la communauté. Ces mises à jour comprennent des améliorations majeures des performances et de l'efficacité de la recherche vectorielle, la prise en charge de madvise, des optimisations pour le décodage des listes d'écritures, des améliorations supplémentaires de la vitesse grâce à SIMD, et bien d'autres choses encore.</p><p>Voici la liste complète des sorties :</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-09-28)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-8114-available">8.11.4</a> (2024-09-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-02-20)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-8113-available">8.11.3</a> (2024-02-08)</p></li><li><p><a href="https://lucene.apache.org/core/corenews.html#apache-lucenetm-992-available">9.9.2</a> (2024-01-29)</p></li></ul><p>Vous pouvez trouver plus d'informations et les notes de version sur la page <a href="https://projects.apache.org/project.html?lucene-core">Lucene Core</a>. En outre, il existe des versions équivalentes de <a href="https://projects.apache.org/project.html?lucene-pylucene">PyLucene</a>.</p><h2>Conclusion</h2><p>Alors que Lucene arrive à maturité, il continue de prospérer grâce à sa communauté dévouée et dynamique. Comme nous l'avons vu, 2024 a été une année incroyablement productive, et nous nous tournons maintenant vers les développements passionnants que 2025 apportera.</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[Aventures de bogues Lucene : Correction d'une exception d'index corrompu]]></title>
    <description><![CDATA[Parfois, il faut des jours pour écrire une seule ligne de code. Ici, nous avons un aperçu de la douleur et du débogage d'un ingénieur pendant plusieurs jours pour corriger une corruption potentielle de l'index Apache Lucene.]]></description>
    <content:encoded><![CDATA[<h2>Soyez prêts : </h2><p>Ce blog est différent des autres. Il ne s'agit pas d'une explication d'une nouvelle fonctionnalité ou d'un tutoriel. Il s'agit d'une simple ligne de code qui a pris trois jours à écrire. Nous allons corriger une corruption potentielle de l'index Apache Lucene. J'espère que vous en tirerez quelques enseignements :</p><ul><li><p>Tous les tests défectueux sont reproductibles, avec suffisamment de temps et les bons outils.</p></li><li><p>Pour que les systèmes soient robustes, il faut qu'il y ait plusieurs niveaux de tests. Cependant, les niveaux de tests plus élevés deviennent de plus en plus difficiles à déboguer et à reproduire.</p></li><li><p>Le sommeil est un excellent débogueur</p></li></ul><h2>Comment les tests d'Elasticsearch</h2><p>Chez Elastic, nous avons une pléthore de tests qui s'exécutent sur la base de code Elasticsearch. Certains sont des tests fonctionnels simples et ciblés, d'autres sont des tests d'intégration "happy path" sur un seul nœud, et d'autres encore tentent de casser le cluster pour s'assurer que tout se comporte correctement dans un scénario de défaillance. Lorsqu'un test échoue continuellement, un ingénieur ou un outil d'automatisation crée un problème sur github et le signale à une équipe particulière pour qu'elle l'étudie. Ce <a href="https://github.com/elastic/elasticsearch/issues/105122">bogue particulier</a> a été découvert lors d'un test du dernier type. Ces tests sont délicats et ne peuvent parfois être répétés qu'après de nombreux essais.</p><h2>Qu'est-ce que ce test teste réellement ?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58aab468587a7d35/6a17dd8e3e03d74b8d4f2b5f/63268f3b5714ebf9df8070ec2e3c4f0486ed822e-1600x1088.jpg" alt="Numéro github : https://github.com/elastic/elasticsearch/issues/105122" /><p>Ce test particulier est intéressant. Il créera un mappage particulier et l'appliquera à un groupe primaire. Ensuite, lors de la création d'une réplique. La principale différence est que lorsque la réplique tente d'analyser le document, le test injecte une exception, provoquant ainsi l'échec de la récupération d'une manière surprenante (mais attendue).</p><p></p><p>Tout fonctionnait comme prévu, à un détail près. Lors du nettoyage du test, nous avons validé la cohérence, et c'est là que ce test a rencontré un problème.</p><p>
Ce test n'a pas donné les résultats escomptés. Lors du contrôle de cohérence, nous vérifions que tous les fichiers de segments Lucene répliqués et primaires sont cohérents. C'est-à-dire non corrompu et entièrement répliqué. Avoir des données partielles ou corrompues est bien pire que d'avoir une défaillance totale. Voici l'effrayante et abrégée trace de pile de l'échec.</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>D'une manière ou d'une autre, lors de l'échec de la réplication forcée, le shard répliqué a fini par être corrompu ! Permettez-moi d'expliquer la partie essentielle de l'erreur en termes simples.</p><p></p><p>Lucene est une architecture basée sur les segments, ce qui signifie que chaque segment connaît et gère ses propres fichiers en lecture seule. Ce segment particulier était validé par ses <a href="https://github.com/apache/lucene/blob/add9c09c84ee66d4522c566c9f679035a0dfec13/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java">SegmentCoreReaders</a> pour s'assurer que tout était en ordre. Chaque lecteur central contient des métadonnées qui indiquent quels types de champs et de fichiers existent pour un segment donné. Cependant, lors de la validation du <a href="https://github.com/apache/lucene/blob/add9c09c84ee66d4522c566c9f679035a0dfec13/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90PointsFormat.java">Lucene90PointsFormat</a>, certains fichiers attendus manquaient. Avec les segments du fichier <code>_0.cfs</code>, nous attendons un fichier en format point appelé <code>kdi</code>. <code>cfs</code> représente le système de fichiers composés "" dans lequel Lucene combinera parfois tous les types de champs et tous les petits fichiers en un seul fichier plus grand pour une réplication et une utilisation des ressources plus efficaces. En fait, les trois extensions de fichiers de points : <code>kdd</code>, <code>kdi</code>, et <code>kdm</code> manquaient. Comment pouvons-nous arriver au point où un segment Lucene s'attend à trouver un fichier de points mais où il n'y en a pas ! Cela ressemble à un bug de corruption effrayant !</p><p></p><h2>La première étape de la correction d'un bogue est de le reproduire.</h2><p></p><p>La reproduction de l'échec de ce bogue particulier a été extrêmement douloureuse. Bien que nous tirions parti des <a href="https://en.wikipedia.org/wiki/Random_testing">tests de valeurs aléatoires</a> dans Elasticsearch, nous veillons à fournir à chaque défaillance une graine aléatoire (espérons-le) reproductible afin de garantir que toutes les défaillances peuvent être étudiées. Cela fonctionne très bien pour toutes les défaillances, sauf celles qui sont dues à une <a href="https://en.wikipedia.org/wiki/Race_condition">condition de course</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>Peu importe le nombre de fois que j'ai essayé, la graine en question n'a jamais répété l'échec localement. Mais il existe des moyens d'exercer les tests et de tendre vers un échec plus reproductible.</p><p></p><p>Notre suite de tests particulière permet d'exécuter un test donné plus d'une fois dans la même commande via le paramètre <code>-Dtests.iters</code>. Mais ce n'était pas suffisant, je devais m'assurer que les fils d'exécution changeaient et augmentaient ainsi la probabilité que cette condition de course se produise. Un autre problème était que le test prenait tellement de temps à s'exécuter que le programme d'exécution du test se mettait en veilleuse. Finalement, j'ai utilisé le cauchemar bash suivant pour exécuter le test de manière répétée :</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>Le <a href="https://github.com/ColinIanKing/stress-ng">stress</a> fait son apparition. Cela vous permet de lancer rapidement un processus qui mangera les cœurs de l'unité centrale pour le déjeuner. L'envoi aléatoire de spam stress-ng tout en exécutant de nombreuses itérations du test défaillant m'a finalement permis de reproduire l'échec. Un pas de plus. Pour stresser le système, il suffit d'ouvrir une autre fenêtre de terminal et d'exécuter :</p>stress-ng --cpu 16<h2>
Révéler le bogue</h2><p>

Maintenant que l'échec du test révélant le bogue est en grande partie reproductible, il est temps d'essayer d'en trouver la cause. Ce qui rend ce test particulier étrange, c'est que Lucene le lance parce qu'il attend des valeurs de point, mais qu'aucune n'est ajoutée directement par le test. Uniquement les valeurs textuelles. Cela m'a poussé à examiner les changements récents apportés à nos champs de <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html">contrôle optimiste de la concurrence</a>: <code>_seq_no</code> et <code>_primary_term</code>. Ces deux éléments sont indexés en tant que points et existent dans chaque document Elasticsearch.</p><p></p><p>En effet, un <a href="https://github.com/elastic/elasticsearch/pull/105036">commit</a> a modifié notre mappeur <code>_seq_no</code>! OUI ! Cela doit être la cause ! Mais mon excitation a été de courte durée. Cela ne fait que modifier l'ordre dans lequel les champs sont ajoutés au document. Avant cette modification, les champs <code>_seq_no</code> étaient ajoutés en dernier au document. Ensuite, ils ont été ajoutés en premier. Il est impossible que l'ordre d'ajout des champs dans un document Lucene soit à l'origine de cet échec...</p><p></p><p>Oui, la modification de l'ordre d'ajout des champs est à l'origine de l'échec. Ceci était surprenant et s'avère être un bogue dans Lucene lui-même ! Le fait de modifier l'ordre dans lequel les champs sont analysés ne devrait pas modifier le comportement de l'analyse d'un document.</p><p></p><h2>Le bogue dans Lucene</h2><p>En effet, le bogue dans Lucene se concentre sur les conditions suivantes :</p><ul><li><p>Indexation d'un champ de valeurs de points (par ex. <code>_seq_no</code>)</p></li><li><p>Essai d'indexation d'un champ de texte lancé lors de l'analyse</p></li><li><p>Dans cet état étrange, nous ouvrons un <a href="https://blog.mikemccandless.com/2011/06/lucenes-near-real-time-search-is-fast.html">lecteur en temps quasi réel</a> de l'auteur qui fait l'expérience de l'exception d'analyse de l'index du texte.</p></li></ul><p>Mais j'ai eu beau essayer, je n'ai pas réussi à le reproduire entièrement. J'ai directement ajouté des points de pause pour le débogage dans la base de code Lucene. J'ai tenté d'ouvrir des lecteurs de manière aléatoire pendant le parcours d'exception. J'ai même imprimé des mégaoctets et des mégaoctets de journaux en essayant de trouver le chemin exact où cette défaillance s'est produite. Je n'ai pas pu le faire. J'ai passé une journée entière à me battre et à perdre.</p><p></p><p>Puis j'ai dormi.</p><p></p><p>Le lendemain, j'ai relu la trace de pile originale et j'ai découvert la ligne suivante :</p><p>
</p>    at org.apache.lucene.index.SoftDeletesRetentionMergePolicy.keepFullyDeletedSegment(SoftDeletesRetentionMergePolicy.java:82)<p>Dans toutes mes tentatives de recréation, je n'ai jamais défini spécifiquement la politique de fusion de la rétention. La <a href="https://github.com/apache/lucene/blob/5f0fa2b291ff9e7d878642f025a70c15b788a470/lucene/core/src/java/org/apache/lucene/index/SoftDeletesRetentionMergePolicy.java">politique SoftDeletesRetentionMergePolicy</a> est utilisée par Elasticsearch pour répliquer avec précision les suppressions dans les répliques et garantir que tous nos contrôles de concurrence sont en charge du moment où les documents sont effectivement supprimés. Dans le cas contraire, Lucene a le contrôle total et les supprimera à chaque fusion.</p><p></p><p>Une fois que j'ai ajouté cette politique et que j'ai répliqué les étapes les plus élémentaires mentionnées ci-dessus, la panne s'est immédiatement répliquée.</p><p>
Je n'ai jamais été aussi heureux d'ouvrir un <a href="https://github.com/apache/lucene/issues/13353">bogue dans Lucene</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89d37c3454ec298b/6a17dd90ec0f8993c75a6511/c7d9ce3de5278923a8454097e2bdf487c861168a-1600x920.jpg" alt="Question Github https://github.com/apache/lucene/issues/13353" /><p>
Alors qu'il se présentait comme une condition de course dans Elasticsearch, il était simple d'écrire un test échouant de manière répétée dans Lucene une fois que toutes les conditions étaient remplies.</p><p></p><p>En fin de compte, comme tous les bons bogues, il a été corrigé avec une seule ligne de code. Plusieurs jours de travail pour une seule ligne de code.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab62db3402b27f7/6a17dd92dbb4ff23e7fb55ad/2c10a02eb41da181b1dceee47186c076c9b14a90-1600x539.jpg" alt="Correction d'une ligne de code" /><p>Mais cela en valait la peine.</p><h2>
Pas la fin</h2><p>J'espère que vous avez apprécié cette aventure avec moi ! Écrire des logiciels, en particulier des logiciels aussi répandus et complexes qu'Elasticsearch et Apache Lucene, est gratifiant. Cependant, il est parfois exceptionnellement frustrant. J'aime et je déteste à la fois les logiciels. La correction des bogues n'est jamais terminée !</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[Elasticsearch et OpenSearch : comparatif de performance pour la recherche vectorielle.]]></title>
    <description><![CDATA[Elasticsearch est d’emblée 2 à 12 fois plus rapide qu’OpenSearch pour la recherche vectorielle]]></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 est jusqu’à 12 fois plus rapide</a> - Chez Elastic, suite aux nombreuses requêtes de notre communauté concernant les écarts de performance entre Elasticsearch et OpenSearch, notamment dans la recherche sémantique et vectorielle, nous avons mené cette série de tests. Le but est d’offrir une comparaison claire et axée sur les données, sans ambiguïté, avec des faits simples pour informer nos utilisateurs. Les résultats montrent qu'<strong>Elasticsearch est jusqu'à 12 fois plus rapide</strong> qu'OpenSearch pour la recherche vectorielle et nécessite donc moins de ressources informatiques. Cela reflète la volonté d’Elastic de se concentrer sur la consolidation de Lucene comme la meilleure base de données vectorielles pour les cas d’utilisation de recherche et de récupération.</p><p>La recherche vectorielle est en train de révolutionner la manière dont nous effectuons les recherches par similarité, en particulier dans des domaines comme l’IA et le Machine Learning. Face à l’adoption de plus en plus répandue des modèles d’intégration de vecteurs, la capacité de rechercher efficacement à travers des millions de vecteurs de haute dimension devient cruciale.</p><p>Elastic et OpenSearch ont choisi des approches très distinctes pour l’exécution des bases de données vectorielles. Pour que ses produits soient le meilleur choix pour les applications de recherche vectorielle, Elastic a investi massivement dans l’optimisation d’Apache Lucene avec Elasticsearch. En revanche, OpenSearch a élargi son champ d’action en intégrant d’autres implémentations de recherche vectorielle et en explorant au-delà de la portée de Lucene. En nous concentrant de manière stratégique sur Lucene, nous pouvons proposer un soutien très intégré dans notre version d’Elasticsearch. Il en résulte un ensemble de fonctionnalités amélioré, où chaque composant complète et amplifie les capacités de l’autre.</p><p>Ce blog offre une comparaison détaillée entre Elasticsearch 8.14 et OpenSearch 2.14, en se basant sur différentes configurations et différents moteurs vectoriels. Dans cette analyse des performances, Elasticsearch s'est avéré être la plateforme supérieure pour les opérations de recherche vectorielle, et les <a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">fonctionnalités</a> à venir creuseront encore davantage l'<a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">écart</a>. Comparé à OpenSearch, il a excellé dans tous les domaines de référence — <strong>offrant des performances 2 à 12 fois plus rapides en moyenne</strong>. Cela s’est produit dans des scénarios utilisant des quantités et des dimensions de vecteurs variables, notamment <code>so_vector</code> (2 millions de vecteurs, 768D), <code>openai_vector</code> (2,5 millions de vecteurs, 1536D) et <code>dense_vector</code> (10 millions de vecteurs, 96D), tous disponibles dans <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">ce référentiel</a> aux côtés des scripts Terraform pour provisionner toute l’infrastructure requise sur Google Cloud et les manifestes Kubernetes pour exécuter les tests.</p><p>Les résultats de ce blog s’ajoutent à ceux d'une étude <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">validée par une tierce partie et publiée précédemment</a>. L’étude avait révélé qu’Elasticsearch est plus rapide de 40%–140% qu’OpenSearch en ce qui concerne les opérations d’analyse de recherche les plus courantes : requêtes textuelles, tri, plages, histogramme de dates et filtrage par termes. Maintenant, nous pouvons ajouter un autre facteur de différenciation : la recherche vectorielle. Maintenant, nous pouvons ajouter un autre facteur de différenciation : la recherche vectorielle.</p><h2>Jusqu’à 12 fois plus rapide d’emblée</h2><p>Nos tests d'évaluation ciblés sur les quatre ensembles de données vectorielles impliquaient à la fois des recherches KNN approximatives et KNN exactes, en tenant compte de différentes tailles, dimensions et configurations, totalisant <code>40.189.820</code> demandes de rechercher non mises en cache. Les résultats : <strong>Elasticsearch est jusqu'à 12 fois plus rapide</strong> qu'OpenSearch pour la recherche vectorielle et nécessite donc moins de ressources de calcul.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="p90 moyen" /><p>Figure 1 : Tâches groupées pour ANN et KNN exact dans différentes combinaisons dans Elasticsearch et OpenSearch.</p><p>Les groupes tels que <code>knn-10-100</code> signifient une rechercher KNN avec  et . Dans la recherche vectorielle HNSW,  détermine le nombre de voisins les plus proches à récupérer pour un vecteur de requête. Il définit le nombre de vecteurs similaires qui seront renvoyés en résultat.  définit le nombre de vecteurs candidats à récupérer à chaque segment. Un plus grand nombre de candidats peut renforcer la précision, au prix de ressources de calcul plus importantes.</p><p>Après avoir testé différentes techniques de quantification et tiré parti des optimisations spécifiques à chaque moteur, nous avons obtenu des résultats détaillés pour chaque piste, tâche et moteur vectoriel, que vous trouverez ci-dessous.</p><h2>KNN exact et KNN approximatif</h2><p>Pour des ensembles de données et des cas d’utilisation variés, l’approche appropriée pour la recherche vectorielle variera. Dans ce blog, toutes les tâches indiquées comme <code>knn-*</code> comme <code>knn-10-100</code> utilisent <strong>Approximate KNN</strong> et <code>script-score-*</code> font référence à <strong>Exact KNN</strong>, mais quelle est la différence entre elles et pourquoi sont-elles importantes ?</p><p>Grâce à sa plus grande évolutivité, la méthode de l’algorithme des plus proches voisins approximatifs (ANN) est la solution préférée lorsque vous traitez des ensembles de données plus substantiels. La méthode des plus proches voisins exacts (KNN) est idéale pour les jeux de données plus modestes qui nécessitent parfois un processus de filtrage.</p><p>La méthode du KNN exact emploie une approche par la force brute, qui consiste à calculer la distance entre un vecteur et chaque autre vecteur du jeu de données. Elle classe ensuite ces distances pour trouver les  voisins les plus proches. Même si cette méthode assure une correspondance exacte, elle fait face à des problèmes d’évolutivité pour les jeux de données volumineux et à haute dimension. Toutefois, il y a de nombreuses situations dans lesquelles le recours au KNN exact est requis :</p><ul><li><p><strong>Réévaluation</strong>: dans les cas qui impliquent des recherches lexicales ou sémantiques suivies d’une réévaluation basée sur les vecteurs, le KNN exact est indispensable. Dans un moteur de recherche de produits, par exemple, on peut d’abord filtrer les résultats de recherche initiaux à l’aide de requêtes textuelles (mots-clés, catégories), puis utiliser les vecteurs associés aux éléments filtrés pour une évaluation de similarité plus précise.</p></li><li><p><strong>Personnalisation</strong>: dans le cas d'un grand nombre d'utilisateurs, dont chacun est représenté par un nombre relativement peu élevé (environ 1 million) de vecteurs distincts, le classement de l'index en fonction des métadonnées de l'utilisateur (p. ex., user_id) et le score par force brute avec des vecteurs deviennent efficaces. Cette approche permet de fournir des recommandations personnalisées ou un contenu personnalisé, en fonction de comparaisons de vecteurs précises qui sont spécifiquement conçues pour les préférences de chaque utilisateur.</p></li></ul><p>Le KNN exact permet d’assurer un classement final et des recommandations précis et adaptés aux préférences de l’utilisateur, car ils sont fondés sur la similarité vectorielle.</p><p>D’un autre côté, le KNN approximatif (ANN) utilise des méthodes qui rendent la recherche de données plus rapide et plus efficace que le KNN exact, en particulier dans les jeux de données volumineux et à haute dimension. Contrairement à une approche par force brute qui mesure la distance exacte entre une requête et tous les points (ce qui pose des défis de calcul et de mise à l’échelle), l’ANN recourt à des techniques spécifiques afin de restructurer efficacement les index et les dimensions des vecteurs interrogeables dans l’ensemble de données. Même si cela peut entraîner une légère inexactitude, cela accélère considérablement le processus de recherche, ce qui en fait une solution de rechange efficace pour les ensembles de données volumineux.</p><p>Dans ce blog, toutes les tâches indiquées comme <code>knn-*</code> comme <code>knn-10-100</code> utilisent <strong>KNN approximatif</strong> et <code>script-score-*</code> font référence à <strong>KNN exact</strong>.</p><h2>Méthodologie de test</h2><p>Même si l'API des opérations de recherche BM25 est similaire pour Elasticsearch et OpenSearch (puisque ce dernier est une copie du premier), cela ne s’applique pas à la recherche vectorielle, laquelle a été introduite après la copie. OpenSearch a adopté une approche différente d’Elasticsearch en ce qui concerne les algorithmes, en introduisant deux autres moteurs - <code>nmslib</code> et <code>faiss</code> <code>lucene</code>plus , chacun avec ses configurations et limitations spécifiques (par exemple, <code>nmslib</code> dans OpenSearch n’autorise pas les filtres, une fonctionnalité essentielle pour de nombreux cas d’utilisation).</p><p>Les trois moteurs emploient l’algorithme HNSW, lequel est très performant pour la recherche approximative des plus proches voisins et particulièrement puissant en présence de données de grande dimension. Il est important de noter que <code>faiss</code> prend également en charge un deuxième algorithme, <code>ivf</code>, mais comme il nécessite une formation préalable sur l'ensemble de données, nous allons nous concentrer uniquement sur HNSW. Le concept principal de HNSW est d’organiser les données en couches de graphes connectés, où chaque couche reflète une granularité différente de l’ensemble de données. La recherche s’amorce à la couche supérieure, qui propose la vue la plus grossière. Elle progresse ensuite vers des couches de plus en plus fines jusqu’au niveau de base.</p><p>Les deux moteurs de recherche ont fait l’objet de tests dans un cadre contrôlé, sous des conditions strictement identiques, afin de garantir un terrain d'essai équitable. La méthode utilisée est comparable à <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">la comparaison de performance publiée précédemment</a>, avec des groupes de nœuds dédiés pour Elasticsearch, OpenSearch et Rally. Le <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">script terraform</a> est disponible (ainsi que toutes les sources) pour provisionner un cluster Kubernetes avec :</p><ul><li><p>1 pool de nœuds pour Elasticsearch avec 3 machines <code>e2-standard-32</code> (128 Go de RAM et 32 processeurs)</p></li><li><p>1 pool de nœuds pour OpenSearch avec 3 machines <code>e2-standard-32</code> (128 Go de RAM et 32 processeurs)</p></li><li><p>1 pool de nœuds pour Rally avec 2 machines <code>t2a-standard-16</code> (64 Go de RAM et 16 processeurs)</p></li></ul><p>Chaque test (ou piste) a été exécuté à 10 reprises pour chaque configuration, qui incluait différents moteurs, différentes configurations et différents types de vecteurs. Chaque piste se compose de tâches qui sont répétées de 1000 à 10 000 fois, en fonction de la piste. En cas d'échec d’une tâche dans une piste (en raison, par exemple, d’une temporisation de réseau), toutes les tâches sont abandonnées. C’est pourquoi tous les résultats sont issus de pistes qui ont démarré et se sont terminées sans problème. L’ensemble des résultats de test sont validés sur le plan statistique, ce qui assure que les améliorations ne sont pas le résultat d’une coïncidence.</p><h2>Résultats détaillés</h2><p>Pourquoi comparer en utilisant le 99e percentile et non la latence moyenne ? Prenons un exemple hypothétique du prix moyen des logements dans un quartier donné. Le prix moyen peut laisser penser que la zone est onéreuse, mais en y regardant de plus près, il peut se révéler que la plupart des logements sont évalués à un prix beaucoup plus bas, et que seules quelques propriétés de luxe font augmenter la moyenne. Le prix moyen peut ne pas refléter avec précision la gamme complète des valeurs des maisons dans la région, comme l’illustre cet exemple. C’est comparable à l’étude des temps de réponse, où le chiffre moyen peut dissimuler des enjeux critiques.</p><h4>Tâches</h4><ul><li><p>KNN approximatif avec k :10 n :50</p></li><li><p>KNN approximatif avec k : 10 n : 100</p></li><li><p>KNN approximatif avec k : 100 n : 1 000</p></li><li><p>KNN approximatif avec k :10, n :50 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k : 10, n : 100 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k :100, n :1000 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k:10 n:100 en conjonction avec l'indexation</p></li><li><p>KNN exact (score du script)</p></li></ul><h4>Moteurs vectoriels</h4><ul><li><p><code>lucene</code> dans Elasticsearch et OpenSearch, tous deux en version 9.10</p></li><li><p><code>faiss</code> dans OpenSearch</p></li><li><p><code>nmslib</code> dans OpenSearch</p></li></ul><h4>Types de vecteurs</h4><ul><li><p><code>hnsw</code> dans Elasticsearch et OpenSearch</p></li><li><p><code>int8_hnsw</code> dans Elasticsearch (HNSW avec quantification automatique 8 bits–: <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">lien</a>)</p></li><li><p><code>sq_fp16 hnsw </code>dans OpenSearch (HNSW avec quantification automatique 16 bits : <a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">lien</a>)</p></li></ul><h4>Recherche prête à l'emploi et recherche de segments simultanés</h4><p>Lucene est une bibliothèque de moteur de recherche de texte hautement performante, rédigée en Java. Elle constitue la pierre angulaire de nombreuses plateformes de recherche, telles qu’Elasticsearch, OpenSearch et Solr. Le cœur du système de Lucene repose sur l’organisation des données en segments. Ces segments sont des index autonomes qui permettent d’exécuter les recherches plus efficacement. Donc, si vous effectuez une recherche sur un moteur basé sur Lucene, cette recherche sera exécutée dans ces segments, de manière séquentielle ou en parallèle.</p><p>OpenSearch a introduit la recherche de segments simultanés en tant qu’indicateur facultatif et ne l’utilise pas par défaut, vous devez l’activer à l’aide d’un paramètre d’index spécial <code>index.search.concurrent_segment_search.enabled</code> comme détaillé <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">ici</a>, avec certaines <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">limitations</a>.</p><p>En revanche, Elasticsearch effectue des recherches sur les segments en parallèle <a href="https://github.com/elastic/elasticsearch/pull/101230">par défaut</a>. C’est pourquoi les comparaisons que nous effectuons dans cet article de blog tiendront compte, outre des différents moteurs et types de vecteurs, des différentes configurations également :</p><ul><li><p>Elasticsearch ootb : Elasticsearch prêt à l'emploi, avec recherche de segments simultanés ;</p></li><li><p>OpenSearch ootb : sans activation de la recherche par segments simultanés ;</p></li><li><p>OpenSearch css : avec la recherche par segments simultanés activée</p></li></ul><p>À présent, examinons en détail les résultats pour chaque ensemble de données vectorielles qui a été testé :</p><h2>2,5 millions de vecteurs, 1536 dimensions (openai_vector)</h2><p>En commençant par la piste la plus simple, mais aussi la plus grande en termes de dimensions, <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> - qui utilise l'<a href="https://huggingface.co/datasets/BeIR/nq">ensemble de données NQ</a> enrichi avec des intégrations générées à l'aide du <a href="https://openai.com/blog/new-and-improved-embedding-model">modèle text-embedding-ada-002</a> d'OpenAI. Il s'agit du plus simple, du fait qu’il ne teste que le KNN approximatif et qu'il ne comporte que 5 tâches. Les tests sont effectués en mode autonome (sans indexation) de même qu’en conjonction avec l’indexation, et avec un seul client ou 8 clients simultanés.</p><h3>Tâches</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong> : recherche sur 2,5 millions de vecteurs avec 8 clients simultanément, k : 10 et n :100</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong> : recherche sur 2,5 millions de vecteurs avec 8 clients simultanément, k : 100 et n : 1000</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>: recherche sur 2,5 millions de vecteurs avec un seul client, k : 10 et n : 100</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>: recherche sur 2,5 millions de vecteurs avec un seul client, k : 100 et n : 1000</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>: indexation sur 2,5 millions de vecteurs tout en recherchant 100 000 documents supplémentaires, k : 10 et n : 100</p></li></ul><p>Les performances moyennes de p99 sont décrites ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="tableau openai_vector" /><p>Nous avons constaté ici qu’Elasticsearch est <strong>3 à 8 fois plus rapide</strong> qu’OpenSearch lors d’une recherche vectorielle effectuée en parallèle de l'indexation (c’est-à-dire. lecture+écriture) avec :10 et :100 et <strong>2 à 3 fois plus rapide</strong> sans indexation pour les mêmes k et n. Pour :100 et :1000 (<em>standalone-rechercher-knn-100-1000-single-client</em> et <em>standalone-rechercher-knn-100-1000-multiple-clients</em> Elasticsearch est <strong>2 à 7 fois</strong> plus rapide qu'OpenSearch, en moyenne.</p><p>Les résultats détaillés montrent les cas exacts et les moteurs vectoriels comparés :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>Rappel</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>10 millions de vecteurs, 96 dimensions (dense_vector)</h2><p><a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a> avec 10 millions de vecteurs et 96 dimensions. Il est basé sur le jeu de données d'images <a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a>. Le jeu de données est créé à partir des 10 premiers millions de vecteurs du fichier « données d’échantillon » appelé <code>learn.350M.fbin</code>. Les opérations de recherche font appel à des vecteurs qui proviennent du fichier de « requêtes de données » query.<code>public.10K.fbin</code>.</p><p>Après une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">fusion forcée</a>, qui est habituellement effectuée sur des index en lecture seule, Elasticsearch et OpenSearch sont très performants sur cet ensemble de données. Cette opération est similaire à une défragmentation, ce qui permet d’avoir une seule « table » pour la recherche.</p><h3>Tâches</h3><p>Chaque tâche fait l'objet d’un préchauffage de 100 requêtes, et la mesure est ensuite effectuée sur les 1000 requêtes suivantes</p><ul><li><p><strong>knn-search-10-100</strong>: recherche sur 10 millions de vecteurs, k : 10 et n : 100</p></li><li><p><strong>knn-search-100-1000</strong>: recherche sur 10 millions de vecteurs, k : 100 et n : 1000</p></li><li><p><strong>knn-search-10-100-force-merge</strong>: recherche sur 10 millions de vecteurs après une fusion forcée, k : 10 et n : 100</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>: recherche sur 10 millions de vecteurs après une fusion forcée, k : 100 et n :1000</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>: indexation sur 10 millions de vecteurs tout en mettant à jour <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">5 % de l'ensemble de données</a>, k : 100 et n : 1000</p></li><li><p><strong>script-score-query</strong>: recherche KNN exacte de <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">2000 vecteurs spécifiques</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>Elasticsearch et OpenSearch ont tous deux obtenu de bons résultats pour le KNN approximatif. Lorsque l’index est fusionné (c’est-à-dire qu’il n’a qu’un seul segment) dans <em>knn-rechercher-100-1000-force-merge</em> et <em>knn-rechercher-10-100-force-merge</em>, OpenSearch fonctionne mieux que les autres lors de l’utilisation de <code>nmslib</code> et <code>faiss</code>, même s’ils sont tous autour de 15 ms et tous très proches.</p><p>Lorsque l'index a plusieurs segments (ce qui est typique lorsqu'un index reçoit des mises à jour), Elasticsearch maintient la latence autour de ~7ms et ~16ms dans les tests <em>knn-search-10-100</em> et <em>knn-search-100-1000</em>, tandis que tous les autres moteurs OpenSearch sont plus lents.</p><p>Lorsque l'index est interrogé et mis à jour simultanément (<em>knn-search-100-1000-concurrent-with-indexing</em>), Elasticsearch maintient une latence inférieure à 15 ms (13,8 ms). Il est presque <strong>4 fois plus rapide</strong> qu’OpenSearch par défaut (49,3 ms) et reste plus rapide lorsque la recherche concurrente de segments est activée (17,9 ms), bien que la différence ne soit pas significative.</p><p>En ce qui concerne le KNN exact, l’écart est bien plus important : Elasticsearch <strong>est 6 fois plus rapide</strong> qu’OpenSearch (~260 ms contre ~1600 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>Rappel</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>2 millions de vecteurs, 768 dimensions (so_vector)</h2><p>Cette <a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">piste</a>, <code>so_vector</code>, est dérivée d’une <a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">extraction des publications de StackOverflow téléchargée</a> le 21 avril 2022. Seuls les documents de questions y figurent, les documents de réponses ayant tous été supprimés. Chaque titre de question a été encodé dans un vecteur en utilisant le modèle de transformateur de phrase <a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a>. Cet ensemble de données contient les 2 premiers millions de questions.</p><p>Contrairement à la piste précédente, chaque document ici contient d'autres champs en plus des vecteurs pour prendre en charge le test de fonctionnalités comme le KNN approximatif avec filtrage et la recherche hybride. <code>nmslib</code> pour OpenSearch est notamment absent dans ce test car <a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">il ne prend pas en charge les filtres</a>.</p><h3>Tâches</h3><p>Chaque tâche fait l'objet d’un préchauffage de 100 requêtes, et la mesure est ensuite effectuée sur les 100 requêtes suivantes. Veuillez noter que les tâches ont été groupées dans un souci de simplicité, le test contenant 16 types de recherche, 2 valeurs k et 3 valeurs n différentes.</p><ul><li><p><strong>KNN-10-50</strong>: recherche sur 2 millions de vecteurs sans filtres, k :10 et n :50</p></li><li><p><strong>knn-10-50-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :10 et n :50</p></li><li><p><strong>knn-10-50-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :10 et n :50</p></li><li><p><strong>KNN-10-100</strong>: recherche sur 2 millions de vecteurs sans filtres, k :10 et n :100</p></li><li><p><strong>knn-10-100-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :10 et n :100</p></li><li><p><strong>knn-10-100-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :10 et n :100</p></li><li><p><strong>KNN-100-1000</strong>: Recherche sur 2 millions de vecteurs sans filtres, k :100 et n :1000</p></li><li><p><strong>knn-100-1000-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :100 et n :1000</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :100 et n :1000</p></li><li><p><strong>exact-knn</strong>: recherche KNN exacte <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">avec et sans filtres</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="table so_vector" /><p>Au cours de ce test, Elasticsearch est <strong>toujours plus rapide</strong> qu’OpenSearch par défaut, sauf dans deux cas où la différence n’est pas très grande (<em>knn-10-100</em> et <em>knn-100-1000</em>). Les tâches impliquant <em>knn-10-50</em>, <em>knn-10-100</em> et <em>knn-100-1000</em> en combinaison avec des filtres montrent une différence allant jusqu'à <strong>7x</strong> (112 ms contre 803 ms).</p><p>Les performances des deux solutions semblent s'équilibrer après une « fusion forcée », ce qui est compréhensible, comme en témoignent <em>knn-10-50-after-force-merge</em>, <em>knn-10-100-after-force-merge</em> et <em>knn-100-1000-after-force-merge.</em> Pour ces tâches, <code>faiss</code> est plus rapide.</p><p>La performance pour le KNN exact est à nouveau très différente : Elasticsearch est cette fois <strong>13 fois plus rapide</strong> qu’OpenSearch (~385 ms contre ~5262 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>Rappel</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 et Lucene, les vainqueurs incontestables</h2><p>Chez Elastic, nous innovons sans cesse avec Apache Lucene et Elasticsearch pour pouvoir proposer la meilleure base de données vectorielles pour les cas d'utilisation de recherche et de récupération, y compris la RAG (Génération augmentée de récupération). Nos avancées récentes ont considérablement amélioré les performances, rendant la recherche vectorielle <a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">plus rapide et plus économe en espace</a> qu'auparavant, en s'appuyant sur les améliorations de Lucene 9.10. Ce blog présente une étude qui montre que lorsque l’on compare les versions les plus récentes, Elasticsearch est jusqu’à 12 fois plus rapide qu’OpenSearch.</p><p>Il convient de noter que les deux produits utilisent la même version de Lucene (<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Notes de publication d'Elasticsearch 8.14</a> et <a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">Notes de publication d'OpenSearch 2.14</a>).</p><p>Le rythme d'innovation d'Elastic nous permettra d'aller encore plus loin, non seulement pour nos clients sur site et Elastic Cloud, mais aussi pour ceux qui utilisent notre <a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">plateforme sans état.</a> Des fonctionnalités comme la prise en charge de la <a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">quantification scalaire vers int4</a> seront proposées avec des tests rigoureux, afin que les clients puissent utiliser ces techniques sans une perte significative d'exactitude, de la même manière que <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">nos tests pour l’int8</a>.</p><p>La capacité d’une recherche vectorielle à être efficace est un critère non négociable pour les moteurs de recherche modernes, du fait de la prolifération des applications d’IA et de Machine Learning. Elasticsearch est la solution qui s’impose pour les organisations qui ont besoin d’un moteur de recherche puissant capable de gérer la demande en données vectorielles de grand volume et de haute complexité.</p><p>L’intégration d’Elasticsearch pour les besoins de recherche vectorielle, que ce soit pour étendre une plateforme établie ou lancer de nouveaux projets, représente une approche stratégique qui générera des bénéfices tangibles et à long terme. Fort de son avantage de performance avéré, Elasticsearch est bien placé pour sous-tendre la prochaine vague d’innovations dans la recherche.</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[Base vectorielle]]></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[Comprendre la quantification scalaire dans Lucene]]></title>
    <description><![CDATA[Découvrez comment Elastic a introduit la quantification scalaire dans Lucene, y compris la quantification automatique par octet, la quantification par segment &amp; performance insights.]]></description>
    <content:encoded><![CDATA[<h2>Quantification automatique des octets dans Lucene</h2><p>Bien que HNSW soit un moyen puissant et flexible de stocker et de rechercher des vecteurs, il nécessite une quantité importante de mémoire pour fonctionner rapidement. Par exemple, l'interrogation de 1MM float32 vectors de 768 dimensions nécessite environ  de ram. Dès que vous commencez à rechercher un nombre important de vecteurs, cela devient coûteux. La quantification des octets est un moyen d'utiliser environ  moins de mémoire. Lucene et, par conséquent, Elasticsearch prennent en charge l'indexation des vecteurs d' depuis un certain temps, mais la construction de ces vecteurs relève de la responsabilité de l'utilisateur. Cela est sur le point de changer, car nous avons introduit la quantification scalaire  dans Lucene.</p><h2>Quantification scalaire 101</h2><p>Toutes les techniques de quantification sont considérées comme des transformations avec perte des données brutes. Cela signifie que certaines informations sont perdues pour des raisons d'espace. Pour une explication approfondie de la quantification scalaire, voir : <a href="https://www.elastic.co/search-labs/scalar-quantization-101">Quantification scalaire 101</a>. À un niveau élevé, la quantification scalaire est une technique de compression avec perte. Un simple calcul permet de réaliser des économies d'espace significatives avec très peu d'impact sur le rappel.</p><h2>Explorer l'architecture</h2><p>Les personnes habituées à travailler avec Elasticsearch sont peut-être déjà familiarisées avec ces concepts, mais voici un aperçu rapide de la distribution des documents pour la recherche.</p><p>Chaque index Elasticsearch est composé de <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/size-your-shards.html#size-your-shards">plusieurs tiroirs (shards</a>). Bien que chaque nuage ne puisse être affecté qu'à un seul nœud, plusieurs nuages par index permettent un parallélisme de calcul entre les nœuds.</p><p>Chaque tesson est composé d'un seul <a href="https://lucene.apache.org/core/9_8_0/core/org/apache/lucene/index/package-summary.html">index Lucene</a>. Un index Lucene se compose de plusieurs segments en lecture seule. Pendant l'indexation, les documents sont mis en mémoire tampon et périodiquement vidés dans un segment en lecture seule. Lorsque certaines conditions sont remplies, ces segments peuvent être fusionnés en arrière-plan en un segment plus large. Tout cela est configurable et comporte son lot de complexités. Mais lorsque nous parlons de segments et de fusion, nous parlons de segments Lucene en lecture seule et de la fusion périodique automatique de ces segments. <a href="https://www.elastic.co/search-labs/vector-search-elasticsearch-rationale">Voici une analyse plus approfondie</a> de la fusion des segments et des décisions en matière de conception.</p><h2>Quantification par segment dans Lucene</h2><p>Chaque segment dans Lucene stocke les éléments suivants : les vecteurs individuels, les indices du graphe HNSW, les vecteurs quantifiés et les quantiles calculés. Par souci de concision, nous nous concentrerons sur la manière dont Lucene stocke les vecteurs quantifiés et bruts. Pour chaque segment, nous conservons la trace des  bruts dans le fichier vec, des vecteurs quantifiés et d'un seul multiplicateur correctif dans le  ainsi que les métadonnées relatives à la quantification  fichier vemq.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1861d0af970fd096/6a17d9887f6f15a45dc099c0/507a4d578f8a5d2225b11d16ac1fc0b7dd39eaa1-1207x228.png" alt="Le fichier .vec Fichier" /><p>Figure 1 : Présentation simplifiée d'un fichier de stockage de vecteurs bruts. Occupe la  de l'espace disque puisque les valeurs  sont de 4 octets. Étant donné que nous quantifions, ces données ne seront pas chargées lors de la recherche HNSW. Ils ne sont utilisés que sur demande expresse (par ex. secondaire par force brute via le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#query-rescorer">rescore</a>), ou pour la re-quantification lors de la fusion de segments.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c52b51939357280/6a17d9897b54f9130f8b3761/ffc6e8bffefc5cd36f14aae315184c89e04a6587-1440x207.png" alt="Le fichier .veq" /><p>Figure 2 : Présentation simplifiée du fichier  fichier. Occupe un espace de  et sera chargé en mémoire lors de la recherche. Les  octets sont destinés à prendre en compte le multiplicateur de correction, utilisé pour ajuster la notation afin d'améliorer la précision et la mémorisation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28e007988f81e5c0/6a17d98b25daabe23208a0d9/555cc92d7d179d8c9711cefbaaac3e0b815d2e42-1440x182.png" alt="Le fichier .vemq" /><p>Figure 3 : Présentation simplifiée du fichier de métadonnées. C'est ici que nous gardons trace de la quantification et de la configuration du vecteur, ainsi que des quantiles calculés pour ce segment.</p><p>Ainsi, pour chaque segment, nous stockons non seulement les vecteurs quantifiés, mais aussi les quantiles utilisés pour créer ces vecteurs quantifiés et les vecteurs bruts originaux. Mais pourquoi conserver les vecteurs bruts ?</p><h2>Une quantification qui évolue avec vous</h2><p>Étant donné que Lucene se concentre périodiquement sur les segments en lecture seule, chaque segment n'a qu'une vue partielle de l'ensemble des données. Cela signifie que les quantiles calculés ne s'appliquent directement qu'à cet échantillon de l'ensemble de vos données. Ce n'est pas très grave si votre échantillon représente correctement l'ensemble de votre corpus. Mais Lucene vous permet de trier votre index de différentes manières. Ainsi, vous pourriez indexer des données triées d'une manière qui ajoute un biais pour les calculs de quantile par segment. De plus, vous pouvez effacer les données quand vous le souhaitez ! Votre échantillon peut être minuscule, ne serait-ce qu'un seul vecteur. Un autre avantage est que vous avez le contrôle sur le moment où les fusions ont lieu. Bien qu'Elasticsearch ait configuré des valeurs par défaut et une fusion périodique, vous pouvez demander une fusion quand vous le souhaitez via l'API <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.10/indices-forcemerge.html">_force_merge</a>. Alors, comment permettre cette flexibilité tout en assurant une bonne quantification et un bon rappel ?</p><p>La quantification vectorielle de Lucene s'adaptera automatiquement au fil du temps. Lucene étant conçu avec une architecture de segments en lecture seule, nous avons la garantie que les données de chaque segment n'ont pas changé et des démarcations claires dans le code pour savoir quand les choses peuvent être mises à jour. Cela signifie que lors de la fusion des segments, nous pouvons ajuster les quantiles si nécessaire et éventuellement ré-équantifier les vecteurs.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d9a2390958c3e82/6a17d98cbe6086e4fd0045d5/646baa8279719d06c05c1a4fdb80a0c060e0dd94-1440x446.png" alt="Quantiles de segments multiples" /><p>Figure 4 : Trois exemples de segments avec différents quantiles.</p><p>Mais la requantification n'est-elle pas coûteuse ? Il y a un certain surcoût, mais Lucene gère les quantiles intelligemment et ne les quantifie complètement que lorsque c'est nécessaire. Prenons l'exemple des segments de la figure 4. Donnons aux segments  et   documents chacun et au segment  seulement  documents. Lucene prend une moyenne pondérée des quantiles et si le quantile fusionné qui en résulte est suffisamment proche des quantiles originaux du segment, nous n'avons pas besoin de quantifier à nouveau ce segment et nous utiliserons les quantiles nouvellement fusionnés.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2596c10ee616cf3/6a17d98eec0f8903b85a6497/58fa20d60dc337ca7108eb01e84a07c330e87ee6-1440x447.png" alt="Quantiles fusionnés" /><p>Figure 5 : Exemple de quantiles fusionnés lorsque les segments  et  ont  documents et que le segment  n'en a que .</p><p>Dans la situation représentée à la figure 5, nous pouvons voir que les quantiles fusionnés qui en résultent sont très similaires aux quantiles originaux en  et  Ils ne justifient donc pas la quantification des vecteurs. Le segment  semble s'écarter trop de la réalité. Par conséquent, les vecteurs de  seront quantifiés à nouveau avec les valeurs de quantile nouvellement fusionnées.</p><p>Il existe en effet des cas extrêmes où les quantiles fusionnés diffèrent considérablement des quantiles initiaux. Dans ce cas, nous prendrons un échantillon de chaque segment et recalculerons entièrement les quantiles.</p><h2>Performance de quantification &amp; numbers</h2><p>Est-il rapide et offre-t-il toujours un bon rappel ? Les chiffres suivants ont été recueillis lors de l'exécution de l'expérience sur une instance GCP <code>c3-standard-8</code>. Pour garantir une comparaison équitable avec , nous avons utilisé une instance suffisamment grande pour contenir des vecteurs bruts en mémoire. Nous avons indexé  vecteurs <a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-simple-embeddings">Cohere Wiki</a> en utilisant le produit intérieur maximal.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa48e3724ad97fed/6a17d98f445de96aca4cff88/81958f99b8a1397f669db48de00f264cacc6b49f-576x455.png" alt="Rappel de quantification" /><p>Figure 6 : Rappel@10 pour les vecteurs quantifiés par rapport aux vecteurs bruts. La performance de recherche des vecteurs quantifiés est nettement plus rapide que celle des vecteurs bruts, et le rappel est rapidement récupérable en rassemblant seulement 5 vecteurs supplémentaires ; visible par .</p><p>La figure 6 illustre l'histoire. Bien qu'il y ait une différence de rappel, comme on peut s'y attendre, elle n'est pas significative. Et la différence de rappel disparaît en rassemblant seulement 5 vecteurs supplémentaires. Tout cela avec des fusions de segments  rapides et 1/4 de la mémoire des vecteurs .</p><h2>Conclusion</h2><p>Lucene apporte une solution unique à un problème difficile. La quantification ne nécessite aucune étape de "formation" ou d'"optimisation". Dans Lucene, cela fonctionnera simplement. Il n'y a pas d'inquiétude à avoir quant à la nécessité de "ré-entraîner" votre index vectoriel si vos données changent. Lucene détectera les changements significatifs et s'en chargera automatiquement pendant toute la durée de vie de vos données. Nous attendons avec impatience le moment où nous intégrerons cette fonctionnalité dans 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[Recherche 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[Mise en œuvre de documents universitaires : Leçons tirées d'Elasticsearch et Lucene]]></title>
    <description><![CDATA[Découvrez des stratégies pour intégrer des documents de recherche dans une application logicielle, en vous appuyant sur nos expériences avec Elasticsearch et Lucene.]]></description>
    <content:encoded><![CDATA[<p>Cet article présente des stratégies pour intégrer des documents académiques dans une application logicielle. Il s'appuie sur des exemples tirés d'Elasticsearch et de Lucene dans l'espoir d'aider d'autres ingénieurs à tirer parti de nos expériences. En lisant ces stratégies, vous vous dites peut-être "mais ce n'est que du développement logiciel !". Et ce serait en effet vrai : en tant qu'ingénieurs, nous disposons déjà des bonnes pratiques et des bons outils, il suffit de les adapter à un nouveau défi.</p><h2>Arrière-plan</h2><p>Lors du développement d'Elasticsearch, nous rencontrons parfois un problème important pour lequel il n'existe pas d'approche simple ou établie. Il est naturel de se demander s'il existe un document universitaire traitant de cette question. D'autres fois, le travail universitaire est une source d'inspiration. Nous tombons sur un article proposant un nouvel algorithme ou une nouvelle structure de données et nous nous disons "ce serait tellement utile !". Voici quelques exemples de la façon dont Elasticsearch et Apache Lucene intègrent des travaux universitaires :</p><ul><li><p><a href="https://research.google/pubs/pub40671/">HyperLogLog++</a> pour les <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.15/search-aggregations-metrics-cardinality-aggregation.html">agrégations de cardinalité</a></p></li><li><p><a href="https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-suresh.pdf">Algorithme C3</a> pour la <a href="https://www.elastic.co/blog/improving-response-latency-in-elasticsearch-with-adaptive-replica-selection">sélection adaptative des répliques</a></p></li><li><p><a href="https://arxiv.org/abs/1603.09320">Graphes hiérarchiques navigables du petit monde (HNSW)</a> pour la recherche du vecteur le plus proche dans Lucene</p></li><li><p><a href="https://jmlr.csail.mit.edu/papers/volume17/15-308/15-308.pdf">Statistique MIC</a> pour <a href="https://github.com/elastic/ml-cpp/pull/488">améliorer la classification par apprentissage automatique</a></p></li><li><p><a href="http://engineering.nyu.edu/~suel/papers/bmw.pdf">Block-max WAND</a> pour une <a href="https://www.elastic.co/blog/faster-retrieval-of-top-hits-in-elasticsearch-with-block-max-wand">recherche plus rapide des meilleurs résultats dans Lucene</a></p></li><li><p>... et <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-combined-fields-query.html">bien d'</a> <a href="https://github.com/elastic/elasticsearch/blob/b2a9328890b23e7ccf6c66a3b13d6d65e453a3dd/server/src/main/java/org/elasticsearch/search/sort/BucketedSort.java#L302-L323">autres encore</a></p></li></ul><p>Les articles universitaires constituent une ressource inestimable pour les ingénieurs qui développent des systèmes à forte intensité de données. Mais leur mise en œuvre peut être intimidante et sujette à des erreurs - les descriptions d'algorithmes sont souvent complexes et des détails pratiques importants sont omis. Les tests constituent un véritable défi : par exemple, comment tester de manière approfondie un algorithme d'apprentissage automatique dont le résultat dépend étroitement de l'ensemble de données ?</p><h2>Évaluez le document comme vous le feriez pour une dépendance logicielle.</h2><p>L'ajout d'une nouvelle dépendance logicielle nécessite une évaluation minutieuse : si l'autre paquet est incorrect, lent ou peu sûr, notre projet pourrait l'être aussi. Avant d'intégrer une dépendance, les développeurs veillent à en évaluer la qualité.</p><p>Il en va de même pour les travaux universitaires que vous envisagez de mettre en œuvre. On peut penser que parce qu'un algorithme a été publié dans un article, il doit être correct et performant. Mais même s'il a été soumis à un processus d'examen, un document universitaire peut présenter des problèmes. Peut-être que la preuve d'exactitude repose sur des hypothèses qui ne sont pas réalistes. Il se peut aussi que la section "expériences" montre des performances nettement supérieures à celles de la ligne de base, mais que cela ne soit valable que pour un ensemble de données spécifique. Même si le document est de grande qualité, son approche peut ne pas convenir à votre projet.</p><p>Lorsque l'on se demande si l'on doit accepter une "dépendance" à l'égard d'un document universitaire, il est utile de se poser les mêmes questions que celles que l'on se poserait à l'égard d'un logiciel :</p><ul><li><p>La bibliothèque est-elle largement utilisée et "testée au combat" ? → D'autres paquets ont-ils mis en œuvre ce document, et cela a-t-il bien fonctionné pour eux ?</p></li><li><p>Existe-t-il des critères de performance ? Ces données vous semblent-elles exactes et équitables ? → L'article comporte-t-il des expériences réalistes ? Sont-ils bien conçus ?</p></li><li><p>L'amélioration des performances est-elle suffisamment importante pour justifier la complexité ? → Le document est-il comparable à une approche de référence solide ? Dans quelle mesure la performance est-elle supérieure à celle de la base de référence ?</p></li><li><p>L'approche s'intégrera-t-elle bien à notre système ? → Les hypothèses et les compromis de l'algorithme sont-ils adaptés à notre cas d'utilisation ?</p></li></ul><p>D'une manière ou d'une autre, lorsqu'un logiciel publie une comparaison de ses performances avec celles de ses concurrents, c'est toujours le logiciel qui est le plus rapide ! Si une tierce partie a conçu les critères de référence, ils peuvent être plus équilibrés. Le même phénomène s'applique aux travaux universitaires. Si un algorithme donne de bons résultats non seulement dans l'article original, mais apparaît également dans d'autres articles comme une référence solide, il est très probable qu'il soit solide.</p><h2>Soyez créatifs avec les tests</h2><p>Les algorithmes tirés d'articles universitaires ont souvent un comportement plus sophistiqué que les types d'algorithmes que nous rencontrons couramment. Il s'agit peut-être d'un algorithme d'approximation qui troque la précision contre une plus grande rapidité. Il peut aussi s'agir d'une méthode d'apprentissage automatique qui prend en compte un vaste ensemble de données et produit des résultats (parfois inattendus). Comment pouvons-nous écrire des tests pour ces algorithmes si nous ne pouvons pas caractériser leur comportement de manière simple ?</p><h3>Focus sur les invariants</h3><p>Lors de la conception des tests unitaires, il est courant de penser en termes d'exemples : si nous donnons à l'algorithme cet exemple d'entrée, il devrait produire cette sortie. Malheureusement, pour la plupart des algorithmes mathématiques, les tests basés sur des exemples ne couvrent pas suffisamment leur comportement.</p><p>Considérons l'algorithme C3, qu'Elasticsearch utilise pour déterminer quel nœud doit traiter une requête de recherche. Il classe chaque nœud à l'aide d'une formule nuancée qui incorpore le service précédent et les temps de réponse du nœud, ainsi que la taille de sa file d'attente. Tester quelques exemples ne permet pas vraiment de vérifier que nous avons compris la formule correctement. Il est utile de prendre du recul et de réfléchir au test des invariants : si le temps de service augmente, le rang du nœud diminue-t-il ? Si la taille de la file d'attente est de 0, le rang est-il déterminé par le temps de réponse, comme le prétend le document ?</p><p>Se concentrer sur les invariants peut s'avérer utile dans un certain nombre de cas courants :</p><ul><li><p>La méthode est-elle censée ne pas tenir compte de l'ordre ? Si c'est le cas, le fait de passer les données d'entrée dans un ordre différent devrait produire le même résultat.</p></li><li><p>Certaines étapes de l'algorithme produisent-elles des probabilités de classe ? Si c'est le cas, la somme de ces probabilités doit être égale à 1.</p></li><li><p>La fonction est-elle symétrique par rapport à l'origine ? Si c'est le cas, l'inversion du signe de l'entrée devrait simplement inverser le signe de la sortie.</p></li></ul><p>Lorsque nous avons mis en œuvre C3 pour la première fois, nous avons eu un problème dans la formule où nous avons accidentellement utilisé l'inverse du temps de réponse à la place du temps de réponse. Cela signifie que les nœuds les plus lents peuvent être mieux classés ! Lors de la correction du problème, nous avons veillé <a href="https://github.com/elastic/elasticsearch/pull/70283">à ajouter des vérifications invariantes</a> afin d'éviter de nouvelles erreurs.</p><h3>Comparer avec une implémentation de référence</h3><p>Parallèlement à l'article, les auteurs ont publié une mise en œuvre de l'algorithme. (Cela est particulièrement probable si l'article contient des expériences, car de nombreuses revues exigent que les auteurs publient le code permettant de reproduire les résultats). Vous pouvez tester votre approche par rapport à cette implémentation de référence pour vous assurer que vous n'avez pas oublié des détails importants de l'algorithme.</p><p>Lors du développement de l'implémentation HNSW de Lucene pour la recherche du plus proche voisin, nous avons <a href="https://issues.apache.org/jira/browse/LUCENE-9937">testé une bibliothèque de référence</a> créée par les auteurs de l'article. Nous avons testé Lucene et la bibliothèque sur le même ensemble de données, en comparant la précision de leurs résultats et le nombre de calculs qu'ils ont effectués. Lorsque ces nombres correspondent étroitement, nous savons que Lucene met fidèlement en œuvre l'algorithme.</p><p>Lors de l'intégration d'un algorithme dans un système, il est souvent nécessaire de procéder à des modifications ou à des extensions, comme l'adaptation à plusieurs cœurs ou l'ajout d'heuristiques pour améliorer les performances. Il est préférable de commencer par mettre en œuvre une version "vanilla", de la tester par rapport à la référence, puis d'y apporter des modifications incrémentielles. Vous pouvez ainsi être sûr d'avoir capturé tous les éléments clés avant de procéder à des personnalisations.</p><h3>Duel contre un algorithme existant</h3><p>La dernière section propose une autre idée d'invariant de test : comparer le résultat de l'algorithme à celui d'un algorithme plus simple et mieux compris. Prenons l'exemple de l'algorithme WAND block-max de Lucene, qui accélère la recherche de documents en ignorant ceux qui ne peuvent pas figurer dans les premiers résultats. Il est difficile de décrire exactement le comportement de l'outil WAND block-max dans tous les cas, mais nous savons que son application ne devrait pas modifier les premiers résultats ! Nos tests peuvent donc générer plusieurs requêtes de recherche aléatoires, puis <a href="https://github.com/apache/lucene/blob/main/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java#L669">les exécuter avec et sans l'optimisation WAND</a> et vérifier que les résultats correspondent toujours.</p><p>Un aspect important de ces tests est qu'ils <a href="https://www.elastic.co/blog/elasticsearch-testing-qa-increasing-coverage-randomizing-test-runs">génèrent des entrées aléatoires</a> sur lesquelles la comparaison est effectuée. Cela peut permettre de mettre en œuvre des cas auxquels vous n'auriez pas pensé et de mettre en évidence des problèmes inattendus. Par exemple, le test de comparaison aléatoire de Lucene pour la notation BM25F a permis de <a href="https://issues.apache.org/jira/browse/LUCENE-10039">détecter des bogues dans des cas subtils</a>. L'idée d'alimenter un algorithme avec des entrées aléatoires est étroitement liée au concept de <a href="https://en.wikipedia.org/wiki/Fuzzing">fuzzing</a>, une technique de test courante dans le domaine de la sécurité informatique.</p><p>Elasticsearch et Lucene utilisent fréquemment cette approche de test. Si vous voyez un test qui mentionne un duel "" entre deux algorithmes (TestDuelingAnalyzers, testDuelTermsQuery...), vous savez que cette stratégie est en action.</p><h2>Utiliser la terminologie du document</h2><p>Lorsqu'un autre développeur travaillera avec votre code, il devra consulter le document pour en suivre les détails. Le <a href="https://github.com/elastic/elasticsearch/blob/4f22f437ee50cacb94b37b457be1da0b8ba0e8ce/server/src/main/java/org/elasticsearch/search/aggregations/metrics/HyperLogLogPlusPlus.java#L24-L39">commentaire sur l'implémentation HyperLogLog++ d'Elasticsearch</a> le dit bien : "Essayer de comprendre ce que fait cette classe sans avoir lu le document est considéré comme aventureux." Ce commentaire de méthode constitue également un bon exemple. Elle comprend un lien vers l'article universitaire et met en évidence les modifications apportées à l'algorithme tel qu'il a été décrit à l'origine.</p><p>Étant donné que les développeurs fonderont leur compréhension du code sur le document, il est utile d'utiliser exactement la même terminologie. La notation mathématique étant laconique, il peut en résulter des noms qui ne seraient pas habituellement considérés comme "de bon style", mais qui sont très clairs dans le contexte de l'article. Les formules tirées d'articles universitaires sont l'une des rares occasions où vous rencontrerez des noms de variables cryptiques dans Elasticsearch, comme <a href="https://github.com/elastic/elasticsearch/blob/4f22f437ee50cacb94b37b457be1da0b8ba0e8ce/server/src/main/java/org/elasticsearch/node/ResponseCollectorService.java#L151">rS et muBarSInverse</a>.</p><p>
<em>La façon recommandée par l'auteur pour lire un article : avec un grand café.</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12d81453c706f7cb/6a17d80e0b0bede6badd3424/d03a8e2a50e173b15a4ec3732810c63ff53321f6-1440x1081.jpg" alt="elastic-blog-academicpaper.jpg" /><h2>Vous pouvez envoyer un courriel à l'auteur</h2><p>Lorsque vous travaillez sur un document difficile, vous pouvez passer des heures à réfléchir à une formule, sans savoir si vous avez mal compris ou s'il s'agit simplement d'une erreur de frappe. S'il s'agissait d'un projet open source, vous pourriez poser une question sur GitHub ou StackOverflow. Mais à qui s'adresser pour obtenir un travail universitaire ? Les auteurs semblent occupés et pourraient être ennuyés par vos courriels.</p><p>Au contraire, de nombreux universitaires aiment entendre que leurs idées sont mises en pratique et sont heureux de répondre à des questions par courrier électronique. Si vous travaillez sur un produit qu'ils connaissent bien, il se peut même qu'ils listent l'application sur leur site web !</p><p>Les universitaires ont également de plus en plus tendance à discuter de leurs articles en public, en utilisant les mêmes outils que ceux utilisés pour le développement de logiciels. Si un article est accompagné d'un logiciel, vous pouvez trouver des réponses aux <a href="https://github.com/facebookresearch/faiss/issues/1928">questions les plus courantes sur Github</a>. Les communautés Stack Exchange telles que "Theoretical Computer Science" et "Cross Validated" contiennent également des <a href="https://cstheory.stackexchange.com/questions/49296/problem-in-the-paper-stable-minimum-space-partitioning-in-linear-time">discussions détaillées sur les articles les plus populaires</a>. Certaines conférences ont commencé à publier en ligne tous les comptes rendus d'articles. Ces revues contiennent des <a href="https://openreview.net/forum?id=H1eA7AEtvS">discussions</a> avec les auteurs qui peuvent apporter des informations utiles sur l'approche.</p><h2>A suivre</h2><p>Cet article se concentre sur les bases du choix d'un document académique et sur la manière de le choisir. </p><p>L'algorithme doit être mis en œuvre correctement, mais il ne couvre pas tous les aspects du déploiement réel de l'algorithme. Par exemple, si l'algorithme n'est qu'un élément d'un système complexe, comment s'assurer que les modifications apportées à cet élément conduisent à des améliorations de bout en bout ? Et que se passe-t-il si l'intégration de l'algorithme nécessite des modifications ou des extensions substantielles que l'article original ne couvre pas ? Il s'agit là de sujets importants sur lesquels nous espérons revenir dans de prochains articles.</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[Recherche 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>