Sections in this lesson
Design a Multi-Tenant RAG Platform
Design ingestion, authorized retrieval and source-backed generation with explicit capacity assumptions and deletion semantics.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Separate ingestion and online query planes.
- Estimate raw vector storage and model token demand.
- Design tenant-safe caches, source updates and deletion.
Before you start
The flow
- 1.Upload & authorize
- 2.Parse and chunk
- 3.Version embeddings
- 4.Retrieve under ACL
- 5.Generate with sources
- 6.Evaluate & invalidate
Architecture and boundaries
Ingestion plane
Authorized upload → source store → bounded parsing workers → chunk/version catalog → embedding/index.
Query plane
Authenticated API → tenant/ACL policy → retrieval/rerank → bounded model context → cited response.
Lifecycle control
Version activation, source deletion and cache invalidation; migration roles are separate from serving roles.
Quality and operations
Labeled retrieval sets, grounding review, traces, latency and per-tenant admission/usage limits.
Design assumptions
These values are example sizing inputs, not measurements or capacity guarantees.
| Variable | Assumption |
|---|---|
| Corpus | 100,000 documents × 10 chunks |
| Representation | 768 dimensions × 4 bytes |
| Queries | 20 requests/second |
| Tokens/request | 1,800 input + 600 output |
Clarify the product contract
Assume employees ask questions over their organization's documents. Each answer must either cite authorized, current evidence or explicitly abstain. Define supported file types, maximum document size, update freshness, deletion requirements and latency/error budgets before selecting a vector database.
Keep source objects authoritative. Chunk IDs include source/version lineage; embedding model and index versions are tracked separately. A successful vector query over a stale policy can still produce the wrong business answer.
Size the expensive paths
At 100,000 documents and ten chunks each, the corpus has one million vectors. With 768 float32 dimensions, raw vectors occupy 3.072 decimal GB. This excludes text, metadata, indexes, replicas and backups; it is not a database disk estimate.
At 20 queries/second with 1,800 input and 600 output tokens per request, demand is 36,000 input and 12,000 output tokens/second. This is workload demand, not guaranteed model throughput. Measure the provider/hardware and apply admission control, queue bounds and per-tenant quotas.
Enforce access before context assembly
Derive tenant and ACL filters from authenticated identity before ranking and generation. Cache keys must account for the permission scope and corpus/index version; a shared semantic cache without those boundaries can leak evidence. Retrieved text remains untrusted even after access checks.
On updates, index a new source version and switch the active view without mixing embedding spaces. On deletion, remove active chunks and invalidate dependent caches, then handle source/backups under the retention policy. An immediate active-index deletion is not proof that every retained copy vanished.
Verify the boundaries independently
Measure retrieval recall against expected sources before grading answer grounding. Track empty retrieval, stale results, foreign-tenant denial, citation support and cost/latency slices. The Phase 2 lab implements real pgvector filtering and deletion over a small fixture corpus; a production ingestion queue, migration system and fleet are design extensions here.
Try it
// Sizing assumptions, not measurements or provider capacity guarantees. Decimal GB.function positive(value) { if (!Number.isFinite(value) || value <= 0) throw new Error('Positive finite input required'); return value;}export function ragCapacity({ documents, chunksPerDocument, dimensions, qps, inputTokens, outputTokens }) { [documents, chunksPerDocument, dimensions, qps, inputTokens, outputTokens].forEach(positive); const chunks = documents * chunksPerDocument; const rawVectorGB = chunks * dimensions * 4 / 1_000_000_000; return { chunks, rawVectorGB, inputTokensPerSecond: qps * inputTokens, outputTokensPerSecond: qps * outputTokens };}export function agentCapacity({ jobsPerSecond, meanSeconds, slotsPerWorker, toolCallsPerJob, headroom }) { [jobsPerSecond, meanSeconds, slotsPerWorker, toolCallsPerJob, headroom].forEach(positive); if (headroom < 1 || !Number.isInteger(slotsPerWorker)) throw new Error('Invalid worker configuration'); const meanInFlight = jobsPerSecond * meanSeconds; return { meanInFlight, workers: Math.ceil(meanInFlight * headroom / slotsPerWorker), toolCallsPerSecond: jobsPerSecond * toolCallsPerJob };}examples/agentic-ai/capacity-planning.mjs · npm run examples:agents. Java examples target 21. Models and usage are fixtures; no live inference or billing guarantee is implied.
Interview questions
Why separate embedding versions from document versions?
A content update and a representation change have different migration and evaluation requirements.
Is 3.072 GB the full database size?
No. It includes only raw float32 vector values under the stated assumptions.
Why can a cache violate tenant isolation?
An answer/evidence reused outside its authorized scope bypasses the original retrieval permission check.
Practice
Add a permission-change event and design cache invalidation for a user losing access to a cited document.
Check your solution
- Storage estimates separate raw values and overhead.
- Updates and deletions retain source lineage.
- No query or cache lookup bypasses permission scope.
Official references
Continue learning
Category sequence
System Design
Lesson 17 / 21
Was this lesson useful?
Change notes: Phase 3: bilingual agent engineering lesson and executable reference.