3. Caching and rate limits¶
Intermediate · 7 min read
Two cheap controls do more for an LLM app's cost and reliability than most model changes. Caching avoids paying twice for the same answer. Rate limits stop one user, bug or attacker from using up your provider quota and budget.
3.1 Where caching fits¶
| Cache | Saves | Risk |
|---|---|---|
| Exact response cache | The whole LLM call, for identical requests | Stale answers after content changes |
| Semantic cache | The whole call, for similar questions | A wrong answer to a question that only looks similar |
| Provider prompt caching | Input cost and time to first token | None, if the prompt prefix is stable |
| Embedding / retrieval cache | Embedding calls and searches | Stale after re-indexing |
3.2 An exact cache with a safe key¶
The cache key must include everything that changes the answer: model, prompt version, parameters, the normalised input and the tenant's scope. Leave out the scope and one customer sees another customer's cached answer.
import hashlib
import json
def cache_key(model, prompt_version, params, user_input, scope) -> str:
normalised = " ".join(user_input.lower().split())
raw = json.dumps([model, prompt_version, params, normalised, scope], sort_keys=True)
return hashlib.sha256(raw.encode()).hexdigest()[:16]
base = {"model": "gpt-4o-mini", "prompt_version": "support-v7", "params": {"temperature": 0}}
a = cache_key(user_input="What is your refund policy?", scope="tenant-acme", **base)
b = cache_key(user_input="what is your REFUND policy?", scope="tenant-acme", **base)
c = cache_key(user_input="What is your refund policy?", scope="tenant-globex", **base)
print("same question, re-typed ->", "HIT" if a == b else "MISS")
print("same question, other tenant ->", "HIT" if a == c else "MISS (correct: never share across tenants)")
same question, re-typed -> HIT
same question, other tenant -> MISS (correct: never share across tenants)
3.3 The semantic-cache trap¶
A semantic cache returns a stored answer when a new question is close to a cached one. It catches paraphrases, but "How do I cancel my order?" and "How do I cancel my subscription?" are close in wording and different in meaning. A loose threshold serves a confident, wrong answer. If you use one, keep the threshold high, scope it per tenant, and limit it to FAQ-style questions.
3.4 Rate limits counted in tokens¶
One request can cost 100 tokens or 100,000, so counting requests isn't enough. Limit requests and tokens per user
per minute, keep the counters in Redis so every instance shares them, and reply 429 with a Retry-After value.
Add a global ceiling just under your provider's quota.
In the full LLMOps guide (PDF)
- A complete response cache with TTL, invalidation and Redis code
- The semantic-cache experiment with real scores, and how to pick a safe threshold
- Prompt-caching rules that actually get cache hits, with before/after costs
- A working sliding-window token rate limiter, step by step
- Handling provider 429s: Retry-After, backoff, fallback models and the Batch API
- Model answers to the interview questions below
See the full guide in the Store → · Get notified when it goes on sale ↓
Interview questions¶
- What should be in the cache key for an LLM response cache?
- What are the risks of semantic caching?
- How would you rate-limit an LLM API?
The full LLMOps guide: 36 pages, 9 chapters. Deployment, serving, caching, observability, evals in CI, cost and security, with complete code and model interview answers. Get one email when it goes on sale.
Next: 4. Observability with Langfuse Back to: LLMOps overview · Notes overview