cd /news/developer-tools/autocompletion-sur-plus-de-100-milli… · home topics developer-tools article
[ARTICLE · art-87501] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Autocomplétion sur plus de 100 millions de documents : du piège des agrégations à l'index dédié

A developer at an unnamed company optimized autocomplete over 100 million documents in Elasticsearch by benchmarking different patterns and replacing costly terms aggregations with a Completion Suggester index. The initial cold-start latency of 5 seconds was reduced to milliseconds after switching from Prefix match to Completion Suggester and optimizing the mapping.

read5 min views1 publishedAug 5, 2026

Le problème n'était pas Elasticsearch mais le choix du pattern utilisé pour l'autocomplétion.

L'autocomplétion semblait répondre vite au début mais en faisant plus de tests j'ai constaté que le premier appel était toujours très long, en moyenne 5 secondes, ce qui est inacceptable à la saisie. Les appels suivants passent par le cache, ce qui rend les autres appels plus rapides, de l'ordre de quelques millisecondes.

Certes la base contient des centaines de millions de documents, mais les données sont pourtant stockées dans Elasticsearch, qui promet des temps de réponse plus performants qu'un SGBD.

La toute première requête a un coût fixe d'initialisation, découpé de la façon suivante :

Une fois ce pipeline « chauffé », les requêtes suivantes bénéficient d'un accès direct à la RAM et d'une réutilisation des connexions, faisant chuter le temps de réponse de plusieurs centaines de millisecondes à quelques millisecondes seulement.

Pour prendre la bonne décision il fallait absolument avoir des éléments chiffrés, et la seule façon de trouver le meilleur pattern était donc de faire un benchmark sur notre volumétrie.

Nous utilisions Prefix match, qui concentre tout son travail au moment de la recherche (query-time).

Les autres patterns tels que Search As You Type et Edge N-gram pré-mâchent le travail lors de la création de l'index, donc le temps de recherche est réduit. C'est ce que j'ai commencé à tester.

Completion Suggester est un autre type de pattern : il utilise une structure de données appelée Finite State Transducer (FST). Le FST est écrit sur le disque à l'indexation et chargé en mémoire au premier suggest

. Le travail est donc fait côté index-time.

Pour évaluer la meilleure solution, nous avons besoin de plusieurs métriques :

took

, en millisecondes)Les premiers tests que j'ai faits étaient sur un index local, car je ne pouvais pas tester sur l'environnement de test à ce moment-là. C'était une erreur : il ne contenait pas autant de données qu'en prod.

À cette échelle, le coût fixe d'initialisation à froid est très visible. Surtout, ces résultats locaux montraient que les appels à la base de données (pour enrichir les résultats) prenaient énormément de temps et écrasaient totalement les métriques d'Elasticsearch.

Stratégie Took Call .NET Enrichissement Total
Prefix 189 ms 504 ms 1563 ms (76 %) 2067 ms
Edge N-gram 153 ms 174 ms 392 ms (69 %) 566 ms
Search As You Type 154 ms 194 ms 384 ms (66 %) 578 ms

En local, l'enrichissement représentait 66 à 76 % du temps total. Sur un véritable environnement avec plus de 100 millions de documents, c'est l'inverse, le took

seul pèse 3,6 à 5,2 secondes, et l'enrichissement devient négligeable.

Le benchmark local ne m'a pas donné de mauvais chiffres, il m'a donné les bons chiffres du mauvais problème.

Après quelques optimisations côté BDD, et en me branchant sur un environnement de test qui contenait des centaines de millions de documents, j'ai constaté qu'en réalité le mur n'était pas la base de données mais l'agrégation.

Que ce soit Prefix match, Edge N-gram ou Search As You Type, nous devions faire une terms aggregation

pour ne pas retourner de doublons et celle-ci était coûteuse sur plus de 100 millions de documents.

Optimisation du mapping des destinations (passage à un dictionnaire, accès O(1)) :

Requête Avant Après Gain
Query 1 391 ms 135 ms -65 %
Query 2 246 ms 51 ms -79 %
Query 3 253 ms 45 ms -82 %
Query 4 285 ms 47 ms -83 %

Et l'enrichissement, qui déclenchait un scan en base à froid (~1500 ms), est passé à zéro accès à la base. La donnée est désormais lue directement depuis l'agrégation.

Ces gains sont réels. Mais à eux seuls, ils n'auraient jamais suffi, c'est exactement ce qui m'a fait perdre du temps.

La dernière option était donc Completion Suggester, qui stocke un ensemble de suggestions dans le FST sur l'index.

Le feed s'était bien déroulé, mais dans notre version, la FST du Completion Suggester sur plus de 10 millions de documents ne tenait pas dans le heap et la première requête suggest

provoquait une erreur java.lang.OutOfMemoryError

.

L'idéal était donc de faire un index dédié, plus petit, contenant uniquement les données destinées à l'autocomplétion.

C'était LA solution, le took

était d'à peine 1 ms à froid (parfois 0), et les optimisations côté base de données avec la mise en cache avaient amélioré les perfs globales.

| Stratégie | Took (ms) | Call .NET (ms) | Enrichissement (ms) | Total (ms) |
|---|---|---|---|---|

| Prefix match + agg (existant) | 3 710 | 3 812 | 391 | 4 203 | | Completion suggester sur l'index principal | — | — | — | OOM | | Completion suggester + index dédié | 1 | 38 | 47 | 85 |

Edge N-gram et Search As You Type n'ont pas été rejoués sur l'environnement de test : les trois approches partagent la même terms aggregation, déjà identifiée comme le goulot.

Tous les chemins empruntés et toutes les embûches ont permis d'avoir une autocomplétion qui répond instantanément.

Tester sur un environnement qui ne reflète pas la PROD peut masquer le réel goulot d'étranglement, pourtant de taille O(N) comme les agrégations sur une centaine de millions de docs. Cela a quand même permis de faire des optimisations côté BDD et .NET.

Le réel mur était la terms aggregation. Agréger des données sur ce volume durant le query-time n'était pas tenable. La vraie question n'était pas « quel match » mais « agrégation ou pas ». Au final j'ai changé de stratégie et le dédoublonnage s'opère au moment de l'index-time, via un index dédié.

La puissance du FST demande une mémoire maîtrisée. Un index d'autocomplétion dédié et léger a été la meilleure solution pour isoler la RAM.

[https://www.elastic.co/search-labs/blog/elasticsearch-autocomplete-search](https://www.elastic.co/search-labs/blog/elasticsearch-autocomplete-search)

[https://www.elastic.co/blog/you-complete-me](https://www.elastic.co/blog/you-complete-me)
── more in #developer-tools 4 stories · sorted by recency
── more on @elasticsearch 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/autocompletion-sur-p…] indexed:0 read:5min 2026-08-05 ·