diff --git a/src/content/docs/projekt/audit-backup.mdx b/src/content/docs/projekt/audit-backup.mdx
index 174a942..d363992 100644
--- a/src/content/docs/projekt/audit-backup.mdx
+++ b/src/content/docs/projekt/audit-backup.mdx
@@ -228,7 +228,7 @@ Backups sind der Voll-Export für Umzug und Desaster-Fall — im Admin unter `/p
| Fortschritt | Ja (Stream) | Ja (SSE-Stream) |
diff --git a/src/content/docs/projekt/suche.mdx b/src/content/docs/projekt/suche.mdx
index ba95db9..ab63a9d 100644
--- a/src/content/docs/projekt/suche.mdx
+++ b/src/content/docs/projekt/suche.mdx
@@ -108,6 +108,62 @@ Alle Modi aus `collection.go:314` — `fields` gilt für `regex` und `ngram`, `v
Aus Projektsicht musst du nichts einrichten — lass `autoRegenerate: true` in der YAML stehen, dann pflegen sich `fuzzy`- und `semantic`-Indizes selbst. Der Regenerate-Flow im tibi-admin (Test-Plan `41-search-index-status`) nutzt genau den Endpunkt aus Schritt 4.
+## Performance-Optionen (Cache und In-Memory-Index)
+
+Beide Optionen sind **standardmäßig aus** und ändern ohne Konfiguration nichts.
+
+### Ergebnis-Cache für `combined` (`combinedCache`)
+
+Wiederholte Combined-Queries (Katalog-Filter, Blättern durch dieselbe Treffermenge) können die gemergte Rangliste kurzzeitig wiederverwenden:
+
+```yaml
+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-`preFilter` und Hook-Filter) und alle Fusionsparameter — Ergebnisse können nicht zwischen Rollen oder Sammlungen wandern.
+- Aktiv: `X-Cache: HIT|MISS` im Response-Header. Bei `HIT` bleiben `X-Search-Sources`/`X-Search-Config` leer (es lief keine Subsearch).
+- Grenzen: `ttl` kurz halten (≤30s bei mehreren Server-Instanzen, die Invalidierung ist node-lokal); nach einem Restore wird der Cache verworfen.
+
+### 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:
+
+```yaml
+# Server-config.yml — globaler Schalter, ohne ihn passiert nichts
+search:
+ inMemory:
+ enabled: true
+ maxMemoryMB: 1024
+
+# Projekt-YAML
+search:
+ - 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 nur für ngram-Quellen von `combined`-Suchen, und nur wenn er fertig geladen und im Budget ist. Sonst — und immer bei Einzel-ngram-Suchen sowie `_count` — läuft die Suche über MongoDB. Der Debug-Header zeigt `inMemory=ram|mongo(loading)|mongo(budget)|mongo(candidates)`.
+- 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, ~197 KB/Doc): Die ngram-Subsearch von „blauer pullover" fällt von ~1,3 s auf wenige Millisekunden, Einzelwort-Suchen von ~1,7 s auf 30–80 ms (jeweils vor dem Rollen-/Katalogfilter).
+
## 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`).
@@ -125,8 +181,11 @@ Aus Projektsicht musst du nichts einrichten — lass `autoRegenerate: true` in d
| `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 |
+| 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 Suche wirkungslos | Facetten sind eigene Route (`…/_facet`) | [Lesen und suchen](/projekt/abfragen-lesen/) beachten |
Weiter: [Lesen und suchen](/projekt/abfragen-lesen/), [Sammlungen anlegen](/projekt/sammlungen-anlegen/), [Felder und Widgets](/projekt/felder-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)
+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 63a2848c 2026-10-19 (tibi-server: combinedCache, ngram.inMemory, _count-Fast-Path, Restore-Rebuild)
diff --git a/src/content/docs/server/konfiguration.mdx b/src/content/docs/server/konfiguration.mdx
index 55fc17c..068a633 100644
--- a/src/content/docs/server/konfiguration.mdx
+++ b/src/content/docs/server/konfiguration.mdx
@@ -36,6 +36,7 @@ config.yml (Datei) < Umgebungsvariable (gewinnt immer)
| `mcp` | `enabled`, `route`, `maxResultItems`, `allowAdminActions` | `MCP_ENABLED`, `MCP_ROUTE`, `MCP_MAX_RESULT_ITEMS`, `MCP_ALLOW_ADMIN_ACTIONS` |
| `sentry` | `dsn`, `environment`, `serverName`, `enableTracing`, `debug` | keine — nur Datei |
| `cors` | `allowOrigins`, `allowCredentials`, … | keine — wirksam ist die Projekt-/Collection-Ebene (siehe [Sicherheit](/server/sicherheit/)) |
+| `search.inMemory` | `enabled`, `maxMemoryMB` (Kill-Switch + Budget für In-Memory-N-Gram-Indizes) | keine — nur Datei |
| `llm` / `embedding` | Provider-Listen | dynamisch, siehe unten |