Skip to content
What Is a Wire-Compatible SQL Caching Proxy?
← Back to blogDatabase Caching

What Is a Wire-Compatible SQL Caching Proxy?

A wire-compatible SQL caching proxy sits between your app and database, speaking the database's own protocol so you cache repeated queries by swapping a hostname, no cache keys, no invalidation logic, no application code changes. Here's how it works, how it differs from Redis, and how Readyset implements it for MySQL and Postgres.

Readyset Team

Readyset Team

2026-10-08 · 12 min read

A wire-compatible SQL caching proxy sits between your application and your database, speaking the same protocol the database speaks. Because of this, your app can connect to the proxy instead of the database without any code changes, just swap the hostname or port.

The idea is simple. Right now, every query goes straight to the database:

Diagram: application connecting directly to the database

With a caching proxy in front, the first time a query runs, it goes to the database as usual and the result gets cached:

The next time that same query runs, the proxy can skip the database entirely and answer from cache.

Diagram: application querying through a caching proxy, served from cache on repeat

This cuts down on database load: fewer round trips, less query execution, and lower CPU and I/O usage, especially for read-heavy workloads where the same queries run over and over.

The tricky part is that caching SQL isn't as simple as storing a query string and its result. The proxy has to figure out when two queries are really asking for the same thing, know when cached data has gone stale and needs to be refreshed, and handle things like transactions, prepared statements, and session state correctly, all while staying invisible to the application.

In short: the goal is to make the database faster without the application ever knowing a cache is there.

How is this different from Redis or application-level caching?

The short version: Redis and application-level caching put the caching decisions in your code. A wire-compatible SQL caching proxy puts them in a separate layer, so your application doesn't have to think about caching at all.

How Redis-style caching works. Your application talks to two systems and has to coordinate between them:

Diagram: application talking to both Redis and the database

Your code has to spell out the caching logic itself:

user = redis.get("user:123")
if user is None:
    user = database.query("SELECT * FROM users WHERE id = 123")
    redis.set("user:123", user)

That means your application now owns a lot of extra work: deciding on cache keys, converting objects to and from a storable format, setting expiration times, invalidating stale entries, handling cache misses, avoiding the "stampede" problem where many requests miss the cache at once and all hit the database together, and deciding upfront which queries are even worth caching. Redis is a general-purpose store, it doesn't know anything about SQL or your database schema. You decide what goes in it, which gives you a lot of control, but all of that logic lives in your application.

How a wire-compatible SQL proxy works.

Diagram: application connecting to the database through a SQL caching proxy in line

Your application just sends its normal SQL, exactly as before:

SELECT id, name, email FROM users WHERE id = 123;

Diagram: the same SELECT query passing untouched through the proxy at every hop, application to proxy to database

The proxy recognizes the query, checks whether it already has a valid and fresh cached answer, and returns it if so, otherwise it goes to the database like normal and caches the result on the way back. Your application never sees a difference and never has to check whether a result came from cache or not.

The core difference, in one line: Redis caches whatever your application explicitly tells it to; a SQL caching proxy caches query results automatically, with no caching API involved, the database connection itself just gets faster.

They're not competitors. A SQL caching proxy doesn't replace Redis, they solve different problems and often work side by side. Redis is still the right fit for sessions, queues, rate limiting, and general application data. A SQL caching proxy takes over one specific job: making repeated database queries faster, without your code managing any of it.

Diagram: Redis and a SQL caching proxy serving different jobs side by side

How is this different from a plain database caching proxy?

They're closely related, a wire-compatible SQL caching proxy is really just a stricter, more transparent version of a database caching proxy. The difference comes down to one thing: how convincingly it impersonates the real database.

Regular caching proxyWire-compatible SQL caching proxy
ProtocolIts own separate APIThe database's native wire protocol
Driver / client libraryOften a different oneSame driver, unchanged
Connection stringPoints at the caching APISame connection string as before
Code changes requiredSpecial calls (cache.get(...)), rewritten queriesNone
ArchitectureApplication → Caching API → DatabaseApplication → Proxy → Database
App's awareness of the cacheKnows it's there, cache logic lives in the codebaseNone, has no idea it's not talking to the real database
What actually happens behind the scenesReads the query, checks for a cached result, returns it if valid and fresh, otherwise queries the database and caches the resultSame steps, but invisibly, the app just sees a normal query and a normal result

Diagram: side-by-side comparison of a regular caching proxy and a wire-compatible SQL caching proxy

A closer look with MySQL

MySQL is one of the databases this kind of proxy typically supports, alongside PostgreSQL. The idea works exactly the same way, the proxy just needs to speak MySQL's specific wire protocol.

When a MySQL client connects, the server speaks first: it sends a handshake packet with a protocol version, server version string, and an authentication challenge (a random "salt"). The client responds with its username, a hashed password computed using that salt, its character set, and various capability flags. A wire-compatible proxy has to generate and validate this exact exchange, including MySQL's specific authentication plugins (mysql_native_password, caching_sha2_password, and others), so that any standard driver, mysql2 for Node, PyMySQL or mysqlclient for Python, Connector/J for Java, completes its handshake without ever suspecting it isn't talking to real MySQL.

Diagram: MySQL handshake and query exchange between application and a wire-compatible proxy

MySQL clients also don't only send raw query text, the protocol defines command types like COM_QUERY for plain queries and COM_STMT_PREPARE/COM_STMT_EXECUTE for prepared statements, which most ORMs and drivers use by default. A proxy that only understood COM_QUERY would break the moment an application used prepared statements, so it needs to track prepared statement handles per connection and cache based on the query template plus its bound parameters, not just the raw bytes sent over the wire.

Results come back as a sequence of packets too, column definitions, then row data, then a closing packet, and the proxy has to reconstruct all of this exactly as MySQL would, matching data types and encodings so the driver doesn't choke on it. On top of that, MySQL connections carry session state (the current database, session variables, isolation level, autocommit mode) and support transactions, so the proxy has to track that state and be careful not to serve cached data where it would break consistency, such as inside an open transaction.

Because all of this is MySQL-specific plumbing, a proxy has to implement it separately from whatever it does for PostgreSQL, which has its own different wire protocol and message framing entirely. Products like ProxySQL, Vitess, and Readyset are examples of tools that do this kind of work for MySQL (some, including Readyset, support both MySQL and PostgreSQL by implementing both protocols side by side). Being "wire-compatible with MySQL" is a specific, real engineering claim: the proxy correctly implements MySQL's handshake, its authentication plugins, its command protocol, its result encoding, and enough of its session and transaction semantics that a MySQL driver, and the application built on top of it, genuinely cannot tell it isn't talking to MySQL itself.

What changes in your application code?

This is where a wire-compatible proxy becomes particularly interesting. With Redis-style caching, adding a cache usually means rewriting the code that touches your data. Your simple query:

result = db.execute("SELECT * FROM products WHERE category = 'laptops'")

turns into something with two paths to think about:

result = redis.get("products:laptops")

if result is None:
    result = db.execute("SELECT * FROM products WHERE category = 'laptops'")
    redis.set("products:laptops", result)

Now every developer touching this code has to understand both the cache-hit path and the cache-miss path.

With a wire-compatible proxy, your code doesn't change at all:

result = db.execute("SELECT * FROM products WHERE category = 'laptops'")

The proxy silently takes on the hit-or-miss decision, unnoticed by anything above it, answering directly on a cache hit and asking the database then caching the result on a miss.

Usually, the only real change is where your application points its connection. For a Postgres app, instead of:

postgres://app:password@database:5432/shop

it connects to:

postgres://app:password@proxy:5432/shop

The same is true for MySQL. Instead of:

mysql://app:password@database:3306/shop

the application connects to:

mysql://app:password@proxy:3306/shop

That's it. If your app uses mysql2 in Node, PyMySQL in Python, or Connector/J in Java, none of that code changes, you're not swapping libraries or adding a cache client, just pointing the same driver at a different host and port. Your ORM keeps generating the same queries it always did, completely unaware that a proxy is now answering some of them itself.

Same driver, same SQL, same ORM, same data access code; nothing in the application layer needs to change.

That said, "no code changes" doesn't mean "no setup at all." Rolling this out in production still usually involves some work: pointing things at the right connection endpoint, adjusting infrastructure, deciding which queries should be cached and for how long, thinking through how the cache stays consistent with the database, and setting up monitoring around it.

So the more accurate claim is: wire compatibility doesn't eliminate configuration, it eliminates the need to rewrite application code, because the proxy looks exactly like the database to everything talking to it. That's what "drop-in" should actually mean.

How Readyset works as a wire-compatible proxy

Readyset is a real-world example of everything above. It sits directly in the path between your application and your database, rather than off to the side the way Redis does.

The application keeps sending normal SQL and getting normal results back, while Readyset separately watches the database's replication stream for data changes.

Your app keeps sending normal SQL queries and getting normal query results back, it has no idea Readyset is even there. Meanwhile, Readyset watches the database's replication stream (its binlog, in MySQL's case) to learn about every write as it happens, so it always knows when its cached answers need updating.

Reads and writes travel different paths. Writes always go straight to the real database, they have to land in the source of truth. Reads are where Readyset earns its keep: if the answer's already cached, it hands it back instantly, no round trip, no query execution, nothing asked of the database at all.

Two common ways to wire it up: the application talking to Readyset directly, or a query router deciding where each query goes. A normal read replica can still exist alongside either setup.

This shows two common ways to set it up. On the left, your application talks to Readyset directly for anything that might be cached, and talks to the database directly for writes or queries Readyset hasn't cached. On the right, a query router sits in front of both and decides where each query should go, so the application doesn't even need to know Readyset exists as a separate destination. Either way, a normal read replica can still exist alongside Readyset, they're solving different problems: a read replica spreads out database load, while Readyset skips hitting the database at all for the queries it already knows the answer to.

This is a step up from a typical read-replica setup. Many teams already scale reads with something like this:

A common existing pattern: HAProxy routes writes to a MySQL primary and reads to a read-only replica, kept in sync through logical replication.

In this setup, HAProxy operates at Layer 4, it just forwards TCP connections to whichever backend a pre-configured port or pool points to, without ever reading what's inside them. It can split traffic toward a writable primary and a read-only replica, but only because the application (or its connection config) already decided which port to use; HAProxy itself has no idea whether a given connection is carrying a SELECT or an INSERT. And either way, every read that reaches the replica still has to run as a full query against a real database. Readyset replaces that pattern with something smarter: because it actually reads and understands the SQL passing through it, it can answer many reads without touching a database at all, not just route them to one.

What actually happens inside Readyset. This is the part that makes it more than a simple cache:

Readyset builds a small internal pipeline for each cached query: base table nodes feed SQL operator nodes that mirror the query's joins, filters, and aggregations, landing in a reader node that holds the ready-to-serve result.

Readyset builds a small internal pipeline for each query it's asked to cache. Changes to the underlying tables (the "base table nodes") flow in from the database, pass through a chain of "SQL operator nodes" that mirror the actual joins, filters, and aggregations your query performs, and land in a "reader node" that holds the ready-to-serve result. When a row in the database changes, only the affected part of this pipeline needs to redo its work, not the whole query from scratch. This pattern is known as incremental view maintenance, and it's why Readyset is better described as a live, self-updating computation rather than a store you fill and empty by hand.

Conclusion: caching, moved to the database wire

The idea behind a wire-compatible SQL caching proxy comes down to one simple move: instead of every application building its own caching logic, you put a proxy between the app and the database that speaks the same protocol, understands the SQL flowing through it, and transparently answers what it can from cache.

That one change gets you a few things at once. Your existing queries, drivers, and ORM code mostly stay untouched. Your app never has to check a cache or invalidate one; it just sends SQL like normal. Because the proxy actually understands queries and the data behind them, it can keep answers correct as things change, not just guess with a timer. And your database itself sees less repeated work, since the proxy is absorbing traffic it would otherwise have to redo.

This is genuinely different from something like Redis. Redis gives you full control over what you cache, but you have to build and maintain that logic yourself. A wire-compatible proxy takes on that job itself, sitting closer to the database, so caching becomes something that just happens rather than something your team maintains.

A plain TCP load balancer like HAProxy can't do this split on its own; it just forwards connections without ever reading what's inside them, so it has no way to tell a cached-eligible read apart from a write. The split only works because the proxy sitting in this position actually speaks SQL and reads every query:

Diagram: a wire-compatible SQL caching proxy answering cached reads directly and passing writes and uncached reads through to MySQL

The bigger point is simple: don't make every team solve database caching on their own. Put that intelligence in the connection layer once, in a proxy that actually understands SQL, not just one that forwards bytes, and let it quietly make things faster for every application behind it. As more systems lean on read-heavy, increasingly complex queries, that's a real advantage, not just quicker responses, but a way to speed things up without asking developers to rethink how their code talks to the database in the first place.

Caching doesn't have to live in your application anymore. It can live in the connection itself.

See deep caching and the command reference for how a cached result stays correct as your data changes. Run rdst analyze to find out which of your own queries are candidates before you write a line of code.

Want to see Readyset in action?

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