Best of Both Worlds: Introducing Readyset Shallow Cache
← Back to blogUncategorized

Best of Both Worlds: Introducing Readyset Shallow Cache

Readyset has always been defined by our incremental dataflow engine. It compiles your SQL into a dataflow graph where base table nodes ingest the replication stream (binlog/WAL) from tables being replicated to Readyset. Internal nodes apply joins, filters, and aggregates, while reader nodes hold the materialized result. Because writes flow through the graph and only affected nodes are updated via incremental view maintenance, the cache stays updated in real-time. Reads are simple lookups on the

Vinicius Grippa

Vinicius Grippa

2026-03-05 · 7 min read

Readyset has always been defined by our incremental dataflow engine. It compiles your SQL into a dataflow graph where base table nodes ingest the replication stream (binlog/WAL) from tables being replicated to Readyset. Internal nodes apply joins, filters, and aggregates, while reader nodes hold the materialized result.

Because writes flow through the graph and only affected nodes are updated via incremental view maintenance, the cache stays updated in real-time. Reads are simple lookups on these reader nodes, providing sub-millisecond latency for fresh data, a process we call deep caching.

Shallow Caches are the high-performance partner to our core Deep Cache engine, designed to reduce the ramp-up time while providing near 100% query coverage. While Deep Caches are built for mission-critical traffic that demands real-time data updates, Shallow Caches handle the rest by prioritizing speed and upstream database protection.

This "catch-all" approach works for any SELECT query, regardless of complexity—making it the ideal solution even for heavy analytical joins or dashboards where a smaller freshness window is a small price to pay for a 1000x speed boost.

The most common approach today remains using in-memory key-value data stores such as Redis to also serve as a SQL query cache. However, unlike Redis, which forces developers to manually write cache invalidation logic and handle "miss" scenarios in the application code, Readyset caching continues to manage the entire lifecycle. Developers do not need to perform any manual tasks to ensure cache coherence; the system handles this automatically, providing a seamless, plug-and-play experience that requires no application code changes.

While this is a TTL-based layer, adjusting TTL values for a cache is optional. Readyset already handles refreshing your results after a TTL expires, and future updates will enable the caching system to subscribe to your replication stream to trigger shallow cache refreshes automatically based on replication streams and dynamically adjust TTLs based on how data changes in your backend database.

Eliminating the "Thundering Herd"

A common issue with applications using general purpose key-value data stores like Redis as a SQL query cache is the latency spike that occurs when a TTL expires, as all clients must suddenly wait for the application to pull new data from the backend. Shallow caching in Readyset prevents this by refreshing caches atomically and in the background. Clients never experience the swap to new results, which is how a true read-through cache should behave.

The end result is a consistent, sub-millisecond access experience for cached data. By merging cache invalidation and refresh into a single component, Readyset removes the burden from the application and ensures a smooth performance profile that a Redis-style caching approach cannot match.

Deep vs. Shallow: A Technical Comparison

Real-World Impact: From Seconds to Milliseconds

Let’s take a look at some examples to see how this works in practice. Using the MySQL Employees Database (~300k employees, ~2.8M salaries), we ran a heavy aggregation (joins, GROUP BY, HAVING). Such queries can be cached with either Deep or Shallow Caches.

Note: This specific query was selected simply as an example of a heavy analytical workload.

Before caching, this query took 6.62 seconds to execute on the standard database. Once cached in Readyset, the execution time dropped to just 0.01 seconds.

-- Original Query Execution: 6.62 seconds
-- Readyset Cached Execution: 0.01 seconds
SELECT DISTINCT e.emp_no, e.first_name, e.last_name, MAX(s.salary) AS highest_salary
FROM employees e 
JOIN salaries s ON e.emp_no = s.emp_no
GROUP BY e.emp_no, e.first_name, e.last_name
HAVING highest_salary > 120000
ORDER BY highest_salary DESC 
LIMIT 10;

Such queries can be supported by either deep or shallow caching. Shallow caching refreshes these results after backend changes in roughly 5–8 seconds, though this interval is configurable. Deep caching avoids upstream queries entirely by using the replication stream to update the cache within milliseconds of receiving records from the backend database.

Sophisticated Cache Management

Shallow Caches aren't a "dumb" cache. They include built-in safeguards to keep your upstream database healthy:

  • TTL (Time-to-Live): Upper bound on how long a cached result is considered fresh (for example, 10 seconds). In Readyset, if you don’t specify a TTL in CREATE SHALLOW CACHE, it defaults to 10,000 ms. Once that window has elapsed since it entered the cache, the entry is marked as expired and will eventually be evicted from the cache.
  • REFRESH: When a request touches an entry older than the configured refresh age, Readyset asynchronously re-executes the query in the background in order to avoid getting expired by the TTL rule. The current request is still served from the existing cached value (a hit), and when the background query completes, Readyset atomically swaps in the new result so clients don’t see extra latency. If you don’t specify REFRESH in CREATE SHALLOW CACHE, Readyset uses an implicit default of half the TTL (so with the 10-second default TTL, the refresh age is 5 seconds).
  • COALESCE (thundering herd protection): On a cache miss, Readyset can coalesce concurrent identical requests into a single upstream query. If 50 clients hit the same uncached entry at once, only one query is issued; the other 49 wait for that result, and then all complete together. If you omit COALESCE in CREATE SHALLOW CACHE, the window defaults to 5,000 ms.
  • REFRESH EVERY SECONDS (automatic scheduled refresh): In addition to on-demand refresh, you can tell Readyset to re-run the query on a fixed schedule, even if no client has hit that cache entry recently. With REFRESH EVERY, Readyset proactively keeps entries fresh in the background, trading a predictable stream of upstream queries for consistently up-to-date cached results. For exceptionally slow queries where execution time exceeds the refresh interval, Readyset can even maintain multiple concurrent refreshes in-flight, ensuring fresh results are available as soon as each one completes.

Note: REFRESH SECONDS and REFRESH EVERY SECONDS are mutually exclusive. Use REFRESH for on-demand background refresh triggered by reads, or REFRESH EVERY for scheduled refresh on a fixed interval—but not both in the same CREATE SHALLOW CACHE statement.

Starting Readyset With Shallow Caches

Shallow caches are always available in Readyset—there’s no separate “shallow-only” binary or install. You run Readyset as usual; shallow caching is built in.To influence how caches are created when you don’t specify DEEP or SHALLOW, set --cache-mode (or env CACHE_MODE) at startup:

  • deep (default) — CREATE CACHE always tries a deep cache; unsupported queries error.
  • shallow — CREATE CACHE always creates a shallow (TTL-based) cache.
  • deep-then-shallow — Try a deep cache first; if the query isn’t supported by the dataflow engine, create a shallow cache automatically.

Use the standard config file (e.g. /etc/readyset/readyset.conf). It’s env-style: one KEY=value per line. Add or uncomment these for shallow cache:

## Shallow cache behavior
## deep | shallow | deep-then-shallow
CACHE_MODE=deep-then-shallow

CREATE SHALLOW CACHE With Custom TTL, Refresh, and Coalesce

We can also use per-cache settings to override the default TTL, REFRESH, and COALESCE policy in the DDL for specific scenarios. Consider this example:

CREATE SHALLOW CACHE
POLICY TTL 30 SECONDS REFRESH 2 SECONDS
COALESCE 5 SECONDS
FROM
SELECT DISTINCT e.emp_no, e.first_name, e.last_name, MAX(s.salary) AS highest_salary
FROM employees e
JOIN salaries s ON e.emp_no = s.emp_no
GROUP BY e.emp_no, e.first_name, e.last_name
HAVING highest_salary > 120000
ORDER BY highest_salary DESC
LIMIT 10;

Think of it like this:

TTL 30 SECONDS

Very similar to a Redis-style TTL: after 30 seconds of no one reading this entry, Readyset treats it as expired and will eventually evict the entry.

REFRESH 2 SECONDS

This is the part that Redis-style approach doesn’t give you out of the box. As soon as a cached result is 2 seconds old, the next read both:

  • returns the existing value immediately (no extra latency), and
  • triggers a background refresh so the cache is updated for future reads.
  • So you get Redis-style TTL, but without users ever “waiting on” the refresh.

COALESCE 5 SECONDS

This is built‑in thundering‑herd protection. If a bunch of clients all miss the cache (or hit it right after eviction) within 5 seconds of each other, Readyset only sends one query to the database and lets the others wait briefly for that result, instead of slamming the DB with 50 identical queries like a naive Redis-style pattern.

Best of Both Worlds

With the introduction of Shallow caches alongside our now default Deep caching approach to the Readyset solution, we are excited to bring options for our customers to offload even more queries to Readyset for up to 70% in cost savings over read replicas with zero code changes.

You can learn more about shallow cache and how to get started here. As always, if you have questions for us, reach out to us on slack or send a note to - hello@readyset.io.

Want to see Readyset in action?

Book a demo and see how Readyset can accelerate your database.

Still scaling the hard way?

Modern applications demand instant performance, even under unpredictable load. Readyset helps you eliminate slow queries, stabilize latency, and scale confidently.

Revolutionize your database performance with Readyset

Serve requests at sub-millisecond latencies with the modern database scaling and query caching system for MySQL and PostgreSQL.

Join our newsletter

Stay updated with the latest news, insights, and developments from Readyset — straight to your inbox.

© 2026 Readyset. All rights reserved.