Redis Cluster keeps a second network plane beside client RESP: the cluster bus. Nodes use it to discover peers, share slot and epoch views, detect failures, vote on failovers, and propagate selected cluster events. This post covers architecture (planes and gossip mechanism), then implementation (protocol wire format and code paths) in git/redis (cluster_legacy.h, cluster_legacy.c).

Related: Network / command path, Pub/Sub, Build from source.

Cluster bus gossip overview


1. Architecture

Redis Cluster’s control architecture is a node-to-node mesh on a dedicated cluster bus port, separate from client RESP. Each node holds a local membership and slot view and exchanges binary clusterMsg packets—periodic gossip for liveness and topology, plus discrete messages for failure, failover, and selected events.

1.1 Two planes

Redis Cluster separates data traffic from control traffic. Clients stay on RESP; only Redis nodes speak the binary bus protocol.

PlanePort (typical)SpeakersRole
Client / data:6379 RESPApps, replication clientCommands, keys, replication stream
Cluster bus / control:16379 (base+10000) binaryRedis nodes onlyMembership, health, slots/epochs, votes, selected events
 clients --RESP--> [ Redis node: data + slots ]
                        |
                        | bus TCP (clusterMsg)
                        v
                   peer ... peer

The bus does not carry ordinary key commands. Gossip scales membership and health without shipping a full node list on every packet.

1.2 Gossip mechanism

Gossip is how Redis Cluster spreads membership and health without shipping a full node list on every packet. It rides only on PING / PONG / MEET: the fixed header always describes the sender (epochs, myslots, flags, …); the body carries a sample of rumors about other nodes (clusterMsgDataGossip rows). Discrete types (FAIL, UPDATE, Pub/Sub, failover votes, …) are separate events on the same links—they are not the gossip sampler.

PatternTypesCadencePurpose
Continuous gossipPING, PONG, MEETPeriodic / on meetLiveness, membership rumors, slot/epoch in header, failure flags in gossip rows
Discrete eventsFAIL, FAILOVER_AUTH_*, UPDATE, PUBLISH / PUBLISHSHARD, MFSTART, MODULEOn demandExplicit flood or unicast for one cluster action

Wire layouts for those types are in §2.3.

1.2.1 Exchange

PING, PONG, and MEET share the same packet kind (data.ping). Semantics differ:

TypeRole
PINGProbe; receiver should answer with PONG
PONGReply; completes the liveness round-trip and carries the receiver’s own gossip sample
MEETLike PING, but forces the receiver to add the sender if unknown
sequenceDiagram
  participant A as Node A
  participant B as Node B
  A->>B: MEET or PING
  Note right of B: apply header, merge gossip sample
  B->>A: PONG
  Note left of A: apply header, merge gossip sample

Default frequency. clusterCron runs every 100 ms. Ping timing is driven by cluster-node-timeout (default 15000 ms / 15 s in redis.conf / config.c):

PathDefault behavior
Per-peer refreshIf there is no outstanding ping and the last PONG from that peer is older than cluster-node-timeout / 2 (7500 ms with the default timeout), send a PING. Override with hidden cluster-ping-interval (ms) when non-zero.
Random probeAbout once per second (iteration % 10 in clusterCron), pick among a few random peers and PING the one with the oldest pong_received.
PONGImmediate reply to PING or MEET (same packet shape); not on a separate timer.
PFAILIf a ping is still unanswered for longer than cluster-node-timeout (15 s default), the peer is marked locally unreachable.

So under defaults, a healthy peer is typically re-pinged on the order of every ~7.5 s when its last pong ages out, plus occasional random pings (~1/s cluster-wide toward the stalest sample). Manual-failover wait may ping the chosen replica continuously.

Each successful exchange updates the sender from the peer’s header and merges the peer’s gossip rows into the local nodes dictionary.

1.2.2 Sampling

The body never lists the whole cluster. clusterSendPing builds a bounded random sample, then forces failure opinions into that sample:

StepRule
Budgetwanted = max(3, floor(N / 10)), where N = dictSize(nodes)
CapAt most freshnodes = N - 2 (exclude self and the packet’s receiver)
DrawPick eligible peers at random (skip handshake / no-addr cases the send loop rejects); use last_in_ping_gossip so the same node is not repeated in one ping wave
PFAIL biasAfter the random draw, append every node the sender already marks PFAIL, even if that exceeds wanted
WireSet hdr->count to the number of gossip entries actually written

Why ~1/10 (and at least three). Source comment on clusterSendPing: within about two cluster-node-timeout windows, nodes exchange several pings/pongs. With ~N/10 gossip slots per packet, the chance that a given master appears often enough in others’ samples is high enough that a node already in PFAIL can collect majority failure reports before reports expire—without paying full-membership bandwidth every round.

What each sampled row carries. A rumor about one third party: id, coarse ping_sent / pong_received (seconds), IP and ports, and that peer’s flags as the sender sees them (MASTER / SLAVE / PFAIL / FAIL / NOADDR, …). Exact field sizes are under §2.3.1.

1.2.3 Merge

clusterProcessGossipSection validates node ids, then for each row:

  1. Corrupt ids — reject the entire gossip section.
  2. Known node, not myself — if the packet sender is a master and the row has PFAIL/FAIL, record a failure report (and maybe promote to FAIL); if the row clears failure flags, drop that master’s report. Optionally refresh local pong_received. If the subject is down locally but gossip says it is up at a new address, update IP/ports and drop the old link.
  3. Unknown node — if the sender is a trusted cluster member, the row is not NOADDR, and the id is not blacklisted, insert a new clusterNode.
  4. Gossip about myself — ignored for state updates.

Over many rounds, random samples plus PFAIL bias yield cluster-wide convergence on membership and failure views with (O(N)) rumor bytes per packet rather than (O(N^2)) full dumps.

1.2.4 Failure detection (weak quorum)

Failure is a local opinion that becomes a cluster flag only with majority support among masters that serve slots. Gossip sampling is what spreads those opinions:

  1. If A cannot reach X within cluster-node-timeout, A sets local PFAIL on X.
  2. Masters advertise PFAIL/FAIL in gossip rows; peers record failure reports from masters. PFAIL bias in §1.2.2 Sampling accelerates this.
  3. If A already has PFAIL on X and reports from a majority of masters (quorum ((\mathit{cluster_size}/2)+1)), A sets FAIL, clears PFAIL, and broadcasts a discrete FAIL message (data.fail) so others can adopt the flag without waiting for another gossip round.
  4. Reachability again can clear FAIL under defined conditions (especially for replicas).

Failure detection PFAIL to FAIL

This agreement is intentionally weak and time-based: partitions may delay visibility, and without majority no replica promotion is authorized.

1.2.5 Replica promotion (to master)

Once the failed master is FAIL, a replica of that master may try to take over on the same bus:

  1. Replica R broadcasts FAILOVER_AUTH_REQUEST (header carries epochs and claimed myslots; body empty). For manual failover, MFSTART runs first and FORCEACK may be set on the request.
  2. Slotted masters that accept the vote reply with unicast FAILOVER_AUTH_ACK.
  3. When R collects enough acks (failover_auth_count reaches quorum), it promotes: SLAVEMASTER, claims the former master’s slots, bumps configEpoch.
  4. Peers learn the new owner through subsequent gossip headers and optional UPDATE packets so client MOVED targets point at R.

Replica promotion after master FAIL

Wire layouts: §2.3.4§2.3.6, §2.3.7.

1.2.6 Design properties (gossip)

PropertyConsequence
Sampled gossip + PFAIL biasBandwidth grows gently with (N); failures propagate faster than uniform random gossip
Weak majority for FAILAvoids single-observer false failovers; still partition-sensitive
Header + sample each pingTopology and health refresh continuously without a full membership dump

2. Implementation

/* cluster_legacy.h */
typedef struct clusterLink {
    connection *conn;
    list *send_msg_queue;
    char *rcvbuf;
    clusterNode *node;   /* NULL until sender is known */
    int inbound;
    /* ... */
} clusterLink;

clusterCron (and related paths) open outbound links and schedule pings. Inbound data accumulates in rcvbuf until a full clusterMsg is present; clusterProcessPacket branches on type.

2.2 Protocol design

2.2.1 Framing

Every bus packet is one binary clusterMsg: fixed header, then a type-specific data body. Signature RCmb, protocol version 1; field offsets are ABI-stable across upgrades.

  1. Header always describes the sender — id, IP, ports, flags, full slot bitmap (myslots), currentEpoch / configEpoch, replication offset, cluster state. Receivers refresh the sender from the header even when they care mainly about the body.
  2. Body is typed — gossip arrays for the ping family; failed node name for FAIL; channel+payload for publish; slot config for UPDATE; module blob for MODULE; often empty for vote / MFSTART (meaning is in type + header epochs/flags).
  3. Trust boundary — unknown senders are ignored for sensitive types. MEET is the intentional exception that creates knowledge of the sender.

clusterMsg header and typed data

2.2.2 Header fields

The fixed header ends at offset 2256 (data begins there). Multi-byte integers are sent in network byte order. Offsets below are from static_assert in cluster_legacy.h.

FieldOffsetSizeMeaning
sig04Must be the ASCII bytes RCmb (Redis Cluster message bus). Used to reject non-bus data on the TCP stream.
totlen44Total packet length in bytes, including this header and the typed data section. Receiver waits until rcvbuf holds at least totlen before calling clusterProcessPacket.
ver82Wire protocol version; currently CLUSTER_PROTO_VER = 1. Nodes reject packets with an unsupported version.
port102Sender’s primary client port (TCP or TLS, whichever is primary for that node). Used with myip / gossip to reach the peer for RESP.
type122CLUSTERMSG_TYPE_* (0–10). Selects how data is interpreted; see §2.3.
count142For PING / PONG / MEET: number of clusterMsgDataGossip entries in data.ping. Unused (typically 0) for other types.
currentEpoch168Cluster epoch as known by the sender—the logical clock advanced on failover elections. Peers adopt a higher epoch when they see one.
configEpoch248If the sender is a master: its own config epoch for the slots it claims. If a replica: the config epoch last advertised by its master. Drives which slot map wins on conflict.
offset328If master: replication offset. If replica: offset processed from the master. Used in failover ranking and manual-failover catch-up (mf_master_offset path).
sender4040Sender node id (CLUSTER_NAMELEN), 40-byte hex SHA1-style name. Primary key in the receiver’s nodes dictionary.
myslots802048Bitmap of 16384 slots (CLUSTER_SLOTS/8 bytes). Bit set ⇒ sender claims that slot (as master). Refreshed on every gossip packet so peers keep MOVED targets current.
slaveof212840If the sender is a replica: master’s node id. If master: typically all zeros / null name.
myip216846Sender IP string (NET_IP_STR_LEN), if not all zeroed. Peers may update the known address when this is present and trusted.
extensions22142For ping-family packets with extension payloads: number of extension records after the gossip array.
notused1221630Reserved; must remain at this offset for ABI stability across upgrades.
pport22462Secondary client port: if port is TCP, this is TLS (and the reverse). Zero when unused.
cport22482Sender’s cluster bus TCP port (often port + 10000 unless announced otherwise).
flags22502Sender’s CLUSTER_NODE_* flags (bitmask), e.g. MASTER, SLAVE, PFAIL, FAIL, HANDSHAKE, NOADDR, NOFAILOVER, …
state22521Cluster state from the sender’s point of view: CLUSTER_OK (0) or CLUSTER_FAIL (1).
mflags22533Per-message flags. Byte 0 (CLUSTERMSG_FLAG0_*): PAUSED (master paused for manual failover), FORCEACK (grant failover auth even if master is up), EXT_DATA (ping body includes extensions). Bytes 1–2 reserved.

How receivers use the header. After validating sig, ver, and totlen, the node looks up or creates the clusterNode for sender, then copies reachability-related fields (ports, myip, flags, epochs, myslots, slaveof, offset, state) according to the processing rules for that type. Gossip rows in data describe other nodes; the header is always the sender’s self-description.

Flags worth recognizing on the wire.

BitNameRole
1MASTERNode is a master
2SLAVENode is a replica
4PFAILSuspected failure (local opinion)
8FAILFailed after majority agreement
16MYSELFThis process (local use)
32HANDSHAKEFirst exchange not finished
64NOADDRAddress unknown
128MEETPending MEET to this node
512NOFAILOVERReplica will not start failover
1024EXTENSIONS_SUPPORTEDPeer understands ping extensions

In the header, flags describe the sender (normally MASTER or SLAVE, plus capability bits). PFAIL / FAIL matter most in gossip rows (see §1.2), where they are the sender’s opinion about other nodes and feed failure reports.

2.3 Data by message type

hdr->type selects the union clusterMsgData arm. The fixed header (§2.2.2) is always present; only data (and how count / mflags / epochs are interpreted) changes.

§TypeIddata arm
2.3.1PING / PONG / MEET0–2data.ping
2.3.2FAIL3data.fail
2.3.3PUBLISH / PUBLISHSHARD4 / 10data.publish
2.3.4FAILOVER_AUTH_REQUEST5(empty)
2.3.5FAILOVER_AUTH_ACK6(empty)
2.3.6UPDATE7data.update
2.3.7MFSTART8(empty)
2.3.8MODULE9data.module

2.3.1 PING / PONG / MEET (0–2) — data.ping

Role. Continuous gossip: liveness round-trip plus membership rumors. MEET additionally inserts the sender if unknown.

Body layout.

data.ping
+------------------+-----+------------------+------------------------+
| gossip[0]        | ... | gossip[count-1]  | ping extensions (opt.) |
+------------------+-----+------------------+------------------------+
  sizeof(clusterMsgDataGossip) each           hdr->extensions records
  • hdr->count = number of gossip entries (see §1.2.2 Sampling).
  • Optional extensions follow &gossip[count] when mflags[0] has CLUSTERMSG_FLAG0_EXT_DATA (getInitialPingExt).

Gossip entry (clusterMsgDataGossip) — rumor about a third node, filled by clusterSetGossipEntry:

FieldSizeContent
nodename40Subject node id
ping_sent4Last ping to that node, seconds (n->ping_sent/1000)
pong_received4Last pong from that node, seconds
ip46Last known IP
port2Primary client port (TCP/TLS per cluster mode)
cport2Cluster bus port
flags2Sender’s view of that node’s CLUSTER_NODE_* flags
pport2Secondary client port
notused12Reserved; 0

Extensions (after the array, 8-byte aligned):

Ext typePayloadPurpose
HOSTNAMENUL-terminated hostnameAnnounced hostname
HUMAN_NODENAMENUL-terminated nameHuman-friendly node name
FORGOTTEN_NODEnode id + TTL (s)Temporary blacklist
SHARDID40-byte shard idShard identity
INTERNALSECRET40-byte secretShard internal secret

Example (PING / PONG). A sends PING to B with count = 4 (three random peers + one PFAIL). B merges gossip, replies PONG with its own header and sample.

Example (MEET). Operator runs CLUSTER MEET 10.0.0.5 7000 on node A. A opens a bus TCP connection to 10.0.0.5:17000 (client port + 10000) and sends a packet with type = MEET, the same data.ping layout as a ping (header describes A, body is a gossip sample). New node N does not yet know A: because the type is MEET, it inserts A into nodes, then replies with PONG (same body shape). Later membership grows through ordinary PING/PONG gossip samples, not further MEET packets.

2.3.2 FAIL (3) — data.fail

Role. After local PFAIL plus majority master reports, broadcast so peers adopt FAIL quickly.

Body (clusterMsgDataFail):

FieldSizeContent
about.nodename40Node id to mark FAIL

No gossip array; count unused.

Example. Master A promotes X to FAIL and broadcasts FAIL with about = X. Peer D adopts the flag from known sender A.

2.3.3 PUBLISH (4) / PUBLISHSHARD (10) — data.publish

Role. Cross-node Pub/Sub. PUBLISH is cluster-wide; PUBLISHSHARD goes only to other nodes in the sender’s shard.

Body (clusterMsgDataPublish; lengths in network order):

FieldSizeContent
channel_len4Channel byte length
message_len4Payload byte length
bulk_datachannel_len + message_lenChannel bytes, then message bytes (flexible; struct shows an 8-byte placeholder)

Example. PUBLISH alerts "disk-full" on A → bus PUBLISH to all nodes. SPUBLISHPUBLISHSHARD only within the shard.

2.3.4 FAILOVER_AUTH_REQUEST (5) — empty data

Role. Replica asks masters for votes to promote.

Body. None beyond the common header. Meaning is in:

Header / flagUse in this type
currentEpoch / configEpochCandidate’s election / config epochs
myslotsSlots the candidate claims
mflags FORCEACKManual failover: vote even if master still up

Broadcast; only slotted masters vote (clusterSendFailoverAuthIfNeeded).

Example. Replica R of failed master X broadcasts FAILOVER_AUTH_REQUEST with X’s former slots in myslots.

2.3.5 FAILOVER_AUTH_ACK (6) — empty data

Role. One master’s yes vote, unicast to the candidate.

Body. Empty. Validity is the sender being a slotted master and senderCurrentEpoch >= failover_auth_epoch. Candidate increments failover_auth_count.

Example. Master A sends FAILOVER_AUTH_ACK to R; at quorum, R promotes.

2.3.6 UPDATE (7) — data.update

Role. Push another node’s slot ownership when a peer’s view is stale.

Body (clusterMsgDataUpdate):

FieldSizeContent
configEpoch8Config epoch of the slots owner (network order)
nodename40Node id that owns the bitmap
slots2048Full 16384-bit slot bitmap for that node

Example. After failover, an UPDATE about R with a higher epoch rewrites B’s slot map so MOVED targets R.

2.3.7 MFSTART (8) — empty data

Role. Replica asks its master to start manual failover (pause writes, set mf_end / mf_slave).

Body. Empty. Sent from a replica to its master to start manual failover (mf_end / mf_slave on the master side).

Example. R sends MFSTART to master M; M pauses clients and pings R with the offset for mf_can_start.

2.3.8 MODULE (9) — data.module

Role. Opaque module cluster API traffic.

Body (clusterMsgModule):

FieldSizeContent
module_id8Module id (endian-safe as stored)
len4Payload length (network order)
type1Module-defined type 0–255
bulk_datalenOpaque payload (flexible; struct shows a 3-byte placeholder)

Example. Module on A unicasts or broadcasts a typed blob; B dispatches to registered receivers.

2.4 Slots and epochs on the wire

Slot ownership and config freshness ride in every gossip header (myslots, configEpoch, currentEpoch). When a node learns a higher config epoch for a peer’s slots than it currently believes, an UPDATE packet’s body (§2.3.6) can push that peer’s name, epoch, and full slot bitmap so redirects stay correct after failover or resharding.

2.5 Design properties (wire)

PropertyConsequence
Separate bus portControl traffic does not share the client connection pool or RESP parsers
Sender header on every packetPeers continuously refresh reachability and claimed slots
Discrete types on same linksOne mesh carries both rumor traffic and explicit cluster actions

Gossip sampling and failure quorum are covered in §1.2.

2.6 Wire format: clusterMsg

Binary framing as designed in §2.2. Concrete header fields:

typedef struct {
    char sig[4];              /* "RCmb" */
    uint32_t totlen;
    uint16_t ver;
    uint16_t port;            /* primary client port */
    uint16_t type;            /* CLUSTERMSG_TYPE_* */
    uint16_t count;           /* gossip entries for ping family */
    uint64_t currentEpoch;
    uint64_t configEpoch;
    uint64_t offset;
    char sender[40];
    unsigned char myslots[CLUSTER_SLOTS/8];
    char slaveof[40];
    char myip[NET_IP_STR_LEN];
    uint16_t extensions;
    /* pport, cport, flags, state, mflags, ... */
    union clusterMsgData data;
} clusterMsg;
CLUSTERMSG_TYPE_*Iddata contentExample
PING / PONG / MEET0–2clusterMsgDataGossip gossip[count] (+ optional extensions)§2.3.1
FAIL3failed node name (clusterMsgDataFail)§2.3.2
PUBLISH / PUBLISHSHARD4 / 10channel + payload lengths and bytes§2.3.3
FAILOVER_AUTH_REQUEST / ACK5 / 6empty beyond header (vote semantics in header/epochs)§2.3.4 / §2.3.5
UPDATE7config epoch, node name, slots bitmap§2.3.6
MFSTART8empty beyond header§2.3.7
MODULE9module id, type, payload§2.3.8

Gossip entry

typedef struct {
    char nodename[CLUSTER_NAMELEN];
    uint32_t ping_sent;
    uint32_t pong_received;
    char ip[NET_IP_STR_LEN];
    uint16_t port;
    uint16_t cport;
    uint16_t flags;
    uint16_t pport;
    uint16_t notused1;
} clusterMsgDataGossip;

Optional extensions (CLUSTERMSG_FLAG0_EXT_DATA): hostname, human nodename, forgotten-node TTL, shard id, internal secret—8-byte aligned after the gossip array.

2.7 Sending: clusterSendPing

/* cluster_legacy.c — behavior */
wanted = max(3, floor(dictSize(nodes) / 10));
/* random eligible nodes: not myself, not receiver, not HANDSHAKE/NOADDR/... */
/* then append every PFAIL node */
clusterSetGossipEntry(hdr, gossipcount++, node);
Implementation detailPurpose
wanted ≈ N/10 (min 3)Probabilistic majority of failure reports within a few timeout windows (see source comment)
last_in_ping_gossipAvoid repeating the same node in one ping wave
PFAIL appended unconditionallyFaster PFAILFAIL propagation
ping_sent stamped on outbound PINGLocal liveness accounting for that peer

2.8 Receiving: clusterProcessGossipSection

Called for ping-family packets after length validation.

EntryImplementation effect
Corrupt node idReject the entire gossip section
Known node ≠ myselfMaster sender + PFAIL/FAIL flags → clusterNodeAddFailureReport; clear report if gossip says up; markNodeAsFailingIfNeeded; may refresh pong_received or update address if a down node is reported reachable elsewhere
Unknown nodeMay insert into server.cluster->nodes (membership growth via gossip/MEET)
markNodeAsFailingIfNeeded(node):
  require local PFAIL
  failures = reports from masters (+ self if master)
  if failures >= (cluster_size/2)+1
      set FAIL; clusterSendFail(name)

2.9 Key files

FileRole
src/cluster_legacy.hclusterMsg, gossip structs, link/node flags, type ids
src/cluster_legacy.cclusterSendPing, clusterProcessGossipSection, clusterProcessPacket, FAIL quorum
src/cluster.h / cluster.cSlots, redirection, exported cluster API
redis.confcluster-enabled, cluster-node-timeout, bus announce options

TopicSummary
ArchitecturePlanes; gossip mechanism (exchange, sampling, failure, promotion) (§1)
ImplementationProtocol design (header, data by type); wire structs; send/receive paths (§2)