Why Your RAG Pipeline Is Slow
Stage-by-stage latency budget with cited ranges; generation and reranking dominate, each isolated by one span measurement.
A slow RAG pipeline is end-to-end query latency that exceeds the budget users feel: the sum of embed, retrieve, rerank, prompt assembly, and generation, not a single mysterious delay. The answer can be correct and still arrive too late, which is a different failure from a wrong chunk. Before swapping models or rebuilding the index, instrument per-stage spans and read p95, not the mean. Echelon Edge’s 2026 pipeline breakdown treats generation as the usual wall-clock leader once multi-stage search and reranking enter the path, and agent-context.org’s 2026 latency guide frames the same wait as compounded hops rather than one slow algorithm. The two stages that almost always dominate are LLM generation and reranking. The rest of the diagnosis is proving which one, or which multiplier, is yours inside why RAG systems fail.
Which stage is eating the RAG latency budget?
A slow RAG pipeline has five distinct causes, and each has a different fix. Dropping top-k when the problem is an always-on cross-encoder, or tuning HNSW when generation dominates, costs time and changes nothing. Pratish Dewangan’s 2025 Medium guide and Deepchecks’ 2026 scale checklist both walk useful tips in order: cache, ANN, rerank, stream, profile. The faster path is differential diagnosis: start from the one symptom (users wait) and run the single span measurement that rules each cause in or out before any code change.
Each row below is a measurement you run first. Tip lists teach stages; this table isolates the stage that actually owns the wait. After you know which row fires, the p95 question decides how you budget and alert so a healthy-looking average cannot hide the same fault again.
| Cause | What you see | Detection (run this first) | Fix |
|---|---|---|---|
| 1 · Generation dominates | Long wait for tokens; upstream spans look fine | generate_ms largest share of e2e | Streaming / shorter context |
| 2 · Reranker always on | Latency rises when candidate count rises | rerank_ms large; scales with k | Reranking cost |
| 3 · Oversized top-k | Bigger k helps quality, then p95 collapses | Δ(e2e) when k is halved | Choosing top-k |
| 4 · Untuned ANN / cold retrieve | Retrieve is slow even with no rerank | retrieve_ms dominates pre-generate | ANN index tuning |
| 5 · No cache on repeats | Popular queries as slow as novel ones | cache hit-rate ≈ 0 on repeat traffic | Caching + monitoring |
Read the table as triage, not as a ranking of how often each cause appears on your corpus. One labelled slow query, five pass/fail spans, and one matching fix. Fleet averages still matter later; they do not name which row fired on the request a user just complained about.
Why does p95 matter more than average latency?
Users feel the slow tail. p95 latency is the threshold below which 95% of requests complete. Jim Allen Wallace (Redis, 2026) states that definition plainly, and notes that a healthy-looking average can coexist with a meaningful share of much slower requests. Pratish Dewangan’s 2025 guide makes the same operational move: set SLOs on p95 and p99, then profile the components that spike those tails rather than optimising a warm mean.
In RAG, a single user query often fans out across an embedding API, a vector store, a reranker, and an LLM. Redis’s write-up of Google’s Tail at Scale work is the reason those rare slow hops become common at the frontend. Budget and alert on p95 (and p99 under high QPS). Where those alerts live in a production stack is monitoring RAG in production. Once the alert is on the right percentile, the next question is which stage inside that percentile actually burned the milliseconds.
Where does the time go in a RAG pipeline?
Query-time RAG is a sum of stages. The published typical-range budget below is not a promise for your corpus or cloud region. Echelon Edge (2026) publishes these stage ranges in its pipeline breakdown; your measured spans will differ, and that difference is the point of the diagnosis rows you run first. A second published example plan (Bhagya Rana, 2025) aims for roughly 1.2 s to first useful tokens with its own stage allotments; treat that as one author’s plan, not a site-wide target.
| Stage | What it does | Published typical range | Source |
|---|---|---|---|
| Query embed | Turn the question into a vector | 20–100 ms | Echelon Edge (2026) |
| Retrieval | ANN / hybrid search over the index | 10–150 ms | Echelon Edge (2026) |
| Reranking | Score top candidates with a cross-encoder | 80–300 ms | Echelon Edge (2026) |
| Prompt assembly | Dedupe, order, budget tokens | 20–80 ms | Echelon Edge (2026) |
| LLM generation | Prefill + decode tokens | 500 ms–5+ s | Echelon Edge (2026) |
Read those rows as a ranking of suspects, not as your SLO. Generation’s published range sits an order of magnitude above the retrieval stages; reranking is the retrieval-side stage whose cost grows with the number of candidates. Those are the two stages treated as almost always the problem. The measured trade for rerankers is at what reranking costs you in latency; the index knobs behind the retrieval row are at HNSW. When generation is the largest bar, the next question is whether the model itself is the wait users feel.
Is the language model the reason it feels slow?
Often yes. Generation is the primary wall-clock bottleneck in Echelon Edge’s 2026 breakdown, with a published typical range of 500 ms to 5+ seconds depending on model size, output length, and hardware. Upstream stages can look healthy while users still wait on tokens. Kapil Deshmukh’s Towards AI sample (2025) reports time to first token falling from about 1.2 s to about 0.28 s after cutting context, which is his measured sample, not a universal constant.
Symptom
Time to first token is high, or the answer drips slowly. Embed and retrieve spans look fine in the trace.
Detection
Attribute wall-clock to stages. If generate_ms (or TTFT + decode) is the largest share of end-to-end latency, generation dominates, not the vector search.
Cause
Autoregressive decoding plus a long prompt: every extra context token taxes prefill before the first token appears.
Fix
Shorten context, cap max_tokens, route easy queries to a smaller model, and stream tokens so perceived latency drops even when full generation does not. Streaming mechanics live at streaming RAG responses.
Pratish Dewangan’s 2025 guide treats streaming and asynchronous serving as perceived-speed levers once generation owns the budget. Prefill still grows with prompt size, so cutting candidates and tokens remains a wall-clock fix, not only a UX trick. When relevance improved only after you added a cross-encoder and wait time jumped with it, generation may be fine and the next cause is the reranker.
Does the reranker make RAG slow?
It can. Reranking improves relevance, but unlike ANN retrieval it scores candidates more carefully and its cost scales with how many you send it. Echelon Edge (2026) puts a typical reranking range at 80–300 ms. Deepchecks’ 2026 scale table names the same trade plainly: cross-encoders raise accuracy on ambiguous queries and are slower than embedding-only retrieval. Pratish Dewangan’s two-stage pattern retrieves a broad candidate set first (for example k of 50 to 200) then reranks a smaller set, which is how teams keep recall without paying cross-encoder cost on every neighbour.
Symptom
Relevance improved after you added a cross-encoder, and wait time jumped. Latency rises when you raise the candidate count.
Detection
Log rerank_ms. If it is large and grows roughly linearly when you double candidates, the reranker is the bottleneck, not retrieval as a whole.
Cause
Cross-encoders evaluate query–document pairs; work scales with candidate set size, and always-on routing pays that cost on easy queries too.
Fix
Shrink the candidate set, use a lighter or conditional reranker, and cache scores for repeated queries. The measured accuracy-for-latency trade is at what reranking costs in milliseconds.
AI Agent Studio (2025) claims running a cross-encoder on every query can add 300–800 ms; cite that as their published claim, then measure yours. When rerank_ms is small but end-to-end still rises as you raise k, the multiplier has moved into prompt size and generation, which is the top-k tax.
Is top-k making the pipeline slower?
Oversized k is a multiplier, not a single slow call. Echelon Edge (2026) calls this the context tax: more chunks mean more rerank work and a larger prompt for the model to prefill. Raising k can look like a quality win until p95 collapses. Jamie Maguire’s May 2026 post shows the extreme form in logs: eight chunks retrieved for a deployment query, five from the same document URI, each with an identical relevance score of 8.665282, so the synthesis step received the equivalent of an entire documentation page.
Symptom
Answers improve when you raise top-k, then the system feels sluggish. Prompt token counts climb with every just-in-case chunk, or many top chunks share one source URI.
Detection
Halve k on a labelled sample. If end-to-end latency drops sharply while answer quality holds, oversized k was multiplying rerank and generation. Log chunk count, token count, and unique document URIs the way Maguire did before blaming the model.
Cause
Candidate count and prompt size expand the workload of every later stage; latency is multiplicative across the path, not a simple sum of independent costs. Paragraph-level KNN without per-document dedupe fills top-N from one page.
Fix
Discipline top-k, dedupe by source document before synthesis (Maguire’s GroupBy DocumentUri), and stop sending whole pages when a paragraph will do. The measured curve between k, quality, and cost is at how many chunks should you retrieve.
Kapil Deshmukh’s Towards AI sample keeps only three chunks after ranking and reports total latency falling from about 2.3 s to about 0.45 s on that FAQ-bot stack; again that is his sample, not your SLO. When retrieve is already slow before any rerank or large prompt, the fault has moved into vector search itself.
Is vector search itself the bottleneck?
Sometimes. When retrieve_ms dominates before generation (cold indexes, brute-force search, cross-region hops, or an untuned ANN), the vector stage is the problem. Echelon Edge (2026) publishes a typical retrieval range of 10–150 ms; outliers above that range are where index work pays off. Pratish Dewangan’s 2025 guide treats HNSW ef_search as a direct latency-recall knob: higher exploration raises recall and slows the query. Deepchecks’ 2026 scale table contrasts HNSW for moderate datasets with IVF-PQ clustering when the collection grows and you need to confine search to top clusters.
Symptom
Retrieve is slow even with reranking disabled. First queries after idle are much slower than warm ones, or each answer waits on a second hop to hydrate text after IDs return.
Detection
If retrieve_ms dominates the pre-generate span, and changing ANN parameters (for HNSW, ef_search) moves that span, you have an index or search problem, not a generation problem.
Cause
Flat search, cold caches, distant regions, untuned ANN settings, or a two-system hop where IDs come from a vector index and canonical text comes from a separate content store (the pattern agent-context.org names as a synchronization and hydration tax in 2026).
Fix
Use ANN (commonly HNSW), co-locate embedding and index, and tune search parameters against a recall floor. Popular vector stores for this work include Weaviate, Pinecone, Qdrant, and Milvus: pick on your constraints, then measure. Weaviate’s vector-index documentation (as of September 2026) documents HNSW as the default approximate index type for that product. Graph-parameter depth is at how HNSW trades recall for search latency.
agent-context.org’s 2026 guide opens with a four-second answer that should have been closer to 400 ms when hops compound; treat that contrast as their framing of architectural tax, not as your measured baseline. When retrieve and generate both look fine on novel questions but popular repeats stay as slow as first hits, the remaining cause is cache.
Would caching fix the slow queries?
Caching fixes repeated work. It does not fix unique long-tail questions. Pratish Dewangan’s 2025 guide puts aggressive caching first among tips and aims for a cache hit-rate of 60% or higher on repeated traffic as his target signal that the full pipeline is being bypassed often enough to matter. Jamie Maguire’s May 2026 post shows the failure mode when a semantic cache exists only on paper: hits logged at Debug, a 0.92 similarity threshold, and KNN score normalisation that differed from the main search path, so the threshold was almost never met and the cache was effectively dead code.
Symptom
Popular or repeated questions are as slow as novel ones. p95 spikes after deploys until any cache warms, or hit logs never appear at the default production log level.
Detection
Measure cache hit-rate on traffic that should repeat. If it sits near zero, and identical normalized queries take the full path every time, missing or miswired cache is the cause.
Cause
No query or retrieval cache, an answer-only cache that misses paraphrases, or a semantic cache whose threshold and score path never fire.
Fix
Cache retrieval candidates and embeddings with TTLs and selective invalidation; raise hit logs to a visible level; align score normalisation so thresholds mean something. Mechanism depth for embeddings, retrieval, and generation caches is at caching in RAG. Watch hit-rate beside p95 in production monitoring dashboards. Long-tail unique queries still need the other four fixes.
Pratish’s 60% hit-rate line is his stated aim for repeated queries, not a published measurement from your traffic. Once you can see hits, the remaining job is naming which stage burned the rest of the budget on the misses, which is what the span logger does.
How do you measure which stage is slow?
Rather than reason about the five causes one at a time, wrap each stage and print the milliseconds. Given a query path you already have, the pattern below logs rewrite, retrieve, rerank, assemble, and generate separately so you can match a row in the diagnosis table. It depends only on the Python standard library. Pratish Dewangan’s 2025 guide and Deepchecks’ pre-scale checklist both insist on per-component latency before tuning; the script is that insistence made pasteable.
Build the wrappers around the callables you already run in production so the dominant stage name matches live behaviour. Aggregate many requests at p50 and p95 offline. Do not promote a cause from a single warm request.
Before you trust the numbers
Run this against your own stack. The published ranges in the budget table are typicals from named sources, not universal constants. Aggregate spans at p50 and p95 offline; do not optimise from a single warm request.
import time
from contextlib import contextmanager
@contextmanager
def span(name, log):
"""Append (name, elapsed_ms) when the block exits."""
t0 = time.perf_counter()
try:
yield
finally:
log.append((name, (time.perf_counter() - t0) * 1000))
def answer(query, *, rewrite, retrieve, rerank, assemble, generate):
"""Return (result, timeline) with one ms row per stage.
Pass callables you already use in production. No target ms are hard-coded;
compare timeline entries to each other and to your p95 history.
"""
timeline = []
with span("rewrite", timeline):
q = rewrite(query)
with span("retrieve", timeline):
candidates = retrieve(q) # first-stage top-k
with span("rerank", timeline):
top = rerank(q, candidates) # optional; no-op if unused
with span("assemble", timeline):
ctx = assemble(top)
with span("generate", timeline):
result = generate(q, ctx)
return result, timeline
# Example: print which stage dominated one request
# result, timeline = answer(...)
# for name, ms in timeline:
# print(f"{name}: {ms:.1f} ms")
# print("dominant:", max(timeline, key=lambda row: row[1])[0])
The script is deliberately transparent: each block is one row of the detection table. It reports where the time went; it does not invent a target budget for you. In production, the same seams become traces and p95 alerts. Wire that at tracing when you need OpenTelemetry-shaped spans, or at monitoring for live alerts when the job is paging on the slow tail.
A slow RAG pipeline is almost never a mystery once stages are timed. Generation and reranking dominate most published budgets; oversized top-k, untuned ANN, and dead caches multiply the rest. Instrument the five spans, budget on p95, change only the lever the measurement names, and keep the slow query in a golden set so the next deploy cannot resurrect the same wait.
- Chunk boundary loss
- Conflicting sources
- RAG debugging
- Retrieval quality degradation
- Enterprise RAG failure
- Hallucination despite context
- RAG latency
- Multi-tenant leakage
- Lost in the middle
- Missing the obvious document
- Multi-hop questions
- Stale index
- Numbers and tables
- Vocabulary mismatch
- Wrong chunk retrieved