This is the checklist to run before and after building on ElastiCache from the SDK. Each item is a rule stated positively with a one-line rationale, covering both the AWS SDK control plane and the Redis-client data plane that sits on top of it.
Work top to bottom - the groups run roughly in dependency order, from engine and topology choice outward to security, resilience, and cost. Most rules hold whether you provision from the SDK or from IaC.
Default to Redis/Valkey; reach for Memcached only for pure KV. Redis/Valkey give structures, replication, failover, and backups; Memcached is a simple multi-threaded key-value store with none of those.
Prefer Valkey for new caches. It is a drop-in, lower-cost fork of Redis OSS, so you get the same features and clients at lower price.
Use the right SDK call per engine.CreateReplicationGroup for Redis/Valkey, CreateCacheCluster with NumCacheNodes for Memcached - mixing them fails.
Stay non-cluster until a node is the bottleneck. Single-shard (NumCacheClusters) is simpler; move to cluster mode (NumNodeGroups) only when memory or write throughput demands sharding.
Consider serverless for spiky or unknown traffic.CreateServerlessCache removes node sizing and bills per data plus ECPUs - ideal before the traffic shape is clear.
Provision Redis/Valkey with the replication-group call and encryption on.
Connect to endpoints, never node IPs. Endpoints are stable DNS names that survive failover and node replacement; IPs change without warning.
Split writes from reads. Send writes to the primary endpoint and reads to the reader endpoint so replicas absorb read load.
Use a cluster-aware client in cluster mode.RedisCluster / ioredis Cluster follow cross-shard MOVED redirects; a plain client cannot.
Resolve endpoints from the SDK at deploy time.DescribeReplicationGroups / DescribeServerlessCaches is the source of truth - inject the address into config, do not hard-code it.
Reuse client connections. Create the Redis client once at module scope and pool connections; reconnecting per request wastes latency and file descriptors.
Give every entry a TTL. A TTL bounds staleness and lets cold keys expire so memory stays free for hot ones.
Set a deliberate eviction policy.allkeys-lru suits a general cache; set maxmemory-policy via a cache parameter group rather than leaving the default.
Design for a cold cache. The app must return correct answers when every read misses - the cache is a performance layer, not the source of truth.
Invalidate on write when reads must be current. Delete the key after the database write so the next read re-populates fresh, instead of waiting for the TTL.
Defend hot keys against stampede. Add TTL jitter, a short recompute lock (SET NX EX), or stale-while-revalidate so one expiry does not stampede the database.
Set the eviction policy on a parameter group with the SDK.
Enable automatic failover and Multi-AZ.AutomaticFailoverEnabled with MultiAZEnabled promotes a replica in another AZ when a primary fails.
Test failover deliberately. Trigger TestFailover in a controlled window and confirm the client reconnects - do not discover failover behavior during an incident.
Handle reconnection in the client. Redis clients drop connections on failover; configure retry/reconnect so a brief blip is not an outage.
Enable backups for Redis/Valkey when state matters. Automatic snapshots to S3 let you restore; Memcached has no persistence at all.
Keep the app correct if the cache is down. Fall back to the database on cache errors so a cache outage degrades latency, not availability.
Trigger a failover to verify resilience before you rely on it.
Right-size or go serverless. Match node type to the working set, or use serverless to skip sizing for variable load; cap serverless with CacheUsageLimits.
Reserve nodes for steady load. Reserved-node pricing beats on-demand for predictable, long-lived caches; keep serverless for spiky traffic.
Watch the cache-health metrics. Alarm on CacheHitRate, Evictions, DatabaseMemoryUsagePercentage, and EngineCPUUtilization to catch undersizing before users feel it.
Treat a low hit rate as a design signal. A poor hit rate means the wrong data, too-short TTLs, or too little memory - not a reason to add nodes blindly.
Tag caches for cost attribution. Consistent tags make spend, ownership, and cleanup discoverable across accounts.
What is the single most important ElastiCache habit?
Designing so the application is correct with a cold, empty cache. The cache is a performance layer over an authoritative database, so every read must fall back correctly on a miss. Almost every other rule - TTLs, eviction, stampede defense - follows from treating the cache as disposable.
Should I use endpoints or node addresses?
Always endpoints. Primary, reader, and configuration endpoints are stable DNS names that survive failover and node replacement, while node IPs change without notice. Resolve them from DescribeReplicationGroups at deploy time and inject them into config.
How do I test that failover actually works?
Call the TestFailover API on a shard during a controlled window and confirm your client reconnects and traffic recovers. Verifying failover deliberately - not during a real incident - is how you learn whether your client's retry configuration is adequate.
Which eviction policy should I set?
allkeys-lru is a solid general default: it evicts the least-recently-used key when memory fills. Use volatile-lru to evict only keys carrying a TTL, or noeviction to reject writes when full. Set it through a cache parameter group with the SDK.
How do I prevent a cache stampede?
Spread TTLs with jitter so hot keys do not expire together, use a short SET NX EX recompute lock so only one request rebuilds a value, or serve stale-while-revalidate. Any of these stops a single expiry from sending a flood of misses to the database.
Serverless or provisioned nodes?
Start serverless for new, spiky, or unpredictable traffic - it removes sizing and costs little when idle. Move to provisioned nodes (ideally reserved) once metrics show steady, high, predictable load, where a right-sized node is cheaper than ECPUs plus storage.
What metrics tell me the cache is healthy?
CacheHitRate (is it earning its keep), Evictions (is memory too small), EngineCPUUtilization (is a node saturated), and DatabaseMemoryUsagePercentage (how full it is). A falling hit rate or rising evictions signals undersizing or a TTL/design problem.
Do these rules apply with IaC too?
Mostly yes. Engine and topology choice, encryption, failover testing, TTL and eviction strategy, stampede defense, tagging, and metric alarms are architectural habits that hold whether you create the cache from the SDK or from CloudFormation, CDK, or Terraform.