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.
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 monitorline, oftenmymaster. - 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.
redis-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 tickedWhat 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.
redis-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 logReads 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.
redis-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
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.
redis-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 yetEntra 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.