Balancing Cost, Delay, and Consistency
This article summarizes how to design an LLM response cache using the Redis Cache Aside pattern.
LLM services may benefit from greater caching than regular APIs. If repeated requests come in with the same question, same document, or same prompt version, there is no need to call the model each time. Reducing model calls reduces latency, reduces costs, and relieves provider rate limit pressure.
However, the LLM response cache can also be dangerous. User permissions may incorrectly reuse answers from other documents, continue to show outdated document-based answers, and even store responses that contain personal information.
Analysis date: 2026-05-12 Practice environment: FastAPI, Redis, PostgreSQL, OpenAI API Main reference materials: Redis Query Caching, Redis TTL/Eviction Docs, OpenAI prompt caching Docs
Key takeaways
- Cache Aside is a pattern that first queries Redis, performs the original task in case of a miss, and then stores it in Redis.
- The LLM response cache must consider ‘question + document scope + prompt version + model + authority scope’ together.
- It is dangerous to cache personal information or authority-dependent responses.
- TTL is a compromise between freshness, cost, and user experience.
- Cache hit rate, saved tokens, and latency reduction should be used as indicators.
1. Why do you need LLM response cache?
LLM calls create three costs:
| expense | explanation |
|---|---|
| delay cost | It may take several seconds for the model to respond |
| monetary expenses | Input/output token cost incurred |
| operating costs | Rate limit, timeout, provider failure response required |
Cache is very effective for services where the same or similar requests are made repeatedly. For example, official document Q&A, error message explanations, product policy information, and repetitive summary tasks can be cache candidates.
2. Cache Aside pattern flow
The basic flow of Cache Aside is simple.
# This is an example.1. Receive a request.2. Create a cache key.3. Search the key in Redis.4. If it is a hit, a cached response is returned.5. If it is a miss, call LLM.6. Verify the response.7. Save it with TTL in Redis.8. Return the response.
The advantage of this pattern is that the application directly controls the cache policy. The downside is that poorly designed cache keys and invalidation policies can result in incorrect data.
3. Cache Key Design
The core of the LLM response cache is the key. Do not use only the question text as the key.
# This is an example.llm:answer:{hash(question + document_scope + prompt_version + model + user_acl_scope)}
| component | Why you need it |
|---|---|
| question | User's Questions |
| document_scope | Which document set was searched |
| prompt_version | Disconnect cache when prompt changes |
| model | Isolate response differences by model |
| user_acl_scope | Prevent response contamination between users with different permissions |
| retrieval_version | Reflection of index/search logic changes |
example:
# This is example code.import hashlibimport json # This declaration shows an example flow.def make_cache_key(payload: dict) -> str: normalized = json.dumps(payload, sort_keys=True, ensure_ascii=False) digest = hashlib.sha256(normalized.encode("utf-8")).hexdigest() return f"llm:answer:{digest}"
4. TTL and Invalidation
TTL is the cache survival time. In the LLM response cache, if the TTL is too long, old answers will remain, and if the TTL is too short, the cache effect will disappear.
| response type | TTL candidate | reason |
|---|---|---|
| Answer based on official documents | 1 hour to 1 day | Adjust according to document change cycle |
| Frequently changing policy answers | short or no cache | Recency is important |
| User Personal Data Reply | Disable cache or per-user cache | Permissions/Privacy Risks |
| Code Example Description | Hours to days | relatively stable |
When a document changes, the associated cache must be invalidated. A simple TTL may cause out-of-date answers to appear immediately after a change.
5. Responses that should not be cached
All LLM responses should not be cached.
Security Caution Cache policies should be designed very conservatively for answers where up-to-dateness and accuracy are important, such as user personal information, full text of internal documents, data with limited rights, and medical, financial, and legal information.
Candidates for banning caches include:
# This is an example.[ ] Answers containing sensitive information for each user[ ] Answer that document access results vary depending on permissions[ ] Real-time data-based answer[ ] Frequently changing information such as latest notices, prices, and policies[ ] Responses indicating that the model is uncertain
6. Cache Stampede response
If caches expire at the same time, many requests may call LLM at once. This can be viewed as a cache stampede.
Here's how to respond:
| method | explanation |
|---|---|
| Lock | Only one request for the same key calls LLM |
| Random TTL | Distribute expiration points |
| Early Refresh | Background renewal before expiration |
| Stale-While-Revalidate | Briefly return the old value and update it later |
In initial implementations, just lock and random TTL will work.
7. FastAPI + Redis example
# This is example code.from fastapi import FastAPIimport redis.asyncio as redisimport json app = FastAPI()r = redis.Redis(host="localhost", port=6379, decode_responses=True) @app.post("/answers")# This declaration shows an example flow.async def create_answer(req: dict): cache_payload = { "question": req["question"], "document_scope": req.get("document_scope"), "prompt_version": "answer.v1", "model": "configured-model-name", } cache_key = make_cache_key(cache_payload) cached = await r.get(cache_key) if cached: return {"source": "cache", "data": json.loads(cached)} answer = await call_llm_and_validate(req) await r.set(cache_key, json.dumps(answer, ensure_ascii=False), ex=3600) return {"source": "llm", "data": answer}
This code is an example to demonstrate the structure. In an actual service, permission scope, personal information masking, timeout, retry, and tracing must be added.
8. Operational indicator design
The important thing about cache is not whether it is attached, but whether it is effective.
| characteristic | meaning |
|---|---|
llm_cache_hit_rate | cache hit rate |
llm_cache_saved_tokens | Savings input/output token estimate |
llm_cache_latency_saved_ms | Latency saved |
llm_cache_key_cardinality | key diversity |
llm_cache_stale_response_count | Number of stale response occurrences |
If the cache hit rate is low, the key may be too granular or the request pattern may not fit the cache.
9. Practical checklist
# This is an example.[ ] Have you classified cacheable and prohibited responses?[ ] Are prompt_version and model included in the cache key?[ ] Has the scope of user authority been reflected in the key?[ ] Are the TTL criteria linked to the document change cycle?[ ] Is there an invalidation strategy when changing documents?[ ] Is there a cache stampede response?[ ] Do you measure cache hit rate and saved tokens?[ ] Is there a DB/LLM fallback path in case of Redis failure?
10. Q&A
Q1. Are prompt caching and Redis response cache the same?
no. prompt caching is a way for providers to reuse repeated prompt prefix calculations, and Redis response cache is a way for applications to store final responses. The two can be used together.
Q2. Can questions with similar meaning be cached?
It's possible, but the risk is high. Since the semantic cache must manage both the similarity threshold and the risk of wrong answers, it is safer to start with the exact cache initially.
Q3. Should the LLM response cache be stored in the DB as well?
If you need conversation history or audit logs, save them in the DB. Redis is for quick reuse, and DB is for history and analysis purposes.
11. References and uncertainty
References
- Redis Query Caching:https://redis.io/tutorials/howtos/solutions/microservices/caching/
- Redis Docs:https://redis.io/docs/latest/
- OpenAI prompt caching:https://platform.openai.com/docs/guides/prompt-caching
uncertainty
- Cache TTL depends on the service domain and data change cycle.
- An LLM provider's prompt caching policy may vary depending on the model and timing.
- Semantic cache requires separate quality evaluation.
Operational example: LLM responses that should not be cached
Redis Cache Aside reduces costs, but applying it to every response is risky. Responses that are highly user-specific or time-dependent, such as personal document summaries, account status, payment information, and latest disclosures, may require narrow cache coverage. The cache key must contain prompt version, model, tenant, permission scope, and input hash, and must not contain the original text of personal information.
| response type | cache judgment | reason |
|---|---|---|
| Public Document FAQs | possible | Input and permissions are stable |
| Personal document summary | limited | Requires user-specific permissions |
| Payment status reply | Usually prohibited | Sensitive information and recency issues |
| Real-time price analysis | Prohibited or very short TTL | Data changes quickly |
A failure case is when questions with similar meaning are grouped with the same cache key. “Is it possible to get a refund?” and “Please tell me the refund process” may seem similar, but the scope of the answer is different. A semantic cache can reduce costs, but it also carries a high risk of reusing incorrect answers. It is safer to start with a deterministic key initially and expand the range after looking at the cache hit ratio and incorrect answer reports.

댓글
GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.