Aperçu

Recherche en texte intégral

La recherche Docsbook est un index de recherche en texte intégral PostgreSQL sur vos pages, accessible via un bouton de recherche dans l’en-tête et un champ de recherche dans la barre latérale. Elle ne coûte rien par requête — aucun modèle n’est appelé — et les requêtes qui n’ont renvoyé aucun résultat constituent l’un des deux signaux les plus utiles produits par votre documentation.

Ce que vous obtenez#

  • Un champ de recherche dans l’en-tête, la barre latérale ou les deux. Les deux peuvent fonctionner simultanément ; un seul suffit généralement.
  • Des résultats accompagnés d’un extrait mis en évidence — une fenêtre contextuelle extraite du texte de la page autour de la correspondance, et non des 200 premiers caractères de la page.
  • Des liens directs vers le titre exact. Une correspondance située dans une section utilise l’ancre réelle de cette section, afin que le lecteur arrive directement au paragraphe plutôt qu’en haut d’une longue page.
  • Les pages traduites en premier. Si le lecteur consulte une version localisée, la ligne traduite est prioritaire ; les pages non traduites apparaissent tout de même, de sorte qu’un site partiellement traduit reste entièrement consultable.
  • Un enregistrement de chaque recherche n’ayant renvoyé aucun résultat, sous la forme d’un webhook search.no_results et d’un rapport des recherches infructueuses.

Comment l’index est-il construit ?#

Écriture directe lors du rendu, pas lors du push. Une page entre dans l’index lorsque Docsbook la rend — après l’envoi de la réponse, de sorte que l’indexation ne retarde jamais l’affichage de la page. Une page traduite est indexée de la même manière lorsque sa traduction est mise en cache. Il n’y a rien à reconstruire manuellement ni de bouton de réindexation.

La conséquence mérite d’être connue : une page que personne n’a ouverte depuis sa publication ne figure pas encore dans l’index. Un site tout neuf renvoie donc peu de résultats tant que ses pages n’ont pas été visitées une première fois. Tant qu’aucune ligne n’existe, le champ de recherche se rabat sur la correspondance avec les noms de fichiers de la liste des pages, afin de ne jamais rester sans résultat.

Contenu d’une ligne :

Champ Contenu
Titre Le title du frontmatter, sinon le H1 de la page, sinon le nom du fichier
Corps Le texte brut de la page : le frontmatter, le balisage des titres, les images, la syntaxe des liens, les blocs de code délimités et la ponctuation Markdown inline sont supprimés
Sections Une entrée par h2/h3, contenant l’ancre rendue du titre et son texte, les blocs <pre> étant supprimés
Langue Vide pour la page originale, le code de langue pour chaque traduction

La recherche s’effectue sur un tsvector généré dans lequel le titre a un poids de A et le corps un poids de B — les libellés de poids de PostgreSQL existent afin que « les mots provenant de différentes parties d’un document [puissent être] pondérés différemment par les fonctions de classement » (PostgreSQL : fonctionnalités supplémentaires de recherche textuelle). Cette colonne est indexée avec GIN.

Comment une requête reçoit-elle une réponse ?#

  1. Les mots vides sont supprimés. websearch_to_tsquery « combine les termes de texte non placés entre guillemets avec l’opérateur & (AND) » (PostgreSQL : contrôle de la recherche de texte), de sorte qu’une phrase complète exige que la page contienne également chaque mot de remplissage. Une liste anglaise de 45 mots vides est d’abord supprimée, uniquement en dehors des guillemets — ainsi, "exact phrase", OR et -word continuent de fonctionner.
  2. Les requêtes en anglais sont réduites à leur racine. Pour l’anglais et le contenu non traduit, la requête et le document sont comparés avec la configuration english, qui utilise un stemmer Snowball qui « réduit les formes variantes courantes des mots à une orthographe de base, ou racine » (PostgreSQL : dictionnaires). Sans cela, une page qui contient « served » ne correspond pas à une requête contenant « serve ».
  3. Les autres langues utilisent l’index stocké. Les paramètres régionaux non anglais sont comparés au vecteur simple stocké, qui « fonctionne en convertissant le jeton d’entrée en minuscules » et n’effectue pas de recherche de racine. L’application de règles de réduction à la racine anglaises à du texte non latin produit des résultats incohérents ; elles ne sont donc volontairement pas appliquées.
  4. Le chemin anglais normalise le classement en fonction de la longueur. Pour les requêtes en anglais, ts_rank s’exécute avec l’indicateur de normalisation 1, qui « divise le classement par 1 + le logarithme de la longueur du document ». Sans cela, un journal des modifications de 76 Ko qui mentionne au passage tous les termes de la requête dépasserait dans le classement la page courte qui traite réellement de la question. Le chemin non anglais appelle ts_rank sans indicateur de normalisation ; dans ces paramètres régionaux, une page longue n’est donc pas pénalisée en raison de sa longueur — voir Limites.
  5. Une ligne par page. L’original et la traduction sont regroupés en un seul résultat, en privilégiant la langue du lecteur, puis le classement. Une correspondance dans le titre est placée au-dessus des correspondances dans le corps uniquement.
  6. L’extrait est généré côté serveur. ts_headline renvoie un fragment de 5 à 18 mots autour de la correspondance ; le client met de nouveau les termes en évidence.

Que se passe-t-il en cas de faute de frappe ?#

Rien ne correspond. La recherche Docsbook n'effectue aucune correspondance approximative, ne calcule aucune similarité par trigrammes et ne propose aucun mécanisme de secours fondé sur la distance d'édition. La racinisation couvre les flexions — serve trouve served — mais pas les fautes d'orthographe : documnetation ne trouve rien.

Il s'agit d'un compromis délibéré, et non d'un oubli, qui s'accompagne d'un mécanisme compensatoire : chaque requête sans résultat vous est signalée. Le signalement est regroupé afin qu'une recherche constitue un seul signal, et non un signal par touche saisie :

  • Le navigateur attend 1,5 seconde après que le lecteur a cessé de saisir avant de signaler un échec, et envoie immédiatement le signalement s'il ferme la boîte de dialogue en pleine recherche — lors d'une mesure effectuée sur un espace de travail réel, un lecteur qui saisissait un mot marquait une pause de 0,9 à 1,3 s entre les caractères, ce qui produisait huit signalements pour un seul mot avant la mise en place de ce mécanisme.
  • Le serveur supprime indépendamment un échec qui constitue une extension préfixée stricte du précédent, provenant du même lecteur et survenant dans un court intervalle, car le point d'accès est public et ne peut pas supposer qu'un client a appliqué une quelconque temporisation.

Ainsi, le fait que documnetation figure dans votre rapport des recherches infructueuses n'est pas un bogue de la recherche : c'est la recherche qui vous indique qu'un lecteur n'a pas pu trouver la page, et c'est précisément sur cela que vous devez agir. Les fautes récurrentes sont à corriger de préférence dans votre contenu, en nommant le terme que le lecteur saisit réellement.

Où se place la boîte de recherche#

Emplacement Idéal pour Compromis
Bouton dans l’en-tête Les visiteurs qui découvrent le site et regardent d’abord la barre supérieure Entre en concurrence avec vos liens d’en-tête pour l’espace disponible
Boîte dans la barre latérale Les lecteurs qui naviguent déjà à l’aide de l’arborescence Masquée sur les écrans étroits lorsque la barre latérale est réduite

Activez le bouton d’en-tête dans Float Widget → ConceptionEn-têteBouton de recherche. Activez la boîte de la barre latérale dans Float Widget → ConceptionBarre latérale gaucheRechercher dans la barre latérale. Les deux sont gratuits avec tous les forfaits.

Pourquoi cette méthode est la bonne (preuves)#

Règle Pourquoi cela fonctionne Source
Conserver un index lexical même lorsqu’une recherche sémantique est disponible Sur plus de 18 jeux de données de recherche, « BM25 est une base robuste » dans les configurations zero-shot, tandis que les moteurs de recherche denses « sont souvent moins performants » — votre corpus est hors domaine pour tout modèle d’embeddings, et les termes exacts sont ceux que saisissent les lecteurs techniques Thakur et al., 2021 — BEIR
Supprimer les mots vides avant que la requête n’atteigne Postgres websearch_to_tsquery applique un AND aux termes non cités, de sorte qu’un seul mot de remplissage absent de la page fait échouer toute la correspondance PostgreSQL : contrôle de la recherche textuelle
Raciner l’anglais, mais pas les autres langues La configuration simple met uniquement les termes en minuscules ; les racineurs Snowball sont spécifiques à chaque langue et réduisent les variantes à une racine PostgreSQL : dictionnaires
Normaliser le classement en fonction de la longueur du document (parcours anglais) L’indicateur 1 « divise le classement par 1 + le logarithme de la longueur du document », de sorte qu’une longue mention incidente ne puisse pas surpasser une courte page consacrée au sujet PostgreSQL : contrôle de la recherche textuelle
Accorder plus de poids au titre qu’au corps Les étiquettes de pondération permettent au classement de traiter différemment les mots provenant de différentes parties d’un document PostgreSQL : fonctionnalités supplémentaires de recherche textuelle

Le même index est l’un des deux moteurs de recherche utilisés par le chat IA — il n’y constitue pas non plus une solution de repli de moindre qualité.

Limites#

  • Aucune tolérance aux fautes de frappe. Voir ci-dessus. Si une faute d’orthographe est importante pour votre public, ajoutez le terme à la page.
  • Les blocs de code ne peuvent pas être recherchés. Le code délimité est supprimé avant l’indexation, et les blocs <pre> sont retirés du texte des sections. Un lecteur recherchant le nom d’une fonction qui apparaît uniquement dans un exemple de code ne le trouvera pas. Point encore en suspens : l’ancienne documentation affirmait que les blocs de code étaient indexés et « classés en dessous du texte » ; l’indexeur les supprime entièrement.
  • La couverture dépend du trafic, pas de votre dépôt. Une page entre dans l’index lors de son premier rendu. Une page publiée mais jamais ouverte est absente des résultats de recherche jusqu’à ce que quelqu’un l’ouvre.
  • Nous ne publions aucun chiffre de latence. Les requêtes en anglais calculent leur tsvector au moment de la requête au lieu de lire l’index simple stocké, ce qui signifie que le chemin anglais effectue davantage de travail par requête à mesure que le corpus s’agrandit. Nous n’avons publié aucun benchmark et nous n’en citerons pas tant que nous n’en aurons pas.
  • La normalisation de la longueur est limitée à l’anglais. Le chemin de requête anglais transmet à ts_rank l’indicateur de normalisation 1 ; le chemin utilisé pour les autres paramètres régionaux appelle ts_rank sans aucun indicateur, ce qui signifie qu’aucune normalisation de la longueur n’est effectuée. Sur un site non anglophone, une page très longue peut donc être mieux classée qu’une page courte traitant plus précisément la requête. Point encore en suspens jusqu’à la réconciliation des deux chemins.
  • La recherche s’effectue par projet. L’index est limité à un seul espace de travail ; il n’existe aucune recherche interprojets.
  • Le signal de recherche infructueuse est regroupé, et non exact. C’est le premier échec d’une chaîne de saisie qui est transmis à un webhook en direct ; la requête transmise peut donc être un préfixe plus court de celle finalement saisie par le lecteur. Le rapport historique récupère la requête finale.

Updated

Cette page vous a-t-elle été utile ?