Zum Inhalt springen
duxt

Die Suche pro Collection von Content als Standard behalten

Ein einziger Index über alle Quellen wurde gegen Contents Suche mit einem Index pro Collection gemessen und ist nicht der Standard der Ebene; der Build-Hook macht ihn stattdessen zu einer Provider-Ebene.

Kontext

Die Suche ist ein Index pro Collection. useDuxtSearch() ruft Contents useSearchCollection() einmal je aktiver Quelle auf, lädt damit deren sql_dump.txt herunter und baut im Browser über WASM-SQLite einen FTS5-Index. Aus dieser einen Form folgen drei Beschwerden:

  • Die Nutzlast wächst mit dem Modell. Ein Dump je aktiver Collection, und Collections vervielfachen sich als Quellen × Versionen × Sprachen. Mehrquellenbetrieb ist duxts Versprechen, und die Suche bezahlt ihn linear.
  • Quellen lassen sich nicht gegeneinander bewerten. Jede Datenbank bewertet in sich selbst, deshalb mischt interleave() die Listen je Quelle im Reihum-Verfahren — die Position ist das Einzige, was vergleichbar ist.
  • Keine Tippfehlertoleranz im Hauptpfad. FTS5 trifft Terme und Präfixe, niemals Beinahetreffer, weshalb useFuzzySearch() einen zweiten Index über dieselben Abschnitte führt und bei jedem Fehlschlag lädt.

Pagefind beantwortet im Prinzip alle drei: ein gestückelter Index, von dem ein Browser nur einen Ausschnitt holt, eine gemeinsame Rangliste mit Filtern und — als Standard von Starlight — die Antwort des nächstliegenden Vergleichsprojekts. Seine Node-API nimmt Records statt gebautem HTML, also verarbeitet addCustomRecord direkt duxt:search:records, und die Ebene muss den Seiten, die sie erweitern, nie eine Rendering-Strategie vorschreiben.

Es wurde gemessen statt diskutiert, über den echten Inhalt von www/: 33 Collections, 2.918 Seiten, 11.937 Records in fünf Sprachen und vier Versionen. scripts/search-index-bench.ts hat die Zahlen unten berechnet, und tests/search-index-bench.test.ts hält die Regel fest, nach der sie gelesen wurden.

Was die Messung ergab

Nutzlast — Pagefind gewinnt deutlich. Die erste Anfrage einer englischen Leserin kostet heute 1.448 KB (1.108 KB WASM-SQLite samt Worker, 340 KB Dumps über die sieben Collections im Umfang), jede weitere ist gratis. Dieselbe Anfrage gegen einen Pagefind-Index kostet 150 KB und jede weitere 13 KB. Selbst wenn man die Datenbank-Engine ganz herausrechnet — Content lädt sie bei Bedarf, und ein clientseitiges queryCollection beim Routenwechsel kann sie ebenfalls herbeirufen — stehen 340 KB gegen 150 KB, und die 340 KB sind die Hälfte, die mit jeder weiteren Quelle wächst, während die 150 KB es nicht tun.

Rangfolge — Pagefind gewinnt, je Sprache. Ein Index über alle Quellen lieferte eine einzige Rangliste, und die Filter ergaben sich ohne Anpassung aus dem Record-Vertrag: source enthielt /, /demo, /demo/api, /demo/changelog, /demo/changelog-flat, /demo/collection, /releases, /tf, und version enthielt main, v0.1.0, v0.2.0, v0.2.6, v0.3.4, v1.x, v2.x, v3.x. interleave() würde entfallen — für die Quellen. Ganz entfiele es nicht: Pagefind indiziert jede Sprache getrennt und wählt zur Laufzeit eine aus, seine Dokumentation beschreibt keinen Weg, zwei gemeinsam zu durchsuchen, und mergeIndex gegen denselben Basispfad wird übersprungen. Der Sprach-Fallback, auf den eine unübersetzte Seite angewiesen ist, braucht also weiterhin eine zweite Instanz und zwei unvergleichbare Bewertungsräume.

Tippfehlertoleranz — Pagefind verliert und nimmt den Fallback mit. Pagefind stemmt je Sprache und hat keine unscharfe Suche. collecton wurde über das Präfix zu 360 Treffern gerettet; verison — ein Dreher — lieferte drei unpassende Seiten. Das ist schlechter als das ersetzte FTS5-Verhalten, denn es ist kein leeres Ergebnis: useDuxtSearch() weicht genau dann auf Fuse aus, wenn der exakte Durchgang nichts liefert, also sind drei falsche Antworten drei Gründe, warum der Fallback nie auslöst. Fuse müsste bleiben, und der Auslöser müsste um einen Provider herum neu geschrieben werden, der selbstbewusst falsch antwortet.

Ausgabe — eine Datei je indiziertem Abschnitt. Der Index schrieb 12.066 Dateien und 6,80 MiB für 11.937 Records, ein Fragment je Abschnitt, gegen die 1.414, die ein Cloudflare-Build von www/ heute schreibt. Cloudflare Workers begrenzt eine Version im kostenlosen Tarif auf 20.000 statische Dateien. Eine Dokumentationsseite, deren Index je Abschnitt, Quelle, Version und Sprache um eine Datei wächst, erreicht das bei neunzig Seiten Ausgangsmaterial, und wer eine Sprache ergänzt, erführe es an einem fehlgeschlagenen Deploy.

Die Paketierung ist kein Hindernis. pagefind@1.5.2 steht unter MIT, liefert sieben vorgebaute Binaries als optionalDependencies für jede Plattform, die engines zusagt, und braucht weder einen Postinstall-Download noch eine node-gyp-Toolchain — die Hürde, an der better-sqlite3 scheiterte. Das Indizieren von 11.937 Records dauerte 6,8 Sekunden, nach 10,0 lag der ganze Index im Speicher. Nichts daran widerspricht ADR-0006: alles geschieht während des Builds.

Entscheidung

Contents Suche pro Collection bleibt der Standard der Ebene. Pagefind wird nicht als das übernommen, was duxt ausliefert.

Von den drei Problemen löst ein einziger Index eines ganz, eines nur innerhalb einer Sprache, und eines verschlimmert er — während er ein Wachstumsgesetz für die Dateizahl mitbringt, das mit der Plattform kollidiert, auf die die eigene Seite der Ebene deployt. Einen Standard ändert man für einen klaren Gewinn, nicht für einen Tausch, und zwei von drei ist eine andere Form, keine bessere.

Pagefind bleibt verfügbar — als Ebene, nicht als Standard.duxt:search:records wurde genau dafür geöffnet, und die Messung bestätigte, dass der Vertrag keine Anpassung braucht: url, title, content, source, version und locale bilden Feld für Feld auf addCustomRecord ab, und die entstehenden Filter sind die Quellidentitäten und Versionsbezeichnungen, die duxt ohnehin berechnet. Wer einen einzigen Index will, schreibt extends: ['@kirchdev/duxt', 'duxt-pagefind'] und bezahlt die Dateizahl wissentlich.

Die Regel überlebt den Kandidaten. searchIndexVerdict in scripts/search-index-bench.ts nennt die sechs Bedingungen, die ein Ersatz erfüllen muss — spürbar kleinere Nutzlast, Quellen gemeinsam bewertet, Tippfehlertoleranz erhalten, ausschließlich zur Bauzeit, Binaries für alles, was engines zusagt, Ausgabe innerhalb der Dateigrenze des Deployments — und gibt zurück, welche ein Kandidat verfehlt. Das „Nein“ dieses ADR ist ein Wert, den diese Funktion liefert.

Konsequenzen

Leser behalten eine Suche, die mehr herunterlädt und danach offline antwortet, und behalten die Tippfehlertoleranz, die Fuse liefert. Die Beschwerde über die Nutzlast bleibt ungelöst: wer viele Quellen einbindet, bezahlt weiterhin je aktiver Collection, und das ist der stärkste Grund, die Sache erneut aufzunehmen.

interleave() bleibt, und mit ihm der Kommentar, der erklärt, warum es keine vergleichbaren Bewertungen zum Sortieren gibt.

Ein erneuter Anlauf ist billig, und die Bedingungen stehen geschrieben. Bekäme Pagefind unscharfe Suche oder schriebe es Fragmente in weniger Dateien, kippten zwei Bedingungen; ein Kandidat, der alle Sprachen in einen durchsuchbaren Raum indiziert, kippte eine dritte. Lasst den Benchmark erneut laufen, statt den Fall erneut zu verhandeln — und schreibt ein neues ADR, das dieses ablöst, denn diese Aufzeichnungen werden nur angehängt.

Nichts hiervon schränkt einen externen Dienst ein. Meilisearch und Typesense beantworten alle drei Beschwerden und waren nie Kandidaten für einen Standard, weil ein Standard niemandem abverlangen darf, einen Dienst zu betreiben oder zu kaufen; sie verarbeiten denselben Hook.

War diese Seite hilfreich?