Suche
Die Suche läuft über search-Blöcke in der Sammlungs-YAML plus Abfrage per q und qName.
Suchbegriffe: Volltextsuche, Hybrid-Suche, Suchindex neu aufbauen.
Index anlegen
Abschnitt betitelt „Index anlegen“content.yml definiert drei Indexe:
| Index | Schlüssel | Zweck |
|---|---|---|
content_text |
$text:$**, defaultLanguage: none |
Volltext über alle Felder |
path_lang |
path + lang, unique |
Pfad je Sprache eindeutig |
translation |
translationKey + lang, unique |
Übersetzung je Sprache eindeutig |
navigation.yml und medialib.yml nutzen nur einen default-Index im Modus regex (kein Textindex nötig).
search-Blöcke
Abschnitt betitelt „search-Blöcke“# content.yml — search (gekürzt)search: - name: combined # Hybrid aus semantic + fuzzy mode: combined rrf: { k: 60, topK: 100, weights: { semantic: 1.5, fuzzy: 1.0 } } - name: fuzzy mode: ngram autoRegenerate: true fields: [name, path, meta.title, meta.description, blocks.headline, blocks.subline, blocks.text, blocks.items.title, blocks.items.text, blocks.items.question, blocks.items.answer] - name: semantic mode: vector autoRegenerate: true fields: [name, meta.title, meta.description, blocks.headline, blocks.subline, blocks.text, blocks.items.title, blocks.items.text, blocks.items.question, blocks.items.answer] vector: { provider: bge_m3 }qName |
Sammlung | Modus |
|---|---|---|
combined |
content |
Hybrid (RRF aus semantic + fuzzy) |
fuzzy |
content |
n-gram, fehlertolerant |
semantic |
content |
Vektor (bge_m3) |
default |
tag, navigation, comment, medialib |
regex |
Lesen mit q
Abschnitt betitelt „Lesen mit q“Grundlagen (Filter, Lookup, Facetten) stehen unter Lesen und suchen. Hier nur die Suche:
GET …/content?q=kontakt&qName=combinedGET …/tag?q=heroGET …/content?q=kontakt&qName=combined&qExplain=1qist der Suchtext,qNamewählt den Block (ohneqNamegreift der Standard).qundqNamegelten auch für_countund_facet— Facetten zählen dann nur die Treffer (Lesen und suchen).qExplain=1zeigt die Gewichtung — nützlich beim Debuggen.- Suche mit und ohne
lookuptesten:?q=…&qName=combined&lookup=blocks.heroImage.image:medialib,blocks.image:medialiblöst Treffer-Bilder gleich auf.
Hybrid-Kurzüberblick
Abschnitt betitelt „Hybrid-Kurzüberblick“combined mischt Vektor- und n-gram-Treffer per Reciprocal Rank Fusion (internal/rankfusion, tibi-server):
score(doc) += weight / (k + rank) # k = 60, weights: semantic 1.5, fuzzy 1.0topK: 100begrenzt die Kandidaten je Quelle und kappt das Fusionsergebnis (MergeRRF). Ohne Angabe imrrf-Block gilttopK: 500.- Das gekappte Fusionsergebnis ist die Treffermenge der Liste und das Standardfenster der Facetten (
_facet?q=…). - Weitere Strategien (
score_rrf,score_sum,rank_score,hybrid), Normierung (minmax,zscore), Gewichte (scoreWeight) und Quellen-Config (sourceConfig) existieren im Code und sind alsrrf-Block in der Projekt-YAML einstellbar — aus Projektsicht reichtcombinedmit den Starter-Gewichten. - Alte Score-Beispiele aus Erzähl-Docs nicht übernehmen — nur diese Formel gilt.
Suchmodi im Überblick
Abschnitt betitelt „Suchmodi im Überblick“Alle Modi aus collection.go:314 — fields gilt für regex und ngram, vector.provider muss auf einen Server-Provider zeigen (collection.go:377):
| Modus | Einsatzfall | Braucht Index/Aufbau |
|---|---|---|
text |
MongoDB-Volltext, eine Textstelle pro Sammlung | Ja — Mongo-Textindex (hasTextIndex; nur ein Textindex je Sammlung) |
regex |
Einfache Teilübereinstimmung (default-Block in tag, navigation, comment, medialib) |
Nein — läuft direkt auf den Feldern, kein Aufbau, keine Queue |
eval |
Eigene JS-Bewertung pro Treffer (eval-Ausdruck) |
Nein — Ausdruck läuft zur Abfragezeit, optional score.expr |
filter |
Feste MongoDB-Filtervorlage mit $subst |
Nein — Filter zur Abfragezeit, optional score.expr |
ngram |
Fehlertolerante Teilwortsuche (Starter-fuzzy) |
Ja — _search-Anreicherung mit autoRegenerate, Queue bis zum Worker-Lauf |
vector |
Semantische Suche (Starter-semantic, Provider bge_m3) |
Ja — Embeddings über Embedding-Provider, Queue bis zum Worker-Lauf |
combined |
Hybrid aus mehreren Blöcken (Starter-combined = RRF aus semantic + fuzzy) |
Je beteiligtem Block (siehe oben); Fusion selbst braucht keinen eigenen Index |
Index aufbauen und prüfen
Abschnitt betitelt „Index aufbauen und prüfen“- Bearbeite Einträge normal —
fuzzyundsemantichabenautoRegenerate: trueund bauen sich selbst nach. - Beobachte im tibi-admin den Hinweis
SearchIndexBuildNotice: Er erscheint im Projekt-Header (Summe offener_queue-Einträge allerngram-/vector-Sammlungen) und im Sammlungs-Header der betroffenen Sammlung. - Warte, bis der Hinweis nach dem nächsten Polling von selbst verschwindet (
_queue= 0). Derregex-Modus (default) hat keine Queue und keinen Hinweis. - Bleibt die Queue stehen oder hast du die Config geändert:
POST /_/:project/_/admin/regenerate-searchaufrufen — optional mit Body{ collection, modes, names, ids, filter, force }(admin_regenerate_search.go:17-25). Mit?async=1nur einreihen und sofort antworten ({"message": "queued", …}), ohnefilterauch synchron möglich. Ungültige IDs ohne einzige gültige Hex-ID brechen mit400 no valid document IDs in requestab. - Status prüfen:
GET /_/:project/_/admin/search-worker/statuszeigt pro Provider Aktiv-Flag, Queue-Tiefe, laufenden Job und Fortschrittszähler (admin_search_status.go:10-15). Auth jeweils: Scopeproject.regenerate-searchplus Token-FlagAllowRegenerateSearch.
Stoppwörter für ngram (ngram.stopwords)
Abschnitt betitelt „Stoppwörter für ngram (ngram.stopwords)“Füllwörter wie „zum”, „der” oder „für” treffen in einer n-gram-Suche jeden Text, in dem sie irgendwo vorkommen — auch Beschreibungen, in denen sie nichts aussagen. ngram.stopwords entfernt sie aus beiden Seiten: aus den gespeicherten N-Grammen und aus der Suchanfrage. Einseitig wäre falsch: stehen gebliebene Stoppwort-N-Gramme blähen die Ähnlichkeitswerte auf und drücken die Unterschiede zwischen relevanten und irrelevanten Treffern zusammen.
search: - name: fuzzy mode: ngram fields: [name, meta.title, meta.description] ngram: sizes: [2, 3] stopwords: presets: [de] # de, en, fr, es, it words: [zum, zur] # optionale Ergänzungen, werden gemerged- Die Presets enthalten reine Funktionswörter: Artikel, Pronomen, Präpositionen, Konjunktionen, Hilfsverben und bedeutungsfreie Adverbien (
internal/utils/ngram_stopwords.go). Negationswörter wie „nicht” oder „kein” bleiben bewusst draußen — „nicht wasserdicht” würde sonst zur Suche nach „wasserdicht”. - Einträge werden mit derselben Normalisiererkette gebildet wie Dokumente und Anfragen. Ohne
normalize-Block laufen alle Zusatznormalisierer, aus „für” wirdfuer; nur mitnormalize.use: []bleibt es beim Standard-Normalisierer und damit beifur. Einträge, die nicht genau ein Wort ergeben, erzeugen einen Projekt-Fehler (internal/models/collection_setup.go). stopwordsgilt nur fürmode: ngram. Ein Block mit anderem Modus und gesetzten Stoppwörtern erzeugt einen Projekt-Fehler; das Projekt lädt trotzdem, die Stoppwörter bleiben in diesem Fall aber aus.- Ein Set gilt für den ganzen Block, nicht je Sprache: Es wirkt auf jedes Wort in den konfigurierten Feldern. Bei gemischtsprachigen Inhalten lieber
wordsstatt Presets nutzen oder einen Block je Sprache anlegen — sonst verschwinden Wörter der falschen Sprache (etwacar/oraus deutschen Texten, wennfraktiv ist).wordsergänzt das Set nur; ein Preset-Wort lässt sich damit nicht wieder freistellen. - Eine Anfrage, die nur aus Stoppwörtern besteht (oder nur aus Begriffen kürzer als jede konfigurierte Größe), liefert null Treffer statt der ganzen Sammlung (
internal/utils/ngram.go).
Nach Änderungen neu aufbauen
Abschnitt betitelt „Nach Änderungen neu aufbauen“ngram.stopwords ist Teil des Anreicherungs-Hashs. Hinzufügen ist unkritisch: Die Anfrage verwirft die Wörter, die Dokumente bleiben über ihre übrigen Begriffe auffindbar. Scores können bis zum Neuaufbau abweichen, weil Kreuz-Wort-N-Gramme ohne die Stoppwort-Lücke neu entstehen. Entfernen braucht den Neuaufbau zuerst — sonst liefern diese Wörter null Treffer, bis jedes Dokument neu angereichert ist:
stopwordsin der Projekt-YAML ändern und das Projekt neu laden.POST /_/:project/_/admin/regenerate-searchmit{ collection, modes: ["ngram"] }aufrufen — oderautoRegenerate: truesetzen und den Reload abwarten.GET /_/:project/:collection/_queuebeobachten, bis der Zähler 0 ist. Erst danach die alten Wörter aus der Suche nehmen.
Performance-Optionen (Cache und In-Memory-Index)
Abschnitt betitelt „Performance-Optionen (Cache und In-Memory-Index)“Beide Optionen sind standardmäßig aus und ändern ohne Konfiguration nichts.
Ergebnis-Cache für combined (combinedCache)
Abschnitt betitelt „Ergebnis-Cache für combined (combinedCache)“Wiederholte Combined-Queries (Katalog-Filter, Blättern durch dieselbe Treffermenge) können die gemergte Rangliste kurzzeitig wiederverwenden:
search: - name: combined mode: combined rrf: { k: 60, topK: 100, weights: { semantic: 1.5, fuzzy: 1.0 } } combinedCache: enabled: true # Default: false ttl: 45s # Default: 45s maxEntries: 1000 # LRU-Cap, Default: 1000- Gecacht werden nur die gemergten IDs + Scores; die Dokumente der Seite werden immer frisch geladen.
- Der Key enthält Query, Config, den effektiven Filter (inkl. Rollen-
preFilterund Hook-Filter) und alle Fusionsparameter — Ergebnisse können nicht zwischen Rollen oder Sammlungen wandern. - Aktiv:
X-Cache: HIT|MISSim Response-Header. BeiHITbleibenX-Search-Sources/X-Search-Configleer (es lief keine Subsearch). - Schreibvorgänge, die einen In-Memory-Index (Vektor oder Ngram) verändern, invalidieren den Cache sofort (der Mutationszähler des Index steckt im Key); die
ttlist die Obergrenze für Änderungen, die noch nicht im Index stehen. Nach einem Restore wird der Cache verworfen. - Grenzen:
ttlkurz halten (≤30s bei mehreren Server-Instanzen, die Invalidierung ist node-lokal).
In-Memory-Index für ngram (ngram.inMemory)
Abschnitt betitelt „In-Memory-Index für ngram (ngram.inMemory)“Für große Sammlungen kann der ngram-Anteil einer Combined-Suche aus dem RAM beantwortet werden statt aus MongoDB:
# Server-config.yml — globaler Schalter, ohne ihn passiert nichtssearch: inMemory: enabled: true maxMemoryMB: 1024
# Projekt-YAMLsearch: - name: fuzzy mode: ngram fields: [title, context] ngram: sizes: [3] queryFilter: { minMatchRatio: 0.4 } inMemory: enabled: true # Default: false maxDocs: 150000 # mehr Dokumente → MongoDB maxMemoryMB: 512 # Budget pro Index- Gebaut wird der Index beim Projekt-(Re-)Load aus den gespeicherten
_search-Ngrammen (nie aus den Quellfeldern), damit er nicht vom MongoDB-Pfad abweichen kann. - Genutzt wird er für ngram-Quellen von
combined-Suchen und für Einzel-ngram-Suchen — dort ersetzt er nur das Ranking, die Seitendokumente werden anschließend per_idgeladen (Projektionen/?selectwirken weiter;?lookup=und?aggregate=erzwingen dagegen MongoDB). Beicombinedzeigt der Debug-HeaderinMemory=ram|mongo(loading)|mongo(budget)|mongo(candidates)|mongo(no-terms);mongo(no-terms)erscheint nur, wenn für die Quelle überhaupt ein In-Memory-Index existiert. - Einzel-ngram-Suchen fallen mit
X-Search-Debug: trueim KopfX-Search-Memorysichtbar auf MongoDB zurück, wenn der Index nicht bereit ist oder die Anfrage etwas braucht, das der Index nicht garantieren kann:mongo(disabled),mongo(loading),mongo(budget),mongo(count)(bei?count=1),mongo(sort)(bei eigenem?sort=),mongo(no-terms)— sowiemongo(lookup),mongo(aggregate),mongo(hook-pipeline)undmongo(hook-filter), wenn die Anfrage Schritte braucht, die in der MongoDB-Pipeline laufen. Der Read-Hook selbst läuft einmal pro Anfrage; nur seinepipelineMod-Funktion kann im MongoDB-Fallback zweimal laufen (Klassifikation + echte Pipeline). Ein Hook, dercontext.filterzurückgibt oder in-place ändert, ersetzt den effektiven Filter samt Suchbedingung und fällt aufmongo(hook-filter)zurück. EinpipelineModaus einem Read-Hook stört den RAM-Pfad nur, wenn er die Ergebnismenge ändern kann ($match,$sort,$group,$project, …), Stufen vor die bestehende Pipeline setzt oder_id/_searchschreibt bzw.$_searchliest. Reine angehängte Feld-Transformationen ($addFields,$set,$unset) laufen auf der geladenen Seite nach der Projektion und bleiben im RAM. - Rollen-
preFilter,?filter=und Hook-Filter gelten weiter: der Index liefert Kandidaten-IDs, die danach gegen den effektiven Filter geprüft werden. - Schreibvorgänge (Anlegen/Ändern/Löschen/Bulk/Worker/Regenerate) halten den Index aktuell; Snapshot- und Backup-Restore bauen alle In-Memory-Zustände neu auf (Indizes + Cache) — alte Treffer können einen Restore nicht überleben. Während des Neuaufbaus laufen Suchen auf MongoDB.
- Budget-Abbruch (zu viele Dokumente, Speicher- oder RSS-Grenze) lässt den Index leer und dauerhaft auf MongoDB; ein Reload startet einen neuen Versuch.
- Gemessen (91k Dokumente): Die ngram-Subsearch von „blauer pullover” fällt von ~1,3 s auf wenige Millisekunden; Einzelwort-Suchen von ~1,4–4,9 s auf 85–158 ms. Das kompakte Index-Layout kostet ≈155 MB statt ≈1,15 GB und lädt in ≈2,7 s.
- Der 20000er-Kandidaten-Cap gilt nur für
combined(dort auf Roh-Kandidaten). Einzel-ngram-Suchen prüfen nur so viele Kandidaten in Rangfolge, wie die Seite braucht (Blöcke à 500_ids).
Vektor-Index: Laden, Speicher, Altformate
Abschnitt betitelt „Vektor-Index: Laden, Speicher, Altformate“Der Vektor-Index wird beim Projekt-Start im Hintergrund aufgebaut; bis dahin liefern Suchen Teilergebnisse. Für 91k Dokumente mit 1024 Dimensionen liegt der Load bei ~14 s (count-balancierte _id-Bereiche, parallel dekodiert) und der Speicher bei ~690 MB pro Config und Sprache. Die Sparse-Suche ist mit dem kompakten Layout von ~190 ms auf ~4 ms gefallen, Dense bleibt bei ~5 ms (parallel über die Kerne).
Zwei Dense-Speicherformen sind im Umlauf: BSON-Binary (aktuell) und ein Legacy-Byte-Array (4× größer, ~5× langsamer beim Laden). Der Server liest beide; für Altbestände gibt es im Server-Repo tools/vectormigrate (erst -dry-run, danach Projekt neu laden). Ein MongoDB-Fallback existiert nicht: fehlt der Index, bricht die Vektor-Suche mit einem klaren Fehler ab.
Was der Vektor-Pfad zusätzlich braucht
Abschnitt betitelt „Was der Vektor-Pfad zusätzlich braucht“- Provider-Name:
vector.provider(z. B.bge_m3) muss mit einem Servernamen aus der Embedding-Registry übereinstimmen (collection.go:377) — sonst löst die Config nicht auf. Unbekannte Provider-Typen brechen ab, defekte einzelne Provider werden übersprungen (registry.go:58-70). - Gesundheit prüfen:
GET /_/:project/_/admin/embedding/statuszeigt Gruppen-/Mitglieder-Gesundheit (an diesem Scope sind Fehlertexte redigiert, nur Zustände und Zähler; Volldetail nur am Server-EndpunktGET /api/v1/admin/embedding/status,admin_embedding_status.go:49-72). - Feinheiten (optional):
documentPrefix/queryPrefix,overflow: truncate(Standard) oderchunkmitchunkSize(Standard 512) undchunkOverlap(Standard 50) —collection.go:376-383. - Debug: Mit Header
X-Search-Debug: trueantwortet die Suche mitX-Search-Config,X-Search-TimingundX-Search-Sources(Treffer-/Score-Spannen je Quelle,search_debug.go:145-159).
Fehlerbilder
Abschnitt betitelt „Fehlerbilder“| Symptom | Ursache | Lösung |
|---|---|---|
| Leere Scores / keine Treffer trotz Daten | Index fehlt oder _queue noch > 0 |
Hinweis im Admin abwarten, dann erneut suchen |
combined findet nichts, fuzzy schon |
Vektor-Anteil ohne Embeddings | semantic-Felder und Provider (bge_m3) prüfen |
400 no valid document IDs in request |
Regenerate mit nur ungültigen ids |
Hex-IDs prüfen oder ids weglassen (Vollaufbau) |
404 collection "…" not found |
Regenerate mit falschem collection-Namen |
Namen gegen api/collections/*.yml prüfen |
| Worker-Queue steht | Provider defekt oder überlastet | search-worker/status und embedding/status lesen, dann Regenerate mit force: true |
Suche über tag/navigation unscharf |
default ist regex, kein Fuzzy |
Erwartung anpassen oder content-Indexe nutzen |
inMemory=ram erscheint nie im Debug-Header |
Globaler Schalter aus, Config ohne inMemory, Index noch nicht fertig oder Budget-Abbruch |
search.inMemory.enabled, ngram.inMemory.enabled prüfen, Reload, Budget-Logs lesen |
| Suche mit einem einzelnen Füllwort liefert nichts | ngram.stopwords verwirft das Wort aus der Anfrage |
Inhaltliches Wort suchen; soll das Wort suchbar bleiben, das Preset weglassen und das Set nur über words bauen |
| Ein entferntes Stoppwort liefert weiterhin nichts | Neuaufbau nach dem Entfernen fehlt oder läuft noch | regenerate-search anstoßen, _queue auf 0 warten |
mongo(no-terms) in X-Search-Sources |
Combined-Quelle ohne verwertbare Terme (nur Stoppwörter oder zu kurze Begriffe) | Kein Fehler — Anfrage präzisieren |
X-Search-Memory: mongo(disabled) bei jeder Suche |
In-Memory-Index nicht aktiviert oder nicht geladen | search.inMemory.enabled und ngram.inMemory.enabled prüfen, Projekt neu laden |
X-Search-Memory: mongo(budget) |
Index-Load über dem Budget abgebrochen (Doc-Anzahl, Speicher oder RSS) | Budget erhöhen oder Collection kleiner halten; Loader-Log lesen |
X-Search-Memory: mongo(sort) / mongo(count) |
Eigenes ?sort= bzw. ?count=1 — der Index kann das nicht garantieren |
Erwartetes Verhalten; ohne sort/count messen |
X-Search-Memory: mongo(lookup) / mongo(aggregate) / mongo(hook-pipeline) / mongo(hook-filter) |
Anfrage nutzt Schritte oder einen Hook-Filter, die in der MongoDB-Pipeline laufen | Erwartetes Verhalten; ohne diese Parameter messen |
| Nach Restore kurz alte Treffer | Restore erfolgt, Index-Neuaufbau läuft noch | Warten bis inMemory=ram wieder erscheint (bis dahin MongoDB-Fallback) |
| Combined-Suche liefert kurz eine ältere Reihenfolge | combinedCache aktiv, TTL noch nicht abgelaufen |
TTL senken oder Cache für kritische Daten abschalten |
?facet= an der Liste wirkungslos |
Facetten sind eine eigene Route (…/_facet) |
q/filter dort mitgeben — Lesen und suchen |
| Facettenzahlen kleiner als erwartet | Fenster ist rrf.topK (Default 500), nicht die ganze Sammlung |
?qLimit=all an _facet setzen oder topK im rrf-Block erhöhen |
Weiter: Lesen und suchen, Sammlungen anlegen, Felder und Widgets.
Verifiziert am e3f92eb 2026-10-14 (tibi-svelte-starter), am 68b4278 2026-10-15 (tibi-server, rankfusion), am 2b091d8 2026-10-13 (tibi-admin-nova, Test-Plan 41-search-index-status), am 63a2848 2026-10-19 (tibi-server: combinedCache, ngram.inMemory, _count-Fast-Path, Restore-Rebuild), am 76f1336 2026-10-19 (tibi-server: ngram.stopwords, Regenerate-Endpunkt, mongo(no-terms)); am 92c3ad74 2026-10-20 (tibi-server: kompakter ngramindex, collection_ngram_search.go, Hook-Pipeline-Klassifikation, X-Search-Memory), am 654b568 2026-10-19 (tibi-server: kompakter Sparse-Vektorindex, Legacy-Dense-Format, MemoryEstimate, vectormigrate, paralleler Range-Loader inkl. Retry/Semaphore), am 9e12a8e 2026-10-19 (tibi-types: ngram.stopwords/queryFilter/inMemory im Schema), am 21472bd 2026-10-19 (tibi-types: ngram.sizes 2–10, combinedCache im Schema), am cf44cd7 2026-10-19 (tibi-svelte-starter: tibi-types-Submodul auf 21472bd), am d0d699d 2026-10-20 (tibi-server: collection_facet_search.go, rrf.topK-Default 500, qLimit)