Redis vs. DynamoDB: When to Use Each, and How to Cache DynamoDB in Java

Published on
August 14, 2026

Redis and DynamoDB are not alternatives to each other. DynamoDB is a managed NoSQL database that stores your data durably; Redis — or Valkey, the engine AWS is actively developing on ElastiCache — is an in-memory data store. It most often sits in front of a database like DynamoDB, and it also holds state that has no database row at all: locks, rate-limit windows, sessions, leaderboards. Teams rarely replace one with the other, and the search query hides the decision they are actually making: do I need a cache in front of DynamoDB, and if so, DynamoDB Accelerator (DAX) or ElastiCache?

On speed AWS's two caches converge, and on freshness they converge for the writes your own application makes and for cached query results — they differ only at the edges. Mostly the choice turns on what the cache is allowed to hold, who operates it, what it costs, where it can be reached from and which accreditations it carries. AWS has written its ElastiCache design down in a Prescriptive Guidance document, with a reference implementation in Python. This article works through the decision, then writes the Java version.

What Redis and DynamoDB Are For

DynamoDB stores items in tables, addressed by a partition key and an optional sort key, and bills you per read and per write rather than per hour. It is serverless in the strict sense: no instance to size, no version to upgrade, no failover to operate. Reads are eventually consistent by default and can be made strongly consistent per request. Items cap at 400 KB, and access patterns you did not design an index for are expensive to serve.

Redis is a key-value store whose values are data structures — strings, hashes, lists, sets, sorted sets, streams, JSON documents — with atomic commands over them. It keeps its working set in RAM and persists asynchronously. It has no query planner and no notion of a table; what it has instead is a library of operations that execute where the data already is.

Both are NoSQL, and the difference that matters here is not the data model. It is the billing unit and who owns the memory.

Amazon DynamoDB Redis / Valkey on ElastiCache

Billing unit

Per request — read and write units, or provisioned capacity

Per node-hour, or per serverless ECPU and stored GB

Latency, in AWS's words

"Single-digit millisecond performance at any scale"

"Microsecond latency performance"

Access

Key lookup, query on a sort key, secondary indexes, vector search

Key lookup plus data-structure operations, optional secondary indexing

Expiration

TTL attribute, deleted "within a few days" of expiry

Per-key TTL, invisible on access from the moment it passes

Coordination

Conditional writes, transactions across items

Atomic commands, Lua scripts, pub/sub, locks built on all three

Item size

400 KB

512 MB per value

Does DynamoDB Need a Cache at All?

Often, no — and that is worth saying before anything else, because a cache is a second failure domain, a second thing to size, and a new class of bug where users see data that no longer exists. AWS's own claim for DynamoDB is "consistent single-digit millisecond performance," and for a service whose latency budget is measured in tens of milliseconds, an extra hop in front of it buys nothing but staleness. If your reads are modest, your keys are evenly distributed, and nothing in your application recomputes the same expensive result twice, the honest advice is to skip the cache.

Three things change that answer.

Cost, at read-heavy scale. DynamoDB bills per read, forever. A configuration blob read four thousand times a second by a fleet of application servers is four thousand billable reads a second for the life of the service, and it will return the same bytes every time. That is the clearest case there is for a cache, and it is an arithmetic argument rather than a latency one.

Hot keys. AWS is explicit that "a partition starts throttling reads of an item when it reaches 3,000 read capacity units," and that "DAX lets you scale reads of a single item beyond 3,000 RCU." Any cache holding that item does the same thing. A single popular row can throttle while the table as a whole sits far below its provisioned capacity.

Expiry you can rely on. DynamoDB's TTL is a background reclamation process, not an expiry: the documentation states it "automatically deletes expired items within a few days of their expiration time," and expired-but-undeleted items still appear in Query and Scan results until they are removed, so AWS recommends filtering them out yourself. Fine for log retention; not fine for a session, a one-time token, a rate-limit window or a lock lease. Redis key expiration is precise to the millisecond and invisible on read the instant it passes.

Where DAX Fits, and Where It Doesn't

DAX is AWS's own answer, and for a large class of applications it is the right one. It is a genuine drop-in: you replace the DynamoDB client with the DAX client and your code does not otherwise change. It turns eventually consistent reads from, in AWS's words, "single-digit milliseconds to microseconds." It absorbs hot keys past the 3,000-RCU partition limit, which any cache does — but DAX does it without a line of new code. And its Java client is current — software.amazon.dax:amazon-dax-client 2.0.11, July 2026, on AWS SDK for Java 2.x, sync and async.

It also has three documented edges, and one documented limit that is no longer true.

No write invalidates the query cache. DAX keeps two caches: an item cache populated by GetItem and BatchGetItem, and a query cache populated by Query and Scan. The item cache is kept fresh on write. The second one is not, and the documentation is unambiguous: "Updates to the item cache, or to the underlying DynamoDB table, do not invalidate or modify the results stored in the query cache," and "DAX does not invalidate Query or Scan result sets based on updates to individual items." A write through DAX itself will not clear a cached query containing that item; only TTL expiry or least-recently-used (LRU) eviction will, and the TTL defaults to five minutes. If your application reads a list, writes to it, and reads it back, DAX can hand you the old list.

Strong consistency bypasses it entirely. A request with ConsistentRead=true is passed straight through and not cached, and so is TransactGetItems. The reads you most want to be fast are often the ones you most want to be correct, and DAX serves those at DynamoDB latency.

It is provisioned infrastructure bolted onto a serverless database. DAX is VPC-only — "access to DAX cluster nodes is restricted to applications running on Amazon EC2 instances within an Amazon VPC environment" — with a maximum of eleven nodes per cluster, and it is billed "per node-hour consumed," where "each partial node-hour consumed is billed as a full hour." There is no serverless option. You chose DynamoDB partly so you would never size an instance again, and DAX hands the instance back. Node-based ElastiCache hands you the same instance, mind — only ElastiCache Serverless escapes this.

The fourth is the one to be careful with, because AWS contradicts itself. The DAX suitability page still says DAX "doesn't currently offer the same compliance accreditations … as DynamoDB. For example, DAX hasn't obtained the SOC accreditation yet." That is out of date: AWS's own Services in Scope by Compliance Program listing, updated in August 2026, shows Amazon DynamoDB Accelerator in scope for SOC 1, 2 and 3. Two AWS pages, two answers — so if a compliance programme gates the decision, read it off the services-in-scope list rather than the DAX chapter, and check the specific programme you need rather than assuming either way.

AWS also sends one category of use elsewhere itself. Under the heading of complex querying beyond key-value access, its DAX suitability page recommends: "use Amazon ElastiCache (Redis OSS) as an alternative. ElastiCache (Redis OSS) supports advanced data structures, such as, lists, sets, and hashes. It also offers features, such as pub/sub, geospatial indexes, and scripting."

What Belongs in Redis and Not in DynamoDB

The short version: put anything that must survive a restart in DynamoDB, and put coordination state and derived values — things that are expensive to derive and safe to lose — in Redis. Both AWS caches treat the cache as a faster copy of table rows, and so does the Java design later in this article. The larger reason teams run Redis alongside DynamoDB is the set of things that are not rows at all — the boundary AWS itself draws when it points data-structure workloads at ElastiCache.

A distributed lock is the clearest example. You can build one on DynamoDB with a conditional write, and people do — but the lease, the renewal and the expiry are yours to get right, and the condition is evaluated at DynamoDB latency. Redisson's RLock ships the lease semantics: when no explicit lease time is given it holds a thirty-second lease by default and a watchdog renews it every ten seconds for as long as the holder is alive. The same goes for a rate limiter, a leaderboard over a sorted set, shared sessions, and any aggregate expensive enough to compute once and read many times. None of these has a DynamoDB row to be a cache of. Atomic counters are the instructive edge case: they do work through DAX, since UpdateItem with ADD is a supported write-through operation — but the increment executes in DynamoDB, at write latency rather than microseconds. The value lands in the item cache afterwards; the atomic operation never runs there.

What AWS's Own ElastiCache Guide Recommends

In November 2024, AWS published a Prescriptive Guidance document titled Integrating Amazon DynamoDB and Amazon ElastiCache by using read-through caching. It is the most directly useful artifact on this subject. It does not adjudicate DAX versus ElastiCache — it says so up front: "DynamoDB also offers DynamoDB Accelerator (DAX) as a DynamoDB-specific memory cache option. This guide is for readers who prefer to use ElastiCache instead." What it does is describe, in detail, the design you should build once you have made that choice.

The recommended architecture is "a client-side shim that adds read-through caching to DynamoDB calls without requiring significant code modification in the application." So far, unsurprising. What is surprising is what it says about writes: "A write-through cache would push entries into the cache during the write operation, but this guide does not suggest doing that, for two reasons" — first, that "when an item is written, there's no indication that it's going to be read anytime soon, and it's wasteful to write cache entries that aren't used," and second, that one item may already be cached several times over under different signature keys — cache keys derived from the shape of the request, so that two different projection expressions produce two separate entries — which means that on a write there is no way to tell which signature key a new entry should be stored under before any request has asked for it.

Instead, AWS recommends invalidation: "write operations can be intelligent, and they can proactively invalidate any item cache entries stored earlier that are relevant to the written item. This keeps the item cache fresh without having to wait for TTL expiry." And it explains why this is easier against DynamoDB than against a relational database: "every write to DynamoDB always specifies the primary keys of the items that are being written. A read-through cache can watch the write calls and perform exact, immediate item cache invalidation."

The second reason is specific to a client-side shim. The first is a general objection to write-through, and it does apply to DAX — AWS documents a write-around strategy for exactly this case, and lists write-heavy workloads among the situations where DAX fits poorly. But that is an argument about which writes are worth caching, not about whether DAX keeps its item cache fresh, which it does. On writes your own application makes, the two converge: DAX updates its entry, a shim invalidates its entry, and both are correct straight afterwards — give or take DAX's replication to its other nodes, which AWS describes as eventually consistent and "usually" under a second. On cached Query and Scan results they converge again, because AWS gives up on that problem in the same guide: "query cache entries have no better option than to expire through TTL settings."

Freshness does still differ at the edges, in both directions — a shim can be driven by DynamoDB Streams to catch writes it never saw, and a shim has its own staleness race that only a TTL bounds. Both are covered below. But the main reasons to build one are everything around the cache: what it may hold, who operates it, what it costs, where it can be reached from, and which accreditations it carries. Those are the axes the rest of this article works through. Note the scope difference while you are here: AWS's shim wraps the DynamoDB client API and keys entries by request signature, so it caches Query and Scan results and lets them time out; the Java design below is keyed by primary key and does not cache result sets at all.

The gap the guide leaves is the interesting part. Its reference implementation "is provided in Python." The closest thing to a Java equivalent is an abandoned GitHub project built on AWS SDK for Java 1.x, which reached end of support on 31 December 2025 — so there is no maintained Java reference for the pattern AWS recommends.

Caching DynamoDB With Redis in Java

Redisson ships the read path in its open-source edition. An RMap given a MapLoader is a read-through map out of the box: "if requested entry doesn't exist in the Redisson Map object when it will be loaded using provided MapLoader object." A MapWriter propagates changes back the other way. The write path — the exact invalidation AWS recommends — is two lines of your own code. Redisson ships no DynamoDB integration and its documented example uses JDBC, so the binding below is ordinary application code. In the pattern AWS recommends, DynamoDB is reached from the loader on the read path and from a two-line invalidation on the write path; each variant that follows — a MapWriter, a read-path fallback, a Streams consumer — adds another place your code touches it.

Read-Through With a MapLoader

Start from a bean-mapped table. The Enhanced Client wants a class annotated with @DynamoDbBean, a public no-argument constructor, a getter and setter for every attribute, and a getter annotated with @DynamoDbPartitionKey. A property with no setter is silently dropped from the schema rather than rejected. Boxed attribute types are worth preferring over primitives — Long rather than long — so that a missing attribute maps to null instead of a default value, though the SDK supports both. A TableSchema is "designed to be static and immutable" and DynamoDbTable instances are described in the AWS docs as "candidates for singletons," which suits a loader that will outlive every request.

// org.redisson.api.options.MapOptions — not the deprecated org.redisson.api.MapOptions
// and its defaults() factory.

DynamoDbTable<Product> products =
        enhanced.table("Product", TableSchema.fromBean(Product.class));

MapLoader<String, Product> loader = new MapLoader<>() {
    @Override
    public Product load(String id) {
        // consistentRead matters if you invalidate on write — see below.
        return products.getItem(r -> r.key(Key.builder().partitionValue(id).build())
                                      .consistentRead(true));
    }

    @Override
    public Iterable<String> loadAllKeys() {
        return products.scan().items().stream()
                       .map(Product::getId)
                       .toList();
    }
};

MapOptions<String, Product> options =
        MapOptions.<String, Product>name("product-cache").loader(loader);

RMap<String, Product> cache = redisson.getMap(options);

Product p = cache.get("a123");   // DynamoDB is read only on a miss

Three notes. loadAllKeys() is used only for bulk preloading, and on DynamoDB it is a full table Scan — implement it, but think before calling loadAll on anything large. On a composite key, the Redis key has to encode both parts: partition and sort value joined by a separator that cannot appear in either. And getItem is an eventually consistent read by default, which is why the loader above uses the request-builder form, for the reason in the next section. The wider pattern is covered in Java caching strategies for Valkey and Redis.

Two operational costs come with this, and both are yours rather than AWS's. A read-through map couples read availability to cache availability: a miss is detected by a round trip to Redis, so an unreachable cluster fails the read path even though DynamoDB is healthy. Decide the fallback deliberately — catching the timeout and calling products.getItem restores availability, but an unconditional fallback sends the whole fleet at DynamoDB the moment the cache blinks, which is a thundering herd. Redisson absorbs part of this: RMap serialises concurrent loads of the same key behind an internal lock, so one expiring hot key does not fan out a getItem per instance. What stays exposed is the fallback path and cold-start fan-out across many keys, where expiring entries slightly early and at random helps. The second cost is serialization: every value crosses a codec each way, so choose it explicitly rather than inheriting the default — our serialization codecs post covers the trade-offs.

One more thing to handle inside load() itself. Redisson wraps the loader so that any exception it throws — a throttle, a timeout, expired credentials — is swallowed, and get() returns null, indistinguishable from an item that does not exist. On a table whose whole reason for having a cache is throttling at 3,000 RCU, that is the failure you will actually hit, and it will reach your users as a 404 rather than a 500. Catch inside the loader and decide there whether to retry, serve a fallback or record the failure; the synchronous path does not even log it.

Invalidating on Write, the Way AWS Describes

// AWS's recommendation: write to DynamoDB, then invalidate the exact key.
// Requires a loader and NO writer — see the note below.
products.putItem(updated);
cache.fastRemove(updated.getId());

// The alternative: let Redisson own the write. This is a different configuration
// of the SAME cache — never run both configurations against one map name.
MapWriter<String, Product> writer = new MapWriter<>() {
    @Override
    public void write(Map<String, Product> entries) {
        entries.values().forEach(products::putItem);
    }

    @Override
    public void delete(Collection<String> ids) {
        ids.forEach(id -> products.deleteItem(
                Key.builder().partitionValue(id).build()));
    }
};

MapOptions<String, Product> writeThrough =
        MapOptions.<String, Product>name("product-cache")
                  .loader(loader)
                  .writer(writer)
                  .writeMode(WriteMode.WRITE_THROUGH);

The second form is write-through caching in the sense AWS argues against: the value lands in Redis whether or not anyone is about to read it. It is still right when the write path knows the value will be read immediately, and WriteMode.WRITE_BEHIND — updates "accumulated in batches and asynchronously written with defined delay to external storage" — is a real answer where DynamoDB write units are the cost driver. Set writeBehindDelay and writeBehindBatchSize explicitly, and be deliberate about what an un-flushed batch means for durability.

One trap worth naming, because it will delete production data. With a MapWriter attached, removing an entry propagates to the backing store through MapWriter.delete(). The invalidation pattern above therefore needs a loader and no writer — otherwise fastRemove does not merely invalidate the cache entry, it also deletes the DynamoDB item. Pick one pattern per map.

Exact invalidation narrows the staleness window; it does not close it. The write and the invalidation are separate operations, so any reader whose load overlaps the write can put a pre-write value into the cache after fastRemove has already run — it need not have missed in between, and Redisson's per-key load lock does not help, because fastRemove does not take that lock. Two flavours: the reader legitimately read the old value before the write landed, or the loader read a lagging replica. consistentRead(true) removes only the second. The first is inherent to cache-aside and read-through alike, which is why this pattern needs a TTL as a backstop rather than as an optional extra. Without one, a stale entry produced that way never expires — unbounded staleness, where DAX's five-minute TTL is at least a bound. Give the entries a lifetime you can defend — and read the next section before you assume you have one.

Invalidation across JVMs is the other half. RLocalCachedMap keeps a near cache inside each process and broadcasts invalidations over pub/sub; its default SyncStrategy.INVALIDATE sends a sixteen-byte hash of the changed key to every other instance rather than the value, which is what you want when ten application servers each hold a copy. Our cache invalidation and distributed caching in Java pages cover the failure modes.

For writes that bypass your application entirely — a Lambda, a batch job, another team's service — no client-side shim can see them, and this is the case DAX handles worst. Its documentation concedes it directly: "if someone else updates the item using a DynamoDB client, bypassing DAX entirely… DAX and DynamoDB hold inconsistent values for the same key until the TTL for the DAX item expires." DynamoDB Streams closes it: consume the stream and fastRemove the corresponding key for each modified record. AWS describes stream records as written "in near-real time," which beats a five-minute timer — but it is an asynchronous path with its own lag and its own failure modes, and if the consumer stalls the staleness is unbounded again — so keep the TTL backstop, and budget for a component to operate rather than a helper method.

TTL That Actually Expires

If the cached entries need their own lifetime rather than inheriting the item's, RMapCacheNative gives every entry a TTL enforced by the server with no eviction task in your JVM — it requires Valkey 9.0 or later, or Redis 7.4 or later. The older RMapCache works on any version and supports max-idle as well as TTL, at a cost Redisson states plainly: it "leads to extra Valkey or Redis calls and eviction task per unique map object name." Either way the guarantee is the one DynamoDB's TTL does not offer — the entry is gone when you said it would be, not within a few days.

One sharp edge, and it is the one that undoes the backstop above if you miss it. Entries written by a MapLoader carry no TTL: MapLoader.load has no way to supply one, and Redisson stores the loaded value with a plain put. Swapping getMap for getMapCacheNative does not by itself give read-through entries a lifetime — and on native eviction a plain write also clears a field TTL that was already there. Set the lifetime explicitly on the paths that matter: call expireEntry(key, ttl) once the load returns, or bypass the loader for those keys and write them yourself with an explicit expiry. Whichever you choose, test that the entries actually disappear before relying on it.

Connecting to ElastiCache From Java

Redisson's configuration docs name ElastiCache, ElastiCache Serverless, ElastiCache Cluster and ElastiCache Global Datastore explicitly in the compatibility matrix, and TLS is a matter of using the rediss:// scheme. IAM authentication is the one piece that needs code: the generic credentialsResolver hook is "invoked during connection for Valkey or Redis server authentication," and Redisson ships an implementation for Microsoft Entra ID but none for AWS, so the ElastiCache token provider is yours to write. The ElastiCache IAM credential provider walkthrough has the implementation; the ElastiCache Java client page covers cluster and serverless configuration.

What Caching DynamoDB Costs

The two services do not bill in comparable units, which is why the cost question is so often answered badly.

DynamoDB on-demand charges $0.625 per million write request units and $0.125 per million read request units in us-east-1, where a write unit covers 1 KB and a read unit covers 4 KB — and an eventually consistent read costs half a unit. That price has held since November 2024, when AWS cut on-demand throughput by 50% and global tables by up to 67%; there has been no reduction since. Provisioned capacity is cheaper for predictable load, at $0.00013 per read capacity unit-hour, and since August 2025 you can move a table from provisioned to on-demand four times in a rolling 24 hours rather than once; the reverse switch is unlimited today.

ElastiCache charges per node-hour, or on serverless per ElastiCache Processing Unit (ECPU) consumed and gigabyte stored. The engine choice is now a pricing decision: Valkey is priced 20% below Redis OSS on node-based clusters and 33% below on serverless, and its serverless minimum metered storage is 100 MB against 1 GB for Redis OSS and Memcached — an order of magnitude difference in the floor cost of a small cache. Database Savings Plans cover DynamoDB and ElastiCache for Valkey alike — up to 18% off DynamoDB on-demand throughput and 12% off provisioned. DAX has no serverless option at all and bills partial node-hours as whole ones. If durability rather than caching is the requirement, Amazon MemoryDB is the third AWS option.

Those published list prices close the loop on the configuration blob from earlier. At $0.125 per million read request units, with an eventually consistent read of an item under 4 KB costing half a unit, a million such reads costs about six cents. The blob read four thousand times a second is roughly 10.4 billion reads a month, or about $650 a month, forever, to return the same bytes — on-demand, us-east-1 list price, one item. Against that, break-even with any cache is simply the cache's monthly cost divided by six cents per million reads. The same item consumes about 2,000 read capacity units at that rate if the reads are eventually consistent, and around 4,000 if they are strongly consistent. Only the second crosses the 3,000-RCU partition throttle: one flag decides whether this is a cost problem or a throttling problem.

A cache removes a share of a per-request charge — reads only, and only the ones that hit — and adds a second bill, hourly on node-based ElastiCache and per-request on serverless. Whether that nets out depends on your reads-per-item ratio, which is why the figure above is an illustration from list prices rather than a benchmark. Treat any performance number you find on this comparison with the same caution: they are almost universally unsourced, and several of the most-quoted are AWS service descriptions passed off as measurements.

What Changed in 2026

Most of what is written about Redis versus DynamoDB describes a world that no longer exists, and 2026 alone has moved both sides.

DynamoDB added native vector search, generally available on 5 August 2026, with "single-digit millisecond latency at 99%+ recall" over embeddings stored alongside ordinary attributes. Valkey 9.0 arrived on ElastiCache in May 2026 with full-text and hybrid search, hash field expiration and polygon geospatial queries, and 9.1 followed in June. The two are converging from opposite directions — the database grew vector search, the cache grew search and structured querying — and any comparison written before mid-2026 puts the boundary in the wrong place.

The larger shift is durability. In June 2026 AWS added multi-AZ persistence to ElastiCache for Valkey 9.0: a synchronous mode that persists across at least two availability zones before acknowledging the client, at single-digit millisecond write latency, or an asynchronous mode that keeps microsecond writes at no extra cost but leaves "up to 10 seconds of uncommitted data" at risk in a failure. The clean old framing — DynamoDB is the system of record, Redis is the disposable cache — is now a default rather than a constraint. On ElastiCache, Valkey is also the engine that is moving: Redis OSS is frozen at 7.1 there while Valkey has reached 9.1, and our Valkey vs. Redis comparison covers the licensing split.

DAX, ElastiCache or Neither? A Decision Rule

Use DynamoDB alone if all of these hold:

  • Your latency budget comfortably absorbs single-digit milliseconds.
  • Per-request read billing is not a line item you notice.
  • No single item is hot enough to throttle.
  • Nothing needs coordination state or a derived value that has no DynamoDB row.

A cache here buys staleness, a second failure domain and an extra thing to operate.

Otherwise use DAX — provided all of these hold:

  • Everything that calls the cache runs in a VPC you can reach: the same one, or a peered one.
  • Every value the cache would hold is a DynamoDB item.
  • Every write goes through your own application.
  • Your hot reads can be served eventually consistent.
  • Your hot reads are lookups by primary key, not Query or Scan with filtering.
  • Node-hour pricing is acceptable.
  • Every compliance programme you need lists DAX in AWS's services-in-scope list — check the list, not the DAX documentation.

Then any one of these settles it:

  • You want the latency win with no new code to own.
  • A single item is hitting the 3,000-RCU partition throttle on eventually consistent reads.
  • Read cost is the driver: the table reads you stop paying for exceed what the cluster costs.
  • You would rather AWS operated the cache, its failover and its patching.

This is a common situation, and DAX is the least-effort correct answer for it.

Otherwise use ElastiCache with a read-through shim. Any one of these puts you here:

  • You need locks, rate limiting, sessions, leaderboards or queues.
  • You cache computed or aggregated values that have no DynamoDB row.
  • The cache must also serve data that never came from DynamoDB.
  • A compliance programme you need covers ElastiCache but not DAX — check AWS's services-in-scope list for that programme, not the DAX documentation.
  • You want serverless or Savings-Plan pricing rather than node-hours.
  • Writes you do not control mean you need Streams-driven invalidation.
  • Your hot reads are Query or Scan with filtering rather than key lookups — the case AWS's own DAX suitability page sends to ElastiCache. The win there comes from remodelling the access pattern onto Redis data structures or a search index, not from the primary-key shim above.

Reach is a smaller difference than it sounds — a peered VPC works for both — but only ElastiCache is documented as reachable from on-premises over VPN or Direct Connect. In exchange you own the shim, its fallback policy and its tests.

One case belongs to neither cache branch, and it is worth saying plainly, because both of those end in a cache and nobody selling one — AWS or us — has an interest in dwelling on where it does not help. If a hot read genuinely requires strong consistency, no cache preserves that: DAX passes such reads through uncached, and a shim serves whatever it loaded last, however recently it loaded it. Making the loader strongly consistent makes the load strong, not the serve. For those reads, read around the cache. The 3,000-RCU ceiling on a single item cannot be raised, so the remedies are structural: shard the item across several keys — updated together in a TransactWriteItems, or the copies diverge and you lose the guarantee you sharded to protect — or, if the item is over 4 KB, shrink it so each read costs fewer read units.

None of these branches is a Redis-versus-DynamoDB decision. DynamoDB stays the system of record; the question was only ever what sits in front of it, and AWS wrote down its own answer — in Python. If you are weighing the cache layer rather than the database, Redis vs. Memcached compares the alternatives and Redis vs. MongoDB works the same problem for a document store.

Redis vs. DynamoDB: Frequently Asked Questions

Can ElastiCache Be Used With DynamoDB?

Yes, and AWS publishes a Prescriptive Guidance document describing exactly how. Its recommended design is a client-side read-through shim that intercepts DynamoDB calls, serves them from ElastiCache on a hit, and — on the write path — proactively invalidates the affected keys rather than writing through. The reference implementation is in Python; the Java equivalent is an RMap with a MapLoader over the DynamoDB Enhanced Client.

Is DynamoDB as Fast as Redis?

By AWS's own descriptions, no. DynamoDB is documented as delivering "single-digit millisecond performance at any scale," while ElastiCache is described as "microsecond latency performance" and DAX as reducing eventually consistent reads "by an order of magnitude from single-digit milliseconds to microseconds." That is AWS's own framing of the gap, and it reflects a network call to a durable distributed store versus a lookup in RAM. These are service-level descriptions rather than benchmarks, and what a Java application measures end to end also includes network round trip and serialization. Note too that AWS makes the same microsecond claim for both of its caching options, so speed is not what distinguishes DAX from ElastiCache.

Can I Use DynamoDB as a Cache?

You can, and for small payloads with no maintenance budget it is a defensible choice — a DynamoDB table with a TTL attribute is zero operational work. The limits show up quickly: items cap at 400 KB, the TTL deletes "within a few days" rather than on time, expired items remain visible to queries until removed, and you are still paying per read. It is a cache with a database's latency and a database's bill.

DAX or ElastiCache — Which Should I Use?

DAX if you want microsecond item reads with no code change, inside a VPC, on a DynamoDB-only access pattern. ElastiCache if you need values that are not DynamoDB items, data structures such as locks, rate limiters or leaderboards, serverless pricing, reach from on-premises, or a compliance programme that covers ElastiCache but not DAX. Freshness is rarely the differentiator: both keep item entries current on write and both let cached query results expire on a timer. It differs at two edges: a shim can catch writes that bypass your application by consuming DynamoDB Streams, and a shim has its own invalidation race that needs a TTL backstop DAX gets by default.

Can Redis Replace DynamoDB?

Usually it should not. DynamoDB gives durability, backups, point-in-time recovery, global tables and a compliance posture that a cache tier is not designed to provide. The answer is less absolute than it was — ElastiCache for Valkey added multi-AZ durability in June 2026 — but replacing a managed durable database with a cache is a decision to make deliberately, not by drift.

How Do I Cache DynamoDB in Spring Boot?

Enable Spring's cache abstraction with @EnableCaching and register a Redis-backed cache manager; Spring has no DynamoDB cache store, so the cache tier is Redis or Valkey either way. RedissonSpringCacheManager sets TTL and max idle time per cache name. For caching whole entities rather than method results, an RMap with a MapLoader reading through DynamoDbEnhancedClient removes the cache-aside code from your service layer entirely.

Does DAX Cache Query and Scan Results?

Yes, in a query cache that is separate from its item cache. AWS documents that DAX "does not invalidate Query or Scan result sets based on updates to individual items" — a cached result set is cleared only by TTL expiry, which defaults to five minutes, or by LRU eviction. Writing an item does not clear a list containing it. AWS reaches the same conclusion for its own ElastiCache design, so this is a property of cached query results generally rather than a DAX defect.