On June 2, 2026, AWS [announced durability for Amazon ElastiCache](https://aws.amazon.com/about-aws/whats-new/2026/06/durability-amazon-elasticache/) starting with Valkey 9.0. That sounds like a small line item, but it quietly changes what ElastiCache is *for*. Until last week, "cache" meant "fast but expendable." Now you can pick, per cluster, whether writes are durable across AZs, and ElastiCache becomes a legitimate primary store for things you used to dump into DynamoDB out of fear.
This post is me thinking out loud about where that matters. I'm a backend/platform engineer whose last real cache project was Redis in n8n. Most of my recent work is AWS AI infra — RAG pipelines, agent orchestration with Step Functions, dead-letter retries. Cache patterns sit at the seam of all of those, and I want to make sure I'm not under-using them on the next project.
## What actually changed
Two new write modes, on top of the existing in-memory speed:
| Mode | Write latency | Data loss window | Use when |
|---|---|---|---|
| Synchronous | single-digit ms | zero (persisted to ≥2 AZs before ack) | money, identity, state you cannot replay |
| Asynchronous | microseconds (free) | up to 10s on AZ failure | hot path you can rebuild from a system of record |
| (legacy) non-durable | microseconds | everything since last snapshot | true ephemeral cache |
Reads stay at microseconds in all three. The transactional log lives across multiple AZs, so failover, restart, and recovery don't lose committed data.
The headline phrase from the AWS blurb — "AI agent long-term memory, AI agent workflow state, knowledge bases for RAG applications" — is unusually direct. AWS is telling you what they expect people to build with this.
## Pattern 1 — RAG semantic cache
In a vanilla RAG path, every user query embeds → searches a vector store → re-ranks → calls the LLM. The vector search and the LLM call are the two expensive steps. Both are deterministic enough to cache.
```
User query
→ embed (cheap)
→ semantic cache lookup by embedding similarity ← ElastiCache
hit → return cached answer (microseconds)
miss → vector store + LLM call
→ write {embedding hash : answer} back ← ElastiCache
```
Key shape options:
- **Exact key**: hash of the normalized prompt. Trivial to implement, low hit rate.
- **Semantic key**: store the embedding and approximate-match on cosine similarity above some threshold. Higher hit rate but you need a vector index *inside* the cache. Valkey/Redis modules support this; you can also keep a small HNSW in memory.
Why durability matters here: if your cache is ephemeral, every cluster restart cold-starts your LLM bill. Async durability is plenty — losing 10 seconds of cached answers is not a correctness problem, just a brief cost spike. Sync durability is overkill. **Default async, and remember you no longer have to choose between "cache" and "real store."**
Pitfall I'd worry about: cache poisoning when the upstream RAG pipeline returns a hallucinated or stale answer once. That answer can sit in cache for the TTL and serve to other users. Always include a content-version key (model name + prompt template version + index version) so a deploy invalidates without manual flush.
## Pattern 2 — Agent workflow state in Step Functions
Step Functions gives you `Map`, `Parallel`, `Wait`, retry policies, and `.waitForTaskToken` for human-in-the-loop. What it doesn't give you cheaply is:
- Mid-execution scratchpad bigger than the 256 KB state payload
- Cross-execution memory ("this agent already saw this customer last hour")
- Fast lookups that aren't worth a DynamoDB round trip
Pre-durability ElastiCache solved the speed problem but felt risky for anything you couldn't reconstruct. With durable Valkey, the pattern becomes clean:
- **Execution scoped state** (lives for one Step Functions run): key `agent:exec:<exec-arn>:<slot>`, TTL = max execution duration + buffer. Use sync writes if losing the slot would corrupt downstream steps; async otherwise.
- **Agent long-term memory** (lives across runs): key `agent:user:<user-id>:facts`, no TTL or a long one. Sync writes — this is the memory the user expects to persist.
- **Tool-call cache** (idempotency for expensive tools): key `tool:<tool-name>:<arg-hash>`, short TTL. Async is fine.
Step Functions Lambda tasks read/write this directly. You no longer need a "memory service" Lambda in front. ElastiCache is the memory service.
## Pattern 3 — DLQ retry coordinator
This is the one I keep underbuilding. SQS dead-letter queues are great at catching failures, but the recovery story is usually a Lambda that drains the DLQ and reprocesses blindly. That works until you have *correlated* failures — same downstream API outage hits 1000 messages, you reprocess them all, they all fail again, you back off and retry, repeat.
A cache-coordinated retry layer fixes this:
```
DLQ message arrives
→ cache lookup: dlq:<failure-fingerprint> ← ElastiCache
- count > N in window → don't retry yet, set circuit-open flag
- count ≤ N → increment, retry, on success delete key
→ on circuit-open: skip retries until TTL expires
```
The fingerprint is whatever isolates the failure class — downstream service ID + error code, for example. The cache holds the rolling counter and the open-circuit flag. Sync durability matters here: if the counter resets on AZ failover, you lose your back-pressure and slam the recovering downstream the moment it comes back. This is a place where the new sync mode pays for itself.
Bonus: the same cache can hold the per-key idempotency token so a retry that *did* partially succeed doesn't double-charge a payment or double-write a record.
## Choosing between sync, async, and DynamoDB
A rough heuristic I'm landing on:
| Need | Pick |
|---|---|
| Rebuildable, hot path, cost matters | ElastiCache async |
| Correctness-critical, low write rate, microsecond reads | ElastiCache sync |
| Audit trail, queries beyond key lookup, infrequent access | DynamoDB |
| Big blob, write-once-read-many | S3, with ElastiCache pointer |
DynamoDB doesn't go away. It's still the right answer for anything you'll query by attribute, paginate, or audit. ElastiCache durability isn't a DynamoDB replacement — it's a way to stop using DynamoDB *as a poor cache*.
## What I'm doing next
- Adding a semantic-cache layer in front of the RAG step in our internal agent. Async writes, embedding-hash key, content-version included.
- Replacing a small DynamoDB "agent scratchpad" table with a Valkey cluster for one workflow as a proof-of-concept. Sync writes for the slot the next state reads.
- Sketching the DLQ coordinator pattern for our most retry-loud Step Function. This one I want to write a small spec for before building.
If the last time you touched a cache was Redis in some workflow tool, this announcement is a reason to revisit. The "cache vs database" line just moved.
On June 2, 2026, AWS announced durability for Amazon ElastiCache starting with Valkey 9.0. That sounds like a small line item, but it quietly changes what ElastiCache is for. Until last week, “cache” meant “fast but expendable.” Now you can pick, per cluster, whether writes are durable across AZs, and ElastiCache becomes a legitimate primary store for things you used to dump into DynamoDB out of fear.
This post is me thinking out loud about where that matters. I’m a backend/platform engineer whose last real cache project was Redis in n8n. Most of my recent work is AWS AI infra — RAG pipelines, agent orchestration with Step Functions, dead-letter retries. Cache patterns sit at the seam of all of those, and I want to make sure I’m not under-using them on the next project.
What actually changed
Two new write modes, on top of the existing in-memory speed:
Mode
Write latency
Data loss window
Use when
Synchronous
single-digit ms
zero (persisted to ≥2 AZs before ack)
money, identity, state you cannot replay
Asynchronous
microseconds (free)
up to 10s on AZ failure
hot path you can rebuild from a system of record
(legacy) non-durable
microseconds
everything since last snapshot
true ephemeral cache
Reads stay at microseconds in all three. The transactional log lives across multiple AZs, so failover, restart, and recovery don’t lose committed data.
The headline phrase from the AWS blurb — “AI agent long-term memory, AI agent workflow state, knowledge bases for RAG applications” — is unusually direct. AWS is telling you what they expect people to build with this.
Pattern 1 — RAG semantic cache
In a vanilla RAG path, every user query embeds → searches a vector store → re-ranks → calls the LLM. The vector search and the LLM call are the two expensive steps. Both are deterministic enough to cache.
User query
→ embed (cheap)
→ semantic cache lookup by embedding similarity ← ElastiCache
hit → return cached answer (microseconds)
miss → vector store + LLM call
→ write {embedding hash : answer} back ← ElastiCache
Key shape options:
Exact key: hash of the normalized prompt. Trivial to implement, low hit rate.
Semantic key: store the embedding and approximate-match on cosine similarity above some threshold. Higher hit rate but you need a vector index inside the cache. Valkey/Redis modules support this; you can also keep a small HNSW in memory.
Why durability matters here: if your cache is ephemeral, every cluster restart cold-starts your LLM bill. Async durability is plenty — losing 10 seconds of cached answers is not a correctness problem, just a brief cost spike. Sync durability is overkill. Default async, and remember you no longer have to choose between “cache” and “real store.”
Pitfall I’d worry about: cache poisoning when the upstream RAG pipeline returns a hallucinated or stale answer once. That answer can sit in cache for the TTL and serve to other users. Always include a content-version key (model name + prompt template version + index version) so a deploy invalidates without manual flush.
Pattern 2 — Agent workflow state in Step Functions
Step Functions gives you Map, Parallel, Wait, retry policies, and .waitForTaskToken for human-in-the-loop. What it doesn’t give you cheaply is:
Mid-execution scratchpad bigger than the 256 KB state payload
Cross-execution memory (“this agent already saw this customer last hour”)
Fast lookups that aren’t worth a DynamoDB round trip
Pre-durability ElastiCache solved the speed problem but felt risky for anything you couldn’t reconstruct. With durable Valkey, the pattern becomes clean:
Execution scoped state (lives for one Step Functions run): key agent:exec:<exec-arn>:<slot>, TTL = max execution duration + buffer. Use sync writes if losing the slot would corrupt downstream steps; async otherwise.
Agent long-term memory (lives across runs): key agent:user:<user-id>:facts, no TTL or a long one. Sync writes — this is the memory the user expects to persist.
Tool-call cache (idempotency for expensive tools): key tool:<tool-name>:<arg-hash>, short TTL. Async is fine.
Step Functions Lambda tasks read/write this directly. You no longer need a “memory service” Lambda in front. ElastiCache is the memory service.
Pattern 3 — DLQ retry coordinator
This is the one I keep underbuilding. SQS dead-letter queues are great at catching failures, but the recovery story is usually a Lambda that drains the DLQ and reprocesses blindly. That works until you have correlated failures — same downstream API outage hits 1000 messages, you reprocess them all, they all fail again, you back off and retry, repeat.
A cache-coordinated retry layer fixes this:
DLQ message arrives
→ cache lookup: dlq:<failure-fingerprint> ← ElastiCache
- count > N in window → don't retry yet, set circuit-open flag
- count ≤ N → increment, retry, on success delete key
→ on circuit-open: skip retries until TTL expires
The fingerprint is whatever isolates the failure class — downstream service ID + error code, for example. The cache holds the rolling counter and the open-circuit flag. Sync durability matters here: if the counter resets on AZ failover, you lose your back-pressure and slam the recovering downstream the moment it comes back. This is a place where the new sync mode pays for itself.
Bonus: the same cache can hold the per-key idempotency token so a retry that did partially succeed doesn’t double-charge a payment or double-write a record.
DynamoDB doesn’t go away. It’s still the right answer for anything you’ll query by attribute, paginate, or audit. ElastiCache durability isn’t a DynamoDB replacement — it’s a way to stop using DynamoDB as a poor cache.
What I’m doing next
Adding a semantic-cache layer in front of the RAG step in our internal agent. Async writes, embedding-hash key, content-version included.
Replacing a small DynamoDB “agent scratchpad” table with a Valkey cluster for one workflow as a proof-of-concept. Sync writes for the slot the next state reads.
Sketching the DLQ coordinator pattern for our most retry-loud Step Function. This one I want to write a small spec for before building.
If the last time you touched a cache was Redis in some workflow tool, this announcement is a reason to revisit. The “cache vs database” line just moved.