Design: Enterprise RAG
RAG over 10M documents for 50k users — the canonical question.
Prerequisites
- How to reason about AI system design — the six-phase skeleton this walkthrough follows.
- RAG pipeline — end-to-end retrieval + generation before enterprise constraints.
- Filtering and metadata — why ACL pre-filters matter inside ANN.
- Production RAG — freshness, ACLs, cost, and degradation patterns.
The problem
Design a company-wide knowledge assistant over all internal documents — natural-language Q&A with citations over roughly 10M documents for 50k employees. Variants of the same problem: document search + Q&A at that scale, or "productionize RAG for an enterprise."
What makes this the canonical design is not the generator. The generation step is the easy 10%. The work is the four things that make enterprise RAG hard: ingestion at scale, incremental freshness, per-user access control, and retrieval quality you can measure.
In the governed enterprise platform, this is the retrieval layer over internal documents with entitlement-filtered search — the feature employees touch every day, and the feature that fails loudly if an M&A doc leaks or last week's policy is still what the assistant cites.
Why it is hard
Tutorial RAG is: chunk a folder of PDFs, embed, top-k, prompt. That shape collapses under enterprise constraints for specific reasons:
- Sources are heterogeneous. SharePoint, Confluence, Drive, tickets, PDFs with tables and scans — connectors and layout-aware parsing become a subsystem, not a script.
- Permissions are per-document and change. Different users see different data. "The assistant leaked the M&A doc" is the failure mode that kills the project. ACL enforcement at retrieval is a core axis, not an add-on.
- Freshness is contractual in spirit even when not on paper. If a doc edited at 9:00 must be answerable at 9:05, nightly batch indexing is wrong; you need event-driven incremental indexing.
- Enterprise queries are full of exact-match tokens. Error codes, project codenames, acronyms, people names — pure dense retrieval loses them. Hybrid search is structural, not optional.
- Quality without measurement is theater. You need a golden set, gates on deploy, and a loop that mines real failures — not a demo that looked good on five hand-picked questions.
- Cost is real but secondary to quality at this QPS. At a few queries per second peak, over- engineering serving is the wrong place to spend complexity.
Alternatives that lose: pure keyword search (no NL intent), pure dense RAG without ACLs (compliance failure), and a single nightly reindex (stale answers that look authoritative).
Requirements and scale
Clarifying questions (each tied to a design consequence)
- What are the document sources and formats? SharePoint/Confluence/Drive/PDFs — decides connector complexity and whether parsing is a major subsystem. PDFs with tables/scans need OCR and layout-aware parsing; wikis are trivial.
- Do users have different permissions on documents? Almost always yes in enterprise. ACL enforcement at retrieval time becomes a core design axis.
- Freshness requirement? If a doc edited at 9:00 must be answerable at 9:05, you need event-driven incremental indexing, not nightly batch.
- Latency target and interaction mode? Chat with streaming tolerates ~1s to first token; a search-results page wants <500ms total.
- Multilingual? Structured data too? Embedding model choice; whether you also need text-to-SQL — scope it out if possible.
- Budget sensitivity? Decides model tier and how aggressive caching must be.
Requirements (assumed after clarification)
| Kind | Requirement |
|---|---|
| Functional | Natural-language Q&A with citations over 10M mixed documents |
| Functional | Respects per-document ACLs |
| Functional | Answers reflect edits within ~15 minutes |
| Non-functional | p95 time-to-first-token < 1.5s |
| Non-functional | ≥90% retrieval recall@10 on a golden set |
| Non-functional | Grounded answers only (cite or refuse) |
| Non-functional | ~$10k/month serving budget |
Capacity estimates
| Quantity | Estimate | Derivation |
|---|---|---|
| Corpus tokens | ~25B | 10M docs × ~2,500 tokens avg → 10,000,000 × 2,500 = 25,000,000,000 |
| Chunks | ~50M | 512-token chunks with ~10% overlap: 25B / ~500 effective tokens per chunk ≈ 50M |
| Vector storage | ~300 GB fp32 → ~75 GB int8 | 1536-dim × 4 B = 6,144 B ≈ 6 KB/vector; 50M × 6 KB = 300,000,000 KB ≈ 300 GB; int8 + HNSW graph overhead → ~75 GB |
| One-time embedding cost | ~$500–3,000 | 25B tokens × $0.02–0.13/M (small vs large embedding model): 25,000 × $0.02 = $500; 25,000 × $0.13 = $3,250 |
| Query load | 25k/day ≈ 0.3 QPS avg, ~3 QPS peak | 50k users × 10% DAU × 5 queries/day = 50,000 × 0.10 × 5 = 25,000/day; 25,000 / 86,400 ≈ 0.29 ≈ 0.3 QPS; peak ~10× ≈ 3 QPS |
| LLM cost/query | ~$0.015–0.02 | ~4k prompt + 300 completion at ~$3/$15 per M (mid-tier, mid-2026): 4,000/1e6 × $3 = $0.012; 300/1e6 × $15 = $0.0045; total ≈ $0.0165 |
| Monthly LLM cost | ~$9–13k pre-caching | 25,000 queries/day × 30 ≈ 750,000 queries/month × $0.015 ≈ $11,250; range ~$9–13k; caching + model tiering brings it under ~$10k budget |
| Index churn | ~100k docs/day ≈ 1–2 docs/s | ~1% of 10M daily = 100,000; 100,000 / 86,400 ≈ 1.16 docs/s — trivially absorbed by a queue |
Takeaway: at 3 QPS peak, this is not a throughput problem — it is a quality, freshness, and permissions problem. Do not over-engineer serving; spend complexity on retrieval and eval.
The design
Ingestion plane
Per-source connectors emit create/update/delete events (webhooks where the source supports them, else polling with change tokens) into a queue. Workers:
- Parse — layout-aware PDF parsing for the hard 20%; simpler extractors for wikis and tickets. See ingestion and chunking.
- Chunk at ~512 tokens respecting headings.
- Prepend a 1–2 sentence document-context string to each chunk before embedding (contextual retrieval — cuts retrieval failure rate materially; see Anthropic's measured results in Go deeper).
- Embed in batches (batch API is often ~50% cheaper than online embed).
- Upsert into vector + keyword indexes.
Full corpus backfill: 25B tokens at ~5k tokens/s/worker with 20 workers:
- Throughput = 20 × 5,000 = 100,000 tokens/s
- Time = 25,000,000,000 / 100,000 = 250,000 s ≈ 250,000 / 86,400 ≈ 2.9 days ≈ 3 days
Plan re-embedding (new model) as a blue/green index swap, never in place.
Index
One vector index (HNSW, int8-quantized, ~75 GB — fits on 2–3 nodes with replication) plus a BM25/keyword index over the same chunks. Hybrid matters in enterprise because queries are full of exact-match tokens embeddings handle poorly: error codes, project codenames, acronyms, people. Merge with reciprocal rank fusion.
Index choice and scaling background: ANN indexes, vector DB landscape, scaling and operations.
ACL enforcement — core, not optional
Store principal lists (users/groups) as metadata on every chunk, synced from the source systems' permission APIs; expand the querying user's groups once per session (cached, 5-min TTL). Enforce as a pre-filter inside the ANN query (filtered HNSW), not post-retrieval — post-filtering can return 0 results for a user who matches few docs, and worse, a bug fails open.
Permission changes flow through the same event queue as content changes; deletes and permission revocations get a fast lane (target: enforced within 1 minute, vs 15 for content).
Common misconception
Post-filtering ACLs after top-k looks simpler but fails open under bugs and fails closed under sparse permissions (user matches few docs → empty result set after filter). Pre-filter inside the ANN query is the correct default for enterprise.
Serving path — latency budget
| Stage | p50 | p95 | Notes |
|---|---|---|---|
| Query rewrite (small LLM, optional) | 150 ms | 300 ms | Skip for short keyword-ish queries |
| Embed query | 20 ms | 50 ms | |
| Hybrid retrieval (ANN + BM25, parallel) | 30 ms | 80 ms | top 100 candidates, ACL pre-filtered |
| Cross-encoder rerank 100 → 8 | 60 ms | 120 ms | The recall-vs-latency knob; see rerankers |
| Context assembly | 5 ms | 10 ms | ~4k tokens |
| LLM time-to-first-token | 400 ms | 900 ms | Streaming; total generation 2–4s |
| Total to first token | ~0.7 s | ~1.5 s | Meets target |
p95 stack check: 300 + 50 + 80 + 120 + 10 + 900 = 1,460 ms ≈ 1.5 s.
Evaluation
- Golden set: 300–500 (query → relevant-chunk → reference-answer) triples; seed with synthetic questions generated per document, then replace with mined real queries. Track recall@10 and MRR for retrieval separately from faithfulness/answer-quality (LLM-as-judge with a rubric, spot-checked by humans). Detail: RAG evaluation.
- Gate: eval suite runs on every prompt, chunking, embedding, or model change; a >2-point recall drop blocks deploy.
- Online: citation-click rate, thumbs ratio, refusal rate, p95 latency, $/query. Weekly triage of worst-rated answers feeds the golden set — the loop is the deliverable, not the score.
Key decisions and tradeoffs
| Decision | Choice | Tradeoff |
|---|---|---|
| Hybrid vs dense-only | Hybrid (ANN + BM25) + RRF | Extra index to operate; catches identifier queries dense retrieval loses |
| ACL filter placement | Pre-filter in ANN | Harder to implement than post-filter; avoids empty results and fail-open bugs |
| Chunk size | ~512 tokens + contextual headers | Larger chunks = fewer vectors but noisier retrieval; headers cost embed tokens |
| Rerank depth | 100 → 8 cross-encoder | +60–120 ms; first lever to shed under load |
| Freshness | Event-driven, 15 min content / 1 min ACL+delete | More moving parts than nightly batch; correct for enterprise trust |
| Serving scale | Few nodes, modest QPS | Do not build multi-region active-active for 3 QPS peak |
| Re-embed strategy | Blue/green index swap | 2× storage during cutover; zero in-place corruption risk |
| Grounding | Cite or refuse | Higher refusal rate; lower silent hallucination |
Key insight
At this scale the dominant engineering surface is who can see what and whether the right chunk was retrieved — not tokens per second through the LLM. Size the system so that fact is obvious, then spend design time where the risk is.
The flows
Flow A — Online query (happy path)
- User submits natural-language question (authenticated session).
- Optional query rewrite for long/ambiguous questions; short keyword-ish queries skip rewrite.
- Embed query; expand user's groups from session cache (5-min TTL).
- Hybrid retrieve top 100 with ACL pre-filter on both vector and BM25 legs.
- Cross-encoder rerank → top 8; assemble ~4k-token context with citations metadata.
- LLM generates grounded answer with citations (or refuses if evidence insufficient).
- Stream tokens; log full trace for eval mining.
Breaks when: ACL metadata stale, hybrid fusion mis-weighted, or context omits the one supporting chunk.
Flow B — Incremental ingest (content update)
- Source emits create/update via webhook or change-token poll.
- Event lands on ingest queue; worker parses and re-chunks the document.
- Contextual headers applied; batch embed; upsert vectors + BM25 postings.
- Doc→chunk mapping updated in document store.
- Answerable within ~15 minutes target.
Breaks when: connector misses events, parser drops tables, or upsert races with a concurrent delete.
Flow C — Permission revoke / delete (fast lane)
- Source emits permission change or delete.
- Event takes fast lane (priority queue).
- All chunks for that doc updated or tombstoned via doc→chunk map.
- User's group-membership cache entry invalidated when membership changes.
- Enforced on next query within ~1 minute; semantic cache keys include permission-set hash so cached answers cannot cross ACL boundaries.
Breaks when: semantic cache is not ACL-scoped, or group cache is not invalidated on membership events.
Flow D — Evaluation and regression gate
- CI runs golden set on every prompt / chunk / embed / model change.
- Recall@10 and faithfulness compared to baseline; >2-point recall drop blocks deploy.
- Online worst-rated answers triaged weekly → new golden triples.
- Canary or full deploy only after gate passes.
Breaks when: golden set is synthetic-only and misses real identifier queries, or online mining never runs.
Failure modes and degradation
| Failure | Response |
|---|---|
| LLM provider outage | Fallback provider behind a gateway; degrade to extractive "here are the top passages" mode — keeps search alive with zero generation |
| Index node loss | Replicas; rebuild from the document store, not from sources |
| Cost drift | Per-team token metering, alert at 120% of forecast |
| Quality regression on model upgrade | Pinned versions, eval gate, canary on 5% of traffic |
| Poisoned/lookalike documents | Retrieved text is untrusted: instruct-and-delimit; never let this system's output trigger actions |
| Overload (rare at 3 QPS) | Drop rerank first, then shrink k, then smaller model |
Degradation order for latency pressure: drop reranking → shrink candidate k → skip query rewrite → smaller model → extractive passages only. Aligns with reliability and latency.
Security posture for retrieved content: security — prompt injection via documents is a real path; treat chunks as untrusted data.
Common drill-downs
A user just lost access to a document. Walk through what happens.
The source emits a permission event → fast-lane queue → metadata update on all chunks of that doc (doc→chunk mapping kept in the doc store) → enforced on next query via pre-filter; group-membership cache TTL (5 min) bounds staleness, and you can invalidate that user's cache entry on the event for ~immediate enforcement. Also: the semantic cache must be ACL-scoped, or a cached answer leaks across permission boundaries — cache key includes the permission-set hash.
Why hybrid retrieval? Wouldn't a better embedding model do?
Embeddings compress 512 tokens into one vector — rare exact identifiers (SKU-4471, "Project Foxtrot") are precisely what gets lost. BM25 catches them at ~zero marginal cost since you need a text index for filters anyway. Know the failure classes, not just the pattern name.
Your recall@10 is 70%. Where do you look first?
In order of measured payoff: chunking bugs (mid-table splits, lost headings) → missing keyword path for identifier queries → query/document vocabulary mismatch (fix with query rewrite or contextual chunk headers) → only then embedding model shopping. Segment the eval by query type before changing anything global.
10M docs becomes 500M. What breaks?
Vector index no longer fits a few nodes → shard by tenant/department (queries rarely span shards); embedding backfill becomes a weeks-long job → prioritize by access frequency; ACL metadata explodes for org-wide docs → represent "everyone" groups specially instead of materializing 50k principals per chunk; eval set must grow per domain.
How do you stop it answering from stale or deleted content?
Deletes get the fast lane (tombstone in index within 1 min); generation prompt includes each chunk's last-modified date and instructs recency preference on conflict; citations carry versioned links so a stale answer is at least auditable.
Test yourself
Peak load is ~3 QPS. A proposal adds multi-region active-active vector indexes 'for scale.' How do you respond?
Post-filter ACLs after retrieving top 100. A restricted user often gets empty answers. Diagnose.
Monthly LLM spend is ~$12k pre-cache against a $10k budget. Which levers, in order?
After switching embedding models, recall@10 drops 5 points on identifier queries only. What failed?
Full re-embed takes ~3 days. How do you cut over without a 3-day quality hole?
Go deeper
- Introducing Contextual Retrieval — Anthropic — the chunk-context technique above, with measured retrieval-failure reductions.
- Systematically Improving Your RAG — Jason Liu — the retrieval-first improvement loop: synthetic evals, hybrid search, feedback mining.
- Building A Generative AI Platform — Chip Huyen — where enterprise RAG sits inside the full platform: gateway, caches, guardrails, observability.
- Building Production-Ready RAG Applications: Jerry Liu — AI Engineer — the most-watched conference talk on exactly this question's production concerns.
Where this connects
- Document Q&A at scale — same retrieval ideas under multi-tenant SaaS throughput where QPS and isolation dominate.
- LLM gateway — the control plane for model routing, budgets, and fallback that enterprise RAG should call rather than hard-coding a provider.
- Advanced RAG — agentic and graph patterns when single-shot hybrid retrieval is not enough.
- Observability — tracing the serving path so recall and cost regressions are visible per stage.