Modelled on src/replication.c, with src/rdb.c and src/config.c, Redis 7.2.14. Every line number on this page is from that release.
syncCommand handles both SYNC and PSYNC
(replication.c:939), but a 7.2 replica only ever sends PSYNC — even
on its very first connection, where it sends PSYNC ? -1. What it sent in the
handshake just before that decides everything downstream: capa eof is what
lets the master fork straight into the replica's socket, and capa psync2 is
what makes +CONTINUE carry a replication ID. Pick a scenario and run it.
repl-diskless-sync defaults to 1 (config.c:3089) and every 7.2 replica
advertises capa eof (replication.c:2739), so socket_target at
replication.c:868 is true out of the box. The disk-backed path is the variant, not the norm.
The replica side is a separate setting with the opposite default.
Writes buffered for this replica since the fork
replication.c:704–706, 719 — WAIT_BGSAVE_END is what starts the accumulation
The replica sends reploff + 1, the first
byte it has not received — not the last one it has (replication.c:2469).
RDB received 0%
Pick a scenario above, then press Run.
Since 7.0 it is not a circular buffer. replBacklog holds a
reference node, a rax index, long long histlen and long long offset
— no buffer member at all (server.h:1112–1122), and
createReplicationBacklog allocates none (replication.c:121).
The bytes live in server.repl_buffer_blocks, the one buffer shared
with every replica's output buffer. Nothing is overwritten in place:
incrementalTrimReplicationBacklog drops the head block only while
histlen > repl-backlog-size and that block's
refcount == 1, meaning the backlog is its last reference
(replication.c:261–285). A lagging replica still referencing the head block therefore
pins the backlog above its configured size — the source calls this out as deliberate,
so that partial resync stays possible as long as it can.
repl-backlog-size defaults to 1mb (config.c:3229).
repl-backlog-ttl defaults to 1 hour (config.c:3251): a master with no replicas
for that long has replicationCron free the backlog and, in the same branch,
call changeReplicationId() and clearReplicationId2()
(replication.c:3865–3893). The first replica to attach afterwards rotates the ID a
second time, before the new backlog is created (replication.c:1050–1060).
| RDB child running | What the arriving replica gets |
|---|---|
disk target, and its capabilities and slave_req match a replica already in WAIT_BGSAVE_END |
copyReplicaOutputBuffer clones that replica's accumulated buffer and it rides the same BGSAVE — no second fork (replication.c:1092–1093) |
disk target, capabilities or slave_req differ |
"Can't attach the replica to the current BGSAVE" — it waits for the next one (replication.c:1098) |
| socket target | always waits for the next BGSAVE; a streaming child cannot take on a new reader (replication.c:1105–1108) |
| none | fork now, or defer to replicationCron when diskless and repl-diskless-sync-delay is non-zero (replication.c:1110–1128, 3969) |
A 7.2 replica reaches for SYNC only after the master fails to
understand PSYNC: syncWithMaster logs "Retrying with SYNC..." and
writes the six bytes by hand (replication.c:2870–2872). The source's own example of who
does this is redis-cli --slave (replication.c:1031–1033).
Such a client is flagged CLIENT_PRE_PSYNC
(replication.c:1034), and replicationSetupSlaveForFullResync guards its reply with
if (!(slave->flags & CLIENT_PRE_PSYNC)) (replication.c:727) — so it
gets the RDB payload with no +FULLRESYNC header in front of it. It also never sent
REPLCONF capa, so mincapa & SLAVE_CAPA_EOF is zero at
replication.c:868 and the transfer is disk-backed no matter what
repl-diskless-sync says.