HDFS Decommission Codes and Introduction
HDFS Decommission Codes and Introduction
Background of HDFS decommission
Currently there are 200+ nodes of 1.22PB HDFS3 and fewer than 100 nodes of 480T HDFS. The decommissioning of nodes is slow, migrating only about 150k blocks per hour on average. A node has about 600k blocks on average, and decommissioning one node takes roughly 4 hours.
Difference between node drop and node decommissioning
Node Decommissioning
First, add the node to the decommissioning list. This tells the NameNode and YARN to stop submitting new tasks and stop writing data to it. Then wait for the blocks on that node to be replicated throughout the cluster. During this time the decommissioning node is preferred as the srcNode (data source), because it has no write requests and its load is low. Other nodes copy blocks from this decommissioning node, so its load will be high during this period. Once all blocks have been replicated, the node state becomes Decommissioned, which can be viewed from the NameNode URL interface.
Note that a decommissioning DataNode also starts replicating 10 minutes and 30 seconds later. It does not start earlier just because decommissioning was actively initiated. The NameNode and DataNode always maintain a simple master/slave relationship: the NameNode never actively initiates any IPC call to the DataNode. Any operation the DataNode must cooperate on is returned via the DatanodeCommand carried on the heartbeat reply.
Node Drop
For example, forcibly stopping a DataNode, the physical machine dying (high load drop, sudden network failure, hardware failure, etc.) are all node drops. By default, 10 minutes 30 seconds later (mainly controlled by two parameters) the NameNode will detect that communication with the node is abnormal and mark it as dropped. The NameNode then looks up all blockids for that node (by IP) and the replica-holding machines, and schedules replication via the heartbeat mechanism. At this point the data source for replication is no longer the dropped node itself, but one of the other nodes holding a replica. Replica placement still follows rack awareness and the replica placement policy.
Network storm caused by a node drop
A node carrying tens of TB, or even hundreds of TB, and millions of blocks going offline will produce a large RPC storm. This is especially challenging for the NameNode in large, high-load clusters: it not only impacts production performance but also creates significant risk, particularly for clusters with bandwidth bottlenecks.
Generally, the NameNode detects a DataNode drop after 103s (heartbeat time) + 25min (NameNode check time, parameter dfs.namenode.heartbeat.recheck-interval) = 10min30s. If within those 10min30s the bandwidth stays saturated, RPC requests are delayed, and DataNode-to-NameNode communication degrades, it is easy for other nodes to keep falling offline, forming a vicious cycle. How should this be avoided?
The NameNode maintains a replication priority queue. For blocks with insufficient replicas, the block with only one replica remaining gets the highest replication priority. So in a cluster with replication factor 2, as soon as one replica goes wrong there is only one replica left, which is the highest priority, and it will be replicated in storm mode. If not controlled well, this can easily affect cluster performance or even bring the cluster down. It is therefore generally not recommended to run a cluster with a replication factor of 2.
Parameters controlling RPC storms caused by node drops
All three parameters live in hdfs-site.xml. Refer to the Apache Hadoop official website for details. Block replication speed is determined by two aspects: the rate at which the NameNode dispatches tasks, and the rate at which DataNodes replicate between themselves. The former can be thought of as the entrance; the latter as the exit.
These parameters are not available in Hadoop 2.7.3.
Ingress Parameters
Control task dispatch from the NameNode side. Changing this parameter requires a restart of the NameNode, not the DataNode.
1 | |
This parameter determines how many blocks the NameNode tells each DataNode to replicate on each heartbeat (3s) when it sends the task list. For example, in a 500-node cluster with this value set to 10, one heartbeat allows the NameNode to tell DataNodes to replicate 10*500=5000 blocks.
If a node drops/decommissions with 800,000 blocks needing replication, how long does it take the NameNode to finish dispatching all replication tasks to DataNodes?
1 | |
Egress Parameters
In contrast to the above, which controls dispatch from the NameNode, the two parameters below control things at the DataNode level. They also require a restart of the NameNode.
1 | |
This parameter controls the maximum number of replication threads on a DataNode. As mentioned above, block replication priority has five levels. This parameter controls replication of blocks other than the highest priority.
1 | |
This parameter controls the number of replication streams for blocks of all priorities on a DataNode, including the highest priority. It is typically used together with the two parameters above.
How to decommission quickly
The essence of making nodes decommission quickly is to improve replica replication speed. This is mainly controlled by the three parameters above: the first controls NameNode task dispatch, the other two control DataNode replication rate, with the precondition that normal production tasks are not affected. The smaller the cluster, the slower decommissioning, because the overall dispatch volume per heartbeat is lower.
For instance, on a 500-node cluster dfs.namenode.replication.work.multiplier.per.iteration=10; on a 50-node cluster this value should be set to 100 so that NameNode dispatch speed is comparable. Tune to your actual cluster size.
1 | |
In a test cluster of 15 HDFS nodes with ~80k blocks each on average, migration reached 63k blocks per hour, a 5x speedup over the initial 12k.
1 | |
Impact
Significantly increases CPU Load, CPU IO Wait, Disk IO Wait, and other resource utilization.
Source Code Analysis
Once the address in dfs.hosts.exclude is written to the configuration, refreshNodes can read the exclude list and act on it.
1 | |
Node addresses are read via dfs.exclude, the nodes are marked as decommissioning, and a monitor is used to track them.
1 | |
Before decommissioning, cluster stats are pre-deducted, for example capacityUsed, capacityTotal, and so on.
The monitor’s run() calls processPendingNodes() to take nodes from pendingNodes and decommission them.
1 | |
After obtaining nodes, check() is called, which retrieves block information for these nodes and processes the pending replication blocks.
1 | |
processPendingReplication()callsisBlockReplicatedOk()(the core), which decides whether a block needs reconstruction and pushes information into thereplication queuewithinblockManager.
1 | |
moveBlocksToPending()checks the queue length and the pending-replication limit, creates a block iterator for each node that needs maintenance or decommissioning, and then for every block keeps callingisBlockReplicatedOk()until the replication limit is reached, at which point it stops and exits the loop.
There is an interesting piece of locking logic inside moveBlocksToPending():
1 | |
It releases the write lock and immediately reacquires it. The blocksProcessed variable tracks how many blocks have been processed. Once it reaches the blocksPerLock threshold, releasing and reacquiring the write lock gives other threads waiting on the write lock a chance to run. This avoids holding the write lock for long periods and blocking other threads, improving concurrency.
checkForCompletedNodesiterates over node states and, if a node’s block migration is complete, adds the node to thetoRemovelist.processCompletedNodestakes thetoRemovelist, iterates through it, and sets final state (for exampledecommissioned), removing the node fromoutOfServiceNodeBlocksandpendingRep.