9 points par GN⁺ 2025-11-18 | 1 commentaires | Partager sur WhatsApp
  • Mise en place d’une architecture de moteur de recherche fonctionnant sans service externe à partir d’une base de données existante, en se concentrant sur la tokenisation, la pondération et le scoring
  • L’idée centrale consiste à tokeniser tout le texte puis à le stocker, puis à faire correspondre les tokens de la même manière au moment de la recherche afin de calculer la pertinence
  • Une combinaison de tokenizers Word, Prefix et N-Gram permet de gérer à la fois les correspondances exactes, les correspondances partielles et les fautes de frappe, chaque tokenizer ayant son propre poids
  • Grâce à un système de pondération et à un algorithme de scoring basé sur SQL, la longueur des documents, la diversité des tokens et la qualité moyenne sont évaluées ensemble
  • La structure offre une grande extensibilité et une forte transparence, ce qui permet d’ajouter librement de nouveaux tokenizers ou types de documents, d’ajuster les poids et de modifier le scoring

Pourquoi construire son propre moteur de recherche

  • Les services externes comme Elasticsearch ou Algolia sont puissants, mais impliquent une API complexe à apprendre et une charge de gestion d’infrastructure
  • Lorsqu’on a simplement besoin d’une fonction de recherche qui s’intègre à la base de données existante et reste facile à déboguer, il est utile de la construire soi-même
  • L’objectif est un moteur de recherche simple qui renvoie des résultats pertinents sans dépendance externe

Concept clé : tokenisation et mise en correspondance

  • Le principe de base consiste à tokeniser tout le texte puis à le stocker, puis à générer les tokens de la même manière au moment de la recherche pour les faire correspondre
  • Lors de la phase d’indexation, le contenu est découpé en unités de tokens puis stocké avec des poids
  • Lors de la phase de recherche, la requête est tokenisée de la même façon afin de trouver les tokens correspondants et de calculer un score
  • Lors de la phase de scoring, les poids stockés sont utilisés pour produire un score de pertinence

Conception du schéma de base de données

  • Deux tables sont utilisées : index_tokens et index_entries
    • index_tokens : stocke les tokens uniques et les poids par tokenizer
    • index_entries : relie les tokens aux documents et stocke le score final en intégrant les poids du champ, du document et du tokenizer
  • Formule de calcul du poids final :
    field_weight × tokenizer_weight × ceil(sqrt(token_length))
  • Des index sont définis pour la consultation des documents, la recherche de tokens, les requêtes par champ et le filtrage par poids

Système de tokenisation

  • WordTokenizer : séparation par mots, suppression des mots courts, pour les correspondances exactes (poids 20)
  • PrefixTokenizer : génération de préfixes de mots, pour l’autocomplétion et les correspondances partielles (poids 5)
  • NGramsTokenizer : génération de combinaisons de caractères de longueur fixe, pour gérer les fautes de frappe et les correspondances partielles (poids 1)
  • Tous les tokenizers appliquent en commun la mise en minuscules, la suppression des caractères spéciaux et la normalisation des espaces

Système de pondération

  • Poids du champ : reflète l’importance du titre, du corps, des mots-clés, etc.
  • Poids du tokenizer : Word > Prefix > N-Gram
  • Poids du document : calculé en combinant les deux éléments précédents et la longueur du token
  • La fonction ceil(sqrt()) est utilisée pour atténuer l’influence des tokens longs ; au besoin, elle peut être remplacée par une fonction logarithmique ou linéaire

Service d’indexation

  • Seuls les documents qui implémentent IndexableDocumentInterface peuvent être indexés
  • Lors de la création ou de la modification d’un document, l’indexation est effectuée via un écouteur d’événements ou des commandes (app:index-document, app:reindex-documents)
  • Procédure :
    • suppression de l’ancien index puis génération de nouveaux tokens
    • exécution de tous les tokenizers sur chaque champ
    • vérification de l’existence du token puis création si nécessaire (findOrCreateToken)
    • insertion par lot (batch insert) dans index_entries avec les poids calculés
  • La structure permet d’éviter les doublons, d’améliorer les performances et de gérer les mises à jour

Service de recherche

  • La requête est traitée avec les mêmes tokenizers afin d’obtenir le même ensemble de tokens que lors de l’indexation
  • Après suppression des doublons, les tokens sont triés par longueur décroissante (les plus longs d’abord) et limités à 300 au maximum
  • Une requête SQL joint les tokens et les documents, puis calcule et trie le score de pertinence
  • Le résultat est renvoyé sous la forme SearchResult(documentId, score)

Algorithme de scoring

  • Score de base : SUM(sd.weight)
  • Correction de diversité des tokens : LOG(1 + COUNT(DISTINCT token_id))
  • Correction du poids moyen : LOG(1 + AVG(weight))
  • Pénalité de longueur du document : 1 / (1 + LOG(1 + token_count))
  • Normalisation : division par le score maximal pour ramener le résultat dans l’intervalle 0~1
  • Un filtre de poids minimal du token (st2.weight >= ?) permet d’éliminer les correspondances insignifiantes à faible poids

Traitement des résultats

  • Les résultats de recherche sont renvoyés sous forme d’ID de document et de score, puis convertis en documents réels via le repository
  • La fonction FIELD() est utilisée pour conserver l’ordre des résultats de recherche lors de la récupération des documents

Extensibilité du système

  • Un nouveau tokenizer peut être ajouté en implémentant TokenizerInterface
  • Un nouveau type de document peut être enregistré en implémentant IndexableDocumentInterface
  • Les poids et la logique de scoring peuvent être ajustés simplement en modifiant le SQL

Conclusion

  • Cette structure constitue un moteur de recherche simple mais réellement fonctionnel, offrant des performances suffisantes sans infrastructure externe
  • Ses atouts sont une logique claire, un contrôle complet et un débogage facile
  • Elle souligne qu’un code que l’on comprend et contrôle directement peut avoir plus de valeur qu’un système complexe

1 commentaires

 
GN⁺ 2025-11-18
Avis Hacker News
  • L’idée de base de la recherche est simple et le domaine est intéressant.
    Mais les vraies difficultés commencent quand il faut gérer de gros volumes de données et traiter des requêtes ambiguës.
    Une approche fondée sur un SGBD peut convenir à l’échelle d’un petit site web, mais elle atteint vite ses limites à l’échelle de la Wikipédia anglophone.
    Pour débuter, le SeIRP e-book est une excellente ressource gratuite.

    • Les gros volumes de données sont évidemment difficiles à gérer, mais je pense que traiter les requêtes ambiguës est un sous-problème de la question « comment déterminer le résultat le plus pertinent ».
      Le fait qu’il n’existe pas de bonne réponse évidente rend cela particulièrement délicat.
      Google affiche parfois des publicités comme étant les « résultats les plus pertinents », ce qui fait de Marginalia Search un bon contre-exemple.
      Je me demande si vous avez déjà consulté les articles du TREC.
    • Aujourd’hui, je pense que le plus gros problème est plutôt d’éviter le spam SEO.
      Un moteur de recherche doit lutter en permanence contre des acteurs adverses qui cherchent des revenus publicitaires.
      Cela devient un jeu du chat et de la souris sans fin, où il faut changer sans cesse les métriques de qualité pour éviter qu’elles soient détournées.
    • Si on fait tourner avec SQLite, sur un seul serveur (de l’ordre de mille dollars), un processus métier centré sur du texte, je me demande quelle taille de dépôt de documents on peut gérer concrètement.
      J’aimerais savoir quelle taille de corpus on pourrait interroger avec une base d’environ 5 secondes par requête et 12 requêtes par minute.
    • J’aime vraiment beaucoup Marginalia Search.
    • La difficulté de la recherche ne tient pas seulement à la taille des données, mais aussi au choix des résultats à renvoyer.
      Par exemple, il est difficile de décider si l’article wiki sur Gilligan’s Island ou un blog de fan est un « meilleur » résultat.
      Si on ajoute à cela la manipulation du classement ou le keyword stuffing, le défi devient bien plus complexe qu’un simple problème de passage à l’échelle.
  • La recherche est vraiment difficile.
    Même des entreprises aussi riches en ressources et en savoir-faire qu’Apple, Microsoft ou OpenAI ont une qualité de recherche médiocre.
    Ce n’est pas qu’un problème technique.

    • Si la plupart des entreprises implémentent mal la recherche, c’est parce que leur culture d’entreprise et leur façon de développer entrent en conflit avec ce type de travail.
      Pour améliorer la qualité de recherche, il faut ajuster finement les paramètres de classement, et ce genre de travail se planifie mal dans des cadres de gestion comme les sprints ou Jira.
      C’est au fond un domaine qui exige confiance et autonomie pour les développeurs.
    • Mais pour certaines entreprises, la mauvaise qualité vient simplement du fait que la recherche n’est pas une priorité.
      Elles investissent des milliards dans les modèles d’IA, tandis que la web app ou la recherche passent au second plan, d’où ce résultat.
  • J’ai travaillé il y a une dizaine d’années avec un collègue en doctorat sur la conception de moteurs de recherche.
    Il parlait avec énormément de passion de l’intégration entre recherche et base de données, et j’ai beaucoup appris grâce à lui.
    J’aimerais un jour explorer en profondeur les entrailles d’Apache Solr et de Lucene.

    • Moi aussi, je pourrais parler pendant des heures de mon domaine, mais il n’y a pas tant de gens que ça que les détails d’implémentation des grands systèmes intéressent.
  • À l’époque, il n’existait pas de solution de recherche open source, donc il fallait la construire soi-même.
    La leçon tirée de cette expérience est : ne construisez pas votre propre moteur de recherche.
    D’innombrables personnes ont passé des années sur ce problème, et si on le fait soi-même, on tombe dans un enfer de maintenance sans fin.
    Dès que commencent les demandes du genre « ajoutez une correction orthographique » ou « l’an prochain, ajoutons aussi une taxonomie », c’est terminé.

  • J’avais vraiment adoré le cours de David Evans, professeur à la Virginia University, sur la construction d’un moteur de recherche.
    Construire soi-même un « moteur de recherche classique » était un projet très amusant.
    Voir le lien du cours et la playlist YouTube.

    • J’ai suivi ce cours moi aussi, et c’était un cours dense et passionnant, même pour des programmeurs débutants.
  • Les moteurs de recherche que j’utilise souvent ont le défaut d’ignorer les sigles ou mots de 2 à 3 lettres.
    Quand on cherche un mot court comme « mp3 » ou « PHP » et qu’il est supprimé, c’est vraiment pénible.

  • J’ai lu Programming Collective Intelligence de Toby Segaran, et cela m’a donné des idées sur la recherche, les recommandations et les classifieurs, entre autres.

    • Moi aussi j’avais aimé ce livre, mais j’ai vu l’auteur dire plus tard sur YouTube que « c’est dépassé maintenant, donc il ne faut plus l’utiliser ».
    • C’était vraiment un excellent livre, et je me demande s’il existe une édition actualisée pour 2025.
  • C’était un article intéressant.
    Je me demande jusqu’où va le niveau d’optimisation des tokenizers utilisés par les moteurs de recherche populaires.

  • Je me demande à quel point ce système serait scalable.
    Elasticsearch montre des performances assez impressionnantes, même au-delà de l’échelle recommandée.

  • Un moteur de recherche textuel simple n’est pas difficile à créer.
    Mais créer un bon moteur de recherche est une toute autre histoire.
    Implémenter simplement un algorithme comme BM25 ne suffit pas.
    La plupart des entreprises que j’ai conseillées utilisaient au départ leur propre solution, puis ont fini par migrer vers Elasticsearch ou Opensearch.
    Une implémentation maison paraît simple au début, mais avec le temps elle se complique à cause des problèmes de ranking et de dégradation des performances.
    Des symptômes comme « c’est lent » ou « les résultats n’ont aucun sens » reviennent sans cesse.
    Elasticsearch résout ce type de problème depuis déjà 10 ans, et c’est aujourd’hui bien plus mature.
    On entend aussi que « la configuration est difficile », mais de nos jours la plupart des choses sont auto-configurées, et il existe de nombreux services managés.
    C’est même plus facile à manier que Postgres.
    Au final, l’essentiel est d’optimiser le mapping de l’index.
    Certains disent « on n’a pas besoin de ces fonctions avancées », mais en pratique la qualité de la recherche est directement liée à la survie du business.
    Si l’on veut une vraie bonne recherche, il faut au bout du compte accepter cette complexité.

    • J’utilise moi aussi Elasticsearch par défaut.
      Des alternatives émergentes comme SeekStorm, souvent mentionnées récemment sur HN, ont l’air intéressantes, mais je n’ai pas encore vu de cas de production réels.
    • Je suis d’accord avec l’idée qu’« il n’y a pas de fonctionnalité inutile ».
      En particulier, le conseil de désactiver le dynamic mapping et d’empêcher l’indexation des champs inutiles m’a semblé utile.
    • Je me demande ce que vous pensez de ManticoreSearch.
      Si je ne me trompe pas, c’est un projet plus ancien que Lucene.