Amazon ElastiCache is a managed in-memory data store. It runs Redis OSS, Valkey, or Memcached for you, and its job in an architecture is almost always the same: sit between your application and a slower, more expensive backing store so that repeated reads come from RAM instead of from disk or from another network service.
This page builds the mental model behind that placement. Once the topology is clear - what the cache holds, what the AWS SDK controls, and what a Redis client controls - every other page in this section reads as an application of one picture.
ElastiCache is an in-memory cache placed in the request path between your app and its database, so that hot reads are served from RAM in sub-millisecond time instead of hitting the database again.
Insight: there are two distinct tools here. The AWS SDK (@aws-sdk/client-elasticache / boto3 elasticache) provisions and describes the cache infrastructure; a Redis client library (redis-py, ioredis, node-redis) does the actual GET/SET at request time. The SDK never moves your cached values.
Key Concepts:cache hit / miss, TTL, backing store, primary and reader endpoints, eviction, cache-aside.
When to Use: read-heavy workloads with data that is expensive to compute or fetch and tolerable to serve slightly stale - session state, product catalogs, computed feeds, database query results.
Limitations/Trade-offs: a cache is a copy, not the source of truth, so you accept staleness bounded by a TTL and you must handle misses; RAM is finite, so entries get evicted.
Related Topics: engine choice, cluster mode and endpoints, serverless capacity, and the cache-aside pattern.
Your database is durable and authoritative, but every read costs a query, an index lookup, and often a network hop to another service such as Amazon RDS or Aurora. When the same rows are read thousands of times per second, most of that work is wasted - the answer has not changed since the last read. A cache stores that answer in memory, keyed by something the application already knows, so the next identical read is a single memory lookup.
Two outcomes define every cache read. A cache hit means the key was present and the value is returned immediately. A cache miss means the key was absent, so the application falls back to the database, and usually writes the result back into the cache for next time. The fraction of reads that hit is the cache hit rate, and it is the single number that tells you whether a cache is earning its keep.
The value in the cache is only ever a copy. To keep that copy from drifting too far from the truth, most entries carry a TTL (time to live) - after which the cache forgets them and the next read re-fetches fresh data. Choosing the TTL is choosing how stale you are willing to be in exchange for how much load you shed.
Here is the SDK's role in that request path. The application connects to an endpoint, and the endpoint is what the SDK reports after the cache is provisioned. The snippet below asks ElastiCache where to connect - it does not read or write any cached key.
# --- Python (boto3) --- AWS SDK: find the address the app will connect to.import boto3ec = boto3.client("elasticache", region_name="us-east-1")groups = ec.describe_replication_groups(ReplicationGroupId="app-cache")["ReplicationGroups"]node_group = groups[0]["NodeGroups"][0]print("write to:", node_group["PrimaryEndpoint"]["Address"])print("read from:", node_group["ReaderEndpoint"]["Address"])
// --- TypeScript (AWS SDK v3) --- AWS SDK: find the address the app will connect to.import { ElastiCacheClient, DescribeReplicationGroupsCommand } from "@aws-sdk/client-elasticache";const ec = new ElastiCacheClient({ region: "us-east-1" });const out = await ec.send(new DescribeReplicationGroupsCommand({ ReplicationGroupId: "app-cache" }));const nodeGroup = out.ReplicationGroups![0].NodeGroups![0];console.log("write to:", nodeGroup.PrimaryEndpoint!.Address);console.log("read from:", nodeGroup.ReaderEndpoint!.Address);
The address it prints is where a Redis client - not the AWS SDK - will send its GET and SET commands.
The clearest way to hold the picture is to separate the two tools by responsibility.
The AWS SDK owns the infrastructure. With CreateReplicationGroup (Redis/Valkey) or CreateCacheCluster (Memcached) it provisions nodes; with DescribeReplicationGroups / DescribeCacheClusters it reports their endpoints, status, and topology; with modify and delete calls it scales or tears them down. These are control-plane operations - infrequent, run at deploy time or from an operations script, and metered as normal AWS API calls.
The Redis client owns the data. At request time, every hot read and write goes through a client library pinned to the cache's endpoint: redis-py in Python, ioredis or node-redis in JavaScript. These libraries speak the Redis wire protocol directly to the cache node. AWS does not provide them and the AWS SDK does not wrap them. This is the crucial separation to internalize - describe_replication_groups finds the address, and redis.get("user:1") uses it.
The endpoints themselves encode the read/write topology. A Redis replication group exposes a primary endpoint that accepts writes and a reader endpoint that load-balances reads across replicas. Sending writes to the primary and reads to the reader is how you scale reads horizontally without changing application logic - the two endpoints are stable DNS names even when the underlying nodes fail over.
The request path, then, is a loop:
The app receives a request that needs some data.
It asks the cache (via the Redis client) for the key.
On a hit, it returns the cached value - done, no database touched.
On a miss, it queries the backing database, returns that value, and writes it into the cache with a TTL so the next request hits.
That loop is the cache-aside pattern, and it is why a cache in front of RDS can cut database load by an order of magnitude on a read-heavy service.
Once the placement is clear, the interesting decisions are about what the cache holds and how honest the copy stays.
What to cache. Good candidates are expensive to produce and read far more than they change: a rendered product page, the result of an aggregate SQL query, a user's session, a rate-limit counter, a leaderboard. Poor candidates are data that must be perfectly current on every read, or data read so rarely that it is evicted before it is reused.
Eviction is not failure. RAM is finite, so when the cache fills, an eviction policy (for Redis, maxmemory-policy such as allkeys-lru) discards entries to make room. A well-tuned cache runs near full and evicts cold keys continuously - that is the design working, not breaking. It does mean any read can miss, so the application must always be correct when the cache is empty.
Freshness is a spectrum you choose. The TTL is the coarse control: short TTLs stay fresh but shed less load; long TTLs shed more load but serve staler data. When a write must be reflected immediately, you either write through the cache (update both the database and the cache) or invalidate the key on write so the next read re-fetches. Those strategies have their own page.
Redis/Valkey versus Memcached is a choice about capability. Redis and Valkey offer rich data structures, replication, failover, and backups; Memcached is a simpler multi-threaded key-value store with no replication. The engine you pick changes which SDK call provisions it and which endpoints you get - the next page in this section makes that decision concretely.
The habit to carry forward: the cache is a performance layer, never the system of record. Design so that a cold, empty cache still returns correct answers - just slower - and everything else about ElastiCache becomes a tuning problem rather than a correctness one.
"The AWS SDK reads and writes my cached values." - It does not. The SDK provisions and describes the cache; a Redis client library (redis-py, ioredis, node-redis) moves the actual keys and values at request time.
"The cache is my database." - It is a copy of data whose source of truth lives elsewhere. Treat it as disposable - the app must stay correct when the cache is empty or a key has been evicted.
"A cache miss is an error." - A miss is the normal fallback path: read the database, populate the cache, continue. Misses are expected, especially right after a deploy or an eviction.
"Longer TTLs are always better because they hit more." - Longer TTLs shed more load but serve staler data. The TTL is a deliberate freshness-versus-load trade-off, not a value to maximize blindly.
"Adding a cache is free performance." - It adds a moving part, a consistency question, and a failure mode. It pays off for read-heavy, tolerant-of-staleness data, and can hurt for write-heavy or must-be-current data.
It runs a managed in-memory data store - Redis OSS, Valkey, or Memcached - that sits between your application and a slower backing database, serving frequently read data from RAM so most reads never reach the database.
Does the AWS SDK move my cached data?
No. The AWS SDK (client-elasticache / boto3 elasticache) provisions the cache and reports its endpoints. A separate Redis client library uses that endpoint to run the actual GET and SET commands at request time.
What is a cache hit versus a cache miss?
A hit means the requested key was in the cache and is returned immediately. A miss means it was absent, so the app falls back to the database, returns that value, and usually writes it into the cache with a TTL for next time.
Why do Redis caches have two endpoints?
The primary endpoint accepts writes; the reader endpoint load-balances reads across replicas. Sending writes to the primary and reads to the reader scales reads without changing application logic, and both stay stable across failover.
What is a TTL and why does every entry need one?
A TTL is a time to live after which the cache forgets an entry. It bounds how stale a cached copy can get and lets the next read re-fetch fresh data, which is how you keep the cache honest against a changing database.
Is the cache the source of truth?
No. It holds copies of data owned by a durable store such as RDS, Aurora, or DynamoDB. Always design so that an empty cache still returns correct answers - the cache only makes correct answers faster.
What happens when the cache runs out of memory?
An eviction policy discards entries to make room - for Redis, maxmemory-policy (for example allkeys-lru) evicts least-recently-used keys. A healthy cache runs near full and evicts cold keys, so any read can miss and fall back to the database.
When is a cache the wrong choice?
When data must be perfectly current on every read, when the workload is write-heavy, or when data is read too rarely to survive in the cache before eviction. In those cases a cache adds complexity and consistency risk for little gain.