Requirements & scope
Problem Statement & Requirements
Design a production Retrieval-Augmented Generation system that ingests enterprise knowledge bases (documents, wikis, databases), indexes them for semantic search, retrieves relevant context at query time, and augments LLM prompts to generate accurate, grounded answers with source citations — similar to systems powering Perplexity AI, ChatGPT with search, enterprise Q&A bots (Glean, Guru), and custom knowledge assistants.
Functional Requirements
- Document ingestion — ingest documents from diverse sources: PDFs, web pages, Confluence, Notion, Slack, Google Drive, databases, APIs
- Chunking & embedding — split documents into semantic chunks, generate vector embeddings for each chunk
- Vector search (retrieval) — given a user query, find the most relevant document chunks via semantic similarity
- Hybrid search — combine vector (semantic) search with keyword (BM25) search for better recall
- LLM generation with context — send retrieved chunks as context to an LLM to generate a grounded answer
- Source citations — every claim in the answer links back to the source document and chunk
- Conversational memory — multi-turn conversations with context carried across turns
- Access control — users only see results from documents they have permission to access
- Incremental sync — documents update automatically when source changes (near real-time)
- Evaluation & feedback — track answer quality; thumbs up/down; use feedback for improvement
Non-Functional Requirements
- Answer quality — relevant, accurate, no hallucination; grounded in retrieved sources
- Low latency — retrieval + generation end-to-end < 3 seconds for simple queries
- Scalability — support 100M+ document chunks, 10K+ concurrent queries
- Freshness — new/updated documents searchable within minutes of change
- Multi-tenancy — thousands of organizations with isolated knowledge bases
- Cost efficiency — minimize embedding compute and LLM token usage
Out of Scope
- LLM training / fine-tuning
- General web search (Perplexity-scale crawling)
- Image/video understanding in documents
- Full AI agent orchestration framework
Scale estimations
Scale Estimations
Traffic
| Metric | Value |
|---|---|
| Organizations (tenants) | 10K |
| Total source documents | 500M |
| Total chunks (after splitting) | 5B |
| Embedding dimensions | 1536 (OpenAI) or 1024 (open-source) |
| Queries / second | 5K RPS |
| Peak queries / second | 15K RPS |
| Documents ingested / day (new + updated) | 10M |
| Chunks processed / day | 100M |
Storage
| Metric | Value |
|---|---|
| Average chunk size (text) | 500 tokens ≈ 2 KB |
| Total chunk text storage | 5B × 2 KB = ~10 TB |
| Vector size per chunk (1536 × float32) | 6 KB |
| Total vector storage | 5B × 6 KB = ~30 TB |
| Metadata per chunk (source, title, permissions, timestamps) | 500 bytes |
| Total metadata | 5B × 500 B = ~2.5 TB |
| BM25 inverted index | ~5 TB |
| Total storage | ~50 TB |
Compute
| Metric | Value |
|---|---|
| Embedding compute (ingestion: 100M chunks/day) | 100M × 0.1 ms/chunk = ~2.8 GPU-hours/day |
| Embedding compute (query: 5K/s) | 5K × 0.5 ms = ~2.5 GPU continuously |
| LLM generation (5K/s × ~500 tokens output) | 2.5M tokens/sec (significant GPU fleet) |
| Vector search (5K/s, top-20 across 5B vectors) | ~50 HNSW index nodes |
| Reranker (5K/s × 20 candidates) | 100K rerank operations/sec |
Bandwidth
| Metric | Value |
|---|---|
| Query response (5K/s × 5 KB avg) | ~25 MB/s |
| Embedding ingestion pipeline | ~50 MB/s |
| Vector index replication | ~30 MB/s |
Layered architecture
High-Level Architecture
High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Ingestion Pipeline
Ingestion PipelineExcalidraw diagram · editable shapes · reveal step by stepExplore
API & contracts
API Design
Query (Chat with Knowledge Base)
POST /api/v1/query
Authorization: Bearer <token>
Request:
{
"query": "How does our refund policy work for international orders?",
"conversation_id": "conv-abc123", // for multi-turn
"knowledge_base_id": "kb-acme-corp",
"filters": {
"source_type": ["confluence", "notion"],
"date_range": {"after": "2025-01-01"},
"tags": ["policy", "customer-facing"]
},
"top_k": 5, // number of chunks to retrieve
"stream": true,
"include_sources": true
}
Response (SSE stream):
data: {"type": "retrieval_complete", "sources": [
{"chunk_id": "chk-001", "source": "Refund Policy FAQ", "url": "https://confluence.acme.com/...", "relevance": 0.94},
{"chunk_id": "chk-002", "source": "Terms of Service v3.2", "url": "...", "relevance": 0.89},
{"chunk_id": "chk-003", "source": "International Shipping KB", "url": "...", "relevance": 0.85}
]}
data: {"type": "content_delta", "text": "Our refund policy for international orders "}
data: {"type": "content_delta", "text": "allows returns within 30 days of delivery "}
data: {"type": "content_delta", "text": "[1]. "}
data: {"type": "content_delta", "text": "For international shipments, the return shipping "}
data: {"type": "content_delta", "text": "cost is borne by the customer unless the item was "}
data: {"type": "content_delta", "text": "defective [2]. "}
...
data: {"type": "done", "usage": {"retrieval_chunks": 5, "prompt_tokens": 1842, "completion_tokens": 287}}Ingest Documents
POST /api/v1/knowledge-bases/{kb_id}/documents
Authorization: Bearer <token>
Request:
{
"source": {
"type": "confluence",
"space_key": "ENG",
"page_ids": ["12345", "67890"], // specific pages, or null for full space
"sync_mode": "incremental" // full | incremental
},
"processing": {
"chunking_strategy": "semantic", // semantic | fixed | recursive
"chunk_size_tokens": 512,
"chunk_overlap_tokens": 50,
"embedding_model": "text-embedding-3-large"
},
"access_control": {
"inherit_source_permissions": true // mirror Confluence page permissions
}
}
Response (202 Accepted):
{
"ingestion_job_id": "job-xyz789",
"status": "processing",
"documents_queued": 2,
"estimated_chunks": 150
}Get Ingestion Status
GET /api/v1/ingestion-jobs/{job_id}
Response (200 OK):
{
"job_id": "job-xyz789",
"status": "completed",
"documents_processed": 2,
"chunks_created": 147,
"chunks_updated": 12,
"chunks_deleted": 3,
"errors": [],
"completed_at": "2026-04-03T10:05:00Z"
}Feedback
POST /api/v1/feedback
{
"query_id": "q-abc123",
"rating": "thumbs_up", // thumbs_up | thumbs_down
"feedback_text": "Answer was accurate but missed the EU exception",
"correct_source": "chk-007" // optional: which chunk should have been retrieved
}Data model
Data Model
Document
DocumentExcalidraw diagram · editable shapes · reveal step by stepExplore
Chunk
ChunkExcalidraw diagram · editable shapes · reveal step by stepExplore
Conversation (Multi-Turn)
Conversation (Multi-Turn)Excalidraw diagram · editable shapes · reveal step by stepExplore
Core design decisions
Core Design Decisions
Decision 1: Chunking Strategy — How to Split Documents
The most impactful decision for retrieval quality. Bad chunking = bad retrieval = bad answers.
Decision 1: Chunking Strategy — How to Split DocumentsExcalidraw diagram · editable shapes · reveal step by stepExplore
Decision 2: Hybrid Retrieval (Vector + Keyword)
Decision 2: Hybrid Retrieval (Vector + Keyword)Excalidraw diagram · editable shapes · reveal step by stepExplore
Decision 3: Reranking — The Quality Amplifier
Decision 3: Reranking — The Quality AmplifierExcalidraw diagram · editable shapes · reveal step by stepExplore
Request flows
Detailed Flow Diagrams
Query Flow (End-to-End)
Query Flow (End-to-End)Excalidraw diagram · editable shapes · reveal step by stepExplore
Document Ingestion Flow
Document Ingestion FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
Performance & caching
Workshop note · added for the website’s common reading format
Start with the dominant access pattern of RAG Pipeline. Document ingestion is one place to inspect capacity and tail latency. Measure before introducing a cache: define the cache key, invalidation policy, stale-data budget, and cold-start behavior.
Batch independent work where the latency budget allows it. Bound queues and concurrency, and verify that an optimization does not move the bottleneck to a dependency.
Advanced design
Vector Database Deep Dive
HNSW Index (Hierarchical Navigable Small World)
HNSW Index (Hierarchical Navigable Small World)Excalidraw diagram · editable shapes · reveal step by stepExplore
Scaling Vector Search to 5B Vectors
Scaling Vector Search to 5B VectorsExcalidraw diagram · editable shapes · reveal step by stepExplore
Advanced Retrieval Techniques
Query Expansion / Rewriting
Query Expansion / RewritingExcalidraw diagram · editable shapes · reveal step by stepExplore
Parent-Child Chunk Retrieval
Parent-Child Chunk RetrievalExcalidraw diagram · editable shapes · reveal step by stepExplore
Edge cases
Handling Edge Cases
Hallucination Prevention
The #1 risk in RAG: LLM generates plausible but wrong information.
Multi-layer defense:
Layer 1: Grounding Prompt
System: "Answer ONLY based on the provided context.
If the context doesn't contain the answer, say
'I don't have enough information to answer this.'
NEVER make up facts not in the context."
Layer 2: Citation Enforcement
System: "Every factual claim must include [N] citing
the source chunk number. If you cannot cite a source,
do not include the claim."
Layer 3: Post-Generation Verification
After LLM generates answer:
→ Extract all factual claims
→ For each claim: verify it exists in the retrieved chunks
→ Flag unsupported claims → remove or mark as "unverified"
Latency cost: ~200-500 ms (can be async)
Layer 4: Confidence Scoring
If retrieval scores are all low (top chunk < 0.5 relevance):
→ Don't attempt to answer
→ "I couldn't find relevant information. Try rephrasing
your question or checking [suggested sources]."
Layer 5: User Feedback Loop
Thumbs down → flag for review
→ Identify: was it a retrieval failure or generation failure?
→ Feed back into chunking/embedding improvementStale / Outdated Information
Problem: Policy changed yesterday but old version still in index
Solution: Near-real-time incremental sync
1. Source connectors poll for changes every 5 minutes
(or webhook-based for supported sources)
2. Changed documents: re-chunk + re-embed ONLY changed chunks
(content_hash comparison → skip unchanged chunks)
3. Deleted documents: remove all chunks from vector + BM25 index
4. Freshness metadata:
→ Each chunk carries source_last_modified timestamp
→ Retrieval can boost recent chunks: score × recency_boost
→ LLM prompt: "Note: [Source 2] was last updated 2 hours ago"
For critical freshness:
→ Pre-retrieval filter: date_range.after = "7 days ago"
→ Or: always include most-recent version of each source documentAccess Control at Scale
Problem: User queries KB but should only see documents they can access
Enterprise scenario:
Alice (engineering) can see: eng-docs, all-company, public
Bob (sales) can see: sales-docs, all-company, public
Implementation:
Option A: Post-Retrieval Filtering (simple but wasteful)
Retrieve top-100 from index (ignoring ACL)
Filter out chunks user can't access
Return remaining top-5
Problem: if top 95 are restricted, return only 5 of low quality
Option B: Pre-Retrieval Filtering (recommended)
Each chunk has access_groups: ["engineering", "all-company"]
At query time: filter = user's groups
Vector search: WHERE access_groups INTERSECT user_groups
→ Only search within permitted chunks
→ Correct results, no wasted retrieval
Option C: Separate Indexes per Access Level
Index per permission group
Query: search ONLY indexes user has access to
+ Fastest (no filter overhead)
- More indexes to maintain
- Shared documents duplicated across indexes
Recommendation: Pre-retrieval filtering (Option B) via metadata filter
Most vector DBs support filtered search natively (Qdrant, Pinecone, Weaviate)Multi-Turn Conversation Context
Problem: Turn 2 query "What about for EU?" is meaningless without Turn 1
Solution: Query Contextualization via LLM
Conversation:
Turn 1: "How does our refund policy work?"
Turn 2: "What about for EU customers?"
Before retrieval, rewrite Turn 2 using conversation context:
Prompt to small LLM (fast, cheap):
"Given this conversation, rewrite the latest query to be standalone:
User: How does our refund policy work?
Assistant: [previous answer...]
User: What about for EU customers?
Rewritten: What is the refund policy specifically for EU customers?"
Now search with: "What is the refund policy specifically for EU customers?"
→ Much better retrieval than searching "What about for EU?"
Cost: ~50 tokens input, ~20 tokens output = negligible
Latency: ~100 ms (use small model like Haiku)Tradeoffs
Tradeoffs & Design Decisions Summary
| Decision | Option A | Option B | Chosen | Why |
|---|---|---|---|---|
| Chunking | Fixed-size (512 tokens) | Semantic/structure-aware | Semantic | Respects document structure; heading context dramatically improves retrieval |
| Search | Pure vector search | Hybrid (vector + BM25) | Hybrid + RRF fusion | +10-30% recall; handles exact terms and semantic similarity |
| Reranking | Skip (use retrieval scores) | Cross-encoder reranker | Cross-encoder | Dramatically improves precision; 20 pairs × 5 ms = 100 ms acceptable |
| Embedding model | OpenAI text-embedding-3 | Open-source (BGE/E5) | Depends on requirements | OpenAI: higher quality, API cost; open-source: self-hosted, no data leaving org |
| Vector DB | pgvector (simple) | Qdrant/Milvus (dedicated) | Dedicated for >10M vectors | pgvector for small scale; Qdrant/Milvus for 5B vectors with HNSW at scale |
| Vector sharding | Hash-based (uniform) | Tenant-based | Tenant-based | No cross-shard queries; natural isolation; query stays within one tenant's data |
| Query expansion | Raw query only | HyDE + multi-query | HyDE for complex, raw for simple | HyDE adds 500 ms + LLM cost; worth it for complex queries, skip for simple ones |
| Chunk size | 256 tokens | 512 tokens | 256-512 with parent-child | Small for precise retrieval; parent chunk returned for richer LLM context |
| Access control | Post-retrieval filter | Pre-retrieval (metadata filter) | Pre-retrieval | Correct results guaranteed; no wasted retrieval on restricted chunks |
| Freshness | Full re-index daily | Incremental (content hash) | Incremental | Re-embed only changed chunks; saves 90%+ compute on updates |
Reliability & fault tolerance
Reliability & Fault Tolerance
Single Points of Failure & Mitigations
Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore
Graceful Degradation
Tier 1 (Reranker down):
→ Skip reranking step; use fusion scores directly
→ Slightly lower quality but functional
→ Latency actually decreases by ~100 ms
Tier 2 (BM25 index down):
→ Pure vector search only (no hybrid)
→ Exact term matches may be missed
→ Retrieval still works semantically
Tier 3 (Embedding service degraded):
→ Cache frequent query embeddings (Redis, TTL=1 hour)
→ Queue new document ingestion (process when recovered)
→ Existing indexed content still searchable
Tier 4 (Primary LLM down):
→ Failover to secondary LLM provider
→ Or: return retrieved chunks directly WITHOUT generation
→ "Here are relevant sources:" + chunk summaries
→ User gets information, just not synthesized
Tier 5 (Vector DB down):
→ Fall back to BM25 only (keyword search)
→ Significantly degraded but still useful
→ Rebuild vector index from stored chunks when recoveredProduction architecture
Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
Further exploration
Evaluation & Continuous Improvement
Evaluation & Continuous ImprovementExcalidraw diagram · editable shapes · reveal step by stepExplore
Interview playbook
Interview Tips
Start with the retrieval-generation separation — "RAG solves the knowledge freshness problem. LLMs have a training cutoff; RAG gives them access to current, private data without retraining. The architecture has two pipelines: ingestion (document → chunks → embeddings → vector index) and query (embed query → retrieve chunks → rerank → generate with context)."
Chunking strategy is the #1 quality lever — "Chunk quality determines retrieval quality, which determines answer quality. Semantic chunking with heading context preserves document structure. A chunk about 'EU refund policy' prepended with 'Refund Policy > International > EU-Specific Rules' retrieves far better than a context-free 512-token block."
Hybrid search (vector + BM25) is essential — Don't say "just use embeddings." Explain: vector search handles semantic similarity ("return policy" ↔ "refund rules"), BM25 handles exact terms ("ERR-4231"). RRF fusion combines them. +10-30% recall improvement. This is the single biggest retrieval improvement.
Two-stage retrieval: recall then precision — Stage 1: fast approximate search (HNSW) returns top-50 candidates. Stage 2: cross-encoder reranker scores each (query, chunk) pair with full token-level attention. Top-5 after reranking are dramatically better than top-5 from retrieval alone.
Hallucination prevention is multi-layered — Grounding prompt ("only use provided context"), citation enforcement (every claim must cite [N]), post-generation verification (check each claim against sources), confidence threshold (low retrieval scores → "I don't know"). No single layer is sufficient.
Access control must be pre-retrieval, not post — "If I retrieve top-100 and filter to authorized-only, I might return poor-quality results. Pre-retrieval filtering (metadata filter in vector DB) ensures only authorized chunks are even considered. Most vector DBs support filtered HNSW natively."
Incremental ingestion saves 90%+ compute — "Hash each chunk's content. On document update, only re-embed chunks where hash changed. A policy doc with 20 chunks where 2 paragraphs changed = re-embed 2 chunks, not 20. At 100M chunks/day, this matters enormously."
Multi-turn requires query rewriting — "'What about for EU?' is meaningless without Turn 1. Use a small fast LLM to rewrite: 'What is the refund policy specifically for EU customers?' Then search with the rewritten query. ~100 ms cost, massive quality improvement."
Parent-child chunking solves the precision-context tradeoff — "Small chunks (256 tokens) for precise retrieval. But return the parent chunk (1024 tokens) to the LLM for richer context. Best of both worlds: precise recall + sufficient context for generation."
End with evaluation metrics — "RAG quality is measurable: retrieval recall@5, MRR, NDCG for retrieval; faithfulness, relevance, hallucination rate for generation; thumbs up/down ratio end-to-end. Nightly automated eval on a test suite of 500 queries catches regressions. Without evaluation, you're flying blind."