Entwurf von RAG für 100 Millionen Dokumente: Sharding, hybrider Suchmechanismus, Kosten
Was ändert sich, wenn ein RAG-System auf Milliarden von Chunks anwächst: asynchrone Einpflege, Routing, Sharding, hybride Abfrage, Neubewertung der Ergebnisse, Autorisierung, Caching und Kostenkontrolle.
Um eine RAG-Demo zum Laufen zu bringen, braucht man einen Nachmittag. Man teilt ein PDF in Abschnitte auf, embeddet sie, speichert die Vektoren in einer Datenbank, holt die fünf nächsten Nachbarn ab, fügt sie in einen Prompt ein und ruft ein Modell auf.
Dieser Ansatz funktioniert einwandfrei für ein paar Tausend Dokumente. Sobald das Korpus auf 100 Millionen Dokumente anwächst, bricht das System zusammen, und die gestellte Frage ändert sich. Es geht nicht mehr darum „welche Vektor-Datenbank sollen wir wählen?“, sondern darum: Wie kann man Milliarden potenzieller Belege auf die 10 bis 20 relevanten Abschnitte reduzieren, ohne die Latenz, die Qualität der Suche, die Verfügbarkeit oder das Budget zu beeinträchtigen?
Das ist ein völlig anderes Ingenieurproblem. In diesem Umfang ist RAG ein verteiltes Daten- und Suchsystem mit einem angeschlossenen Sprachmodell am Ende der Kette.
Was das System tun muss
Um die Diskussion konkret zu halten, gehen wir von folgenden Anforderungen aus:
- Mehr als 100 Millionen Dokumente in Formaten wie PDF, HTML und DOCX
- Millionen von Nutzern, die Fragen in natürlicher Sprache stellen
- Zitate, die zu jeder Antwort hinzugefügt werden
- Dokumente, die im Laufe der Zeit aktualisiert und gelöscht werden
- Schnelle Abfrage – etwa 1 bis 3 Sekunden, bevor die Erstellung beginnt
- Hohe Verfügbarkeit
- Asynchrone Aufnahme von Dokumenten
Alles reduziert sich auf eine wichtige Anforderung: unter potenziell Milliarden von Datenfragmenten die richtigen 10 bis 50 Informationen finden.
Begonnen wird mit der Mathematik
Der erste Fehler bei großen RAG-Designs besteht darin, ein Dokument und einen Vektor als dasselbe zu betrachten. Nehmen wir an:
Documents = 100,000,000
Average chunks/document = 30
Embedding dimensions = 768
Storage/dimension = 4 bytes (float32)
Die Anzahl der Vektoren beträgt dann:
100,000,000 × 30
= 3,000,000,000 vectors
Drei Milliarden Vektoren. Der Rohdatenspeicher für sie beträgt etwa:
3B × 768 × 4 bytes
≈ 9.2 TB
Und das sind noch nur die Zahlen selbst. Eine tatsächliche Implementierung beinhaltet außerdem:
ANN index structures
chunk text
document metadata
ACL metadata
IDs
lexical indexes
replicas
snapshots
WALs
temporary indexes
operational headroom
Durch zwei Kopien des Vektorindex liegen Sie bereits bei über 18 TB an Rohvektoren, noch bevor die Suchstrukturen mitgezählt werden. Wenn Dokumente im Durchschnitt 50 Blöcke enthalten und nicht 30:
100M documents × 50 chunks
= 5 billion vectors
Deshalb beschreibt der Satz „Wir legen alle Datenblöcke in eine Vektordatenbank und führen eine Ähnlichkeitssuche durch“ keine großskalige Architektur. Er beschreibt lediglich den Ausgangspunkt.
Zwei getrennte Bereiche
Die sauberste Art, das System zu organisieren, besteht darin, es in zwei unabhängige Bereiche aufzuteilen – einen für die Eingabe und einen für die Abfrage. Diese Trennung ist wichtig. Ein Kunde, der 20 Millionen neue Dokumente hochlädt, sollte die Abfrageverzögerung nicht von 300 ms auf 8 Sekunden erhöhen. Die Eingabe wird auf Durchsatz optimiert, die Abfrage auf Verzögerung – beides durch denselben Prozessablauf zu zwingen, ist ein zuverlässiger Weg, später mit Betriebsproblemen konfrontiert zu werden.
Die Vektordatenbank ist nicht die Quelle der Wahrheit
Bewahren Sie die Originaldokumente in günstigen Objektspeichern wie S3, GCS oder Azure Blob auf. Die Vektordatenbank ist ein Index, und Indizes können neu erstellt werden. Wenn Sie in sechs Monaten vom Embedding-Modell E1 auf E2 wechseln, möchten Sie die Vektoren aus den gespeicherten Originalen neu erzeugen, anstatt zu versuchen, den Inhalt direkt aus der Vektordatenbank selbst wiederzugeben.
Auch das Objektwersionierung hilft dabei, denn ein Update kann als neue Version aufgezeichnet werden, anstatt die alte zu löschen. Amazon S3 kann beispielsweise mehrere Versionen eines Objekts speichern, und Lebenszyklusrichtlinien können ältere Versionen auf günstigere Speicherebenen verschieben oder sie verfallen lassen.
Die Einpflege muss asynchron erfolgen
Nehmen wir an, ein Kunde lädt 500.000 PDFs hoch. Man möchte keine POST /documents-Anfrage, die diese Dateien analysiert, mit OCR verarbeitet, bereinigt, in Blöcke aufteilt, einbettet, indiziert und erst danach eine 200 zurückgibt. Ein solcher Aufruf könnte stundenlang dauern. Stattdessen sollte das Endpunkt-Script nur sehr wenig tun:
POST /documents
│
▼
Store document
│
▼
Create ingestion event
│
▼
Return 202 Accepted
Alles Weitere findet separat statt und wird durch Ereignisse gesteuert. Kafka ist eine Option für die Grundstruktur, da Themen in Partitionen aufgeteilt werden, an denen Verbraucher parallel arbeiten können, und innerhalb jeder Partition eine Ordnung gewahrt bleibt – was nützlich ist, wenn Ereignisse, die einen Dokument- oder Tenant-Schlüssel teilen, eine vorhersehbare Reihenfolge benötigen.
Das spezifische Tool ist weniger wichtig als die Eigenschaft, die es bietet: Für die Aufnahme sind Puffer notwendig. Wenn Ihr Embedding-Dienst 30.000 Blöcke pro Sekunde verarbeitet und plötzlich mit einer Arbeitslast von 100.000 Blöcken pro Sekunde konfrontiert wird, sollte das System einen Rückstau bilden und nicht zusammenbrechen.
Jeder Aufnahmeschritt muss idempotent sein
In diesem Maßstab werden Aufgaben zweimal ausgeführt. Nachrichten werden erneut übermittelt. Arbeiter sterben nach dem Schreiben von Daten, bevor sie die Aufgabe bestätigen. Anfragen laufen ab, obwohl die entfernte Seite die Arbeit bereits abgeschlossen hat. Planen Sie all das von Anfang an, indem Sie aus stabilen Eingaben deterministische Identifikatoren ableiten:
document_id
document_version
chunk_index
embedding_model_version
Ein Chunk-ID kann dann ein Hash der Teile sein, die ihn definieren:
hash(
tenant_id +
document_id +
document_version +
chunk_index
)
Das Doppelte Erhalten desselben Indexierungsereignisses erzeugt nicht mehr doppelten Inhalt, weil
upsert(chunk_id, ...)
Es ist sicher, es erneut zu versuchen. Eine exakt-einmalige Ausführung ist schwer zu erreichen. Idempotente Ausführung ist in der Regel das realistischere Ziel.
Chunking ist eine Infrastrukturentscheidung
Auf einem kleinen Korpus wirkt eine Änderung der Chunk-Größe unbedenklich. Bei Milliarden von Vektoren kostet das jedoch echtes Geld. Nehmen wir 100 Mio. Dokumente: Eine Strategie, die pro Dokument 20 Chunks erzeugt, liefert 2 Mrd. Vektoren, während eine Strategie mit 60 Chunks pro Dokument 6 Mrd. Vektoren ergibt. Eine scheinbar harmlose Änderung hat somit alle Werte einfach verdreifacht:
embedding compute
vector storage
indexing work
retrieval fan-out
reindexing time
replication traffic
Chunking ist also nicht einfach nur ein Einstellparameter in der NLP-Technologie. Es handelt sich dabei um einen Parameter zur Kapazitätsplanung.
Jeder Chunk sollte außerdem ausreichend Metadaten enthalten, um ihn auf seine Herkunft zurückverfolgen zu können:
{
"chunk_id": "c_982...",
"document_id": "doc_51...",
"document_version": 8,
"tenant_id": "tenant_17",
"page": 43,
"section": "Risk Factors",
"language": "en",
"created_at": "...",
"acl_groups": ["finance", "executives"],
"embedding_version": "embed_v4"
}
Der Text selbst kann je nach Ihrer Speicherrichtlinie an einem anderen Ort gespeichert werden, doch die Abstammungslinie muss bei der Abrufung erhalten bleiben. Andernfalls werden Zitate früher oder später zu einem Problem.
Suchen Sie nicht das gesamte Korpus, es sei denn, es ist unbedingt notwendig
Das könnte die wichtigste Idee in der gesamten Konzeption sein. Nehmen wir die Frage „Was hat sich in unserer europäischen Rückerstattungspolitik in diesem Quartal geändert?“ Eine naive Implementierung durchsucht einfach alle 3.000.000.000 Vektoren. Doch die Frage sagt uns bereits viel:
organization = current tenant
region = Europe
topic = refund policy
time = current quarter
permissions = documents user may access
Eine Architektur zur Suche nach Audiodaten nutzt diese Einschränkungen gezielt aus, indem sie Milliarden von Datenblöcken auf einen kleinen Teil reduziert, bevor eine Ähnlichkeitssuche beginnt. Die genauen Zahlen hängen von der Arbeitslast ab, daher sollten sie als Beispiele angesehen werden. Das Prinzip bleibt jedoch gleich: Zuerst die Routierung, anschließend die Abrufung.
Sharding macht die Vektorlage verteilt
Irgendwann kann ein einzelner Knoten das gesamte durchsuchbare Korpus nicht mehr speichern, weshalb der Index aufgeteilt werden muss. Verteilte Vektor-Engines wie Qdrant skalieren horizontal, indem sie Punkte über verschiedene Shards verteilen, wobei die Replication Kopien dieser Shards auf mehreren Knoten platziert, um Verfügbarkeit und zusätzliche Lesekapazität zu gewährleisten.
Doch es gibt einen Haken: Wenn jede Abfrage zu jedem Shard weitergeleitet werden muss, führt das Hinzufügen von Shards letztendlich zu mehr Koordinationsaufwand anstatt ihn zu verringern. Ein besseres Design sorgt dafür, dass so viele Abfragen wie möglich weitergeleitet werden können. Attribute wie diese können Ihnen zeigen, welche Shards tatsächlich teilnehmen müssen:
tenant_id
region
document namespace
language
time partition
Dies bringt vor allem in Mehrbenutzer-Systemen Vorteile. Anstatt
Query
↓
128 shards
eine Anfrage könnte durchgeführt werden
tenant_id = 429
↓
Shard group 18
↓
4 shards
Das derzeitige verteilte Design von Qdrant ermöglicht beispielsweise benutzerdefiniertes Sharding, sodass Operationen auf ausgewählte Shard-Schlüssel abzielen können und nicht die gesamte Sammlung beeinflusst wird. Diese Erkenntnis gilt unabhängig vom Anbieter: Eine gute Partitionierung verringert die Anzahl der abzuschickenden Abfragen.
Replikation beantwortet eine andere Frage
Menschen verwechseln oft Sharding mit Replikation, doch sie lösen unterschiedliche Probleme. Sharding beantwortet die Frage, wie man ein zu großes Datensatz für einen einzelnen Rechner aufteilt. Replikation beantwortet die Frage, was passiert, wenn einer dieser Rechner ausfällt. Stellen Sie sich folgende Struktur vor:
Shard A → Node 1
Shard B → Node 2
Shard C → Node 3
Falls Node 2 ausfällt, verschwindet ein Teil des Korpus zusammen mit ihm. Bei Replikation:
Shard A → Node 1 + Node 4
Shard B → Node 2 + Node 5
Shard C → Node 3 + Node 6
Das System setzt die Beantwortung von Anfragen fort, selbst wenn einzelne Knoten ausfallen. Redundanz kostet Speicherplatz, Schreibbandbreite und Betriebsaufwand, doch ein Produktions-Suchcluster ohne sie weist einige unangenehme Ausfallmöglichkeiten auf.
Vector-Suche allein reicht nicht aus
Stellen Sie sich vor, jemand fragt: „Was besagt die Richtlinie HR-48291?“ Eine semantische Suche könnte Material zu HR-Richtlinien im Allgemeinen liefern, während die lexikalische Suche hervorragend darin ist, den genauen Token HR-48291 zu finden. Umgekehrt bei der Frage: „Können Mitarbeiter nach der Adoption eines Kindes zusätzlichen Urlaub nehmen?“ Hier kommt die semantische Suche zum Tragen. Die beiden Ansätze überschneiden sich, decken aber unterschiedliche Bereiche ab – deshalb ist eine hybride Suchstufe, die beide Methoden nutzt, die übliche Wahl.
Elicasts aktuelle Richtlinien für die hybride Suche gehen denselben Weg und kombinieren lexikale sowie vektorbasierte Suchverfahren, wobei die Reciprocal Rank Fusion als Methode vorgeschlagen wird, um die Ranglisten zu verschmelzen. Eine gute Eigenschaft der RRF ist, dass man nicht vortäuschen muss, ein BM25-Score und eine Kosinusähnlichkeit seien vergleichbare Werte – stattdessen werden die Rangpositionen kombiniert. In vereinfachter Form:
RRF(d) = Σ 1 / (k + rank_i(d))
wo rank_i(d) die Position des Dokuments d im Suchsystem i ist.
Achten Sie auf sequenzielles Vorfiltern
Es gibt einen verlockenden Abkürzungsweg:
3B documents
↓
BM25 returns 10,000
↓
vector search only those
Es kann für einige Arbeitslasten funktionieren, kann aber auch die semantische Wiedererkennung zerstören. Stellen Sie sich vor, ein Benutzer tippt „Welche Regeln gelten für Mitarbeiter, die ein Kind adoptieren?“ und es gibt ein Dokument mit dem Text: „Mitarbeiter, die ein Kind durch Adoption aufnehmen, haben Anspruch auf sechzehn Wochen Familienurlaub.“ Die Überschneidung der Schlüsselwörter mit den Worten des Benutzers ist gering. Wenn BM25 das Dokument bereits vor der semantischen Suche aussortiert, kann kein Embedding-Modell es noch retten.
Daher ist nach strengen Routing-Vorgaben eine bessere Vorgehensweise, Kandidaten parallel aus beiden Suchmechanismen zu erzeugen und anschließend die zusammengeführte Liste neu zu ranken. Die strengen Einschränkungen selbst gehören zu einer separaten Kategorie:
tenant
permissions
document status
Diese sollten normalerweise bereits vor Beginn der Abfrage durchgesetzt werden.
Autorisierung kommt vor, bevor das LLM irgendetwas sieht
Stellen Sie sich ein Unternehmenskorpus vor, das Folgendes enthält:
Engineering documents
HR documents
Board documents
Legal documents
Executive compensation
Customer contracts
Semantische Relevanz bedeutet keine Erlaubnis. Ein Dokument des CFO könnte mathematisch am ehesten mit der Frage eines Mitarbeiters übereinstimmen, doch das gibt dem Mitarbeiter dennoch nicht das Recht, es zu lesen. Eine falsche Gestaltung sieht so aus:
Vector Search
↓
Retrieve confidential chunk
↓
Send chunk to LLM
↓
Check permission
Bis die Berechtigungsprüfung ausgeführt wird, ist der vertrauliche Text bereits in eine nachgelagerte Komponente übertragen worden. Der korrekte Ablauf klärt zunächst, wer der Benutzer ist:
User
↓
Identity
↓
Groups / roles / ACL
↓
Authorized search space
↓
Retrieval
Das bedeutet, dass die Zugriffssteuerungs-Metadaten effizient an der Abfrage teilnehmen müssen. In sehr großen Skalen wird ein ACL-System, das nach einer Abfrage Millionen von Einträgen durchscannen muss, sowohl zu einem Sicherheitsproblem als auch zu einem Leistungsproblem.
Die Abfrage liefert Kandidaten, nicht Kontext
Nehmen wir an, eine hybride Abfrage ergibt 400 Kandidaten. Fügen Sie nicht alle 400 Einträge in die Anfrage ein. Rangieren Sie sie zunächst um:
Vector candidates: 200
Lexical candidates: 200
│
▼
Fusion
│
▼
~250 unique chunks
│
▼
Reranker
│
▼
top 20–40
Die erste Suchstufe ist darauf ausgelegt, plausibel relevante Ergebnisse schnell zu finden. Der Neubewertungsmechanismus dient einem anderen Zweck: Bei einem deutlich kleineren Datensatz wird die Relevanz sorgfältiger geschätzt. Dieses zweistufige Verfahren ermöglicht es einem aufwändigen Relevanzmodell, mit Hunderten statt Milliarden von Elementen zu arbeiten.
Dann werden die Redundanzen beseitigt
Selbst ein guter Neubewertungsmechanismus kann Ergebnisse wie diese liefern:
Chunk 1 → page 14
Chunk 2 → page 14
Chunk 3 → page 15
Chunk 4 → page 14
Chunk 5 → page 15
Alle fünf könnten im Grunde denselben Punkt machen, und das Versenden aller verschwendet Kontext-Token. Bevor Sie den endgültigen Prompt erstellen, wenden Sie eine Kombination dieser Schritte an:
deduplication
document diversity
section diversity
near-duplicate detection
adjacent-chunk merging
token budgeting
Mannchmal benötigt eine Antwort auch ihre Nachbarn. Wenn der Block 47 die Lösung enthält, aber mitten in einer Erklärung beginnt, kann der Kontextbuilder diese holen.
chunk 46
chunk 47
chunk 48
Anstatt zu hoffen, dass die Vektorabfrage jede zusammenhängende Teilmenge eigenständig abruft.
Der Aufbau des Kontexts verdient eine eigene Schicht
Eine überraschend häufige Implementierung betrachtet den Kontext lediglich als Folgendes:
context = "\n\n".join(retrieved_chunks)
Produktionsysteme funktionieren besser mit einem dedizierten Kontextbuilder. Seine Aufgabe besteht darin, bewertete Belege zu verwenden und eine effiziente Prompt-Struktur ohne Verlust von Zitateninformationen zu erstellen. Konzeptionell gesehen:
Retrieved Evidence
↓
Deduplicate
↓
Merge related chunks
↓
Enforce token budget
↓
Preserve source IDs
↓
Order evidence
↓
Generate context
Das Modell könnte dann etwas erhalten, das strukturell so aussieht:
SOURCE S1
Document: Employee Handbook
Page: 42
Version: 18
Text: ...
SOURCE S2
Document: European Leave Addendum
Page: 7
Version: 4
Text: ...
QUESTION
...
und man wird angewiesen, auf S1, S2 usw. zu verweisen. Das ist weitaus sicherer, als von einem Modell zu verlangen, URLs oder Dokumentidentifikatoren aus dem Gedächtnis wiederzugeben.
Die Verständnisprüfung von Abfragen sollte kostengünstig sein
Einige Abfragen profitieren von einer Normalisierung vor der Suche. Ein Benutzer tippt „Wie stark ist das Umsatzwachstum im letzten Jahr in Europa gewesen?“ Unter Berücksichtigung des heutigen Datums sowie der eigenen Terminologie des Unternehmens kann die Suche mit strukturierten Feldern, die aus diesem Satz extrahiert werden, besser funktionieren:
metric = revenue
region = Europe
year = 2025
intent = financial comparison
In einem Gespräch muss eine Nachfrage wie „Wie sieht es mit Asien aus?“ im Einklang mit dem vorherigen Kontext neu formuliert werden:
How much did revenue grow in Asia in 2025?
Der Schlüsselpunkt bei der Gestaltung ist, dass diese Schritte selten Ihr teuerstes Reasoning-Modell erfordern. Verwenden Sie das günstigste Komponente, das die Aufgabe zuverlässig erledigt. Eine ausgereifte AI-Stack-Lösung sieht in der Regel so aus:
small model
↓
classification
small model
↓
query rewriting
embedding model
↓
retrieval
reranker
↓
relevance
large model
↓
final reasoning
und nicht so:
largest model everywhere
Zerlegung für mehrstufige Fragen
Manche Fragen lassen sich nicht zuverlässig aus einem einzigen Abschnitt beantworten. Eine Frage wie „Welches europäische Produkt verzeichnete den größten Umsatzrückgang, und wie hat die Geschäftsleitung dies erklärt?“ lässt sich in zwei Suchvorgänge aufteilen:
Question 1:
Which European product had the largest decline?
Question 2:
What explanation did management provide for that product?
Dann kann die Abfrage für jede Unterfrage getrennt nach Beweisen suchen, und die Ergebnisse werden vor der Erstellung zusammengeführt.
Complex Query
│
▼
Query Decomposer
/ \
/ \
▼ ▼
Subquery A Subquery B
│ │
▼ ▼
Retrieval Retrieval
\ /
\ /
▼ ▼
Evidence Join
│
▼
LLM
Zu diesem Zeitpunkt wirkt RAG in großem Maßstab eher wie ein Abfragenplanungssystem als wie eine einfache Suche nach dem nächsten Nachbarn.
Frische beeinflusst die Indizierungsmethode
Nehmen wir an, das Korpus enthält 100 Mio. Dokumente, von denen täglich 2 Mio. sich ändern. Eine wöchentliche Neubildung des Indexes ist vermutlich nicht das, was man möchte, daher ist eine inkrementelle Indizierung erforderlich:
Document updated
│
▼
new document version
│
▼
parse changed document
│
▼
generate new chunks
│
▼
embed
│
▼
write new index entries
│
▼
mark previous version inactive
Logische Versionierung ist besser als sofortiger zerstörerischer Ersatz. Zum Beispiel bei einem Dokument:
document_id = D17
version 41 → status=inactive
version 42 → status=active
Abfragen nach Filtern
status = active
Alte Einträge können im Hintergrund gelöscht werden. Aus Sicht der Anwendung erfolgt die Aktualisierung atomar, wodurch es nicht notwendig ist, dass jedes Speichersystem zur gleichen Zeit geändert wird.
Versionieren Sie auch die Modelle
Früher oder später wechseln Sie die Embedding-Modelle, und Sie sollten niemals Vektoren speichern, ohne zu wissen, was sie erzeugt hat. Halten Sie zumindest Folgendes nachvollziehbar:
embedding_model
embedding_version
embedding_dimensions
chunking_version
parser_version
Andernfalls könnten Sie nach sechs Monaten feststellen, dass 15 % eines Index mit einem älteren Pipeline erstellt wurden, und es gäbe keine einfache Möglichkeit, herauszufinden, um welche 15 % es sich handelt. Eine Migration kann an parallelen Indexen durchgeführt werden:
Documents
│
┌─────────┴─────────┐
▼ ▼
embedding model V3 embedding model V4
│ │
▼ ▼
Index V3 Index V4
│
▼
shadow traffic
│
▼
evaluate
│
▼
switch alias
Verlagern Sie keine Milliarden von Vektoren und hoffen Sie einfach nicht, dass die neue Suchmethode besser ist. Messen Sie die Leistung vor dem Umstieg.
Caching auf mehreren Ebenen
Nicht jede Anfrage erfordert den gesamten Prozessablauf. Wenn Tausende von Mitarbeitern fragen „Wie viele Urlaubstage haben wir?“, dann ist es nicht notwendig, den gesamten
embedding
→ distributed retrieval
→ reranking
→ large LLM
Jedes einzelne Mal ist verschwenderisch. Eine mehrschichtige Cache-Struktur macht mehr Sinn:
User Query
│
▼
Exact Response Cache
│ miss
▼
Semantic Cache
│ miss
▼
Retrieval Cache
│ miss
▼
Search
Ein Embedding-Cache ist der offensichtliche Begleiter:
hash(normalized_query)
↓
query embedding
Es wird schwierig, wenn sich der Inhalt ändert. Eine gespeicherte Antwort für
Policy version 17
Kann einmal falsch ablaufen.
Policy version 18
Es geht live. Der Cache-Schlüssel oder das Invalidation-Verfahren muss daher über die Grenzen des Korpus und der Versionen Bescheid wissen. Das Speichern von Modellausgaben ohne Frische-Regeln ist gefährlich.
Planen Sie für Fehlschläge – nicht nur für den erfolgreichen Ablauf
Große Systeme versagen schrittweise. Rechnen Sie mit Dingen wie:
embedding provider unavailable
one vector shard unavailable
search cluster degraded
reranker timeout
LLM rate limited
LLM provider outage
queue backlog growing
parser crashing on malformed PDF
OCR worker running out of memory
Jeder Fehler erfordert eine ausdrückliche Richtlinie. Möglicherweise:
Vector search fails
↓
Can lexical search answer?
↓
yes
↓
return degraded retrieval mode
oder:
Primary LLM unavailable
↓
Fallback model
↓
Lower quality but service continues
oder:
Document parser fails 5 times
↓
Dead-letter queue
↓
record failure reason
↓
operator / automated remediation
Endloses Wiederholen ist keine Widerstandsfähigkeit. Manchmal handelt es sich dabei einfach um eine endlose Schleife mit einer zusätzlichen Rechnung für die Cloud-Dienste.
Rückdruck
Stellen Sie sich vor, die Eingabe erhält normalerweise
20M chunks/hour
und die Einbettungskapazität kann damit umgehen
25M chunks/hour
Alles ist in Ordnung. Dann führt ein Kunde eine Migration durch und erzeugt
200M chunks/hour
Ohne Rückdruck verläuft der Fehler wie folgt:
embedding service overloaded
↓
timeouts
↓
retries
↓
more load
↓
more failures
↓
retry storm
Mit einem Puffer vor den Arbeitern:
incoming workload
↓
durable queue
↓
workers consume at safe rate
↓
backlog temporarily grows
Die Kapazität kann nun unabhängig von der Ankunftsrate skaliert werden. Das ist ein weiterer Grund, warum die Eingabeschicht ihre eigene Architektur benötigt.
Die Beobachtbarkeit muss die Qualität der Abrufung abdecken
Übliche Überwachungsberichte zeigen Dinge wie:
HTTP 200 rate
CPU
memory
disk
p95 latency
error rate
requests/second
Alles nützlich, aber ein RAG-Dienst kann in 800 ms ein HTTP 200 zurückgeben und dennoch eine völlig falsche Antwort liefern. Man benötigt auch telemetrische Daten, die speziell für KI konzipiert sind. Eine nützliche Trace-Information pro Anfrage könnte wie folgt aussehen:
request_id: rq_912
query rewrite:
"parental policy?" → "parental leave policy"
vector retrieval:
200 candidates
145 ms
lexical retrieval:
200 candidates
91 ms
fusion:
278 unique candidates
reranking:
278 → 20
212 ms
context:
13 chunks
8,420 tokens
LLM:
input 9,104 tokens
output 681 tokens
1.8 sec
citations:
3 documents
total:
2.4 sec
Die Offline-Bewertung benötigt daher Metriken, die Fragen wie folgende beantworten: Wurden die richtigen Belege abgerufen, standen sie hoch genug im Rang, wurde die Antwort durch diese Belege gestützt, verbesserte eine erneute Sortierung ausreichend viele Abfragen, um die damit verbundene Verzögerung zu rechtfertigen, und welche Abfrugruppen versagen am häufigsten? Das ist handlungsorientierter als die Erfassung von Halluzinationen als ein einziger globaler Prozentsatz.
Bewerte das Abrufen getrennt von der Generierung
Wenn die endgültige Antwort falsch ist, gibt es mindestens zwei unterschiedliche Versagungskategorien. Bei einem Abrufversagen gelangen die benötigten Belege niemals in den Kontext:
Correct document exists
↓
retriever misses it
↓
LLM never sees it
↓
wrong answer
Auch eine umfassende Bearbeitung des Prompts kann das nicht beheben. Bei einem Generationsscheitern ist das richtige Beweismaterial tatsächlich angekommen:
correct evidence
↓
context
↓
LLM
↓
incorrect interpretation
Nun können die Gestaltung der Anfrage, die Auswahl des Modells, die Organisation des Kontexts oder die Strategie der Argumentation von Bedeutung sein. Wenn man nur die Endantworten bewertet, erscheinen diese beiden identisch, weshalb ein seriöses Bewertungsverfahren die Zwischenschritte überprüfen muss.
Legen Sie einem Zeitrahmen für die Latenz fest
„Das System muss schnell sein“ ist keine Anforderung. Legen Sie stattdessen jedem Schritt einen Zeitrahmen fest. Zur Veranschaulichung nehmen wir an, das Ziel ist eine erste Antwort innerhalb von etwa 2,5 Sekunden, wobei die Zeit auf folgende Weise aufgeteilt wird:
API + auth 50 ms
query understanding 100 ms
retrieval 300 ms
fusion 20 ms
reranking 250 ms
context construction 30 ms
LLM time-to-first-token 1,200 ms
network / safety margin 550 ms
-----------------------------------
target ~2,500 ms
Dies sind keine universellen Zahlen. Die Übung ist wichtig, weil sie zeigt, wohin die Zeit tatsächlich fließt. Wenn das Abrufen 1,8 Sekunden in Anspruch nimmt, wird ein Wechsel der LLM-Anbieter den Hauptengpass nicht beeinflussen. Wenn das LLM 90 % der Verzögerung ausmacht, ist es unwahrscheinlich, dass die Mühe, einen Monat lang 40 ms aus der Vektorabfrage herauszuholen, die beste Nutzung der Ressourcen ist.
Auch bei den Kosten gilt dieselbe Disziplin
Ein großes RAG-System weist viele separate Kostenzentren auf:
object storage
document parsing
OCR
embedding generation
vector storage
vector replicas
lexical search
metadata database
network transfer
reranking
LLM input tokens
LLM output tokens
observability
backups
So ermittelt man den Kosten pro nützlicher Einheit, zum Beispiel:
cost / 1,000 indexed documents
cost / million chunks
cost / query
cost / successful answer
cost / tenant
Das Letztere ist im SaaS-Bereich am wichtigsten. Möglicherweise stellen Sie fest:
Tenant A
5% of revenue
42% of LLM spend
Ohne Messung pro Mieter sieht man nur eine unerwartet hohe monatliche Infrastrukturrechnung.
Halten Sie teure Speicherlösungen für diejenigen bereit, die sie benötigen
Ein hilfreiches Bild ist ein Gradient von teuer und schnell bis billig und groß:
expensive / fast
▲
│
vector indexes
search indexes
Redis caches
│
metadata DB
│
object storage
│
▼
cheap / large
Das vollständige PDF hat keinen Grund, im Vektorindex zu gespeichert zu werden. Reservieren Sie teuren Speicher nur für diejenigen Abrufanfragen, bei denen er benötigt wird – lassen Sie den Objektspeicher die Quelldokumente aufbewahren und die Suchinfrastruktur die suchbaren Darstellungen. Wenn das Korpus in den Hundertterabyte-Bereich kommt, wird dieser Unterschied äußerst wichtig.
Heiße und kalte Daten
Nicht alle 100 Millionen Dokumente werden gleich häufig abgerufen. Vielleicht:
Last 30 days → 70% of queries
Last 12 months → 25%
Older archive → 5%
Alles auf dieselbe Weise zu behandeln, verschwendet Infrastruktur. Eine Möglichkeit ist eine solche Aufteilung:
Query
│
├────→ Hot index
│
└────→ Archive index when necessary
Zeitbasiertes Routing verhindert außerdem, dass eine Frage zu den Ereignissen dieser Woche auf zehn Jahre an historischen Daten stößt. Große Systeme werden effizienter, indem sie Arbeit vermeiden, und nicht nur dadurch, dass sie unnötige Arbeiten schneller erledigen.
Mehrfachnutzung ändert alles
Nehmen wir an, Ihre Plattform dient
10,000 organizations
Wahrscheinlich möchten Sie das auf keinen Fall.
10,000 completely independent vector clusters
Doch das Zusammenfassen aller Mieter in einem einzigen Namespace führt zu Problemen durch störende Mitbenutzer sowie zu Autorisierungsproblemen. Ein praktikabler Ansatz besteht darin, die Mieter nach ihrer Größe zu gruppieren:
Small tenants
↓
shared shard groups
Medium tenants
↓
partitioned shared infrastructure
Very large tenants
↓
dedicated shard groups
Dadurch wird verhindert, dass ein Kunde 500 Mio. Datensätze importiert und damit Tausende kleinere überflutet, und es wird eine Isolierungsstrategie für Unternehmenskunden bereitgestellt, die sie benötigen. Keine einzige Strategie zur Partitionierung nach Nutzern eignet sich für alle.
Das LLM steht kurz vor dem Ende
Betrachten Sie, wie viel an Ingenieurarbeit wir bereits erledigt haben, bevor wir zum Modell kommen. Das ist absichtlich so. Ein ausgereifter RAG-Anfrageablauf verläuft wie folgt:
User
↓
API Gateway
↓
Authentication
↓
Authorization
↓
Rate Limiter
↓
Query Understanding
↓
Shard Routing
↓
Hybrid Candidate Retrieval
↓
Rank Fusion
↓
Reranker
↓
Deduplication
↓
Context Builder
↓
LLM
↓
Citation Validation
↓
Response
Das LLM ist wichtig, aber bei 100 Millionen Dokumenten ist es nur ein Bestandteil von vielen.
Das vollständige Produktionsbild
Zusammenfassend zeigt sich folgender Ablauf: zunächst der Abfragenpfad, dann der Eingabepfad und schließlich die Systeme, die beide unterstützen:
CLIENTS
│
▼
┌──────────────┐
│ API Gateway │
└──────┬───────┘
│
┌──────▼───────┐
│ Auth / ACL │
│ Rate Limits │
└──────┬───────┘
│
▼
┌────────────────────┐
│ Query Orchestrator │
└─────────┬──────────┘
│
┌─────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Cache Query Rewrite Metadata
Filters
│ │ │
└─────────────┼──────────────┘
▼
Shard Router
│
┌────────────┴────────────┐
▼ ▼
Lexical Search Vector Search
│ │
└───────────┬─────────────┘
▼
Rank Fusion
│
▼
Reranker
│
▼
Deduplicate
│
▼
Context Builder
│
▼
LLM
│
▼
Citation Validation
│
▼
RESPONSE
================================================================
INGESTION SYSTEM
Documents
│
▼
Object Store
│
▼
Event Queue
│
┌────────────┼────────────┐
▼ ▼ ▼
Parser Parser Parser
│ │ │
└────────────┼────────────┘
▼
Normalization
│
▼
Chunker
│
┌──────────────┴──────────────┐
▼ ▼
Embedding Queue Text Index Queue
│ │
┌─────┼─────┐ ▼
▼ ▼ ▼ Lexical Index
GPU GPU GPU
│ │ │
└─────┼─────┘
▼
Vector Index
================================================================
SUPPORTING SYSTEMS
Metadata DB → document state, versions, ownership
Object Storage → original source documents
Vector Cluster → semantic retrieval
Search Cluster → lexical retrieval
Redis → caches and rate limiting
Message Queue → asynchronous ingestion
Observability → traces, metrics, evaluation
Secrets / IAM → service authorization
Evaluation System → retrieval + answer-quality tests
Anti-Muster, die vermieden werden sollten
Bei der Betrachtung einer solchen Architektur würden folgende Aspekte sofort Bedenken hervorrufen:
One giant vector collection with no routing strategy
Synchronous PDF ingestion
Only vector retrieval, no lexical search
ACL filtering after retrieval
No document versioning
No embedding model version
No reranking
Sending top-100 chunks directly to the LLM
Using the LLM for every trivial classification task
No dead-letter queue
No retrieval-level evaluation
No tenant-level cost attribution
Treating the vector DB as permanent document storage
Re-embedding the entire corpus for every small content change
Benchmarking only average latency instead of tail latency
Einer von ihnen kann bereits als Prototyp dienen. Gemeinsam, in der Größenordnung von Milliarden Vektoren, werden sie äußerst problematisch.
Das Grundprinzip
Die nützlichste Art, über große RAG-Systeme nachzudenken, ist nicht diese:
question
→ vector search
→ LLM
Das ist es:
billions of possible evidence units
↓
routing constraints
↓
relevant partitions
↓
cheap candidate search
↓
hundreds of items
↓
expensive reranking
↓
tens of passages
↓
context optimization
↓
LLM
↓
grounded answer
Das System funktioniert wie ein Trichter. In jeder Phase investieren wir mehr Rechenleistung in immer weniger Kandidaten:
Cheap operation → huge search space
Moderate operation → smaller candidate set
Expensive operation → tiny candidate set
Most expensive model → final context only
Genau diese Idee sorgt dafür, dass die Architektur wirtschaftlich tragfähig bleibt.
Was man mitnehmen sollte
Ein RAG-System für ein paar Tausend Dokumente ist im Wesentlichen eine Aufgabe der Anwendungsentwicklung. Bei 100 Millionen Dokumenten verwandelt es sich in eine Suchinfrastruktur, verteilt Systeme, Datenverarbeitung, Sicherheitsmaßnahmen und Kosteneinschränkungen – alles erfordert gleichzeitig Aufmerksamkeit. Die Vektordatenbank ist wichtig, genauso wie das Embedding-Modell und das LLM, doch die Architektur, die sie umgibt, ist wichtiger, als die meisten Anleitungen zugeben.
Das skalierbare mentale Modell: Speichern Sie die Quellen kostengünstig, verarbeiten Sie sie asynchron, führen Sie Versionierungen durch, teilen Sie gezielt auf, setzen Sie früh Autorisierungsregeln um, holen Sie Daten mit mehreren Signalen ab, bewerten Sie die Ergebnisse streng, liefern Sie dem Modell nur hochwertige Belege und messen Sie jede Phase für sich. Wenn das Korpus Milliarden von Teilen umfasst, besteht die beste Optimierung nicht mehr darin, die Suche zu beschleunigen. Vielmehr geht es darum, die Struktur so anzupassen, dass der größte Teil des Systems niemals mit dem größten Teil des Korpus in Berührung kommt.