NameNode high availability (QJM and ZooKeeper)

One NameNode means one machine whose failure stops the whole file system. HA runs a standby NameNode that follows every change through a quorum of JournalNodes, and ZooKeeper promotes it automatically. This lesson builds a real HA cluster in Docker, breaks it three different ways, and shows from the logs how Hadoop prevents two NameNodes from ever being active at once.

Module 7 · HDFS advanced⏱ 30 minAdvancedReal failover on a 9-container clusterSplit-brain prevented liveRuns in your browser (Python)8 practice problems
1 The story

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?

NameNode nn1ACTIVE NameNode nn2STANDBY ZooKeeper: who is active? jn1jn2jn3 JournalNodes: the shared edit log (majority must accept) writes edits reads edits DataNodeDataNodeDataNode DataNodes send heartbeats and block reports to BOTH NameNodes, so the standby is always warm. myphatshala.in
Arjun Sir

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.

Riya

Who decides when to switch?

Arjun Sir

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.

2 Definition

NameNode high availability

Formal definition (write this in exams)

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.

In simple words

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

NameserviceA logical name for the HA pair, e.g. hdfs://canteen; clients never name a single NameNode.
Active / standbyThe NameNode serving clients / the warm spare replaying edits.
JournalNode (JN)A small daemon storing the shared edit log; run 3 or 5.
QJMQuorum Journal Manager: writes succeed when a majority of JNs accept.
ZKFCZKFailoverController: health-checks its NameNode and runs the election in ZooKeeper.
FencingMaking sure the old active can no longer act (e.g. ssh and kill it).
EpochA number that grows with each new active writer; JNs reject older epochs.
Split-brainTwo nodes both believing they are active: the disaster HA must prevent.

Settings and numbers from our lab (Hadoop 3.5.0)

ha.zookeeper.session-timeout.ms10000: 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.ms20000: without a JournalNode majority for 20 s, the NameNode shuts itself down
ipc.client.connect.timeout20000: a client that tries the dead NameNode first waits up to 20 s (our write took 22 s)
dfs.ha.zkfc.port8019 (ZKFC RPC port)
dfs.ha.tail-edits.periodWe set 5s in the lab so the standby follows quickly (the shipped default is larger)

HA at three levels

Pick your level. New to this? Start at Beginner and climb.

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.

3 Types and ways to use it

Ways to protect the NameNode

No HA (Secondary NN)HA with NFS shared editsHA with QJM (standard)
If the NameNode diesCluster down until manual restoreStandby takes overStandby takes over in seconds
Shared edit logNone (only checkpoints)One NFS filer3 or 5 JournalNodes, majority
Single point of failure?Yes, the NameNodeThe NFS filerNo (tolerates 1 of 3 JN failures)
Split-brain protectionNot neededFencing onlyEpochs + fencing
Who checkpointsSecondary NameNodeStandbyStandby

Failover modes and admin commands

Automatic

dfs.ha.automatic-failover.enabled=true + ZooKeeper + ZKFC. Used in production.

ZooKeeper

Manual / graceful

hdfs haadmin -failover nn2 nn1 for planned maintenance or patching.

haadmin

Check state

hdfs haadmin -getAllServiceState, or tag.HAState in the NameNode's /jmx.

monitoring

Observer reads

Hadoop 3 Observer NameNodes serve read-only requests to take load off the active.

scale reads

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

4 Step by step

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

  1. The HA configuration

    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)
    The client sees one name, 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.
  2. First start: format, bootstrap, elect

    # 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 -getAllServiceState
    nn1:8020 active nn2:8020 standby
    nn2 copies nn1's fsimage; both ZKFCs race for the lock znode in ZooKeeper, and nn1 won. ZooKeeper now holds /hadoop-ha/canteen/ActiveStandbyElectorLock and ActiveBreadCrumb (we listed them with the ZooKeeper client).
  3. Crash the active while a client writes

    docker kill lab7-nn1-1
    15: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: 12
    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.
  4. What the standby's ZKFC did

    grep … /var/log/hadoop/hadoop-hadoop-zkfc-nn2.out
    15: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 state
    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.
  5. Freeze the active (the zombie test)

    docker pause lab7-nn1-1 ; sleep 25 ; docker unpause lab7-nn1-1
    15: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=active
    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.
  6. Lose the JournalNode majority

    docker stop lab7-jn3-1 ; … ; docker stop lab7-jn2-1
    15: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.
    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 (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.

5 See it happen

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.

Automatic failover with QJM 6 steps
What you see admin@edge : ~ $ hdfs haadmin -getAllServiceStatenn1:8020 activenn2:8020 standbywrite 1 ok · write 2 ok · write 3 ok 15:44:25 nn1 crashes15:44:27 nn2 is standby15:44:31 nn2 is standbywrite 4 … waiting 15:44:36 nn2 is active15:44:46 write 4 ok (took 22 s)15:44:48 write 5 ok (took 2 s)files written: 12 of 12 (another day: nn1 active again)15:40:51 nn1 frozen (pause)15:41:16 nn2=activeepochs 7 → 8 15:41:19 nn1 wakes upIPC's epoch 7 is less thanthe last promised epoch 8(from jn1, jn2 and jn3) SHUTDOWN_MSG: Shutting downNameNode at nn1nn1:8020=Failed nn2:8020=active myphatshala.in
Behind the screen ZooKeeperlock znode nn1 + ZKFC nn2 + ZKFC jn1jn2jn3 ACTIVE · epoch 2standby, tailingnn1 holds the lock; edits go to all 3 JNs crashedno heartbeats to ZooKeeper; session expires in ~10 s ACTIVE · epoch 3nn2's ZKFC takes the lock, fences, promotes nn2 frozen · epoch 7ACTIVE · epoch 8JNs promise: epoch ≥ 8 onlya new active always gets a higher epoch from a JN majority thinks: ACTIVE, epoch 7ACTIVE · epoch 8every JN rejects epoch 7 shut downACTIVE · epoch 8one writer, one history: no split-brain myphatshala.in

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.

6 Real-world use

HA at work

activestandby

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 NameNodes→3 or 5 JournalNodes→ZooKeeper
patch

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.

Patch standby→Graceful failover→Patch the other
GC pause

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.

Pause > 10 s→Failover→Old NN exits
alert:HAState changed

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.

/jmx→HAState watch→On-call alert

Simplified; organisations differ in tools and policies.

7 Run the code

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)
nn2 is ACTIVE with epoch 8 nn2 wrote 'mkdir /results' (accepted by 3 JNs) nn1 REJECTED: epoch 7 is less than promised 8 edit log: ['mkdir /results']
  • 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=3
lab7-datanode-1 Up About a minute lab7-datanode-2 Up About a minute lab7-datanode-3 Up About a minute lab7-jn1-1 Up About a minute lab7-jn2-1 Up About a minute lab7-jn3-1 Up About a minute lab7-nn1-1 Up About a minute lab7-nn2-1 Up About a minute lab7-zk-1 Up About a minute $ docker exec lab7-nn1-1 hdfs haadmin -getAllServiceState nn1:8020 active nn2:8020 standby

Real 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)
15: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:28 nn2 is standby 15:44:29 nn2 is standby 15:44:31 nn2 is standby 15:44:32 nn2 is standby 15:44:33 nn2 is standby 15:44:34 nn2 is standby 15:44:36 nn2 is active --- client log (datanode-1) 15:44:19 write 1 ok (took 2 s) 15:44:21 write 2 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) 15:44:50 write 6 ok (took 2 s) 15:44:52 write 7 ok (took 2 s) 15:44:55 write 8 ok (took 3 s) 15:44:57 write 9 ok (took 2 s) 15:44:59 write 10 ok (took 2 s) 15:45:01 write 11 ok (took 2 s) 15:45:03 write 12 ok (took 2 s) --- ZKFC log on nn2 2026-10-21 15:44:34 INFO ActiveStandbyElector:1019 - Checking for any old active which needs to be fenced... 2026-10-21 15:44:35 INFO NodeFencer:97 - ====== Beginning Service Fencing Process... ====== 2026-10-21 15:44:35 INFO NodeFencer:115 - ====== Fencing successful by method org.apache.hadoop.ha.ShellCommandFencer(/bin/true) ====== 2026-10-21 15:44:35 INFO ZKFailoverController:412 - Trying to make NameNode at nn2/172.20.0.7:8020 active... 2026-10-21 15:44:35 INFO ZKFailoverController:419 - Successfully transitioned NameNode at nn2/172.20.0.7:8020 to active state 15:45:04 files written: 12

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)
15:40:49 before : nn1:8020=active nn2:8020=standby | epochs: jn1=7 jn2=7 jn3=7 15:40:51 >>> docker pause lab7-nn1-1 (active NameNode frozen, e.g. a 60 s GC pause) 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) --- old active nn1, last log lines 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 2026-10-21 15:41:20 INFO ExitUtil:248 - Exiting with status 1: Error: flush failed for required journal (JournalAndStream(mgr=QJM to [172.20.0.4:8485, 172.20.0.2:8485, 172.20.0.5:8485], str 2026-10-21 15:41:20 INFO NameNode:822 - SHUTDOWN_MSG: SHUTDOWN_MSG: Shutting down NameNode at nn1/172.20.0.6 15:41:39 after : nn1:8020=Failed nn2:8020=active

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.sh
15: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. nn1: SHUTDOWN_MSG: Shutting down NameNode at nn1/172.20.0.6 nn2: java.io.IOException: Timed out waiting 20000ms for a quorum of nodes to respond. nn2: SHUTDOWN_MSG: Shutting down NameNode at nn2/172.20.0.7

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

8 Line by line

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.

Example 1BeginnerTiming · failover

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.

Input
15:44:25 · 15:44:34 · 15:44:35
Expected output
detection: 9 s   takeover: 1 s   total: 10 s

Approach

  1. Convert times to seconds.
  2. Detection = check time − crash.
  3. 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.

Example 2IntermediateSizing · JournalNodes

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.

Input
p(down) = 0.01
Expected output
3 JNs: majority lost 0.029800% of the time
5 JNs: majority lost 0.000985% of the time

Approach

  1. Majority lost when more than n/2 JNs are down at once.
  2. Sum binomial probabilities.
  3. 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.

Example 3AdvancedCost · downtime

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?

Input
55 min · 300 × ₹900/h · 40 × ₹15,000 · ₹6 lakh
Expected output
one outage costs about Rs 847,500
HA pays for itself after 1 outage(s)

Approach

  1. Cost per outage = people + jobs.
  2. Divide HA cost by outage cost.
  3. 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.

9 Quiz and revision

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.

P1Easy

Failure tolerance

For 3 and 5 JournalNodes, print how many may fail while edits can still be written.

Input
3, 5
Expected output
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")
3 JNs tolerate 1 failure 5 JNs tolerate 2 failures

How it works: 3 JNs tolerate 1 failure; 5 tolerate 2.

P2Easy

Parse haadmin output

Find the active NameNode from hdfs haadmin -getAllServiceState output.

Input
two lines
Expected output
['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"])
['nn2:8020']

How it works: nn2:8020. Monitoring scripts parse exactly this.

P3Easy

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?

Input
2 → 8
Expected output
6 new active writers
Show solution and explanation
print(8 - 2, "new active writers")
6 new active writers

How it works: 6: every transition to active (automatic or manual) takes a new epoch.

P4Medium

Slowest write

From the client log, find the slowest write and its duration.

Input
write lines
Expected output
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)
15:44:46 write 4 ok (took 22 s)

How it works: Write 4, 22 s: the client's 20 s connect timeout to the dead nn1 address plus the retry on nn2.

P5Medium

Detect a failover in JMX samples

Given HAState samples every 5 s from nn2, print when it became active.

Input
timestamps and states
Expected output
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 = s
state change at 15:44:40 standby -> active

How it works: A simple change detector is how failover alerts are built from /jmx.

P6Medium

Quorum check

Write a function that says whether the active can write, given which of 3 JNs are up.

Input
[True, False, True]
Expected output
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]))
True False

How it works: True with 2 of 3, False with 1 of 3, matching the lab.

P7Business problem

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?

Input
12 × 20 min × 300 × ₹900/h
Expected output
Rs 1,080,000 per year
Show solution and explanation
print(f"Rs {12 * 20 / 60 * 300 * 900:,.0f} per year")
Rs 1,080,000 per year

How it works: ₹10.8 lakh a year (illustrative) from rolling patches with haadmin -failover.

P8Advanced

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.

Input
3 JNs at epoch 4
Expected output
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))
nn1: 3 promises -> ACTIVE nn2: 0 promises -> refused

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.

HDFS · architectureFresher

“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.
Operations · incidentExperienced

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.
Design · placementExperienced

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.
Concepts · split-brainExperienced

“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”.