Back to Notes

Design an Enterprise RAG Q&A System

Problem 02 — Design an Enterprise RAG Question-Answering System

ingestion · hybrid retrieval · permissions · citations

Companion: [[RAG & LLM System]] (older, shorter HLD page).

Requirements

Functional: employees ask questions over Confluence, Google Drive, Slack, Jira. Answers cite sources. Users only see content they already have access to. New/edited docs searchable within 15 min. Non-functional: 50k employees, 10M documents, p95 answer < 5 s, zero permission leaks, says "I don't know" rather than invent.

Estimates

10M docs × ~20 chunks = 200M chunks
200M × 768 dims × 4 B ≈ 615 GB fp32  → int8 ≈ 155 GB, sharded across nodes
                                     → IVF-PQ (m=96) ≈ 19 GB + rescore
Queries: 50k users × 5/day = 250k/day → ~3 QPS avg, ~15 peak (small!)
Ingestion: ~1% docs change daily = 100k docs → 2M chunks re-embedded/day

Query load is small. Hard parts: ingestion correctness, permissions, answer quality.

Architecture

flowchart TB
  subgraph Ingestion
    SRC[Connectors] --> Q[[Change Queue]]
    Q --> P[Parse and OCR]
    P --> CH[Chunker]
    CH --> EM[Embedding Model]
    EM --> VDB[(Vector Index)]
    CH --> KW[(BM25 Index)]
    SRC --> ACL[(ACL Store)]
  end
  subgraph Query
    U[User] --> QR[Query Rewriter]
    QR --> HY[Hybrid Retrieve]
    HY --> VDB
    HY --> KW
    HY --> F[ACL Filter]
    F --> RR[Cross-encoder Reranker]
    RR --> LLM[LLM with Citations]
    LLM --> V[Citation Check]
    V --> U
  end
  ACL --> F

Deep dives

Chunking

Split on structure (headings, paragraphs, table boundaries), target 300–800 tokens, 10–15% overlap. Prepend doc title + section path before embedding ("Step 3: restart the pod" keeps meaning). Metadata: doc_id, chunk_index, updated_at, source, ACL group IDs.

Why hybrid retrieval

Dense handles paraphrase ("how do I get reimbursed" ↔ "expense policy"). BM25 handles exact tokens: ERR_4012, ticket IDs, codenames. Top 50 from each → RRF merge → cross-encoder rerank to top 5–8.

Permissions — the part that gets people fired

Pre-filter (recommended)Post-filter
User's group IDs as metadata filter inside ANN search. No leak possible; needs a vector DB with efficient filtered search.Retrieve then drop forbidden chunks. Simple, but may return too few results; leak risk if a step is skipped.

Resolve group membership at query time from the IdP (cache minutes). Sync ACL changes through the same change queue. Never cache final answers across users.

Freshness and deletes

Webhooks where available, else polling. Hash chunk content; re-embed only changed chunks. Deletes must propagate fast to every index + invalidate answer cache.

Grounding and hallucination

Prompt: answer only from passages, cite passage IDs, say "I don't know" if insufficient. Post-check: every cited ID exists in context; optionally a small model verifies each claim is supported.

Changing the embedding model

Means re-embedding all 200M chunks. Build new index in parallel (blue-green), compare retrieval metrics on golden set, then switch reads.

Evaluation

300–500 real questions with labelled relevant docs. Measure retrieval (recall@10, MRR) separately from generation (faithfulness, answer relevance, citation precision via LLM-as-judge calibrated on human labels) to know which stage failed. Online: thumbs, citation click-through, "no answer" rate.

Likely follow-ups

  • A question needs facts from 40 documents: what breaks?
  • Tables and diagrams inside PDFs?
  • Malicious wiki page injecting instructions?
  • When fine-tune instead?

Related: [[Vector Quantization]] · [[GenAI Vocabulary]] · [[index|GenAI index]]