Notes
What they are
Both are small, strongly consistent, replicated key-value stores built for coordination, not for application data. ZooKeeper is the older one, written in Java, using a protocol called ZAB, and it sits under systems like HBase and older Kafka. etcd is written in Go, uses Raft, and is the brain of every Kubernetes cluster. Consul from HashiCorp plays a similar role.
- small, strongly consistent,
- replicated key-value store
- for coordination, not data
- ZooKeeper: Java, ZAB protocol
- (Hadoop, HBase, older Kafka)
- etcd: Go, Raft protocol
- (Kubernetes, many Go apps)
- similar: Consul (Raft too)
Data model
The data models differ a little. ZooKeeper is a tree of nodes called znodes, like a file system, each holding a small value. A znode can be ephemeral, deleted automatically when its client's session dies, and sequential, with a counter appended to its name. etcd is a flat, sorted key-value space where you read ranges by prefix, every change bumps a global revision number, and keys can be attached to a lease.
ZooKeeper: tree of znodes
/app/config "v=42"
/app/leader "host-3"
/app/locks/lock-0007 (ephemeral,
sequential)
etcd: flat, ordered keys
/app/config rev 1081
/app/leader lease 7f3a
range reads by prefix Consensus
Every write goes through the leader. It appends the write to its log, sends it to the followers, and once a majority have it, the write is committed and applied in the same order on every member. If the leader dies, the others elect a new one. During a network split, only the side with a majority can accept writes, so two halves can never disagree. Concepts episode 34 covers Raft step by step.
- 1. client sends write
- (followers forward it)
- 2. leader appends to its log,
- sends to followers
- 3. majority has it → commit
- 4. apply in the same order
- everywhere
- leader dies → election;
- only a majority side can
- make progress
Ensemble size
How many members? A write needs a majority, so three members survive one failure and five survive two. Four is no better than three, because its majority is three, so it still tolerates only one loss. Seven survives three but every write waits for four acknowledgements, so it is slower. Most deployments use three or five, spread across availability zones.
members majority can lose 1 1 0 3 2 1 4 3 1 (no gain) 5 3 2 7 4 3 (slower) more members = slower writes spread across 3 zones
Sessions + leases
The feature that makes coordination work is tying data to liveness. In ZooKeeper a client holds a session kept alive by heartbeats; if it stops, its ephemeral znodes are deleted. In etcd you grant a lease, say ten seconds, attach keys to it and keep it alive; if the client crashes, the lease expires and the keys disappear. That is how a dead leader or a dead lock holder is cleaned up automatically.
- ZooKeeper session: client
- heartbeats; on timeout its
- ephemeral znodes are deleted
- etcd lease: grant(10 s),
- attach keys, keepalive;
- lease expires → keys deleted
- this is how "who is alive"
- and "who holds the lock"
- clean up after crashes
Watches
Clients can watch a key or prefix and be notified when it changes, instead of polling. Change the config and every service watching it hears about it within milliseconds. Classic ZooKeeper watches fire once and must be set again; newer versions add persistent watches. etcd watches are a stream starting from a revision, so a client that reconnects can resume exactly where it left off without missing a change.
- client watches a key or prefix
- → notified when it changes
- ZooKeeper: one-shot watch,
- re-register after it fires
- (persistent watches in 3.6+)
- etcd: a stream from a
- revision; never misses one
- config push, membership,
- leader changes
Leader election
Leader election is a few lines on top of this. In ZooKeeper, each candidate creates an ephemeral, sequential znode under an election path. The lowest number is leader, and every other candidate watches only the one just ahead of it, which avoids a stampede. In etcd, candidates try to create a key with their lease only if it does not exist yet; the winner leads and the rest watch. When the leader dies, its key vanishes and the next one takes over.
- ZooKeeper: each candidate
- creates an ephemeral +
- sequential znode; lowest
- number leads; each watches
- the one just before it
- etcd: create /election/x with
- a lease only if absent
- (txn); others watch it
- leader dies → key gone → next
Locks + fencing
Locks have a famous trap. A client holds a lock, then pauses for a long garbage collection. Its lease expires, another client takes the lock, and then the first wakes up and writes, believing it still holds it. The fix is a fencing token: each lock grant comes with an increasing number, the transaction id in ZooKeeper or the revision in etcd, and the storage rejects any write carrying an older number than it has already seen.
- problem: holder pauses (GC),
- lease expires, a second
- client takes the lock, then
- the first wakes and writes
- fix: every grant returns an
- increasing number
- (zxid / etcd revision)
- storage rejects any write
- with an older token
- (Concepts episode 31)
Reads
Reads are a subtle interview point. etcd reads are linearizable by default: the leader confirms it is still the leader before answering, so you always see the latest committed value; you can opt into faster local reads that may be slightly stale. ZooKeeper serves reads from whichever member you are connected to, which may be a little behind, and you call sync first when you need the latest. Writes are always ordered through the leader.
- etcd: linearizable by default
- (leader confirms it is still
- leader); serializable reads
- are local and faster
- ZooKeeper: reads served by
- the member you are on,
- possibly behind; call sync()
- first for the latest
- writes always linearizable
Real uses
Where you meet them. Kubernetes stores every pod, service and secret in etcd, and controllers watch for changes. Patroni uses etcd to decide which Postgres replica becomes primary. HBase and Solr rely on ZooKeeper. A common pattern for service discovery: each instance registers itself under a prefix with a lease, and clients watch the prefix. They also hold shard maps, feature flags and which instance runs a scheduled job.
- Kubernetes: every pod,
- service and secret in etcd
- Patroni: Postgres failover
- HBase, Solr, older Kafka:
- ZooKeeper
- service discovery: register
- with a lease, watch prefix
- feature flags, shard maps,
- config, cron leadership
Where they hurt
Where they hurt. They hold small data: about a megabyte per znode in ZooKeeper, a request size of about one and a half megabytes in etcd, and an etcd database quota of two gigabytes by default. Writes are limited to thousands, maybe low tens of thousands, a second, because each one needs a disk flush and a majority round trip. Slow disks make the whole cluster slow. Lose the majority and writes stop entirely.
- • small values only:
- ZK ~1 MB/znode, etcd 1.5 MB
- per request
- • etcd quota 2 GB default
- (8 GB suggested max)
- • writes: thousands to low
- tens of thousands per second
- • latency = disk fsync +
- network round trip
- • majority lost = no writes
Use it when
Use them for small, critical facts that every node must agree on: who is leader, who holds a lock, which members are alive, the current shard map or config. Avoid them for application data, queues, high write rates or large values; that belongs in a database, Kafka or object storage. In an interview, you rarely add one by name; you say the system uses a consensus-backed store for election and config.
- USE when
- leader election, locks
- membership, discovery
- config every node must see
- consistently
- AVOID when
- application data, queues,
- high write rates, big blobs
- → databases, Kafka, S3
Numbers
Numbers to have ready. Three or five members, surviving one or two failures. Write latency in one data centre is about one to ten milliseconds, set mostly by disk flush time. Throughput is roughly a thousand to thirty thousand writes a second depending on hardware and value size. etcd caps requests at one and a half megabytes and its database at two gigabytes by default. Session and lease timeouts are usually five to thirty seconds.
ensemble size 3 or 5 tolerates 1 or 2 failures write latency ~1-10 ms (same DC) writes per second ~1k - 30k etcd request max 1.5 MB etcd db quota 2 GB default ZK znode max ~1 MB session / lease ~5-30 s typical
Say it like this
How to say it. Only one scheduler instance may run the job at a time. Each instance campaigns for leadership in etcd with a ten second lease; the winner runs, the rest watch the key. Every write the leader makes carries the revision as a fencing token, so a paused former leader cannot corrupt anything. etcd runs as three members across zones, separate from the application database.
- "One scheduler instance must
- run the job at a time. Each
- instance campaigns in etcd
- with a 10 s lease; the winner
- runs, the rest watch. Each
- job carries the lease
- revision as a fencing token,
- so a stale leader’s writes
- are rejected."