Données et bases de données

Apache Solr : index inversé, chaînes d'analyse et réglage de la pertinence

Intermédiaire9 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Un moteur de recherche ne répond pas à la même question qu'une base de données : il ne cherche pas quelles lignes correspondent, mais quels documents sont les plus pertinents. Ce guide explique l'index inversé et la chaîne d'analyse qui l'alimente, puis couvre le schéma Solr, l'indexation et les parsers de requêtes, le scoring BM25, les facettes et le surlignage, les collections SolrCloud et le travail concret de réglage de la pertinence.

Ce que vous allez apprendre

  • Expliquer pourquoi un index inversé répond à des requêtes textuelles qu'un index relationnel ne sait pas traiter
  • Concevoir une chaîne d'analyse à base de tokenizers et de filtres, et comprendre pourquoi analyse à l'indexation et à la requête doivent concorder
  • Définir des types de champs Solr, indexer des documents et les interroger avec edismax
  • Exploiter facettes, surlignage et suggestions pour construire une véritable expérience de recherche
  • Régler la pertinence avec des boosts de champ, des boosts de phrase et des function queries, et faire tourner Solr en cluster

La première fois qu'une équipe remplace une requête LIKE '%term%' par un moteur de recherche, ce qui surprend n'est pas la vitesse. C'est que les résultats reviennent dans un ordre utile. Une base de données répond à une question booléenne — cette ligne correspond-elle ? — et c'est à vous d'inventer le classement. Un moteur de recherche répond à une question graduée : parmi les documents qui correspondent, lesquels satisfont le mieux la requête, compte tenu de la rareté de chaque terme et de la place qu'il occupe dans le document ?

Apache Solr est un serveur de recherche bâti sur Apache Lucene, qui fait tourner depuis vingt ans des recherches de catalogues, de sites et de documents. Ce guide porte sur Solr 9 : ce que contient réellement l'index, comment le texte devient des tokens, comment définir un schéma et l'interroger, et comment se règle la pertinence une fois les bases en place.

Pourquoi un moteur de recherche n'est pas un index de base de données

Un index B-tree associe la valeur complète d'une colonne aux lignes qui la contiennent. Un moteur de recherche construit un index inversé : un dictionnaire de termes, chacun pointant vers une liste de postings — les documents qui contiennent ce terme, avec positions et fréquences.

Doc 1: "Estuary birds of northern Portugal"
Doc 3: "Bird migration over the estuary"

term        postings (doc: positions, freq)
----------------------------------------------
bird        1:[2]/1   3:[1]/1
estuari     1:[1]/1   3:[5]/1
migrat      3:[2]/1
portug      1:[5]/1

Deux conséquences en découlent. Chercher un mot revient à consulter un dictionnaire et à fusionner des listes, ce qui reste rapide, que le corpus compte dix mille documents ou dix millions. Et comme l'index enregistre combien de fois et où chaque terme apparaît, il peut noter les correspondances au lieu de se contenter de les trouver.

Le modèle de scoring par défaut est BM25. Concrètement, un document obtient un meilleur score lorsque le terme de la requête y apparaît plus souvent (avec des rendements fortement décroissants), lorsque ce terme est rare dans l'ensemble du corpus, et lorsque le champ qui le contient est court — un terme dans un titre de cinq mots pèse davantage que le même terme noyé dans un corps de mille mots. Lucene utilise BM25 comme similarité par défaut depuis Solr 6 ; l'ancien modèle TF-IDF reste disponible pour la compatibilité.

Le compromis est bien réel. Solr n'a pas de transactions, pas de jointures dignes de ce nom et pas de mise à jour en place ; modifier un seul champ réécrit le document entier. Traitez-le comme une projection dérivée d'un système de référence, jamais comme la référence elle-même — ce rôle revient au moteur relationnel, avec les réflexes de conception et d'indexation présentés dans le guide sur les plans d'exécution.

La chaîne d'analyse

Le texte brut devient des termes d'index en passant par un analyseur, composé d'une chaîne de filtres de caractères, d'un tokenizer et d'une chaîne de filtres de tokens. Bien régler cette chaîne représente l'essentiel du travail derrière une bonne expérience de recherche.

  • Les filtres de caractères s'appliquent à la chaîne brute : suppression du HTML, conversion de œ en oe, normalisation de la ponctuation.
  • Le tokenizer découpe le flux en tokens. StandardTokenizer suit les frontières de mots Unicode ; WhitespaceTokenizer ne coupe que sur les espaces ; ClassicTokenizer préserve des éléments comme les références produit et les noms d'hôte.
  • Les filtres de tokens transforment le flux : mise en minuscules, suppression des mots vides, application de synonymes, suppression des accents (ASCIIFoldingFilter), découpage de WiFi-2.4GHz en morceaux (WordDelimiterGraphFilter), réduction des mots à leur racine (SnowballPorterFilter pour de nombreuses langues, ou le plus prudent KStemFilter pour l'anglais).

La règle qui sème le plus de confusion : l'analyse à l'indexation et l'analyse à la requête doivent être compatibles. Si l'index réduit birds à bird mais que la requête ne le fait pas, rien ne correspond. Solr permet de déclarer les deux chaînes séparément, et pour de bonnes raisons : l'expansion des synonymes et la génération d'EdgeNGram ne sont souvent appliquées qu'à l'indexation, afin que la requête reste un terme unique. L'écran Analysis de l'interface d'administration affiche les deux flux de tokens côte à côte et répond à « pourquoi ça ne correspond pas ? » plus vite que n'importe quel raisonnement.

Schéma et types de champs

Un schéma Solr déclare des types de champs (une chaîne d'analyse plus des options de stockage) et les champs qui les utilisent. Solr 9 utilise un schéma géré, modifiable via la Schema API, mais c'est la forme XML qui rend la structure la plus lisible :

<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.ASCIIFoldingFilterFactory"/>
    <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" expand="true"/>
    <filter class="solr.KStemFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.ASCIIFoldingFilterFactory"/>
    <filter class="solr.KStemFilterFactory"/>
  </analyzer>
</fieldType>

<field name="id"        type="string"  indexed="true"  stored="true" required="true"/>
<field name="title"     type="text_en" indexed="true"  stored="true"/>
<field name="body"      type="text_en" indexed="true"  stored="false"/>
<field name="category"  type="string"  indexed="true"  stored="true" docValues="true"/>
<field name="price"     type="pdouble" indexed="true"  stored="true" docValues="true"/>
<field name="published" type="pdate"   indexed="true"  stored="true" docValues="true"/>

Quatre attributs portent le sens. indexed rend un champ interrogeable. stored permet de renvoyer sa valeur d'origine. docValues construit une structure orientée colonne utilisée pour le tri, les facettes et les function queries — activez-le sur tout ce que vous facettez ou triez, et vous pourrez alors désactiver stored sur les gros textes que vous n'affichez jamais. Enfin, le type string n'est pas analysé : il correspond à l'identique, ce qui est exactement ce qu'on veut pour des identifiants, des codes de catégorie et des valeurs de facette, et exactement ce qu'on ne veut pas pour de la prose.

Les champs sur lesquels vous filtrez de manière structurelle doivent être de type string ; ceux dans lesquels des humains tapent des mots doivent être un TextField. Se tromper sur cette distinction est l'erreur de schéma la plus courante.

Indexer et interroger

Les documents sont envoyés en JSON sur HTTP, un par un ou par lots. Travaillez toujours par lots — les commits document par document sont la cause classique d'un indexeur lent.

curl -X POST 'http://localhost:8983/solr/catalogue/update?commitWithin=10000' \
  -H 'Content-Type: application/json' --data-binary '[
  { "id": "BK-1042", "title": "Field Guide to Estuaries",
    "body": "A practical guide to birds, tides and salt marshes.",
    "category": "reference", "price": 24.90, "published": "2026-02-11T00:00:00Z" }
]'

Utilisez commitWithin (ou les réglages autoCommit de solrconfig.xml) plutôt que des commits explicites : un hard commit écrit les segments sur disque, un soft commit rend les documents visibles aux searchers, et déclencher l'un ou l'autre à chaque document ruine le débit. La recherche en quasi temps réel, c'est un soft commit toutes les une ou deux secondes, pas à chaque écriture.

Les requêtes passent par le handler select. Le parser edismax est le bon choix par défaut pour une saisie utilisateur : il ne lève jamais d'erreur de syntaxe sur une ponctuation parasite, et il répartit une requête sur plusieurs champs avec des poids indépendants.

GET /solr/catalogue/select?defType=edismax
  &q=estuary birds
  &qf=title^4 body^1 category^2
  &pf=title^8
  &mm=2<-1
  &fq=category:reference
  &fq=price:[* TO 30]
  &sort=score desc, published desc
  &rows=20&fl=id,title,price,score

Chaque paramètre a sa raison d'être. qf liste les champs à interroger et leurs boosts. pf favorise les documents où les termes de la requête apparaissent sous forme de phrase : c'est ainsi que « estuary birds » l'emporte sur un document qui cite les deux mots dans des paragraphes sans rapport. mm (minimum should match) fixe combien de termes doivent être présents — 2<-1 signifie « avec deux termes, exiger les deux ; au-delà, tolérer qu'il en manque un ». Les filter queries fq sont booléennes, n'influencent pas le score et sont mises en cache séparément : les contraintes structurelles ont donc leur place là plutôt que dans q. Ajouter debugQuery=true renvoie la requête analysée et une explication complète du score pour chaque document ; c'est de loin l'outil de diagnostic le plus utile de Solr.

Facettes, surlignage et suggestions

Un champ de recherche seul ne fait pas une expérience de recherche. Trois fonctionnalités transforment les résultats en navigation :

Les facettes comptent les documents correspondants par valeur et alimentent la barre latérale de filtres. La JSON Facet API gère l'imbrication et les statistiques dont les anciens paramètres sont incapables :

{
  "query": "estuary birds",
  "filter": ["category:reference"],
  "facet": {
    "by_category": { "type": "terms", "field": "category", "limit": 10,
                     "facet": { "avg_price": "avg(price)" } },
    "price_bands": { "type": "range", "field": "price", "start": 0, "end": 100, "gap": 20 }
  }
}

Le surlignage renvoie des extraits où les termes trouvés sont marqués, pour que l'utilisateur voie pourquoi un résultat correspond. Le highlighter unified est le choix par défaut actuel et donne ses meilleurs résultats avec storeOffsetsWithPositions activé sur le champ.

Suggestions et orthographe. Le composant suggester fournit l'autocomplétion à partir d'un dictionnaire construit sur un champ ; le composant spellcheck propose des corrections « vouliez-vous dire » tirées des termes de l'index lui-même. Tous deux se configurent dans solrconfig.xml et doivent être reconstruits à mesure que le contenu évolue.

Faire tourner Solr en cluster

SolrCloud est le mode distribué de Solr. Une collection est un index logique découpé en shards ; chaque shard possède une ou plusieurs répliques réparties sur des nœuds différents ; une réplique par shard est élue leader et reçoit le trafic d'indexation. L'état du cluster et la configuration résident dans Apache ZooKeeper, qui gère aussi l'élection du leader. La configuration est téléversée sous forme de configset, si bien que chaque nœud exécute le même schéma et le même solrconfig.xml.

# Upload a configset, then create a sharded, replicated collection
solr zk upconfig -n catalogue -d /opt/configsets/catalogue -z zk1:2181
curl 'http://localhost:8983/solr/admin/collections?action=CREATE&name=catalogue&numShards=2&replicationFactor=2&collection.configName=catalogue'

Le dimensionnement obéit à quelques règles fiables. On découpe en shards pour la taille de l'index et le débit d'indexation, on réplique pour le débit de requêtes et la disponibilité ; une requête est envoyée à tous les shards puis les résultats sont fusionnés, donc des shards superflus ajoutent de la latence au lieu d'en retirer. Gardez un heap JVM modéré et laissez beaucoup de RAM au cache de pages du système d'exploitation, car Lucene s'appuie sur des fichiers mappés en mémoire. Un Solr mono-nœud dans un conteneur convient au développement — voir les fondamentaux de Docker — mais la production exige au moins trois nœuds ZooKeeper.

Régler la pertinence

Le travail sur la pertinence est empirique. Partez d'un ensemble de requêtes réelles dont les bons résultats ont été validés, changez une seule chose, puis mesurez à nouveau.

  • Les boosts de champ (qf) traduisent le fait qu'une correspondance dans le titre compte plus qu'une correspondance dans le corps. Gardez des rapports modérés ; des boosts extrêmes rendent tout le reste insignifiant.
  • Les boosts de phrase (pf, pf2, pf3) récompensent la proximité, et c'est généralement le changement isolé le plus rentable.
  • Les function queries intègrent au score des signaux non textuels : boost=recip(ms(NOW,published),3.16e-11,1,1) favorise les documents récents, bf=sqrt(popularity) les documents populaires. Préférez le boost multiplicatif au bf additif, afin qu'un signal fort ne puisse pas écraser le score textuel.
  • Synonymes et mots vides corrigent l'écart de vocabulaire entre votre façon d'écrire et la façon dont les utilisateurs cherchent. Entretenez le fichier de synonymes comme un actif produit — c'est là que vit la connaissance métier.
  • Le learning to rank reclasse les N premiers résultats à l'aide d'un modèle entraîné. Cela n'en vaut la peine qu'une fois les bases épuisées et si vous disposez de données de clics.

Solr n'est pas le bon outil pour tous les problèmes qui ressemblent à de la recherche. Les gros volumes de données générées par des machines, avec une rétention basée sur le temps, relèvent d'une plateforme de logs — voir la gestion centralisée des logs avec la suite Elastic — et une simple recherche par mot-clé dans des documents que vous stockez déjà peut se satisfaire des index texte de MongoDB, présentés dans le guide MongoDB.

Points clés à retenir

  • Un index inversé associe les termes à des listes de postings avec fréquences et positions, ce qui rend possible une recherche classée ; BM25 récompense la fréquence d'un terme, sa rareté et la brièveté des champs.
  • La chaîne d'analyse (filtres de caractères, tokenizer, filtres de tokens) définit ce qui est interrogeable, et les chaînes d'indexation et de requête doivent produire des tokens compatibles.
  • Utilisez string pour les valeurs structurelles exactes et un TextField pour la prose ; activez docValues sur tout ce que vous triez, facettez ou utilisez dans une fonction.
  • Indexez par lots avec commitWithin et des soft commits ; les hard commits document par document sont l'erreur de performance classique.
  • Interrogez les saisies utilisateur avec edismax, placez les contraintes structurelles dans des filtres fq mis en cache, et diagnostiquez le scoring avec debugQuery=true.
  • Réglez la pertinence avec des boosts de champ et de phrase et des function queries multiplicatives, en mesurant par rapport à un ensemble fixe de requêtes évaluées plutôt qu'à l'intuition.

Sources et lectures complémentaires

Questions fréquentes

Puis-je me contenter de la recherche full-text de ma base de données plutôt que de Solr ?
Pour un champ de recherche portant sur quelques centaines de milliers de lignes, avec des exigences de pertinence modestes, la recherche full-text de PostgreSQL ou les index texte de MongoDB suffisent largement et vous épargnent un service à exploiter. Passez à Solr lorsque vous avez besoin d'une analyse configurable par langue, d'une navigation à facettes, du surlignage, de suggestions, d'un réglage de la pertinence par boosts de champ et de phrase, ou d'un volume de requêtes qui perturberait votre base transactionnelle.
Faut-il utiliser Solr comme stockage de données principal ?
Non. Solr n'a ni transactions, ni clés étrangères, ni mise à jour en place — une mise à jour réécrit le document entier — et la réindexation après un changement de schéma est une opération courante qui suppose que les données existent ailleurs. Gardez le système de référence dans une base de données et considérez l'index Solr comme une projection dérivée de celle-ci, que l'on peut reconstruire.
Pourquoi ma phrase exacte ne trouve-t-elle rien ?
Presque toujours à cause d'un décalage d'analyse. Les chaînes d'analyse à l'indexation et à la requête doivent produire des tokens comparables : un stemmer, un filtre de mise en minuscules ou un filtre de synonymes appliqué d'un seul côté casse la correspondance sans le moindre message. Ouvrez l'écran Analysis de l'interface d'administration Solr, collez la valeur indexée et la requête, puis comparez les flux de tokens étape par étape.
En quoi Solr diffère-t-il d'Elasticsearch ?
Les deux reposent sur Apache Lucene et partagent la même structure d'index, les mêmes notions d'analyse et le scoring BM25, si bien que leur comportement de recherche est globalement comparable. Ils diffèrent par leurs API, leur clustering (SolrCloud s'appuie sur ZooKeeper, Elasticsearch sur une couche de coordination intégrée), leur licence et leur écosystème. Solr reste sous licence Apache et excelle dans la recherche classique de sites et de catalogues ; Elasticsearch domine les usages de logs et d'observabilité.