FA Dev Tools

English中文

DynamoDB vs S3 for Caching — How to Choose (2026)

aws/dynamodb-vs-s3-caching-2026 · Updated · Tags: aws, dynamodb, s3, caching, database


DynamoDB vs S3 for Caching — How to Choose (2026)

Colour key: DynamoDB S3 Hybrid

TL;DR

What they are: DynamoDB is a key-value / document database — small items, millisecond lookups, per-item expiry. S3 is an object store — files of any size, very cheap per GB, but slower per request and with no real query.

For a cache of computed results (API responses, AI output, rendered fragments) keyed by a hash: 🏆 DynamoDB wins the default case — exact-key lookup, native per-item TTL, conditional writes and atomic counters are exactly what a cache needs.

Switch to S3 when the values are big (tens of KB and up, or anything near 400 KB), when you store a lot of them for a long time, or when a browser/CDN should download the cached file directly.

DynamoDB — pick it whenS3 — pick it when
AdvantageSingle-digit ms reads · per-item TTL · conditional + atomic writes · query by key range / GSI~11× cheaper storage per GB · no size limit worth worrying about · CDN / presigned-URL delivery · browsable as files
Don't use it if you need…❌ values over 400 KB❌ to keep many GB for months (storage is ~11× S3)❌ to serve the cached blob straight to a browser❌ per-item expiry at minute/hour precision❌ to query the cache (only prefix listing)❌ counters, or many tiny high-frequency writes

Hybrid: keep the key, metadata and TTL in DynamoDB and put the body in S3, storing the S3 key in the item. You get DynamoDB's lookup and expiry with S3's price per GB.


Summary table — the factors that decide it

Bold = winner of that row. Prices are us-east-1 list prices (on-demand DynamoDB, S3 Standard) — other regions such as Sydney are somewhat higher. Check the pricing pages before committing.

#FactorDynamoDBS3Winner
1Read latencysingle-digit mstens of ms (≈100 ms+ first byte on small objects)DynamoDB
2Max value size400 KB per itemTB-scale per objectS3
3Expiryper-item TTL (epoch-seconds attribute)lifecycle rule — per prefix/tag, whole daysDynamoDB
4Storage cost$0.25 / GB-month$0.023 / GB-monthS3 (~11×)
5Write cost$0.625 / M write units (1 unit per 1 KB)$5.00 / M PUTs (any size)DynamoDB under ~8 KB · S3 above
6Read cost$0.125 / M read units (4 KB each; eventually consistent = half)$0.40 / M GETs (any size)DynamoDB under ~24 KB · S3 above
7QueryingPK + sort-key ranges, GSIs, filterslist by prefix onlyDynamoDB
8Conditional writes✅ any condition expression✅ If-None-Match / If-Match only (since 2024)DynamoDB
9Atomic counters (hit counts)✅ UpdateItem ADD❌ noneDynamoDB
10Direct delivery to clients❌ must go through your API✅ CloudFront / presigned URLsS3
11Browse / inspect by handconsole item explorer✅ files — download, open, diffS3
12Consistencyeventual by default, strong on requeststrong read-after-write alwaysS3
13Throughputon-demand scales automatically; each partition caps at 3,000 read units / 1,000 write units per second3,500 PUT / 5,500 GET per second per prefix, scales with prefixestie
=Total5 + rows 5–6 for small values5 + rows 5–6 for large valuesDynamoDB for small cached results

The raw count is a draw — value size breaks it. For a cache, rows 1 and 3 (latency and per-item expiry) matter most, which is why DynamoDB is the default until values get large.

Row 5 crossover: an 8 KB write is 8 write units = $5 per million — the same as one S3 PUT. Row 6 crossover: a 24 KB eventually-consistent read is 6 × 0.5 = 3 read units = $0.375 per million, just under one S3 GET. Transactional reads/writes cost double.


Worked cost example

1 million cached results, each read once, stored for a month (us-east-1, eventually consistent reads):

Value sizeDynamoDB writes + reads + storageS3 PUTs + GETs + storageCheaper
1 KB$0.63 + $0.06 + $0.25 = ≈ $0.94$5.00 + $0.40 + $0.02 = ≈ $5.42DynamoDB (5.8×)
8 KB$5.00 + $0.13 + $2.00 = ≈ $7.13$5.00 + $0.40 + $0.18 = ≈ $5.58S3 (1.3×)
30 KB$18.75 + $0.50 + $7.50 = ≈ $26.75$5.00 + $0.40 + $0.69 = ≈ $6.09S3 (4.4×)

A few thousand cache entries a month cost cents on either. Choose on latency, expiry and size limit, and only let price decide once you're in the millions of items or many GB.


How to choose

Step 1 — the hard blockers. Any one decides it outright:

#If…Then
1Any value can exceed 400 KBS3 — or Hybrid if you also need the rows below
2Entries must expire per item (hours, not days)DynamoDB — S3 lifecycle works in whole days per prefix
3A browser or CDN should fetch the cached file directlyS3 + CloudFront / presigned URLs
4You need hit counters or "write only if absent" with conditions beyond existenceDynamoDB

Step 2 — otherwise, score your workload:

Answer yes →Points
Typical value is under ~8 KB+2 DynamoDB
Typical value is over ~30 KB+2 S3
Cache read latency is on the user's critical path+2 DynamoDB
You need to list / query entries (by user, date, type)+2 DynamoDB
You'll keep many GB for months+2 S3
Entries are keyed by a hash and looked up exactly+1 DynamoDB
People want to open cached output as files+1 S3
What you're caching takes seconds to produce (AI, heavy reports)tie — either is ~1000× faster than a miss

Per-item TTL is the feature caches need most, and it's the one S3 can't match.


Pros

DimensionDynamoDBS3
Lookup✅ GetItem by key in single-digit msSimple GetObject by key
Expiry✅ Per-item TTL — set an epoch-seconds attribute; deletes are free (no write units)✅ Lifecycle expiry is also free, set once per prefix
SizePlenty for text, JSON and most API responses✅ No practical limit — images, PDFs, big JSON, archives
Price✅ Cheapest for small items — a 1 KB write is 8× cheaper than an S3 PUT✅ ~11× cheaper storage, flat price per request whatever the size
Writes✅ Rich condition expressions, atomic counters, transactions✅ If-None-Match for "create only if missing"
Queries✅ Sort-key ranges and GSIs — "latest 20 results for user X" in one call—
Delivery—✅ Serve straight to clients through CloudFront or presigned URLs — no API in the path
Ops✅ Serverless, on-demand billing, point-in-time recovery✅ Serverless, versioning, replication, storage classes
VisibilityConsole item explorer✅ Entries are files you can download, open and diff

Cons

DimensionDynamoDBS3
Size❌ 400 KB hard cap per item, attribute names included—
Price❌ Billed per KB — big values get expensive fast (30 KB write = 30 units) · ❌ storage ~11× S3❌ $5 per million PUTs even for a 100-byte object — expensive for many tiny writes
Expiry❌ TTL deletion is lazy — typically within a few days, and expired items still come back in reads until then, so filter on the TTL yourself❌ Whole days only, per prefix or tag — no "expire this one entry in 2 hours"
Latency—❌ Tens to ~100+ ms per request — slow for a hot cache in front of a fast API
Queries❌ Access patterns must be designed up front; a Scan reads the whole table❌ No query — only lexicographic prefix listing
Counters—❌ No atomic increment; read-modify-write races
Delivery❌ Every read goes through your code—
Hot keys❌ One very hot partition key is capped (3,000 read units / 1,000 write units per second)❌ Per-prefix request caps; spread keys across prefixes at high rates
Lock-in❌ AWS-only APIS3 API is a de-facto standard (R2, MinIO, GCS interop)

Similarities — stop agonising over these

  • Serverless — no servers, no capacity planning on on-demand DynamoDB or S3.
  • Durability — both replicate across multiple AZs in a region (except S3 One Zone / Express classes).
  • IAM — the same IAM policies, KMS encryption at rest, VPC endpoints.
  • Cold misses dominate — if the thing you cache takes seconds to compute, a 5 ms vs 80 ms hit is noise to the user; both are a huge win over a miss.
  • SDK — both are one call in every AWS SDK (GetItem / PutItem vs GetObject / PutObject).

The core split — one sentence

Small, hot, expiring entries → DynamoDB. Big, cold, long-lived blobs → S3.


Cache design tips (either store)

  1. Hash the inputs into the key. Build the key from everything that changes the output — parameters, model or engine version, and a format/prompt version you bump to invalidate old entries.
  2. Don't store more than you need. A hash key means the raw input (which may be private) never has to be stored.
  3. Make the cache best-effort. If the store is unreachable, treat it as a miss — never fail the request because the cache failed.
  4. Give users a way round it. A "refresh" / bypass flag for when a cached answer is stale or wrong.
  5. Different TTLs for different inputs. Output from immutable input can live for months; output derived from a live web page should expire in hours or days.
  6. DynamoDB: check TTL on read. Because deletion is lazy, compare the item's ttl to now and treat an expired item as a miss.
  7. S3: use a dedicated prefix (for example cache/) so one lifecycle rule can expire the whole cache.

Other options

  • ElastiCache / Valkey (Redis-compatible) — sub-ms, in-memory, per-key TTL. Best for very hot, high-QPS caches; you pay for nodes (or serverless capacity) whether it's busy or not.
  • DAX — an in-memory cache in front of DynamoDB, microsecond reads, no code change beyond the client.
  • S3 Express One Zone — single-digit-ms S3 in one AZ, higher storage price; worth a look if you want S3 semantics at DynamoDB-like latency.
  • CDN / HTTP cache — if the result is a public GET response, Cache-Control headers may be all the cache you need.

DynamoDBS3
PricingDynamoDB pricingS3 pricing
ExpiryTime to Live (TTL)Lifecycle expiration
LimitsService quotasPerformance guidelines
Conditional writesCondition expressionsConditional requests
Large itemsStoring large items (the hybrid pattern)—

Caveats

  1. Prices move. Figures are us-east-1 list prices (DynamoDB on-demand after the Nov 2024 cut, S3 Standard). Your region, free tier and data-transfer charges change the totals — recheck the pricing pages.
  2. Latency figures are typical, not guaranteed. Measure from your own compute region; cross-region calls add far more than the store itself.
  3. TTL is not a timer. Neither DynamoDB TTL nor S3 lifecycle deletes at the exact moment; both are background sweeps. Build expiry checks into the read if staleness matters.
  4. The scorecard assumes a cache — exact-key lookup of generated results. For other workloads (data lake, media library, transactional records) the answer can flip.

Disclaimer: These documents are personal notes. The information may be incomplete, out of date or wrong, so check it before you rely on it.