24.2
Caching at Every Layer
Edge, API, application, database and session caching, and their trade-offs.
18 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Draw the full caching path of a typical AWS application (client → CloudFront → API Gateway → Lambda or EC2 → ElastiCache or DAX → database, with S3 behind CloudFront) and say what each layer caches.
- Explain the one trade-off behind every layer: the closer to the user you cache, the faster and cheaper each request, but the harder the copy is to invalidate and the staler it can get.
- Configure and reason about each layer’s TTL and invalidation: CloudFront TTLs and invalidations, the API Gateway stage cache (REST only, TTL, cache keys, client bypass with
Cache-Control: max-age=0), ElastiCache TTLs and delete-on-write, the DAX item TTL. - Decide when a cache saves money and when it only adds a bill.
- Choose lazy loading, write-through or both on this path, and name the data that must never be cached.
- Pick DAX or ElastiCache, and keep session state out of the web tier.
- Read an exam scenario’s keywords and name the caching layer it wants.
#Why cache at every layer
Every earlier chapter added one cache: CloudFront in lesson 11.1, ElastiCache in lesson 7.5, DAX in lesson 15.5, the API Gateway stage cache in lesson 15.6. This lesson does not teach them again. It puts them on one request path and asks the architect’s questions: which layer should answer which request, how long may each copy live, what happens to a price change on its way through four caches, and which data must never be cached at all.
Caching is used at many layers for three reasons, and the exam uses all three words: to make the application faster (lower latency), to reduce load on the database and the compute behind it, and to save cost.
#The request path of a flash sale
Think of a Daraz-style shop on the night of an 11.11 flash sale. Shoppers in Dhaka, Chattogram and Sylhet open the app; the shop runs in the Singapore Region.
| Layer | Service | What it can cache | Typical TTL |
|---|---|---|---|
| Edge | CloudFront | Images, CSS/JS, whole pages, even API responses, at a PoP near the shopper | Seconds to a year; default 24 h |
| API | API Gateway stage cache | Responses of REST API methods (by default GET only) | Default 300 s, up to 3,600 s |
| Application | ElastiCache (Valkey, Redis OSS, Memcached) | Whatever your code puts there: query results, computed pages, sessions | You choose per key |
| Database | DAX for DynamoDB; read replicas for RDS | DynamoDB items and query results; read copies of an RDS database | DAX items: default 5 min |
| Storage | S3 behind CloudFront | The origin of the static files the edge caches | (origin, not a cache) |
#The one trade-off: close is fast, far is fresh
Six words sum up the whole picture: caching, TTL, network, computation, cost, latency. Every layer trades them against each other in the same direction:
- Closer to the user (the edge): the request saves the network trip to the Region, all the computation behind it and the database query. Lowest latency, lowest cost per request. But the copy sits in hundreds of places, so it is the hardest to invalidate and can be the stalest.
- Closer to the database (ElastiCache, DAX): each request still pays for the network and the compute in front, but the cache is in one place your code controls, so invalidating a key is one command and the data can be kept almost fresh.
- Staleness adds up. If the API cache keeps a response for 60 s and the Valkey key it was built from had a 60 s TTL, a shopper can see an old price for almost two minutes. The worst case is the sum of the TTLs of every layer that holds a copy.
So the design question for each kind of data is: how far up the path can it go before its staleness hurts? A product photo can go all the way to the edge; a product list can sit at the API for a minute; a price can sit in Valkey if it is deleted on change; the stock count of the last 40 phones cannot be cached anywhere.
One request path, four places to cache
A flash-sale shop: shoppers in Dhaka, the application in the Singapore Region. Follow a product image, a product list, a full miss, a price change and a stock count, and see which layer answers each one.
Scenario
Static files are answered a few milliseconds from the shopper and never reach the Region.
The first shopper of the morning asks for a product photo. The nearby PoP does not have it yet (a miss), so CloudFront fetches it from the S3 bucket through its Regional Edge Cache, keeping the bucket private with OAC.
#Layer 1: the edge (CloudFront)
Recap (lessons 11.1 and 11.2): CloudFront is AWS’s global CDN. It keeps copies at more than 750 edge locations (PoPs) close to users, with larger Regional Edge Caches behind them. A request is answered by the nearest PoP; only a miss goes back to the origin (S3, an ALB, EC2, API Gateway or any HTTP server).
What matters on the full path:
- TTL. How long a copy counts as fresh before CloudFront checks the origin again. The origin’s
Cache-Control: max-agedecides it within the minimum and maximum TTL of the cache policy; with no header the default TTL is 24 hours (86,400 s). When the TTL ends, CloudFront revalidates or fetches the new version. - Cache key. By default the domain and URL path. Add to it (through the cache policy) exactly the query strings, headers and cookies that change the response, and nothing else: a missing value serves the wrong copy, an extra one ruins the hit ratio.
- Behaviours per path. Static files under
/img/*use a long-TTL policy such as CachingOptimized; dynamic/api/*usually uses CachingDisabled, so the edge passes it on. Even then the edge helps: TLS ends near the user and the request travels on the AWS network. - Invalidation.
create-invalidationremoves paths from every edge before the TTL ends. The first 1,000 paths a month are free, then 0.005 USD per path (a wildcard path counts as one). The better habit for files that change is a versioned file name (banner-v2.jpg,app.3f9a1c.js): a new name is a new cache key, nothing to invalidate, and it even beats browser caches that an invalidation cannot reach. - S3 behind CloudFront. Keep the bucket private and let only the distribution read it with Origin Access Control (OAC). Data from AWS origins to CloudFront is free, so serving S3 through CloudFront is usually cheaper as well as faster.
#Layer 2: the API (API Gateway stage cache)
Recap (lesson 15.6): a REST API stage can have a cache. When a request’s key is in it, API Gateway answers without calling the backend. On the full path this layer sits behind the edge and in front of all your compute: a hit here saves the Lambda invocation (or the EC2 work) and everything behind it.
The facts the exam and production both care about:
- REST APIs only. HTTP APIs and WebSocket APIs have no built-in cache; put CloudFront with a caching policy in front if you need one.
- Per stage, with a size from 0.5 GB to 237 GB. Creating or resizing it takes a few minutes, and resizing empties it.
- TTL: default 300 s, from 0 (off) up to 3,600 s. Per method you can override the TTL or turn caching off.
- GET only by default. When you enable the stage cache, only GET methods are cached; other methods need an explicit override (and rarely should be cached).
- Cache key: the method and path, plus the cache key parameters you choose (query strings, headers, path parameters). Forget a parameter that changes the response and one shopper gets another shopper’s answer.
- A response larger than 1 MB is not cached. Cached data can be encrypted. Watch the CloudWatch metrics
CacheHitCountandCacheMissCount. - Flush the whole stage cache from the console or with
flush-stage-cache; the backend then takes the full load until the cache refills.
#Client bypass: Cache-Control: max-age=0
A client that needs a fresh answer can send Cache-Control: max-age=0. API Gateway then calls the backend and replaces the cached entry with the new response. Who may do this is controlled by two settings on the stage or method:
- Require authorization (
requireAuthorizationForCacheControl). When it is on, the caller needs an IAM permission for the actionexecute-api:InvalidateCacheon that method’s resource ARN. When it is off, any client can bypass and refresh the cache, and AWS warns that this can raise latency for everyone. - Unauthorized request handling (
unauthorizedCacheControlHeaderStrategy) for callers without the permission:FAIL_WITH_403: the request is refused with 403.SUCCEED_WITH_RESPONSE_HEADER: the header is ignored, the cached answer is served, and a warning header is added.SUCCEED_WITHOUT_RESPONSE_HEADER: the header is ignored silently.
#Does the API cache save money?
The notes’ exam trick says API caching can “reduce API Gateway cost by limiting backend calls”. Read it carefully:
- The cache is billed per hour by size, whether it is used or not, and is not in the free tier: in us-east-1 about 0.02 USD an hour for 0.5 GB (about 14.60 USD a month) up to 3.80 USD an hour for 237 GB (about 2,770 USD a month).
- Every request is still an API Gateway request. A cache hit is billed like a miss (3.50 USD per million REST requests for the first tier). The API Gateway line of the bill never goes down.
- What goes down is the backend: Lambda invocations and duration, EC2 capacity, database load.
A worked example. A catalogue API gets 200,000 GET requests a month, served by a 128 MB Lambda function that runs 100 ms. Lambda costs 0.20 USD per million requests plus 0.0000166667 USD per GB-second: 0.04 USD + 200,000 × 0.1 × 0.125 × 0.0000166667 ≈ 0.04 + 0.04 = 0.08 USD a month. A 0.5 GB cache costs about 14.60 USD. Caching here makes the API faster but the bill 182 times bigger.
Now the flash sale: 50 million requests a month with a 90% hit ratio. The cache saves 45 million invocations: 9 USD of requests + 45,000,000 × 0.1 × 0.125 × 0.0000166667 ≈ 9.38 USD of duration, about 18 USD, plus millions of database queries that would otherwise need a bigger database. Here the 14.60 USD cache pays for itself. Caching reduces cost only when backend savings beat the cache fee.
The API Gateway stage cache
A REST API stage with a 0.5 GB cache and a 60-second TTL in front of a Lambda function. Watch a hit, a cache key that forgets a parameter, and clients that try to skip the cache.
Scenario
The first request after a deployment. Only GET methods are cached by default when you turn the stage cache on; POST, PUT and DELETE always reach the backend.
Self-checkQuestion
A REST API in front of Lambda receives 300,000 GET requests a month, almost all different. The team enables a 0.5 GB stage cache to “reduce cost”. What happens to the bill?
#Layer 3: the application cache (ElastiCache)
Recap (lesson 7.5): ElastiCache is an in-memory layer your code puts in front of the database (RDS, Aurora, DynamoDB, anything), so the database does not run the same query again and again. Unlike the layers above, it caches whatever your code decides: a query result, a computed product page, a session, a leaderboard.
#Which engine
The notes describe two versions, Redis and Memcached. Since 2024 ElastiCache runs three engines:
- Valkey: the open-source fork of Redis, run by the Linux Foundation, same commands and clients. AWS’s recommended default, and cheaper than the others.
- Redis OSS: the open-source Redis engine (what the exam still calls “Redis”).
- Memcached: the simple, older cache.
Plus ElastiCache Serverless (for all three engines): no nodes to size, data replicated across AZs, billed by data stored and processing units. Everything the exam says about “Redis” applies to Valkey.
| Valkey / Redis OSS | Memcached | |
|---|---|---|
| High availability | Replicas, Multi-AZ automatic failover | None: no replication, no failover (nodes can sit in different AZs, but a lost node’s data is gone) |
| Persistence | Backup and restore (snapshots) | None on node-based clusters |
| Data types | Strings, hashes, lists, sets, sorted sets (leaderboards), geospatial, streams, pub/sub | Plain key-value |
| Scaling | Replicas for reads, shards for writes | Add nodes; the client spreads keys (pure horizontal) |
| Threads | Commands on one thread | Multi-threaded |
| Cross-Region | Global Datastore | No |
#Lazy loading and write-through on this path
Lesson 7.5 teaches both strategies step by step. Here is what they mean when the cache sits in the middle of a four-layer path.
- Lazy loading (cache-aside). The app reads the cache first; on a hit it answers, on a miss it reads the database, answers, and writes the result into the cache. Only data someone asks for is cached, so memory is saved, and a failed or empty node is not fatal (reads just go to the database). The costs: a miss penalty (three trips: cache get, database query, cache set, so the first read is slower than no cache at all), and stale data, because a database update does not touch the cache.
- Write-through. Every write to the database is followed by a write to the cache. Reads of written data always hit and are never stale. The costs: a write penalty (two writes per update), cache churn (data nobody will read fills memory), and missing data on a new node, which stays empty for a key until that key is written again.
The practical answer is to combine them: lazy loading so any miss refills the cache (new nodes, old data), write-through or delete-on-write so changed data is fresh, and a TTL on every key so data nobody reads leaves and any missed update is bounded. When memory is full, the eviction policy removes keys (the default volatile-lru evicts the least recently used key among keys with a TTL), which is one more reason every key should have a TTL.
Prices in Valkey: lazy loading, write-through and what never goes in
The flash-sale shop keeps product prices in ElastiCache for Valkey in front of Aurora. Watch a stale read, the fix of deleting on write, an empty new node under write-through, and the stock count that skips the cache.
Scenario
A shopper opens the phone page. Lazy loading: the app asks Valkey first, and the price 1,499 is there (TTL 60 s).
#What never goes in a cache
Some data must be exact at the moment it is used. Caching it anywhere, at any TTL, turns a speed problem into a correctness problem:
- Stock counts and seats during a sale: read them from the database, and make the purchase a conditional write (decrement only if stock > 0).
- Account balances at the moment of a payment, as in a bKash-style wallet: a balance can be cached for the home screen, never for the debit decision.
- Per-user data on a shared key: a cart or profile cached at the API layer under a key without the user is shown to the next user.
- One-time tokens, OTPs and anything security-sensitive.
#Layer 3 for DynamoDB: DAX
Recap (lesson 15.5): DynamoDB answers in single-digit milliseconds. DynamoDB Accelerator (DAX) is an in-memory cache cluster built only for DynamoDB that answers cached reads in microseconds and takes read load off the table.
- API-compatible. DAX speaks the DynamoDB API. You swap the DynamoDB client for the DAX client and point it at the cluster endpoint;
GetItem,QueryandPutItemstay the same. The notes’ “no code rewrite” is right, with the precision that the client and endpoint change (a small change, not zero). - Read-through and write-through: misses are read from the table and cached; writes through DAX go to the table and then update the item cache.
- Eventually consistent reads only. A strongly consistent read (
ConsistentRead=true) or a transactional read passes straight through to the table and is not cached. - Default TTL 5 minutes for both the item cache and the query cache. The query cache (results of
QueryandScan) is not updated by writes. - Writes that bypass DAX (another application, an import job, a global-table replica) leave the cache stale until the TTL ends.
- Runs as a cluster in your VPC (one primary and up to 10 read replicas; three nodes in three AZs for production), billed per node-hour. It helps reads, not writes.
| Question says | Choose |
|---|---|
| DynamoDB + microsecond latency + without rewriting the application | DAX |
| Read-heavy DynamoDB table, a hot key throttles reads | DAX |
| Cache the result of a computation or aggregation across many items | ElastiCache |
| Cache for RDS / Aurora or any non-DynamoDB source | ElastiCache |
| Session store, leaderboard, pub/sub, rate-limit counters | ElastiCache |
| Strongly consistent reads must be faster | Neither helps; DAX passes them through |
DAX: microseconds for DynamoDB reads
The shop’s product catalogue is a DynamoDB table with a three-node DAX cluster in front. The app uses the DAX client with the same GetItem calls. Watch which reads DAX can serve and which it cannot.
Scenario
The code calls GetItem exactly as before; only the client and the endpoint changed. That is what “no application rewrite” means for DAX.
#Session state: keep the web tier stateless
Recap (lessons 9.1, 9.2 and 6.4): session data, such as who is logged in and what is in the cart, must not live only on one EC2 instance. Save it in an external store and give the browser only a session ID. The notes call this session caching.
- ElastiCache for Valkey or Redis OSS: sub-millisecond reads, a TTL per session equal to the session timeout; use a replica with Multi-AZ, because losing the store logs everyone out.
- DynamoDB: durable and serverless, with a TTL attribute so expired sessions delete themselves. Good when sessions must survive anything.
- Memcached also works if losing sessions on a node failure is acceptable.
Sticky sessions (ALB stickiness cookies) are the shortcut, not the fix. They keep a user on one instance, so a cart in memory survives only while that instance lives: a scale-in, a failed health check or a deployment loses it. They also spread load unevenly: a new instance from a scale-out receives only new users, while the busy instance keeps its stuck users. Sessions in a shared store remove both problems; the web tier becomes stateless, and any instance can serve any request.
Session state: on the instance or in a shared store
Carts behind an ALB and an Auto Scaling group during a flash sale. First the cart lives in the instance’s memory with sticky sessions, then in a DynamoDB table every instance can read.
Scenario
Sticky sessions keep a shopper on one instance; when that instance goes, so does the cart.
Rahim adds two phones to his cart. EC2 A keeps the cart in its own memory, and the ALB sets a stickiness cookie so his next requests also go to A.
#Two cache-like alternatives
- RDS read replicas (lesson 7.1) take read queries off the primary without any cache code: send reports and read-only pages to the replica endpoint. They are copies of the whole database, updated asynchronously (seconds of lag), and they still run every query; a cache returns a stored answer without running anything. Choose replicas when reads are varied (reporting, search pages) and a little lag is fine; choose ElastiCache when the same few results are read again and again.
- S3 behind CloudFront with OAC: for static files the “cache layer” is simply the edge, with S3 as the private origin. No ElastiCache is needed for images, scripts or downloads.
#What to cache where
| Data | Edge (CloudFront) | API cache | Valkey / DAX | Never cache | Why |
|---|---|---|---|---|---|
| Product photos, CSS/JS (versioned names) | Yes, 1 year | Never changes under the same name | |||
| Home page HTML for anonymous users | Yes, 30–60 s | Same for everyone, small staleness is fine | |||
| Product list per category | Possible with a short TTL | Yes, 60 s | Yes, as a fallback | Same for everyone, asked thousands of times | |
| Price of one product | Short TTL only | Yes, delete on write | At checkout | Must be fresh when money is involved | |
| Shopper’s cart and login | No | Sessions: Valkey or DynamoDB | Per user; must survive instance loss | ||
| DynamoDB item reads (catalogue) | DAX | Microseconds, same code | |||
| Leaderboard of the sale’s top buyers | Valkey sorted set | Computed data, updated often | |||
| Stock count, wallet balance, OTPs | Yes | Must be exact at every moment |
#End-to-end example: an 11.11 flash sale
The Daraz-style shop’s design for 11.11, layer by layer:
Edge: product images and scripts
All images and scripts live in a private S3 bucket behind CloudFront with OAC, under versioned names with a one-year TTL. They never reach the Region again after the first request per edge.
API: the product list for 60 seconds
GET /api/products?cat=…is cached at the REST API stage for 60 s withcatandpageas cache key parameters. During the sale this one setting turns thousands of identical requests a minute into one Lambda run per category per minute. Require authorization for cache control, so only the admin app can force a refresh.Application: prices in Valkey
The Lambda functions read prices from ElastiCache for Valkey with lazy loading and a short TTL, and the seller’s price update deletes the key. The Valkey cluster has replicas in another AZ, so a node failure is a failover, not an empty cache and a database stampede.
Sessions: carts in DynamoDB
Carts and logins live in a DynamoDB table keyed by session ID with a TTL attribute. Any function or instance can serve any shopper.
Never cached: the stock count
The stock of the 40 sale phones is read from the database and each purchase is a conditional write. A few milliseconds slower, and never oversold.
Try the design in the tracer below: switch each layer on and off, change the TTL and the time since the seller’s last change, and watch where each request is answered, how long it takes and whether the answer can be wrong.
Request tracer: where is it answered?
A shopper in Dhaka, the shop in the Singapore Region. Choose a request and the cache layers, say how long ago the seller changed the data, then send the request.
Request
CloudFront caches images
API Gateway cache TTL
Valkey in the app
Seller changed the data … ago
Press “Send the request” to trace it.
#What goes wrong
- Stacked TTLs. Each layer’s TTL looked harmless alone; together a price change takes minutes to reach shoppers. Add up the TTLs of every layer that holds the same data, and invalidate from the bottom up (database, Valkey key, API stage, edge).
- A cache key without the user. A per-user response cached under a shared key shows one user’s cart or profile to another. Turn caching off for per-user methods or put the user in the key.
- A cache key without a parameter that changes the response: the laptops page shows phones.
- Open cache bypass. “Require authorization” off on an API stage: any client can send
max-age=0on every request and the backend gets the full load. - Default TTL surprises. CloudFront keeps objects without
Cache-Controlfor 24 hours; a price list changed at noon is old until tomorrow. - Cold cache after a node failure. A lazy-loaded cache with no replica comes back empty, every read misses, and the database is hit by a stampede. Use replicas and Multi-AZ, jitter on TTLs.
- Paying for an idle cache. An API stage cache for an API with few repeated requests costs more than it saves.
- DAX for the wrong reads. Strongly consistent reads, write-heavy tables and writes that bypass the cluster gain nothing from DAX.
- Sticky sessions as the session store. The cart disappears with the instance.
#Caching in system design
- Start from the data, not the service: for each kind of data, decide how stale it may be, and cache it as far up the path as that staleness allows.
- Static at the edge, shared at the API, computed in the app cache, exact in the database.
- Make every cache disposable: the system must stay correct (only slower) if any cache is empty. That rules out keeping the only copy of anything in a cache, and it is why the database stays the source of truth.
- Watch the hit ratio of each layer (
CacheHitRatein CloudFront,CacheHitCount/CacheMissCountin API Gateway,CacheHits/CacheMissesandEvictionsin ElastiCache). A cache with a low hit ratio costs money and adds a hop. - The same path appears in lesson 24.1 for events and lesson 24.3 for blocking bad clients: the edge is also the cheapest place to stop traffic you do not want.
| Exam wording | Layer and service |
|---|---|
| Global users, latency, offload origin, static and dynamic content | Edge: CloudFront |
| Reduce load on Lambda / EC2 behind a REST API, repeated identical requests | API: API Gateway caching |
| Client must force fresh data | Cache-Control: max-age=0 + execute-api:InvalidateCache |
| HA, Multi-AZ, failover, backup, pub/sub, leaderboards | ElastiCache for Redis (Valkey / Redis OSS) |
| Simple key-value, purely horizontal, multi-threaded | ElastiCache for Memcached |
| Prevent stale data | Write-through |
| Conserve memory, cache only requested data | Lazy loading with a TTL |
| DynamoDB, microseconds, no code rewrite | DAX |
| Stateless web tier, survive termination, avoid sticky sessions | Sessions in ElastiCache or DynamoDB |
#Try it yourself
# 1. Edge: see whether CloudFront answered from cache, then invalidate one path
curl -sI https://d111111abcdef8.cloudfront.net/img/p881-v3.webp | grep -iE 'x-cache|age'
# x-cache: Hit from cloudfront
# age: 5312
aws cloudfront create-invalidation --distribution-id E1ABCDEXAMPLE \
--paths "/api-docs/index.html"
# { "Invalidation": { "Id": "I2J0I21PCUYOIK", "Status": "InProgress", ... } }
# 2. API: provision a 0.5 GB stage cache and cache every GET for 60 s
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
--patch-operations op=replace,path=/cacheClusterEnabled,value=true \
op=replace,path=/cacheClusterSize,value=0.5 \
op=replace,path=/*/*/caching/enabled,value=true \
op=replace,path=/*/*/caching/ttlInSeconds,value=60
# { "stageName": "prod", "cacheClusterEnabled": true, "cacheClusterSize": "0.5",
# "cacheClusterStatus": "CREATE_IN_PROGRESS", ... }
# 3. API: only callers with execute-api:InvalidateCache may bypass; others get 403
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
--patch-operations op=replace,path=/*/*/caching/requireAuthorizationForCacheControl,value=true \
'op=replace,path=/*/*/caching/unauthorizedCacheControlHeaderStrategy,value=FAIL_WITH_403'
# 4. API: turn caching off for the per-user cart method (path /cart is written ~1cart)
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
--patch-operations op=replace,path=/~1cart/GET/caching/enabled,value=false
# 5. API: empty the whole stage cache after an urgent price correction
aws apigateway flush-stage-cache --rest-api-id a1b2c3d4e5 --stage-name prod
# 6. Application: a serverless Valkey cache for prices
aws elasticache create-serverless-cache --serverless-cache-name prices \
--engine valkey --subnet-ids subnet-0a1b2c3d subnet-0e5f6a7b \
--security-group-ids sg-0123456789abcdef0
# { "ServerlessCache": { "ServerlessCacheName": "prices", "Status": "creating", "Engine": "valkey", ... } }
# 7. Lazy loading by hand with valkey-cli: miss, set with a TTL, hit, delete on write
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls GET price:881
# (nil)
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls SET price:881 1499 EX 60
# OK
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls TTL price:881
# (integer) 57
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls DEL price:881
# (integer) 1
# 8. DynamoDB: a three-node DAX cluster in front of the catalogue table
aws dax create-cluster --cluster-name catalogue-dax --node-type dax.r5.large \
--replication-factor 3 --iam-role-arn arn:aws:iam::111122223333:role/DAXServiceRole \
--subnet-group-name dax-private --security-group-ids sg-0123456789abcdef0
# { "Cluster": { "ClusterName": "catalogue-dax", "TotalNodes": 3, "Status": "creating", ... } }
# 9. Sessions: let DynamoDB delete expired sessions on its own
aws dynamodb update-time-to-live --table-name sessions \
--time-to-live-specification Enabled=true,AttributeName=expiresAt
# { "TimeToLiveSpecification": { "Enabled": true, "AttributeName": "expiresAt" } }
# 10. Clean up the paid caches
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
--patch-operations op=replace,path=/cacheClusterEnabled,value=false
aws dax delete-cluster --cluster-name catalogue-dax
aws elasticache delete-serverless-cache --serverless-cache-name prices
Then watch the hit counts: in CloudWatch, CacheHitCount and CacheMissCount for the API stage, and CacheHits, CacheMisses and Evictions for the Valkey cache, while you repeat the same request and a different one.
#Summary
Key takeaways
- One path, four caches: CloudFront (edge) → API Gateway stage cache → ElastiCache or DAX (application/database) → database, with S3 behind CloudFront.
- The trade-off: the closer to the user, the lower the latency and cost per request, but the harder to invalidate and the staler. Staleness adds up across layers: worst case is the sum of the TTLs.
- CloudFront: default TTL 24 h, cache key and cache policy per behaviour, invalidation (1,000 paths free a month, then 0.005 USD per path) or better versioned file names; S3 origin private with OAC.
- API Gateway cache: REST APIs only, per stage, 0.5–237 GB, TTL default 300 s, max 3,600 s, GET only by default, cache key parameters, flush. Bypass with
Cache-Control: max-age=0needsexecute-api:InvalidateCacheonly when require authorization is on. Billed per hour, and requests are still billed: it saves money only if backend savings beat the fee. - ElastiCache: Valkey (recommended), Redis OSS, Memcached, plus Serverless. Redis for HA, Multi-AZ, backup, pub/sub, leaderboards; Memcached for simple, multi-threaded, purely horizontal key-value.
- Lazy loading: only requested data, but miss penalty and stale reads. Write-through: never stale, but write penalty, churn and empty new nodes. Combine both, TTL strongly recommended (not mandatory), delete on write.
- DAX: DynamoDB only, microseconds, API-compatible (swap client), eventually consistent reads only, default TTL 5 min, in your VPC.
- Sessions in ElastiCache or DynamoDB make the web tier stateless; sticky sessions lose state and spread load unevenly.
- Never cache stock counts, balances at payment time, OTPs, or per-user data on a shared key.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
An origin sends no Cache-Control or Expires header, and the CloudFront cache behaviour uses default TTL settings. How long does an edge keep the object?
Self-checkQuestion
Which kind of API Gateway API has a built-in response cache?
Self-checkQuestion
What are the default and the maximum TTL of an API Gateway stage cache?
Self-checkQuestion
What is the default TTL of the DAX item cache?
Self-checkQuestion
Which two capabilities does ElastiCache for Valkey or Redis OSS have that ElastiCache for Memcached does not?
Choose 2
Self-checkQuestion
A media company serves users in Asia, Europe and America from an ALB in one Region. Pages contain static images and dynamic parts. It wants lower latency worldwide and less load on the ALB with minimal changes. What should it do?
Self-checkQuestion
A game stores player profiles in DynamoDB. Reads must drop to microseconds and the team cannot rewrite the data-access logic. Which solution fits?
Self-checkQuestion
A shop caches product lists at the API Gateway stage with a 300 s TTL. The Lambda behind it reads prices from Valkey with lazy loading and a 300 s TTL, never deleting keys. A price is changed in the database. What is the longest a shopper might still see the old price?
Self-checkQuestion
An exam question says the cache must never return outdated data after updates, and reads must stay fast. Which strategy fits best?
Self-checkQuestion
A team wants to keep cache memory small and cache only the data users actually request, accepting a slower first read. Which strategy fits?
Self-checkQuestion
Which two are disadvantages of write-through caching?
Choose 2
Self-checkQuestion
The notes say a TTL is mandatory with lazy loading. What is accurate?
Self-checkQuestion
An API stage has a cache, but nobody turned on require authorization for cache control. A price-comparison bot sends Cache-Control: max-age=0 on every request. What happens?
Self-checkQuestion
Which two are valid values for how an API Gateway stage treats unauthorized cache-invalidation requests?
Choose 2
Self-checkQuestion
A product list API uses /products?cat=phones and /products?cat=laptops. After enabling the stage cache, shoppers who ask for laptops sometimes get phones. What is the fix?
Self-checkQuestion
After enabling a stage cache, a user reports seeing someone else’s cart from GET /cart. The user is identified by a token in a header. What is the best fix?
Self-checkQuestion
A web tier of EC2 instances behind an ALB keeps sessions in memory with sticky sessions. Users lose their carts when the Auto Scaling group scales in. Which two changes make the tier stateless?
Choose 2
Self-checkQuestion
During a flash sale an Auto Scaling group adds two instances, but the old instance stays at 90% CPU while the new ones are almost idle. Sessions use ALB stickiness. What is the cause?
Self-checkQuestion
An application uses DAX. Its checkout reads items with ConsistentRead=true and finds them slow. Why?
Self-checkQuestion
A dashboard shows the top 100 products by sales, computed from millions of DynamoDB items every minute. Which cache fits this result best?
Self-checkQuestion
A nightly import job writes new prices directly to a DynamoDB table with the normal DynamoDB client. The website reads through DAX and shows old prices for a few minutes each morning. Why?
Self-checkQuestion
In a flash sale, 40 phones are sold at a special price. Where should the remaining stock count be cached?
Self-checkQuestion
An RDS MySQL database is overloaded by varied reporting queries that can tolerate a few seconds of lag. The team wants to avoid writing cache code. What fits best?
Self-checkQuestion
Which two practices fit static images served from S3 through CloudFront?
Choose 2
Self-checkQuestion
A team invalidates 4,000 individual paths in one month across its distributions. Invalidation costs 0.005 USD per path after the first 1,000 free paths. What is the charge?
Self-checkQuestion
Which statement about ElastiCache for Memcached is NOT correct?
Self-checkQuestion
An urgent price error must disappear for every user at once. Which cached copy is the hardest to remove quickly?
Self-checkQuestion
Which two exam phrases point to ElastiCache for Redis (Valkey or Redis OSS) rather than Memcached?
Choose 2
Self-checkQuestion
A mobile backend is an HTTP API in API Gateway in front of Lambda. The catalogue endpoint gets the same request thousands of times a minute and the team wants a response cache with a 60-second TTL. What should it do?
Self-checkQuestion
A Valkey cache with a single node and lazy loading is replaced after a hardware fault. Database CPU jumps to 100% for several minutes. What would have prevented this most directly?
Self-checkQuestion
An architect wants the cheapest and fastest answer per request for a page that is identical for all anonymous visitors and may be 30 seconds old. Where should it be cached?