The day the NameNode went down
Exam-result week at the university. The results portal reads marks files from HDFS. At 10:40 the NameNode server's motherboard dies. Every DataNode is healthy, every block is safe on disk, and yet nobody can open a single file: the only machine that knows which blocks make which file is gone. Restarting on new hardware from the last checkpoint takes the admin team almost an hour. Riya, watching the angry messages, asks the obvious question: why was there only one?
In Hadoop 1 the NameNode was a single point of failure. Since Hadoop 2 you can run two (or more) NameNodes: one active, the others standby. The standby replays every change from a shared edit log kept on JournalNodes, and receives block reports from all DataNodes, so it is ready to take over in seconds.
Who decides when to switch?
ZooKeeper and a small process next to each NameNode, the ZKFailoverController. And there's a harder question: what if the old active isn't dead, just frozen, and wakes up later? Two active NameNodes writing different histories would corrupt the file system. Today we build a real HA cluster with 2 NameNodes, 3 JournalNodes and ZooKeeper, crash it, freeze it, and starve it, and watch exactly what Hadoop does.
NameNode high availability
HDFS high availability (HA) runs two or more NameNodes for one namespace (a nameservice): exactly one is active and serves clients; the others are standby. With the Quorum Journal Manager (QJM), the active NameNode writes every namespace edit to a group of JournalNodes (typically 3 or 5) and an edit succeeds only when a majority acknowledges it; standbys tail these edits to keep their namespace current, while DataNodes send heartbeats and block reports to all NameNodes. Automatic failover uses a ZooKeeper ensemble and a ZKFailoverController (ZKFC) beside each NameNode: the ZKFC monitors its NameNode's health and holds an ephemeral lock znode while it is active; when the active's ZooKeeper session expires, another ZKFC takes the lock, fences the old active, and transitions its NameNode to active. JournalNodes additionally enforce a monotonically increasing epoch, rejecting writes from any NameNode with an older epoch, which prevents split-brain.
Two NameNodes, one in charge. The one in charge writes every change to three JournalNodes; the spare reads those changes and stays up to date. ZooKeeper notices when the one in charge disappears and promotes the spare in about ten seconds. A NameNode that was replaced and comes back is refused by the JournalNodes, so there is never more than one boss.
Words you'll meet in this lesson
hdfs://canteen; clients never name a single NameNode.Settings and numbers from our lab (Hadoop 3.5.0)
| ha.zookeeper.session-timeout.ms | 10000: a crashed active is noticed after about 10 s (our run: kill at 15:44:25, new active at 15:44:35) |
|---|---|
| dfs.qjournal.write-txns.timeout.ms | 20000: without a JournalNode majority for 20 s, the NameNode shuts itself down |
| ipc.client.connect.timeout | 20000: a client that tries the dead NameNode first waits up to 20 s (our write took 22 s) |
| dfs.ha.zkfc.port | 8019 (ZKFC RPC port) |
| dfs.ha.tail-edits.period | We set 5s in the lab so the standby follows quickly (the shipped default is larger) |
HA at three levels
Beginner A cricket team captain
- The captain (active NameNode) makes every decision; the vice-captain (standby) watches every ball and knows the match situation.
- The scorebook is kept by three scorers (JournalNodes). A decision counts only if at least two of them write it down.
- If the captain is injured, the umpire (ZooKeeper) hands the armband to the vice-captain. If the old captain walks back on, the scorers refuse to record his decisions: the new captain has a newer armband number.
Intermediate How a failover happens
- The active's ZKFC holds an ephemeral znode (
ActiveStandbyElectorLock) in ZooKeeper. It disappears when its session expires (10 s without contact). - The standby's ZKFC grabs the lock, fences the old active, and tells its NameNode to become active. Our log: “Checking for any old active” → “Fencing successful” → “Successfully transitioned … to active state” within one second.
- Clients use a failover proxy provider: they know both addresses and retry the other on errors such as
StandbyException. - Manual, graceful switch:
hdfs haadmin -failover nn2 nn1(2.2 s in our lab).
Advanced Why split-brain can't happen with QJM
- Each new active asks the JournalNodes for a new epoch greater than any they have seen; a majority must promise it. Our epochs went 2 → 3 → 4 → … with every switch.
- A frozen old active wakes up with epoch 7 and tries to write: every JN answers “IPC's epoch 7 is less than the last promised epoch 8”, and the NameNode exits. Even with a fake fencer (
/bin/true), the shared log cannot be corrupted. - Fencing is still needed in production (e.g.
sshfence): an old active could otherwise answer stale reads to clients for a while. - With fewer than a majority of JNs, no NameNode can write, so both shut down: HDFS chooses consistency over availability (the CP side of the CAP theorem).
- Since Hadoop 3, more than two NameNodes are supported (one active, several standbys), and Observer NameNodes can serve reads.
Exam tip: For “Explain NameNode HA with QJM”: draw active + standby NameNodes, 3 JournalNodes, ZooKeeper with two ZKFCs, and DataNodes reporting to both. Then list: shared edits via quorum (majority), standby tails edits, automatic failover via ZooKeeper session + ZKFC, fencing and epochs to prevent split-brain. Mention the Secondary NameNode is not used in HA; the standby does checkpointing.
Ways to protect the NameNode
| No HA (Secondary NN) | HA with NFS shared edits | HA with QJM (standard) | |
|---|---|---|---|
| If the NameNode dies | Cluster down until manual restore | Standby takes over | Standby takes over in seconds |
| Shared edit log | None (only checkpoints) | One NFS filer | 3 or 5 JournalNodes, majority |
| Single point of failure? | Yes, the NameNode | The NFS filer | No (tolerates 1 of 3 JN failures) |
| Split-brain protection | Not needed | Fencing only | Epochs + fencing |
| Who checkpoints | Secondary NameNode | Standby | Standby |
Failover modes and admin commands
Automatic
dfs.ha.automatic-failover.enabled=true + ZooKeeper + ZKFC. Used in production.
Manual / graceful
hdfs haadmin -failover nn2 nn1 for planned maintenance or patching.
Check state
hdfs haadmin -getAllServiceState, or tag.HAState in the NameNode's /jmx.
Observer reads
Hadoop 3 Observer NameNodes serve read-only requests to take load off the active.
scale readsClients and jobs should always use the nameservice URI (hdfs://canteen/…), never hdfs://nn1:8020/…. A path with a hard-coded NameNode host breaks the moment that host becomes standby.
Building and breaking a real HA cluster
Our lab: ZooKeeper, 3 JournalNodes, NameNodes nn1 and nn2 (each with a ZKFC) and 3 DataNodes, all apache/hadoop:3.5.0 containers. The full files are in “Run it on your PC”.
The HA configuration
The client sees one name,CORE-SITE.XML_fs.defaultFS=hdfs://canteen CORE-SITE.XML_ha.zookeeper.quorum=zk:2181 HDFS-SITE.XML_dfs.nameservices=canteen HDFS-SITE.XML_dfs.ha.namenodes.canteen=nn1,nn2 HDFS-SITE.XML_dfs.namenode.rpc-address.canteen.nn1=nn1:8020 HDFS-SITE.XML_dfs.namenode.shared.edits.dir=qjournal://jn1:8485;jn2:8485;jn3:8485/canteen HDFS-SITE.XML_dfs.client.failover.proxy.provider.canteen=org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider HDFS-SITE.XML_dfs.ha.automatic-failover.enabled=true HDFS-SITE.XML_dfs.ha.fencing.methods=shell(/bin/true)canteen, with two possible addresses.shell(/bin/true)is a “fencer that always succeeds”, acceptable only in a lab; we'll see why QJM keeps us safe anyway.First start: format, bootstrap, elect
nn2 copies nn1's fsimage; both ZKFCs race for the lock znode in ZooKeeper, and nn1 won. ZooKeeper now holds# on nn1, once: hdfs namenode -format -nonInteractive -clusterId canteen-cluster hdfs zkfc -formatZK -nonInteractive # on nn2, once: hdfs namenode -bootstrapStandby -nonInteractive # on both: hdfs --daemon start zkfc; hdfs namenode hdfs haadmin -getAllServiceStatenn1:8020 active nn2:8020 standby/hadoop-ha/canteen/ActiveStandbyElectorLockandActiveBreadCrumb(we listed them with the ZooKeeper client).Crash the active while a client writes
About 10 s to fail over: the ZooKeeper session timeout. The client's write 4 was in progress: it tried nn1 (20 s connect timeout to the dead address), then nn2, and succeeded. 12 of 12 writes succeeded; the application only saw one slow write.docker kill lab7-nn1-115:44:15 before: nn1:8020=active nn2:8020=standby 15:44:25 >>> docker kill lab7-nn1-1 (the active NameNode crashes) 15:44:27 nn2 is standby ... 15:44:34 nn2 is standby 15:44:36 nn2 is active 15:44:24 write 3 ok (took 3 s) 15:44:46 write 4 ok (took 22 s) 15:44:48 write 5 ok (took 2 s) ... 15:45:03 write 12 ok (took 2 s) 15:45:04 files written: 12What the standby's ZKFC did
Lock taken → old active fenced → standby promoted, all within about a second. Before becoming active, nn2 reads any remaining edits from the JournalNodes, so no change is lost.grep … /var/log/hadoop/hadoop-hadoop-zkfc-nn2.out15:44:34 ActiveStandbyElector - Checking for any old active which needs to be fenced... 15:44:35 NodeFencer - ====== Beginning Service Fencing Process... ====== 15:44:35 NodeFencer - ====== Fencing successful by method ShellCommandFencer(/bin/true) ====== 15:44:35 ZKFailoverController - Trying to make NameNode at nn2/172.20.0.7:8020 active... 15:44:35 ZKFailoverController - Successfully transitioned NameNode at nn2/172.20.0.7:8020 to active stateFreeze the active (the zombie test)
A frozen process (like a long garbage-collection pause) is the dangerous case: it doesn't know it was replaced. When nn1 woke and tried to write with epoch 7, all three JournalNodes refused, and nn1 shut itself down. Split-brain prevented, even with a fencer that does nothing.docker pause lab7-nn1-1 ; sleep 25 ; docker unpause lab7-nn1-115:40:49 before : nn1:8020=active nn2:8020=standby | epochs: jn1=7 jn2=7 jn3=7 15:41:16 during : nn2=active | epochs: jn1=8 jn2=8 jn3=8 15:41:19 client write to the new active: ok 15:41:19 >>> docker unpause lab7-nn1-1 (the old active wakes up, still thinking it is active) 172.20.0.5:8485: IPC's epoch 7 is less than the last promised epoch 8 ; journal id: canteen 172.20.0.4:8485: IPC's epoch 7 is less than the last promised epoch 8 ; journal id: canteen 172.20.0.2:8485: IPC's epoch 7 is less than the last promised epoch 8 ; journal id: canteen Exiting with status 1: Error: flush failed for required journal ... SHUTDOWN_MSG: Shutting down NameNode at nn1/172.20.0.6 15:41:39 after : nn1:8020=Failed nn2:8020=activeLose the JournalNode majority
With 2 of 3 JournalNodes, everything works. With 1 of 3, the active can't get a majority for 20 s and stops; the standby can't become active either. HDFS stops rather than risk losing edits. Starting the JournalNodes and NameNodes again (docker stop lab7-jn3-1 ; … ; docker stop lab7-jn2-115:45:56 nn1:8020=standby nn2:8020=active 15:46:00 write 1: ok 15:46:00 >>> stop jn3 (2 of 3 JournalNodes left) 15:46:02 write 2: ok 15:46:02 >>> stop jn2 (1 of 3 left: no majority) 15:47:32 write 3: FAILED (no answer) 15:47:42 nn1:8020=Failed nn2:8020=Failed nn1: java.io.IOException: Timed out waiting 20000ms for a quorum of nodes to respond. nn2: java.io.IOException: Timed out waiting 20000ms for a quorum of nodes to respond.restore.sh) brought the cluster back with all files.
Put the three JournalNodes on three different machines (ideally different racks), never all on the NameNode hosts. Losing two JournalNodes stops the whole file system, as step 6 showed.
Crash, takeover, and the zombie that wakes up
Six steps from our lab. Left: what admins and clients see. Right: ZooKeeper, the ZKFCs and the JournalNodes.
- nn1 is active, nn2 standby. A client writes a file every 2 seconds.
- nn1 crashes. For a few seconds nn2 is still standby; one client write is waiting.
- About 10 s later nn2 is active. The waiting write succeeds (22 s), and all 12 writes complete.
- A different test: nn1 is frozen, not dead. nn2 takes over and the epoch rises from 7 to 8.
- nn1 wakes up and tries to write edits with epoch 7. All three JournalNodes refuse.
- nn1 shuts itself down. There is only ever one active writer.
- nn1's ZKFC holds the lock znode; nn1 writes each edit to the JournalNodes; nn2 tails them.
- nn1 crashes. Its ZooKeeper session stops being renewed.
- After the 10 s session timeout the lock disappears; nn2's ZKFC takes it, fences nn1 and makes nn2 active with a higher epoch.
- In the zombie test, nn1 is only frozen. nn2 becomes active with epoch 8, and the JNs promise to ignore anything older.
- nn1 wakes up and sends edits with epoch 7. The JNs reject them.
- nn1 exits. The epoch rule guarantees a single writer even if fencing fails.
Try it yourself: failover in the playground
This playground has two NameNodes. Upload a file, stop the active NameNode, and watch the standby take over.
HA at work
1. Every production cluster
Enterprise distributions and admin guides treat NameNode HA with QJM as the standard for production clusters; a single NameNode is accepted only for dev and test.
2. Rolling upgrades and patching
Admins patch the standby, fail over to it with haadmin -failover, then patch the old active. The file system stays online during OS and Hadoop upgrades.
3. Long garbage-collection pauses
A NameNode with a very large heap can pause for many seconds. If that exceeds the ZooKeeper timeout, a failover happens and the paused NameNode exits on waking, exactly our zombie test. Admins tune heap and timeouts to avoid needless failovers.
4. Monitoring failovers
Monitoring reads tag.HAState from each NameNode's JMX and alerts on changes. A failover is not an outage, but it means something failed and should be investigated.
Simplified; organisations differ in tools and policies.
A quorum journal with epochs, in Python
Three JournalNodes each remember the highest epoch they have promised. A NameNode becomes the writer by getting a majority to promise its new epoch; an edit is accepted only by JNs that have not promised a higher one. Run it to replay our zombie test.
jns = {"jn1": 7, "jn2": 7, "jn3": 7} # last promised epoch on each JournalNode
log = []
def become_active(name):
epoch = max(jns.values()) + 1
ok = [j for j in jns if jns[j] < epoch]
if len(ok) >= 2: # majority of 3
for j in ok:
jns[j] = epoch
print(f"{name} is ACTIVE with epoch {epoch}")
return epoch
print(f"{name} could not get a majority")
def write(name, epoch, edit):
accepted = [j for j in jns if epoch >= jns[j]]
if len(accepted) >= 2:
log.append(edit)
print(f"{name} wrote {edit!r} (accepted by {len(accepted)} JNs)")
else:
print(f"{name} REJECTED: epoch {epoch} is less than promised {max(jns.values())}")
e1 = 7 # nn1 was active with epoch 7, then froze
e2 = become_active("nn2") # nn2 takes over
write("nn2", e2, "mkdir /results")
write("nn1", e1, "delete /results") # the zombie wakes up
print("edit log:", log)epoch = max(jns.values()) + 1A new writer always asks for a higher epoch than any JournalNode has seen. Our lab epochs went 7 → 8.accepted = [j for j in jns if epoch >= jns[j]]A JournalNode refuses any write whose epoch is lower than what it promised: “IPC's epoch 7 is less than the last promised epoch 8”.if len(accepted) >= 2:A write needs a majority (2 of 3). That is why losing two JournalNodes stopped the whole cluster.
Run it on your PC: build and break an HA cluster
Create a folder hdfs-ha-lab with four files from the lesson's lab: config (the HA settings shown in Part 4), docker-compose.yaml, nn.sh and zk.sh (ZooKeeper runs from the jars inside the Hadoop image, so no extra image is needed). Start it with docker compose -p lab7 up -d --scale datanode=3, wait a minute, then run the three experiments. It needs about 4 GB of free RAM.
Lab files: compose, NameNode start script, ZooKeeper
x-hadoop: &hadoop
image: apache/hadoop:3.5.0
env_file: [./config]
volumes: [./nn.sh:/opt/nn.sh:ro, ./zk.sh:/opt/zk.sh:ro]
services:
zk: { <<: *hadoop, hostname: zk, command: ["bash", "/opt/zk.sh"] }
jn1: { <<: *hadoop, hostname: jn1, command: ["hdfs", "journalnode"] }
jn2: { <<: *hadoop, hostname: jn2, command: ["hdfs", "journalnode"] }
jn3: { <<: *hadoop, hostname: jn3, command: ["hdfs", "journalnode"] }
nn1: { <<: *hadoop, hostname: nn1, command: ["bash", "/opt/nn.sh"], depends_on: [zk, jn1, jn2, jn3] }
nn2: { <<: *hadoop, hostname: nn2, command: ["bash", "/opt/nn.sh"], depends_on: [nn1] }
datanode: { <<: *hadoop, command: ["hdfs", "datanode"], depends_on: [nn1, nn2] }
# ---- nn.sh ----
#!/bin/bash
# Start a NameNode plus its ZKFailoverController in one container.
# nn1 formats the namespace; nn2 copies it from nn1 (bootstrapStandby).
NAME=/tmp/hadoop-hadoop/dfs/name
if [ ! -d $NAME/current ]; then
if [ "$(hostname)" = "nn1" ]; then
hdfs namenode -format -nonInteractive -clusterId canteen-cluster
hdfs zkfc -formatZK -nonInteractive
else
until hdfs namenode -bootstrapStandby -nonInteractive; do sleep 5; done
fi
fi
hdfs --daemon start zkfc
exec hdfs namenode
# ---- zk.sh ----
#!/bin/bash
# Run a single ZooKeeper server using the ZooKeeper jars shipped inside the Hadoop image.
mkdir -p /tmp/zk && printf 'tickTime=2000\ndataDir=/tmp/zk\nclientPort=2181\nadmin.enableServer=false\n4lw.commands.whitelist=*\n' > /tmp/zk/zoo.cfg
exec java -cp "/opt/hadoop/share/hadoop/common/lib/*" org.apache.zookeeper.server.quorum.QuorumPeerMain /tmp/zk/zoo.cfg
# Start: docker compose -p lab7 up -d --scale datanode=3Real output. The config file with all HA properties is in Part 4, step 1, plus dfs.namenode.http-address for both NameNodes, dfs.journalnode.edits.dir, dfs.namenode.name.dir and dfs.replication=3.
Experiment 1: crash the active during writes
#!/usr/bin/env bash
# ha_failover.sh: crash the active NameNode while a client keeps writing, and watch HDFS fail over.
# Run from the folder with docker-compose.yaml (project name lab7).
P=lab7
state() { docker exec $P-nn2-1 hdfs haadmin -getAllServiceState 2>/dev/null | awk '/^nn[12]:8020/{printf "%s=%s ", $1, $2}'; }
echo "$(date +%T) before: $(state)"
# a client on datanode-1 writes one small file every loop, and logs success or failure
docker exec -d $P-datanode-1 bash -c 'for i in $(seq 1 12); do
start=$(date +%s)
if echo "order $i" | hdfs dfs -put -f - /orders_$i.txt >/tmp/err_$i 2>&1; then r=ok; else r=FAILED; fi
echo "$(date +%T) write $i $r (took $(( $(date +%s) - start )) s)"
done > /tmp/writer.log 2>&1'
sleep 8
echo "$(date +%T) >>> docker kill $P-nn1-1 (the active NameNode crashes)"
docker kill $P-nn1-1 > /dev/null
for t in $(seq 1 30); do
s=$(docker exec $P-nn2-1 hdfs haadmin -getServiceState nn2 2>/dev/null)
echo "$(date +%T) nn2 is $s"
[ "$s" = "active" ] && break
done
until [ "$(docker exec $P-datanode-1 bash -c 'wc -l < /tmp/writer.log')" -ge 12 ]; do sleep 2; done
echo "--- client log (datanode-1)"
docker exec $P-datanode-1 cat /tmp/writer.log
echo "--- ZKFC log on nn2"
docker exec $P-nn2-1 bash -c 'grep -hE "Checking for any old active|Beginning Service Fencing|Fencing successful|Trying to make|Successfully transitioned" /var/log/hadoop/*zkfc*.out | cut -c1-150 | tail -5'
echo "$(date +%T) files written: $(docker exec $P-nn2-1 hdfs dfs -ls / 2>/dev/null | grep -c orders_)"
# Run: bash ha_failover.sh (then: docker start lab7-nn1-1)Real output: about 10 s to fail over, one slow write, 12 of 12 writes succeeded.
Experiment 2: freeze the active (zombie)
#!/usr/bin/env bash
# zombie.sh: freeze the active NameNode (it is alive but silent), let the other take over,
# then wake the old one up and watch the JournalNodes refuse its writes.
P=lab7
state() { docker exec $P-nn2-1 hdfs haadmin -getAllServiceState 2>/dev/null | awk '/^nn[12]:8020/{printf "%s=%s ", $1, $2}'; }
epoch() { for j in 1 2 3; do printf "jn$j=%s " "$(docker exec $P-jn$j-1 cat /tmp/hadoop-hadoop/dfs/journal/canteen/current/last-promised-epoch)"; done; }
echo "$(date +%T) before : $(state)| epochs: $(epoch)"
echo "$(date +%T) >>> docker pause $P-nn1-1 (active NameNode frozen, e.g. a 60 s GC pause)"
docker pause $P-nn1-1 > /dev/null
sleep 25
echo "$(date +%T) during : nn2=$(docker exec $P-nn2-1 hdfs haadmin -getServiceState nn2 2>/dev/null) | epochs: $(epoch)"
echo "order after takeover" | docker exec -i $P-datanode-1 hdfs dfs -put -f - /after_takeover.txt 2>/dev/null && echo "$(date +%T) client write to the new active: ok"
echo "$(date +%T) >>> docker unpause $P-nn1-1 (the old active wakes up, still thinking it is active)"
docker unpause $P-nn1-1 > /dev/null
sleep 20
echo "--- old active nn1, last log lines"
docker logs $P-nn1-1 2>&1 | grep -E "epoch|IPC's epoch|FATAL|Error: flush|shutting down|SHUTDOWN_MSG|Transitioning to standby|standby" | cut -c1-190 | tail -6
echo "$(date +%T) after : $(state)"
# Run: bash zombie.sh (then: docker start lab7-nn1-1)Real output: the JournalNodes refuse epoch 7 after promising epoch 8, and the old active shuts down.
Experiment 3: lose the JournalNode majority
#!/usr/bin/env bash
# jn_quorum.sh: the active NameNode needs a majority (2 of 3) of JournalNodes to write edits.
P=lab7
put() { if echo "x" | timeout 90 docker exec -i $P-datanode-1 hdfs dfs -put -f - /q_$1.txt > /dev/null 2>&1; then echo "$(date +%T) write $1: ok"; else echo "$(date +%T) write $1: FAILED (no answer)"; fi; }
state() { docker exec $P-datanode-1 hdfs haadmin -getAllServiceState 2>/dev/null | awk '/^nn[12]:8020/{printf "%s=%s ", $1, $2}'; }
echo "$(date +%T) $(state)"
put 1
echo "$(date +%T) >>> stop jn3 (2 of 3 JournalNodes left)"; docker stop $P-jn3-1 > /dev/null
put 2
echo "$(date +%T) >>> stop jn2 (1 of 3 left: no majority)"; docker stop $P-jn2-1 > /dev/null
put 3
sleep 10
echo "$(date +%T) $(state)"
for n in nn1 nn2; do docker logs $P-$n-1 2>&1 | grep -E "Timed out waiting|SHUTDOWN_MSG: Shutting" | tail -2 | cut -c1-120 | sed "s/^/$n: /"; done
# Run: bash jn_quorum.sh then restore with: bash restore.shReal output: with 1 of 3 JournalNodes, both NameNodes stop after the 20 s quorum timeout. restore.sh starts JNs, then NameNodes; all files were intact.
An election in ZooKeeper
Two ZKFCs try to create the same ephemeral lock znode. The first wins; when its session expires the znode vanishes and the other wins.
Worked examples: real problems, solved and traced in memory
Three HA calculations: failover time, JournalNode sizing, and what an hour of NameNode downtime costs. Each runs live and is traced in memory.
How long does a crash failover take?
Problem statement
From our ZKFC log: crash at 15:44:25, “Checking for any old active” at 15:44:34, “Successfully transitioned” at 15:44:35. Split the outage into detection and takeover.
15:44:25 · 15:44:34 · 15:44:35
detection: 9 s takeover: 1 s total: 10 s
Approach
- Convert times to seconds.
- Detection = check time − crash.
- Takeover = transition − check.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
3 or 5 JournalNodes?
Problem statement
Each JournalNode is down for maintenance or failure about 1% of the time, independently. Estimate the chance that a majority is unavailable with 3 and with 5 JNs.
p(down) = 0.01
3 JNs: majority lost 0.029800% of the time 5 JNs: majority lost 0.000985% of the time
Approach
- Majority lost when more than n/2 JNs are down at once.
- Sum binomial probabilities.
- Compare.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
What does an hour without the NameNode cost?
Problem statement
Without HA, a NameNode hardware failure took 55 minutes to recover. 300 analysts and 40 production jobs depend on HDFS. Analyst time costs ₹900/hour; each delayed job costs ₹15,000 in penalties. HA hardware (an extra server and 3 JournalNodes) costs ₹6 lakh. How many such failures pay for HA?
55 min · 300 × ₹900/h · 40 × ₹15,000 · ₹6 lakh
one outage costs about Rs 847,500 HA pays for itself after 1 outage(s)
Approach
- Cost per outage = people + jobs.
- Divide HA cost by outage cost.
- Round up.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Check yourself
Quick revision
Lesson 7.1 in one screen
- HA = one active + standby NameNode(s) per nameservice; no Secondary NameNode.
- QJM: edits to 3 or 5 JournalNodes; a majority must accept. Lose the majority → NameNodes stop (lab: “Timed out waiting 20000ms for a quorum”).
- ZKFC + ZooKeeper: ephemeral lock znode; session timeout 10 s → automatic failover (lab: ~10 s).
- Failover steps: lock → fence old active → transition standby to active.
- Epochs stop a zombie: “IPC's epoch 7 is less than the last promised epoch 8” → old active exits.
- Clients use the nameservice and a failover proxy provider; a dead address can cost a 20 s connect timeout.
- Commands:
hdfs haadmin -getAllServiceState,-failover nn2 nn1,hdfs zkfc -formatZK,namenode -bootstrapStandby. - Lab: 12/12 writes survived a crash; graceful failover 2.2 s.
Interview and viva questions
How does NameNode high availability work in Hadoop?
Two or more NameNodes serve one namespace: one active, the rest standby. The active writes every edit to a quorum of JournalNodes; standbys read those edits and receive block reports from all DataNodes, so they hold an up-to-date namespace and block map. ZooKeeper and a ZKFailoverController next to each NameNode run an election; when the active fails, a standby is fenced in and promoted automatically.
What is the role of ZooKeeper in HDFS HA?
It provides the lock used for leader election. The active NameNode's ZKFC holds an ephemeral znode while its session is alive. If the NameNode or ZKFC dies or is partitioned, the session expires, the znode disappears, and another ZKFC acquires it and triggers failover. ZooKeeper does not store HDFS metadata.
What is fencing and why is it needed?
Fencing ensures that a previously active NameNode can no longer act as active, for example by killing it over SSH. With QJM, epochs already prevent the old active from writing edits, but fencing also prevents it from serving stale reads to clients that still contact it.
What is the difference between the Secondary NameNode and the Standby NameNode?
The Secondary NameNode only merges the edit log into a new fsimage (checkpointing) and cannot replace the NameNode. The Standby NameNode keeps a live, up-to-date copy of the namespace, performs checkpointing, and can become active within seconds.
Why use an odd number of JournalNodes?
Writes need a majority. With 3 JNs one may fail; with 4 still only one may fail (majority 3), so the fourth adds cost without tolerance. 5 JNs tolerate two failures.
What happens to clients during a failover?
Clients using the nameservice retry on the other NameNode. In-flight calls may be delayed by timeouts (in our lab one write took 22 s), but they complete once the new active is up; applications normally see only a pause.
Practice problems
HA exercises using the real timestamps, epochs and logs from the lab.
Failure tolerance
For 3 and 5 JournalNodes, print how many may fail while edits can still be written.
3, 5
3 JNs tolerate 1 failure 5 JNs tolerate 2 failures
Show solution and explanation
for n in [3, 5]:
t = n - (n // 2 + 1)
print(n, "JNs tolerate", t, "failure" if t == 1 else "failures")How it works: 3 JNs tolerate 1 failure; 5 tolerate 2.
Parse haadmin output
Find the active NameNode from hdfs haadmin -getAllServiceState output.
two lines
['nn2:8020']
Show solution and explanation
out = """nn1:8020 standby
nn2:8020 active"""
print([l.split()[0] for l in out.splitlines() if l.split()[1] == "active"])How it works: nn2:8020. Monitoring scripts parse exactly this.
Epoch sequence
The lab showed epochs 2, 3, 4 and 7, 8 across failovers. How many active-role changes happened between epoch 2 and epoch 8?
2 → 8
6 new active writers
Show solution and explanation
print(8 - 2, "new active writers")How it works: 6: every transition to active (automatic or manual) takes a new epoch.
Slowest write
From the client log, find the slowest write and its duration.
write lines
15:44:46 write 4 ok (took 22 s)
Show solution and explanation
log = ["15:44:19 write 1 ok (took 2 s)", "15:44:24 write 3 ok (took 3 s)", "15:44:46 write 4 ok (took 22 s)", "15:44:48 write 5 ok (took 2 s)"]
slow = max(log, key=lambda l: int(l.split("took ")[1].split()[0]))
print(slow)How it works: Write 4, 22 s: the client's 20 s connect timeout to the dead nn1 address plus the retry on nn2.
Detect a failover in JMX samples
Given HAState samples every 5 s from nn2, print when it became active.
timestamps and states
state change at 15:44:40 standby -> active
Show solution and explanation
samples = [("15:44:25", "standby"), ("15:44:30", "standby"), ("15:44:35", "standby"), ("15:44:40", "active")]
prev = None
for t, s in samples:
if prev and s != prev:
print("state change at", t, prev, "->", s)
prev = sHow it works: A simple change detector is how failover alerts are built from /jmx.
Quorum check
Write a function that says whether the active can write, given which of 3 JNs are up.
[True, False, True]
True False
Show solution and explanation
def can_write(up):
return sum(up) > len(up) // 2
print(can_write([True, False, True]), can_write([True, False, False]))How it works: True with 2 of 3, False with 1 of 3, matching the lab.
Patching without downtime
Monthly OS patching restarts the NameNode for 20 minutes. Without HA, 300 analysts wait (₹900/hour each). What does HA save per year on patching alone?
12 × 20 min × 300 × ₹900/h
Rs 1,080,000 per year
Show solution and explanation
print(f"Rs {12 * 20 / 60 * 300 * 900:,.0f} per year")How it works: ₹10.8 lakh a year (illustrative) from rolling patches with haadmin -failover.
Majority from votes
Simulate the epoch protocol: JNs promise a new epoch only if it is higher than their current one. Run two competing candidates with epochs 5 and 5 and show only one can win.
3 JNs at epoch 4
nn1: 3 promises -> ACTIVE nn2: 0 promises -> refused
Show solution and explanation
jns = [4, 4, 4]
def propose(name, epoch):
yes = 0
for i, e in enumerate(jns):
if epoch > e:
jns[i] = epoch; yes += 1
return f"{name}: {yes} promises -> {'ACTIVE' if yes >= 2 else 'refused'}"
print(propose("nn1", 5))
print(propose("nn2", 5))How it works: nn1 gets 3 promises; nn2 with the same epoch gets 0 and is refused. Real QJM uses the same rule: a strictly higher epoch from a majority.
Real-world scenario questions (asked in company interviews)
HA is a favourite topic for Hadoop administrator and senior data engineer interviews.
“Is the NameNode a single point of failure?”
The classic question.
The interviewer is checking: Hadoop 1 vs Hadoop 2+ HA.
Show a strong answer
- In Hadoop 1, yes. Since Hadoop 2, HA runs an active and standby NameNode sharing edits through JournalNodes (QJM).
- ZooKeeper + ZKFC fail over automatically in seconds; epochs and fencing prevent split-brain.
- The Secondary NameNode was never a backup; it only checkpoints.
Failovers happen every few days at peak hours
No hardware is failing, but the active NameNode keeps losing its role around 2 pm.
The interviewer is checking: GC pauses vs ZooKeeper timeouts.
Show a strong answer
- Check NameNode GC logs: a pause longer than the ZooKeeper session timeout (10 s default) makes ZKFC lose the lock, and the woken NameNode exits.
- Fix the cause: heap sizing, GC tuning, fewer objects (small files), RPC load from a misbehaving job.
- Only then consider a slightly longer session timeout; it also slows real failovers.
Where do you put JournalNodes and ZooKeeper?
Designing a new 60-node cluster with 2 racks of masters.
The interviewer is checking: failure domains.
Show a strong answer
- 3 JNs and 3 ZooKeeper servers on separate machines across racks, so one rack failure leaves a majority (ideally 3 racks for masters).
- The two NameNodes on different racks and power feeds; JN disks dedicated and fast (edits are synchronous).
- Use real fencing (sshfence or a power fencer) and monitor HAState.
“What if fencing fails? Can two NameNodes both be active?”
A follow-up to test depth.
The interviewer is checking: QJM epochs.
Show a strong answer
- With QJM, a new active gets a higher epoch from a majority of JournalNodes; they reject writes from older epochs, so the old active cannot change the namespace and exits on its first write.
- It could briefly serve stale reads, which is why fencing is still configured.
- Real lab log: “IPC's epoch 7 is less than the last promised epoch 8”, then “Shutting down NameNode”.