Elasticsearch ES|QL permet d'effectuer des recherches full-text sur des données que vous n'avez jamais indexées
Les fonctions MATCH et TO_TEXT permettent d'effectuer une recherche full-text sur des données que vous n'avez jamais indexées. Effectuez des recherches sur des colonnes calculées, des champs non mappés et des sources fédérées avec ES|QL.
La fonction MATCH d'ES|QL permet désormais d'effectuer des recherches full-text sur des données que vous n'avez jamais indexées, y compris les colonnes calculées, les champs non mappés, les chaînes assemblées à la volée et même les données fédérées stockées dans S3. La nouvelle fonction TO_TEXT indique à ES|QL de traiter n'importe quelle chaîne comme du texte analysable ; MATCH peut ainsi générer des tokens, normaliser la casse et mettre en correspondance des termes avec des valeurs qui n'existent que le temps de la requête. Il s'agit d'une véritable analyse, qui va bien au-delà de la correspondance de patterns via LIKE et RLIKE proposée par la plupart des moteurs de requête pour les chaînes non indexées. Disponible dès maintenant sur Elastic Cloud Serverless et en préversion technique dans Elasticsearch 9.5.
Comment MATCH et TO_TEXT permettent la recherche full-text sur n'importe quelle expression ES|QL
Commençons par une requête qui était impossible dans Elasticsearch 9.4, qui utilise la commande EVAL :
FROM cooking_blog
| EVAL summary = TO_TEXT(CONCAT(title, description))
| WHERE MATCH(summary, "pancakes")
| KEEP title, authorDans cet exemple, le résumé ne dispose d'aucune configuration de mapping ou d'analyseur. Il n'est pas non plus associé à un index inversé. Il n'existe que le temps de cette requête, mais il est tout de même possible d'effectuer des recherches dessus. Deux ajouts permettent d'y parvenir.
Premièrement, la fonction MATCH accepte désormais n'importe quelle expression comme premier argument, et non plus seulement un champ mappé. Cela inclut les colonnes générées par EVAL ainsi que les résultats de fonctions utilisés directement dans l'expression. Cela englobe également les champs non mappés chargés directement depuis le document d'origine. De plus, tous les types de données habituellement acceptés par MATCH sont compatibles avec ce nouveau cas d'utilisation.
Deuxièmement, la nouvelle fonction TO_TEXT, qui est la première fonction de conversion ES|QL qui produit une sortie de type texte. Jusqu'à présent, les colonnes de texte ne pouvaient provenir que de champs mappés indexés, et toutes les chaînes générées par les expressions ES|QL étaient des valeurs de mots-clés plutôt que de texte. Cette distinction est importante car MATCH traite les deux différemment : les valeurs de texte sont analysées, tandis que les valeurs de mots-clés sont comparées exactement, reproduisant ainsi le mécanisme par lequel une requête MATCH sur un champ de mot-clé indexé est réécrite en requête de terme. TO_TEXT(x) permet d'indiquer à ES|QL de traiter cette chaîne comme du texte intégral.
Cette fonctionnalité est disponible en préversion technique dans Elasticsearch 9.5, et, à ce titre, présente certaines limitations :
Pour l'instant, elle se limite au filtrage. Une opération MATCH portant sur une expression ne contribue pas encore au score de pertinence ; seuls les matchs sur des champs indexés influencent ce score.
Les options de requête telles que la tolérance aux erreurs ne sont pas encore prises en charge lors de l'utilisation de MATCH sur une expression.
Le texte généré à l'exécution est analysé à l'aide de l'analyseur standard. Ce comportement n'est pas encore configurable.
Ces limitations seront résolues prochainement.
Pourquoi utiliser la recherche full-text plutôt que LIKE ou RLIKE dans ES|QL ?
ES|QL disposait déjà de deux méthodes pour rechercher des chaînes de caractères sans index : LIKE (caractères génériques) et RLIKE (expressions régulières). Les deux fonctionnant sur n'importe quelle expression de chaîne, on peut donc se demander ce que MATCH apporte. La réponse est l'analyse, une forme plus avancée de recherche qui utilise des techniques telles que la lemmatisation et les synonymes. Elle intègre également la gestion des mots vides.
LIKE effectue une simple correspondance de sous-chaîne, sans aucune compréhension des mots qui composent la chaîne. Supposons, par exemple, que vous recherchiez des messages de log à propos d'un renard :
FROM app_logs
| WHERE message LIKE "*fox*"La correspondance "Fox spotted near the henhouse" n'est pas détectée en raison de la majuscule, alors que la correspondance "Outfoxed by the competition", qui n'a aucun rapport avec un renard, est détectée. Le système échoue dans les deux cas : faux négatifs liés à la casse et faux positifs dus à des sous-chaînes incluses dans d'autres mots.
Les expressions régulières peuvent résoudre le problème de la casse, mais la gestion des limites de mots devient vite complexe. Quelque chose comme :
FROM app_logs
| WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"Et même là, ce n'est pas encore tout à fait ça. L'expression ne détecte pas le mot "fox" en fin de phrase s'il est suivi d'un point d'exclamation ou d'interrogation, et elle ne tient pas compte des tabulations, des guillemets ou des parenthèses. Chaque correction allonge le pattern, obligeant la personne suivante qui lira la requête à procéder à une ingénierie inverse pour comprendre ce qu'elle fait réellement.
MATCH résout le problème, car il fait passer aussi bien la requête que la valeur par un analyseur qui découpe le texte à l'aide de tokens et le convertit en minuscules, puis compare les termes entre eux :
FROM app_logs
| WHERE MATCH(TO_TEXT(message), "fox")Cette requête correspondra à des valeurs comme "The quick brown fox" et "FOX spotted near the henhouse", mais pas à "Outfoxed by the competition" ou "FOXTROT protocol enabled", quelle que soit la ponctuation entourant les mots. Bien entendu, tout cela fonctionne également pour les requêtes comportant plusieurs termes, comme MATCH(TO_TEXT(message), "brown fox"), exactement comme on peut s'y attendre.
Des travaux sont en cours pour permettre l'utilisation des 36 analyseurs linguistiques dédiés, avec la prise en charge des langues naturelles sur des données jamais indexées ni mappées.
Cas d'utilisation de la recherche full-text pour des données non indexées et non mappées
Les exemples ci-dessus portaient sur des valeurs calculées à partir de champs mappés. Les cas d'utilisation les plus intéressants de la fonction MATCH d'ES|QL appliquée à des expressions concernent des données qui, auparavant, n'étaient pas du tout interrogeables. Examinons quelques-uns de ces cas.
Comment rechercher des champs non mappés dans ES|QL sans ajouter de mapping
Il arrive parfois que l'on doive exclure un champ de nos mappings, comme une trace de pile détaillée ou une charge utile de requête brute. On peut même omettre un blob à déboguer. L'indexation de l'un de ces éléments consommerait de l'espace disque et de la mémoire pour chaque document, ce qui ne se justifierait pas pour un champ que l'on n'interroge peut-être qu'une fois par trimestre.
Cette décision a toujours été irrévocable, car les champs non mappés étaient totalement invisibles pour les requêtes. Dans Elasticsearch 9.5, vous pouvez utiliser SET unmapped_fields="load" pour permettre à ES|QL de charger directement les champs non mappés depuis le document source en tant que mots-clés. En enveloppant ensuite le résultat dans TO_TEXT, vous pouvez exécuter une recherche full-text sur ces champs :
SET unmapped_fields="load";
FROM app_logs
| WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
| KEEP @timestamp, service.name, messageIci, stack_trace n'a jamais été mappé. Chaque valeur est récupérée depuis les documents d'origine et analysée à la volée ; la correspondance est établie ligne par ligne. C'est un travail conséquent, qui ne sera jamais aussi rapide qu'une recherche via un index inversé. Toutefois, ce champ que vous n'aviez pas indexé n'est plus impossible à interroger. Vous pouvez ainsi conserver un mapping léger pour les besoins courants tout en étant capable de répondre, le moment venu, à une question qui ne se pose qu'une fois par trimestre.
Recherche full-text sur un champ de mots-clés sans réindexation
Les champs de mots-clés offrent de nombreuses possibilités. Ils permettent une correspondance exacte, des agrégations rapides et le tri des données ; c'est pourquoi tant de champs sont finalement mappés de cette manière. Toutefois, le mapping est défini au moment de l'arrivée des données, et il est fréquent de se retrouver dans une situation où l'on souhaite exploiter ces données différemment de ce qui avait été prévu initialement. Par exemple, le champ product_name a peut-être été mappé en tant que mot-clé parce que les tableaux de bord s'appuyaient sur lui pour des agrégations. Or, après avoir accumulé une année de données produits, un utilisateur peut soudainement vouloir effectuer des recherches au sein même des valeurs de ce champ.
L'ancienne solution consiste à convertir le mapping en texte (ou à ajouter un champ multiple) et à tout réindexer. Cette opération est longue et coûteuse et, bien souvent, les utilisateurs ne veulent pas s'en donner la peine. La nouvelle solution se résume à un seul appel de fonction :
FROM products
| WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
| KEEP product_name, brand, priceTO_TEXT convertit à la volée les valeurs de mot-clés en texte, permettant ainsi à MATCH de les analyser plutôt que de les comparer de manière exacte. Cela vous permet d'interroger un champ de mot-clé sans avoir à créer de mapping ni à réindexer le document source. Si cette recherche est amenée à devenir une requête courante, l'indexation du champ en tant que texte reste la solution idéale à long terme. Toutefois, TO_TEXT vous permet d'obtenir une réponse immédiate, sans travail supplémentaire.
Recherche sur le même champ dans des index aux mappings différents
ES|QL peut couvrir plusieurs index, et un même champ ne doit pas nécessairement présenter la même structure dans chacun d'eux. Lorsqu'un même champ possède des types différents selon les index, ES|QL le traite comme un type d'union, et une fonction de conversion résout le conflit. Prenons l'exemple d'un champ de message qui est de type texte dans le modèle d'index de cette année, alors qu'il était de type mot-clé dans celui de l'année précédente :
FROM logs-2025, logs-2026
| EVAL msg = TO_TEXT(message)
| WHERE MATCH(msg, "connection reset")Chaque valeur est analysée au moment de la requête, qu'elle provienne de l'index texte ou de l'index mots-clés. Les valeurs de type mot-clé issues des anciens index sont découpées en tokens et converties en minuscules, tout comme les autres ; ainsi, la recherche "connection reset" permet de trouver "Connection RESET by peer", quel que soit l'index dans lequel la donnée est stockée.
Un autre cas intéressant est celui où un champ est mappé dans un seul index mais également présent (et non mappé) dans l'autre :
SET unmapped_fields="load";
FROM logs-2025, logs-2026
| WHERE MATCH(TO_TEXT(error_details), "timeout")Il existe une nuance ici qu'il convient de souligner. Si error_details est mappé dans logs-2026 mais pas dans logs-2025, Elasticsearch ne peut pas déléguer cette requête à Lucene, car les index où le champ n'est pas mappé ne renverraient silencieusement aucun résultat. Au lieu de cela, le planificateur détecte que le champ n'est potentiellement pas mappé et évalue l'intégralité de la clause MATCH ligne par ligne, quelle que soit la provenance des lignes. Vous n'avez pas besoin de savoir à quels index appartient le mapping de ce champ ; la requête répond simplement à la question.
Comment ES|QL analyse le texte au moment de la requête sans index inversé
Lorsque ES|QL planifie une opération MATCH sur une expression, il analyse la chaîne de requête une seule fois, au préalable, pour la transformer en un ensemble de termes. La manière dont chaque ligne est ensuite évaluée dépend du type de l'expression :
Type d'expression | Traitement | Comportement correspondant |
texte (via TO_TEXT) | L'analyseur segmente la valeur à l'aide de tokens et la convertit en minuscules | Comparaison token par token ; une ligne correspond si un token correspond à un terme de requête quelconque (sémantique OU) |
mot-clé, adresse IP, date, numérique | Aucune analyse ; constante de la requête convertie une seule fois vers le type natif | Comparaison exacte par ligne |
Ces deux méthodes contournent entièrement Lucene et évaluent les valeurs ligne par ligne. Le chemin non textuel reproduit exactement le comportement d'une requête de correspondance lorsqu'elle est déléguée à Lucene pour ces types de champs ; la sémantique reste donc cohérente, que la requête utilise un index ou non.
Une recherche via un index inversé s'effectue au moment de l'ingestion et n'interagit jamais avec les documents non correspondants lors de l'exécution de la requête. À l'inverse, une opération MATCH effectuée à l'exécution réalise cette analyse au moment de la requête, pour chaque ligne traitée. La première méthode est rapide car le travail a déjà été accompli en amont, tandis que la seconde offre de la flexibilité, car les données n'ont pas besoin d'avoir été indexées au préalable.
Prochaines évolutions de la recherche full-text dans ES|QL
Tout ce qui est présenté dans cet article constitue la première étape d'une initiative plus vaste visant à permettre à la recherche ES|QL de fonctionner sur n'importe quelles données, et pas seulement sur celles indexées au préalable. Nous travaillons activement à lever les limitations mentionnées précédemment, et la roadmap va encore plus loin :
Calcul du score. Les correspondances évaluées à l'exécution contribueront au score (_score) ; vous pourrez ainsi effectuer un tri par pertinence, même si les données n'ont jamais été indexées.
MATCH_PHRASE sur les expressions. Déjà disponible dans Elastic Cloud Serverless et prochainement dans la Suite Elastic version 9.6.
Analyseurs configurables. Prise en charge des analyseurs pour MATCH et MATCH_PHRASE sur les expressions, permettant l'utilisation d'analyseurs linguistiques, de la lemmatisation et de synonymes au moment de la requête.
Options de correspondance. Des options telles que la tolérance et l'opérateur pour les correspondances à l'exécution.
Recherche vectorielle. Génération d'embeddings par ligne et exécution de l'algorithme des k plus proches voisins (kNN) sur des expressions dense_vector évaluées à l'exécution, permettant d'étendre la recherche sémantique aux données non indexées.
Essayez dès aujourd'hui la recherche full-text ES|QL sur les expressions
Vous pouvez essayer la recherche au moment de l'exécution dès maintenant. Elle est disponible dans Elastic Cloud Serverless, où les nouvelles fonctionnalités ES|QL sont déployées en priorité, et proposée en préversion technique dans Elasticsearch 9.5. Commencez par consulter la documentation de référence sur les fonctions de recherche ainsi que les limites actuelles sur la page dédiée aux limitations ES|QL. Cette préversion technique est destinée à recueillir votre avis : si vous effectuez une recherche sur un élément qui n'a jamais été indexé et que le résultat vous surprend – en bien ou en mal –, n'hésitez pas à nous en faire part.
