← index

syncCommand: PSYNC, the fork, and the backlog

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.

Scenario

replication.c:940 syncCommand

Configuration

config.c:3089, 3185, 3154

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.

Master

idle
replid
replid2
second_replid_offset
master_repl_offset
repl_backlog->offset
repl_backlog->histlen
RDB child

Writes buffered for this replica since the fork

replication.c:704–706, 719 — WAIT_BGSAVE_END is what starts the accumulation

Replica

offline
cached_master replid
cached_master reploff
next byte wanted

The replica sends reploff + 1, the first byte it has not received — not the last one it has (replication.c:2469).

RDB received 0%

Replication buffer blocks

replication.c:261 incrementalTrimReplicationBacklog
solid inside the backlog window dashed trimmed, no longer reachable red the block holding the requested offset

Wire and decisions

pick a scenario

Pick a scenario above, then press Run.

What the backlog actually is

server.h:1112

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).

When a new replica costs no fork

replication.c:1063 syncCommand
RDB child runningWhat 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)

Footnote: the literal SYNC command

replication.c:1031

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.