
Introducing Automatic Failover and Multi Server Support
Starting in Readyset 1.33, you can hand Readyset a list of servers. It fails over automatically, resumes CDC on another server from the last GTID with no rows lost or applied twice, and spreads proxied queries across your replicas. MySQL with GTIDs; PostgreSQL covered with limits.

Marcelo Altmann
2026-10-29 · 6 min read
Most production MySQL deployments run a primary and a few replicas. If the primary fails, you promote a replica and point your application at it. That's failover: how a database stays up when a server goes down.
Starting in Readyset 1.33, you can give Readyset a list of servers. It handles failovers automatically, resumes replication on another server without losing or repeating changes, and spreads proxied queries across your replicas.
Readyset sits between your application and the database, and it talks to the database in two ways. It reads the replication stream (change data capture, or CDC) to keep its caches current. It also forwards queries it doesn't cache, along with cache misses, to the database.
Until now, each of those connections pointed at exactly one server. That left two gaps. If the server Readyset replicated from went down, the CDC stream stopped, and caches fell behind until someone manually repointed Readyset. And if you had several replicas with spare capacity, Readyset still sent all its proxied traffic to one of them.
This release supports MySQL with GTIDs enabled. PostgreSQL is covered with limitations.
Here's how it works:

How do you configure Readyset with multiple servers?
--upstream-db-url and --cdc-db-url now take a list. Separate the URLs with commas (or repeat the option), and add an optional |<weight> from 1 to 100 to each one. The default weight is 50.
--cdc-db-url 'mysql://user:pass@replica-a:3306/db|60,mysql://user:pass@replica-b:3306/db|50,mysql://user:pass@replica-c:3306/db'
A single URL still works exactly as before, so existing deployments don't need to change anything.
The two flags keep separate lists. --cdc-db-url is where replication and snapshotting read from. --upstream-db-url is where Readyset sends queries it doesn't serve from cache. If you only set --upstream-db-url, CDC uses the same list.
Every server in a list must have gtid_mode=ON, use the same database name, and have identical lower_case_table_names, time zone, and server collation. Readyset connects to each server at startup and checks this. A wrong password or a server without GTIDs stops startup with an error that names the server. If a server is unreachable, it gets logged and marked down, and startup continues as long as one server passes. In a large pool, one replica that is rebooting shouldn't block a restart.
To check the list of servers, you can execute the following query:
SELECT * FROM readyset.upstream_servers;
You get one row per server with its weight, its purpose (cdc, proxy, or both), whether replication is on it right now, whether it's up or down, and how many connections this Readyset adapter holds to it. SHOW READYSET STATUS also has a new CDC Server row.
readyset> SELECT * FROM readyset.upstream_servers;
+-----------+------+----------+------+--------+------------+------------+--------+------------+-------------------------------+-------------+
| host | port | database | user | weight | purpose | active_cdc | health | down_since | last_ok | connections |
+-----------+------+----------+------+--------+------------+------------+--------+------------+-------------------------------+-------------+
| 127.0.0.1 | 3401 | NULL | root | 50 | cdc, proxy | 1 | up | NULL | 2026-10-14T12:42:55.062+00:00 | 2 |
| 127.0.0.1 | 3402 | NULL | root | 50 | cdc, proxy | 0 | up | NULL | 2026-10-14T12:42:55.373+00:00 | 3 |
| 127.0.0.1 | 3403 | NULL | root | 50 | cdc, proxy | 0 | up | NULL | 2026-10-14T12:42:55.062+00:00 | 1 |
| 127.0.0.1 | 3404 | NULL | root | 50 | cdc, proxy | 0 | up | NULL | 2026-10-14T12:42:55.062+00:00 | 1 |
+-----------+------+----------+------+--------+------------+------------+--------+------------+-------------------------------+-------------+
4 rows in set (0,004 sec)
You can change either list while Readyset is running, without stopping replication:
ALTER READYSET CHANGE CDC TO 'mysql://user:pass@replica-a:3306/db|60,mysql://user:pass@replica-d:3306/db';
ALTER READYSET CHANGE UPSTREAM TO 'mysql://user:pass@replica-a:3306/db,mysql://user:pass@replica-d:3306/db';
Readyset connects to each new server and runs the same checks as at startup before it accepts the list. If any check fails, it rejects the statement, and the old list stays. If replication's current server is still in the new list, replication carries on without a pause. If you removed it, Readyset moves replication to a server from the new list.
The list is stored in Readyset's Authority, so it survives restarts. This has one side effect worth knowing: after the first start, Readyset reads the list from the Authority and ignores the command-line flag. Use ALTER READYSET from then on.
What happens when the server Readyset replicates from fails?
Replication is single server. Readyset will connect to the server with the highest weight as the preferred one. It will stay streaming CDC events from the server until a failure happens. When that server stops answering, Readyset retries it up to --upstream-connect-retries times (3 by default). Each attempt is bounded by --upstream-connect-timeout (5 seconds by default). Once the retries are exhausted, Readyset moves to the next server by weight and resumes from the last GTID it applied.
GTIDs are what make this safe. A GTID names the same transaction on every server in the topology, so Readyset can ask the new server, "have you seen everything I've already applied?" and resume from exactly that point. No rows are lost or applied twice. This is also why a multi-server list requires GTIDs: a binlog file name and position only mean something on the server that wrote them. For more information about GTID, see Introducing MySQL GTID Support and Zero-Downtime Failover in Readyset.
Readyset doesn't fail back. If replication moves from replica-a to replica-b, it stays on replica-b even after replica-a recovers. To move back, either change the list or STOP and START replication.
Snapshotting always uses the same server as replication. The snapshot records a position on the server it read from, and keeping both on one server means that position is always valid for the stream that follows. With a multi-server list, Readyset records that position as a GTID set from the start. If the server fails in the middle of a snapshot, Readyset starts the snapshot again on the next server. That's the same path Readyset already took for any snapshot error.
How does Readyset spread queries across replicas?
Queries Readyset sends upstream follow a round-robin algorithm, because here there are many connections to spread out rather than one stream.
Each client session picks a server at random from the servers in --upstream-db-url that are up, and keeps it until the client disconnects. Weight doesn't affect this choice. A shallow cache miss goes to that session's server, and so does any query Readyset doesn't cache. With 100 sessions over four servers, you'll see roughly 25 on each, give or take a few. That's normal for random picks, and readyset.upstream_servers will show it.
Shallow caches have their own background workers that refresh entries before they expire. Those workers also pick a server at random when they connect and again when they reconnect, so refresh traffic spreads across the pool too.
When a session's server dies, Readyset closes the session and terminates the client. If it reconnects, it will get a new server connection. The dead server is marked down the first time a connection to it fails. After that, new sessions skip it, so only one connection pays the connect timeout. A background health check pings every server every 10 seconds (--upstream-health-interval-secs) and marks servers down or up again. That catches a dead server even when no session happens to be using it, which is common in a large read pool.
Two background tasks don't spread out: the row-level security catalog poller and the cache permission checker always use the highest-weight server.
Does this work with PostgreSQL?
This release covers MySQL with GTIDs. PostgreSQL still takes a single server: a replication slot and its LSN belong to one server, and no position carries over to another. For PostgreSQL, and for moving to a server that isn't in your list, the manual steps in the failover docs still apply.
Try it yourself, download Readyset here.


