Aller au contenu
duxt

Conserver la recherche par collection de Content comme défaut

Un index unique sur toutes les sources a été mesuré face à la recherche à un index par collection de Content et n'est pas le défaut de la couche ; le hook de build en fait plutôt une couche fournisseur.

Contexte

La recherche, c'est un index par collection. useDuxtSearch() appelle le useSearchCollection() de Content une fois par source active, ce qui télécharge le sql_dump.txt de cette collection et construit un index FTS5 dans le navigateur au-dessus de SQLite en WASM. Trois reproches découlent de cette seule forme :

  • La charge grandit avec le modèle. Un dump par collection active, et les collections se multiplient en sources × versions × langues. Le multi-source est la proposition de duxt, et la recherche la paie linéairement.
  • Les sources ne peuvent pas être classées les unes contre les autres. Chaque base classe à l'intérieur d'elle-même, d'où interleave() qui alterne les listes par source : la position est la seule chose comparable qui existe.
  • Aucune tolérance aux fautes de frappe sur le chemin principal. FTS5 fait correspondre des termes et des préfixes, jamais des approximations, si bien que useFuzzySearch() porte un second index sur les mêmes sections et se charge à chaque échec.

Pagefind répond en principe aux trois : un index découpé dont le navigateur ne prend qu'une tranche, une liste classée unique avec des filtres et — en tant que défaut de Starlight — la réponse du projet comparable le plus proche. Son API Node prend des enregistrements plutôt que du HTML construit, donc addCustomRecord consomme directement duxt:search:records, et la couche n'a jamais à imposer une stratégie de rendu aux sites qui l'étendent.

Cela a été mesuré plutôt que débattu, sur le contenu réel de www/ : 33 collections, 2 918 pages, 11 937 enregistrements en cinq langues et quatre versions. scripts/search-index-bench.ts a calculé les chiffres ci-dessous, et tests/search-index-bench.test.ts fixe la règle avec laquelle ils ont été lus.

Ce que la mesure a trouvé

Charge — Pagefind gagne, nettement. La première requête d'une lectrice anglophone coûte aujourd'hui 1 448 Ko (1 108 Ko de SQLite en WASM et son worker, 340 Ko de dumps sur les sept collections concernées) et chaque requête suivante est gratuite. La même requête contre un index Pagefind coûte 150 Ko, et chaque suivante 13 Ko. Même en retirant entièrement le moteur de base de données — Content le charge à la demande, et un queryCollection côté client lors d'un changement de route peut l'invoquer aussi — cela fait 340 Ko contre 150 Ko, et les 340 Ko sont la moitié qui grandit avec chaque source ajoutée, quand les 150 Ko ne bougent pas.

Classement — Pagefind gagne, par langue. Un index sur toutes les sources a renvoyé une liste classée unique, et les filtres sont sortis du contrat d'enregistrement sans la moindre adaptation : source contenait /, /demo, /demo/api, /demo/changelog, /demo/changelog-flat, /demo/collection, /releases, /tf, et version contenait main, v0.1.0, v0.2.0, v0.2.6, v0.3.4, v1.x, v2.x, v3.x. interleave() disparaîtrait — pour les sources. Pas entièrement : Pagefind indexe chaque langue séparément et en sélectionne une à l'exécution, sa documentation ne décrit aucun moyen d'en chercher deux ensemble, et mergeIndex contre le même chemin de base est ignoré ; le repli linguistique dont dépend une page non traduite réclame donc toujours une seconde instance et deux espaces de score incomparables.

Tolérance aux fautes — Pagefind perd, et emporte le repli avec lui. Pagefind lemmatise par langue et n'a pas de correspondance floue. collecton a été rattrapé par préfixe jusqu'à 360 résultats ; verison — une inversion — a renvoyé trois pages sans rapport. C'est pire que le comportement FTS5 qu'il remplace, car ce n'est pas un résultat vide : useDuxtSearch() se rabat sur Fuse précisément quand la passe exacte ne renvoie rien, donc trois mauvaises réponses sont trois raisons pour que le repli ne se déclenche jamais. Fuse devrait rester, et le déclencheur qui l'appelle devrait être réécrit autour d'un fournisseur qui répond avec assurance et à côté.

Sortie — un fichier par section indexée. L'index a émis 12 066 fichiers et 6,80 Mio pour 11 937 enregistrements, un fragment par section, contre les 1 414 qu'écrit aujourd'hui un build Cloudflare de www/. Cloudflare Workers plafonne une version à 20 000 fichiers statiques sur l'offre gratuite. Un site de documentation dont l'index grandit d'un fichier par section, par source, par version et par langue atteint ce plafond avec quatre-vingt-dix pages de matière première, et qui ajouterait une langue le découvrirait à un déploiement en échec.

L'empaquetage n'est pas un obstacle. pagefind@1.5.2 est sous MIT, livre sept binaires précompilés en optionalDependencies couvrant toute plateforme promise par engines, et n'exige ni téléchargement en postinstall ni chaîne node-gyp — la barre que better-sqlite3 n'avait pas franchie. Indexer 11 937 enregistrements a pris 6,8 secondes et, à 10,0, l'index entier tenait en mémoire. Rien là-dedans n'entre en conflit avec ADR-0006 : tout se passe pendant le build.

Décision

La recherche par collection de Content reste le défaut de la couche. Pagefind n'est pas adopté comme ce que duxt livre.

Des trois problèmes, un index unique en résout un entièrement, un autre seulement à l'intérieur d'une langue, et il aggrave le troisième — tout en ajoutant une loi de croissance du nombre de fichiers qui se heurte à la plateforme où le site de la couche se déploie lui-même. On change un défaut pour un gain net, pas pour un échange, et deux sur trois est une autre forme, pas une meilleure.

Pagefind reste disponible, en couche et non en défaut.duxt:search:records a été ouvert exactement pour cela, et la mesure a confirmé que le contrat n'a besoin d'aucune adaptation : url, title, content, source, version et locale se projettent champ par champ sur addCustomRecord, et les filtres qui en sortent sont les identités de source et les libellés de version que duxt calcule déjà. Qui veut un index unique écrit extends: ['@kirchdev/duxt', 'duxt-pagefind'] et paie le nombre de fichiers en connaissance de cause.

La règle survit au candidat. searchIndexVerdict, dans scripts/search-index-bench.ts, énonce les six conditions qu'un remplaçant doit remplir — charge nettement moindre, sources classées ensemble, tolérance aux fautes préservée, uniquement au build, binaires couvrant engines, sortie sous le plafond de fichiers du déploiement — et renvoie celles qu'un candidat manque. Le « non » de cet ADR est une valeur que cette fonction renvoie.

Conséquences

Les lecteurs gardent une recherche qui télécharge davantage puis répond hors ligne, et gardent la tolérance aux fautes qu'apporte Fuse. Le reproche sur la charge reste non résolu : qui rassemble beaucoup de sources paie toujours par collection active, et c'est la raison la plus forte d'y revenir.

interleave() reste, ainsi que le commentaire qui explique pourquoi il n'y a pas de scores comparables avec quoi classer.

Y revenir coûte peu et les termes sont écrits. Que Pagefind acquière la correspondance floue, ou émette ses fragments en moins de fichiers, et deux conditions basculent ; un candidat indexant toutes les langues dans un seul espace de recherche en ferait basculer une troisième. Relancez le banc d'essai plutôt que de replaider le dossier — et consignez un nouvel ADR qui remplace celui-ci, car ces enregistrements ne font que s'ajouter.

Rien de tout cela ne contraint un service externe. Meilisearch et Typesense répondent aux trois reproches et n'ont jamais été candidats à un défaut, parce qu'un défaut ne peut pas exiger d'un consommateur qu'il opère ou achète un service ; ils consomment le même hook.

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