AWS ElastiCache
Managed Redis and Memcached. Cluster mode and its client requirement, eviction policies, the cache-aside pattern and failover behaviour.
Amazon ElastiCache
ElastiCache runs managed Redis, Valkey or Memcached, in-memory data stores with sub-millisecond latency. The usual job is taking read load off a database, holding sessions so your application can be stateless, and rate limiting.
The most useful thing to be clear about up front: a cache is not a database. Data in ElastiCache can disappear: a node fails, memory fills and keys are evicted, a maintenance window replaces a node. Anything that must survive belongs in RDS or DynamoDB, with the cache in front of it.
Redis, Valkey or Memcached
| Redis / Valkey | Memcached | |
|---|---|---|
| Data structures | Lists, sets, sorted sets, hashes, streams | Strings only |
| Persistence | Optional snapshots | None |
| Replication | Yes, with automatic failover | No |
| Multi-threaded | Partly | Yes |
| Pub/sub, Lua, transactions | Yes | No |
Choose Redis or Valkey unless you specifically want a simple multi-threaded key-value cache and nothing else. Memcached has no replication, so a node failure loses that node's data with no failover.
Valkey is the open-source fork created after Redis changed licence, is API-compatible, and is priced lower on ElastiCache. For new clusters it is worth considering by default.
Serverless is the easy starting point
aws elasticache create-serverless-cache \
--serverless-cache-name orders-cache \
--engine valkey \
--cache-usage-limits 'DataStorage={Maximum=10,Unit=GB},ECPUPerSecond={Maximum=5000}'
No node types, no cluster sizing, no patching windows. It scales with demand and bills on storage and compute consumed. For a new workload where you do not yet know the access pattern, it removes every capacity decision.
Provisioned clusters remain cheaper at steady, predictable load, and are what you need for fine control over node types and placement.
Provisioned, with replication
aws elasticache create-replication-group \
--replication-group-id orders-cache \
--replication-group-description "orders read cache" \
--engine valkey --cache-node-type cache.r7g.large \
--num-node-groups 1 --replicas-per-node-group 2 \
--automatic-failover-enabled --multi-az-enabled \
--at-rest-encryption-enabled --transit-encryption-enabled \
--cache-subnet-group-name private-subnets \
--security-group-ids sg-0123456789abcdef0
--automatic-failover-enabled with --multi-az-enabled is what makes this production-grade: a primary failure promotes a replica in another availability zone, typically within a minute or two. Without them a node failure is an outage requiring manual intervention.
cache.r7g is Graviton and memory-optimised. Better price-performance than the older m and r generations for almost every cache workload.
Cluster mode changes your client
The decision with the largest application impact.
Cluster mode disabled gives one shard: a primary plus replicas. Total data is limited to one node's memory. Writes go to the primary, reads can go to replicas.
Cluster mode enabled shards data across multiple node groups, so capacity scales horizontally.
aws elasticache create-replication-group \
--replication-group-id orders-cache \
--replication-group-description "sharded" \
--engine valkey --cache-node-type cache.r7g.large \
--num-node-groups 3 --replicas-per-node-group 1 \
--automatic-failover-enabled
Your client library must support cluster mode, and this is where people get stuck. A standard Redis client connecting to a cluster-mode endpoint receives MOVED redirections it does not understand:
(error) MOVED 15495 10.0.1.42:6379
Use the cluster-aware client, redis-py-cluster or redis-py in cluster mode, ioredis with Redis.Cluster, Lettuce for Java, and the configuration endpoint rather than a node address.
Multi-key operations are also constrained: keys touched together must live in the same slot, which means hash tags:
user:{4821}:profile
user:{4821}:orders
The braces make Redis hash only the enclosed part, so both keys land on the same shard and can be used in one transaction or MGET.
Start with cluster mode disabled unless you know you will exceed one node's memory. Enabling it later requires client changes.
Eviction policy
What happens when memory fills, and the default is wrong for a cache:
aws elasticache modify-cache-parameter-group \
--cache-parameter-group-name orders-params \
--parameter-name-values 'ParameterName=maxmemory-policy,ParameterValue=allkeys-lru'
| Policy | Behaviour |
|---|---|
volatile-lru | Default. Evicts only keys with a TTL |
allkeys-lru | Evicts any key, least recently used |
allkeys-lfu | Evicts least frequently used |
noeviction | Rejects writes when full |
The default volatile-lru only evicts keys that have a TTL, so if your application writes keys without expiry, memory fills and writes start failing:
(error) OOM command not allowed when used memory > 'maxmemory'
For a pure cache, allkeys-lru is correct: it always has something to evict. Use allkeys-lfu when a small set of keys is much hotter than the rest. Only use noeviction when losing a key would be a correctness problem, and at that point reconsider whether this should be a cache.
Always set a TTL anyway. It bounds staleness, and it means a key nobody reads disappears rather than occupying memory indefinitely.
Cache-aside
The pattern behind most cache usage:
def get_order(order_id):
key = f"order:{order_id}"
cached = r.get(key)
if cached:
return json.loads(cached)
order = db.query("SELECT * FROM orders WHERE id = %s", order_id)
r.setex(key, 300, json.dumps(order)) # 5-minute TTL
return order
On write, invalidate rather than update:
def update_order(order_id, data):
db.execute("UPDATE orders SET … WHERE id = %s", order_id)
r.delete(f"order:{order_id}")
Deleting is safer than writing the new value, because a failed cache write leaves stale data indefinitely while a failed delete is corrected by the next read.
Two failure modes worth designing for. A cache stampede happens when a popular key expires and hundreds of requests all miss and hit the database simultaneously. Add jitter to TTLs, or have one request populate while others briefly serve stale. And a cache that must be available for the application to work has become a dependency rather than an optimisation, so make the code path fall back to the database when Redis is unreachable.
Failover behaviour
aws elasticache test-failover --replication-group-id orders-cache \
--node-group-id 0001
Test this before you need it. During a failover the primary endpoint DNS is repointed, so clients must reconnect and re-resolve. A client caching the resolved address keeps trying a node that is no longer primary, which surfaces as READONLY You can't write against a read only replica.
Most modern clients handle it. Verify yours does, with a real failover test.
Use the reader endpoint to spread reads across replicas, and remember replication is asynchronous, so a read immediately after a write may return the old value.
Monitoring
aws cloudwatch get-metric-statistics --namespace AWS/ElastiCache \
--metric-name DatabaseMemoryUsagePercentage \
--dimensions Name=ReplicationGroupId,Value=orders-cache \
--start-time 2026-09-15T00:00:00Z --end-time 2026-09-16T00:00:00Z \
--period 300 --statistics Average
| Metric | Watch for |
|---|---|
CacheHitRate | Below ~80% means the cache is not earning its place |
DatabaseMemoryUsagePercentage | Above 80% (evictions imminent) |
Evictions | Sustained non-zero means undersized |
CurrConnections | Approaching the node limit |
ReplicationLag | Stale reads from replicas |
SwapUsage | Above ~50 MB is a serious problem |
A low hit rate usually means TTLs are too short, keys are too specific to be reused, or the cached data is not actually read repeatedly.
Common errors
(error) OOM command not allowed when used memory > 'maxmemory'
Memory full and the eviction policy cannot free anything. Set allkeys-lru.
(error) MOVED 15495 10.0.1.42:6379
A non-cluster-aware client against a cluster-mode endpoint.
(error) READONLY You can't write against a read only replica
Writing to a replica, or a stale connection after failover.
CROSSSLOT Keys in request don't hash to the same slot
A multi-key operation across shards. Use hash tags.
Connection timeouts. Security group must allow 6379 from the client, and ElastiCache is not reachable from outside the VPC.
Quick reference
| Task | Command |
|---|---|
| Serverless cache | aws elasticache create-serverless-cache --engine valkey |
| Replication group | create-replication-group --automatic-failover-enabled --multi-az-enabled |
| Set eviction policy | modify-cache-parameter-group --parameter-name-values … |
| Test failover | aws elasticache test-failover |
| Endpoints | describe-replication-groups --query '…NodeGroups[].PrimaryEndpoint' |
| Scale replicas | aws elasticache increase-replica-count |
| Watch memory | DatabaseMemoryUsagePercentage in AWS/ElastiCache |
Key takeaways
- A cache is not a database. Everything in it must be reconstructible.
- Choose Redis or Valkey: Memcached has no replication or failover.
- Serverless removes every capacity decision and suits unknown access patterns.
- Enable automatic failover and multi-AZ, or a node failure is an outage.
- Cluster mode requires a cluster-aware client and hash tags for multi-key operations.
- Change
maxmemory-policytoallkeys-lru. The default only evicts keys with a TTL. - Invalidate on write rather than updating: a failed delete self-corrects.
- Test failover deliberately, and make sure your client reconnects.
- Alarm on memory percentage and evictions, and check the hit rate is worth having.
Check your understanding
Writes rejected on a full cache
A Redis cache fills up and writes start failing with "OOM command not allowed when used memory > maxmemory". The application writes many keys without TTLs. What is the fix?
MOVED errors after enabling sharding
After migrating to a cluster-mode-enabled replication group, the application fails with "MOVED 15495 10.0.1.42:6379". It uses a standard Redis client and the primary node address. What is wrong?
Frequently Asked Questions
Should I use Redis, Valkey or Memcached on ElastiCache?
Redis or Valkey for almost everything. Memcached offers only strings, no replication and no failover, so losing a node loses that node's data with no automatic recovery. It suits a simple multi-threaded key-value cache and little else. Redis adds lists, sets, sorted sets, hashes, streams, pub/sub, optional persistence and automatic failover. Valkey is the open-source fork created after Redis's licence change, is API-compatible, and is priced lower on ElastiCache, which makes it a reasonable default for new clusters.
What is the difference between cluster mode enabled and disabled?
Disabled means a single shard: one primary with replicas, so your dataset must fit in one node's memory. Enabled shards data across several node groups, scaling capacity horizontally. The important consequence is on the client. Cluster mode requires a cluster-aware library and the configuration endpoint, because nodes reply with MOVED redirections for keys they do not own, and multi-key operations need hash tags to keep related keys in the same slot. Start with cluster mode disabled unless you know you will outgrow one node.
Why is ElastiCache rejecting writes when the cache is full?
Because the default eviction policy, volatile-lru, only evicts keys that have a TTL. If your application writes keys without expiry, memory fills with keys the policy will not touch and Redis returns OOM command not allowed. Change maxmemory-policy to allkeys-lru in the parameter group so any key can be evicted, which is the correct behaviour for a cache. Setting TTLs on writes as well bounds staleness and stops unread keys accumulating.
What is the cache-aside pattern?
Read from the cache first; on a miss, read from the database, write the value into the cache with a TTL, and return it. On update, delete the cached key rather than rewriting it. A failed delete is corrected by the next read, whereas a failed write leaves stale data indefinitely. Two things to design for: add jitter to TTLs so a popular key expiring does not send hundreds of simultaneous requests to the database, and make the code fall back to the database when the cache is unreachable.
What happens during an ElastiCache failover?
A replica is promoted and the primary endpoint's DNS is repointed, typically completing within a minute or two when automatic failover and multi-AZ are enabled. Clients must reconnect and re-resolve; one that caches the resolved IP keeps talking to a node that is now a replica and sees READONLY You can't write against a read only replica. Most current client libraries handle this correctly, but verify yours with test-failover before relying on it in production.
Can I store session data in ElastiCache?
Yes, and it is one of the best uses. It lets your application servers be stateless, which is what makes horizontal scaling and rolling deployments work. Just accept the consequence: cache data can be lost to a node failure or eviction, so losing sessions means users are logged out. Enable multi-AZ with automatic failover, set TTLs matching your session lifetime, and if being logged out is unacceptable, either enable Redis persistence or keep a durable copy elsewhere.