ML System DesignTechPrufer

Optimizing a RAG System: Five Bottlenecks, Two Escape Paths Each

A continuously updated corpus, heterogeneous formats, and interactive latency — how a zero-dollar Faiss MVP fractures five ways, and why 94% of the $427,700/month enterprise bill is one line item.

Engineering 100 → 10,000 RPS13 min read
RAG optimization bottlenecks abstract

Most RAG designs are drawn as if the corpus holds still. It doesn't. The interview prompt — question answering over a continuously updated internal corpus, with citations, low latency, and PDFs/HTML/wiki pages all mixed together — is really an optimization problem: five components that each break at a different scale, and a budget that decides which fork you take at each one.

What the Prompt Actually Asks

Design the end-to-end pipeline — ingestion, chunking, embeddings, index, retriever, reranker, generator — then defend three claims with numbers:

  • Freshness: new and updated documents flow in continuously — no batch rebuilds, no stale answers.
  • Quality: high answer quality with citations, which means reducing hallucinations by construction, not by prompt-begging.
  • Latency: interactive use — and an evaluation plan (offline + online) that catches drift before users do.

Phase 1: The Zero-Dollar MVP

Bootstrap everything on one machine with open source. A manual script reads files with pypdf and BeautifulSoup, chunks naively at 500 characters with 50 overlap, embeds with all-MiniLM-L6-v2 (384-d) on CPU, and stores vectors in an in-memory Faiss index with chunk metadata in SQLite. Queries run k-NN for the top 5 chunks — no reranker — and a quantized 7B model (Llama-2 or Mistral via llama.cpp) writes the answer.

Ingestion (manual, batch)
PDF / HTML / wiki
pypdf + BS4 parse
500-char chunks
Faiss + SQLite
Query (interactive)
Query (50 B)
MiniLM embed (10–50 ms)
k-NN top-5 (no rerank)
7B LLM → cited answer

Figure 1: The zero-dollar MVP — one machine, no reranker, eventual everything.

mvp_napkin_math.txt
corpus (1 GB text, 500-char chunks) ≈ 1,000,000 vectors
index = 1M × 384 dims × 4 B (fp32)  = 1.5 GB    → fits in RAM ✓
query embed (MiniLM, CPU)           = 10–50 ms  ✓
faiss k-NN over 1M vectors          = tens of ms ✓

7B LLM inference:  CPU → seconds/query
                   RTX 3060 → 1–2 s/query
100 RPS × 1–2 s   = 100–200 s of compute per wall-clock second ✗

honest capacity: 1–5 RPS, single-user latency only

Every stage upstream of the LLM clears the bar. Generation alone caps the MVP at 1–5 RPS — the 100 RPS target is aspirational the day the system ships.

Phase 2: Five Fracture Points

Grow the corpus and the traffic, and the MVP fractures in five distinct places — each with its own arithmetic. Memorize the numbers, not the slogans:

83 hrs

Manual ingestion per day

1,000 new or updated documents daily × 5 minutes of hand-holding each = ~83 hours of human work per day. The cron-and-pray script dies first.

2.7 hrs

CPU-bound embedding backlog

200,000 new chunks/day × 50 ms per MiniLM embedding = 10,000 seconds of continuous CPU — competing with query-time embedding for the same cores.

15.36 GB

RAM just for the index

10M chunks × 384 dims × 4 bytes. In-memory Faiss on one node runs out of RAM before it runs out of documents — and it is a single point of failure.

60%

Precision at k=5

Naive fixed-size chunks + raw k-NN means 2 of 5 retrieved chunks are noise. No reranker → noisy context → hallucinations. Quality, not RPS, breaks here.

200×

LLM instances for 100 RPS

A 7B model at 2 s/inference on CPU needs 200 parallel instances to hold 100 RPS. Impossible on one machine, and users still wait >5 s per answer.

Notice that only two of the five are throughput problems. Ingestion and embedding are freshness problems, and retrieval precision is a quality problem — the answer feels slow because it's wrong, and the user's time-to-resolution balloons even while your p99 looks healthy.

Phase 3: The Evolutionary Forks

Every bottleneck forks into a no-money hack and a paid solution. The optimization skill is knowing which fork to take per component — not one answer for the whole system.

BottleneckNo-money hackPaid solution
ingestionunstructured.io OSS + pypdf + BeautifulSoup on a cron; inotify triggersS3 events → Lambda → AWS Textract / Unstructured managed API
embeddingsQuantized MiniLM, batch queue on a refurbished GPU boxOpenAI text-embedding-3-large (~$0.20/M tokens) or Cohere Embed v3
vector indexDisk-based ANN (Annoy / Hnswlib) + hand-rolled shards + Redis metadataManaged vector DB — Milvus: 1B vectors, 99% recall at 10 ms
retrievalSemantic chunking + self-hosted BM25 hybrid + recency heuristicsCohere Rerank over the top 50–100; hybrid search in the vector DB
generationGGUF/AWQ quantization + vLLM continuous batching on owned GPUsGPT-4o / Claude / Gemini APIs — inference in tens to hundreds of ms

The fork that decides your architecture is the vector index, because it's the one where the hack and the product differ by an order of magnitude:

Hack: Shard It Yourself

  • Disk-based ANN — Annoy or Hnswlib load indexes from disk, trading latency for RAM headroom.
  • Manual sharding — custom routing or fan-out-and-merge across machines; every shard-split is your on-call problem.
  • Metadata in self-hosted Redis or sharded Postgres — consistency between vectors and text is on you.
HnswlibAnnoyRedis

Paid: Managed Vector DB

  • Milvus benchmark: 1 billion vectors at 99% recall in 10 ms — with replication and backups built in.
  • Meta-scale precedent: ANN indexes over billions of document vectors serving retrieval at sub-100 ms.
  • Hybrid search + filtering come free — the features you'd otherwise hand-build in the retrieval fork.
MilvusPineconeQdrantWeaviate

For generation, the same asymmetry: Cloudflare reports LLM inference in tens of milliseconds for small models and hundreds for large ones on Workers AI — latencies a self-hosted vLLM stack only matches after serious GPU spend and MLOps investment. The hack (quantization + continuous batching) is a real path, but you are signing up to operate an inference company on the side.

Evaluation: Offline Scores, Online Drift

Offline, measure retrieval with Recall@k, MAP, and NDCG against a labeled query set, then score the end-to-end system with RAGAS — four metrics, each of which localizes a different failure:

RAGAS metricQuestion it answersFailure it catches
faithfulnessIs the answer factually consistent with the retrieved context?Hallucinations that sound right but cite nothing.
answer relevanceDoes the answer actually address the question?Fluent non-answers and topic drift.
context relevanceWas the retrieved context on-topic for the query?Retriever noise crowding out signal.
context recallIs everything the ground truth needs present in the context?Chunking that splits the answer in half.

Online, three monitors run continuously — because a continuously updated corpus means the ground truth itself moves:

System performance

Per-component latency (embed, search, LLM), throughput, and error rates — Prometheus + Grafana for metrics, Jaeger for traces across the pipeline.

Quality proxies

Thumbs up/down on answers, repeated queries, and follow-up rates — cheap online signals that a RAGAS regression is reaching users.

Data & concept drift

Watch the embedding distribution of incoming documents and the relevance scores of retrieved chunks — anomalies mean new topics or shifted user intent.

The Edge Cases That Separate Answers

Long documents

Hierarchical chunking (document → section → paragraph) with recursive retrieval: find the relevant section first, then the chunks inside it. Multi-query generation lifts recall on complex questions.

Out-of-domain queries

Threshold on retrieval confidence and answer “I don't have enough information” below it. A grounded refusal beats a fluent hallucination every time.

Real-time updates

Stream document changes through Kafka into the embedding pipeline; accept eventual consistency on index updates rather than blocking queries on rebuilds.

Cost controls

Cache query embeddings and LLM answers for common queries; batch embedding and inference; route easy questions to smaller fine-tuned models.

Phase 4: The Final Boss — a $427,700 Invoice

Take Path B everywhere at enterprise scale — 100M documents, ~100M queries/month — and price it. The striking part isn't the total; it's the shape:

LLM inference (GPT-4o)$400,000/mo
Vector DB (100M vectors)$15,000/mo
Reranker (Cohere)$10,000/mo
Ingestion (Textract + Lambda)$1,700/mo
Embeddings (10M updates/mo)$1,000/mo
Hypothetical monthly bill, all-paid stack · bars scaled to LLM spend
cloud_bill.txt
ingestion   100K docs × 10 pages × $1.50/1K pages (Textract)  = $1,500
            + Lambda & S3                                      = $200
embeddings  10M updated chunks × 500 tok × $0.2/1M tok        = $1,000
vector DB   100M vectors, 10K RPS (managed, enterprise)       ≈ $15,000
reranker    100M queries × $0.10/1K reranks                   = $10,000
LLM         50B input tok × $5/1M   = $250,000
            10B output tok × $15/1M = $150,000                = $400,000
                                                        ───────────────
total                                                  ≈ $427,700/month

“94% of the bill is one component. Every optimization that doesn't reduce LLM tokens is rounding error.”

Where the leverage lives
Answer caching → smaller fine-tuned models for easy queries → tighter context via reranking

This is also why the reranker pays for itself: $10,000/month of Cohere Rerank that trims the context from 10 sloppy chunks to 3 precise ones cuts input tokens on a $250,000/month line item — and improves faithfulness at the same time. In RAG, cost optimization and quality optimization are frequently the same change.

Takeaway

Optimizing RAG is not one decision — it's five. Ingestion, embeddings, index, retrieval, and generation each fracture at a different scale (83 hours of manual parsing, 2.7 hours of embedding backlog, 15.36 GB of index RAM, 60% precision at k=5, 200 parallel LLM instances), and each forks into a no-money hack or a managed service. Take the forks independently, wrap the pipeline in RAGAS plus drift monitoring, and remember the invoice: at enterprise scale the LLM is ~94% of $427,700/month, so every real optimization is ultimately a token optimization.

References & Further Reading

Practice This Question

This post is adapted from our interview question bank — the original comes with structured prep, the full four-phase grounded design, and space for your own notes.