Redis Sentinel failover without double writes

How SQLly follows a Redis Sentinel failover: reads re-resolve the master and retry once; write scripts stop with master changed, so nothing runs twice.

By Updated

The short version

After a Sentinel failover, SQLly retries your reads and never retries your writes.

A Sentinel connection in SQLly names the master and lists the sentinels, and SQLly asks them for the current master every time it connects. Scripts that only read can go to a replica. When the master changes mid-session, a read-only script re-resolves once and carries on. A script that writes stops with master changed, re-run, because running a write twice is worse than asking you to press Run again.

Connecting by master name

Sentinel runs beside a Redis or Valkey deployment, watches the master, and promotes a replica when the master fails. So a client shouldn't hold on to a master's address. It should ask the sentinels. In the connection editor, pick Redis or Valkey and turn on Connect through Sentinel, then fill in:

  • Master name: the name in the sentinels' sentinel monitor line, often mymaster.
  • Sentinels: a comma-separated list such as 10.0.0.1:26379, 10.0.0.2:26379, 10.0.0.3:26379. A host without a port uses 26379.
  • Sentinel password: only when the sentinels themselves have requirepass. It goes to the system keychain.

The main user and password sign in to the master and the replicas, and the database and TLS mode apply to them as on a direct connection. On every connect SQLly asks the sentinels in order with SENTINEL GET-MASTER-ADDR-BY-NAME, connects to the address it gets, and checks with ROLE that the server really is a master. A sentinel that is down, or that still names the old master halfway through a failover, is skipped and the next one is asked. You can run the same lookup yourself:

redis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
# 1) "10.0.0.5"
# 2) "6379"

Or paste a URL and the form fills itself:

redis+sentinel://app:[email protected]:26379,10.0.0.2:26379/mymaster/2?sentinelPassword=s3cret

The URL lists the sentinels, then the master name, then an optional database. Use rediss+sentinel:// for TLS, and add preferReplica=true (or readFrom=replica) to read from replicas.

Screenshot to comeredis-sentinel-editor-fields.webp — the Redis connection editor with Connect through Sentinel on, master name mymaster, the sentinel list filled in, and Prefer replica for reads ticked
A Sentinel connection names the master and the sentinels, never a fixed host.

What Test Connection reports

Press Test and the result names which sentinel answered and which master it named:

Sentinel 10.0.0.1:26379 named 10.0.0.5:6379 as the master of 'mymaster'

When a replica is serving reads, the line adds read-only scripts use replica …. The test then runs the same staged trace as a direct connection (TCP, TLS, AUTH, HELLO/PING, SELECT) against the master the sentinels named. A wrong password or a certificate problem therefore shows up as "AUTH failed" or "TLS failed" on the right server, not as a vague Sentinel error.

Screenshot to comeredis-sentinel-test-connection-master.webp — the connection editor after Test on the Sentinel fixture, showing "Sentinel 127.0.0.1:26379 named 127.0.0.1:6390 as the master of 'mymaster'" and the staged trace in the diagnostic log
Test Connection says which sentinel named which master, then traces that master step by step.

Reads on a replica, chosen by the command classifier

With Prefer replica for reads on, a script made only of read-only commands runs on a replica the sentinels report healthy. "Read-only" is decided by SQLly's shared Redis safety classifier, the one its safety checks use to tell reads from writes, so there is one list of which commands write, not a second one that can drift apart. Routing happens per script, not per line:

# runs on a replica: every line reads
GET user:42:name
HGETALL user:42:profile
ZRANGE leaderboard 0 9 WITHSCORES

# runs on the master: one line writes, so the whole script goes there
GET user:42:name
INCR user:42:visits

Anything that writes, any script that uses SELECT, and the commands that describe the server for the dashboards and tools all run on the master. When no replica is healthy, the master answers. One thing to keep in mind: replicas are asynchronous, so a read can briefly return a value older than the master's. Leave the option off when that matters.

Failover: the central decision

When the master fails over while a connection is open, the next command fails. Either the connection dropped, or the demoted server answers READONLY You can't write against a read only replica. SQLly treats a dropped connection, READONLY, MASTERDOWN and LOADING as signs that the master may have moved. It asks the sentinels again, once per script, and moves the connection to the new master. What happens next depends on whether the script writes.

A read-only script retries

Reads are idempotent. Running GET user:42:name twice costs one extra round trip, so a read-only script re-runs the command that failed on the new master and carries on. You see nothing unusual.

A write script stops

A script that writes is never re-run for you. It stops with a message like this one:

line 1: master changed, re-run — Sentinel 10.0.0.1:26379 now names 10.0.0.6:6379 as the master of 'mymaster' (was 10.0.0.5:6379). The script writes, so it was not retried (it may have stopped part-way; check before re-running). Cause: …

The connection already points at the new master, so pressing Run again works. The question is whether you should press it, and only you can answer that. Here is why.

When a connection drops in the middle of a command, the client doesn't know whether the server executed it. The command may have run and the reply been lost, or the command may never have arrived. From the client's side the two look the same. Take this script:

INCR orders:count
LPUSH queue:emails job:981

Say the master applies the INCR, starts the LPUSH, and dies before the reply arrives. If the replica had already received those writes when it was promoted, both are on the new master. A client that "helpfully" retried would make orders:count one too high and push job:981 onto the queue twice, so the customer gets two emails. If the replica hadn't received them, a retry is exactly right. The client can't tell which case it is in. You can check, with GET orders:count and LRANGE queue:emails 0 9, before deciding.

For writes, at most once beats convenience. A stopped script costs you one look and one click. A duplicated write costs you a data repair, and you may not notice it for days.

That is also why the retry happens once per script and only on the first failover. A script that hits a second failure is reported as it is. And it's why the message says the script "may have stopped part-way": in a multi-line script, earlier lines already ran on the old master. The same rule applies elsewhere in SQLly's Redis support. A command timeout fails without re-running, and a re-established SSH tunnel never replays the command that was in flight.

Screenshot to comeredis-sentinel-failover-write-refused.webp — a query tab on the Sentinel fixture after SENTINEL FAILOVER mymaster, where INCR demo:counter stopped with "master changed, re-run" naming the new master in the Messages pane
After a failover, a write script stops and names the new master instead of running the write again.

The live test for this does the failover for real. It writes a value, forces SENTINEL FAILOVER on a one-master, one-replica, three-sentinel fixture, and checks that a read follows the new master on its own. It then checks that a write on the demoted connection fails with "master changed, re-run", that the refused write did not land on the new master, and that running it again succeeds.

Why Sentinel refuses tunnels, proxies and relays

A Sentinel connection dials the sentinels, the master and the replicas directly. If you set an SSH tunnel or a proxy, you get this message instead of a connection to the wrong place:

Sentinel connections dial the sentinels and the master directly; SSH tunnels, proxies and Kubernetes port-forwards are not supported with Sentinel yet

The reason is the same one that makes clusters hard behind a bastion. The sentinels hand out master and replica addresses as they see them, and every sentinel, master and replica would need its own forward, which would change again after each failover. A single tunnel to "the Redis server" can only ever reach one of those, and after a failover it would be the wrong one. We would rather refuse clearly than connect to a demoted master through a stale forward. Relay routes say Redis Sentinel connections are not available through a relay yet; connect directly, because the relay's connection payload doesn't carry the Sentinel settings.

Screenshot to comeredis-sentinel-tunnel-refused.webp — the connection editor for a Sentinel connection with an SSH tunnel turned on, after Test, showing the refusal that SSH tunnels, proxies and Kubernetes port-forwards are not supported with Sentinel yet
Sentinel with a tunnel is refused up front rather than connecting through a stale forward.

Entra sign-in follows the failover

With Microsoft Entra sign-in, a connection must sign in with a token that is still valid, and Azure drops it when the token expires. SQLly re-authenticates open connections before that happens, and on Sentinel this covers the master, the replica serving reads, and the new master after a failover. The new master signs in with the newest token SQLly has, not the one the session started with, so a failover an hour into a session doesn't fail on an expired login. The sentinels themselves use the separate sentinel password, if they have one.

Limits

  • No SSH tunnels, proxies, Kubernetes port-forwards or relay routes on Sentinel connections.
  • The Profiler and Pub/Sub viewer attach to the current master. On Entra they end when the token they started with expires, so start them again.
  • The published Redis matrix report was recorded before Sentinel support landed. The Sentinel paths are covered by their own opt-in live tests against the local fixture, not by a cloud deployment.

Setup details are on the Sentinel docs page, and the release overview has everything else in this Redis release. Download SQLly and point it at your sentinels.