java-topology/defects/hadoop/patch/hadoop-0004-ticket.md

2.8 KiB
Raw Blame History

UNDF: UNDF-2026-000000099

hadoop-0004: HDFS Balancer Dispatcher — srcBlocks ArrayList.contains() O(n²) in block receive loop

Severity

HIGH — called on every block report during HDFS balancing; grows quadratically with blocks per source datanode

File

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/balancer/Dispatcher.java

Lines

807 (srcBlocks field declaration), 910 (!srcBlocks.contains(block) inside per-block loop)

Also: hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/balancer/MovedBlocks.java Line 43 (locations field), 56 (addLocation does locations.contains(loc) inside a per-datanode loop)

Pattern

CWE-407: O(n) ArrayList.contains() inside a loop.

Dispatcher.java

// DEFECTIVE (line 807)
private final List<DBlock> srcBlocks = new ArrayList<DBlock>();

// DEFECTIVE (line 910) — inside for (BlockWithLocations blkLocs : newBlksLocs.getBlocks())
if (!srcBlocks.contains(block) && isGoodBlockCandidate(block)) {
    srcBlocks.add(block);
}

srcBlocks is an ArrayList. Each call to getReceivedBlocks() iterates all blocks in newBlksLocs and checks srcBlocks.contains(block) — O(S) where S = current size of srcBlocks. For B blocks reported, total cost is O(B × S) = O(B²) as S grows toward B.

During HDFS balancing of a large cluster (millions of blocks per datanode), this becomes the dominant inner-loop cost.

MovedBlocks.java (same PR)

// DEFECTIVE (line 43)
protected final List<L> locations = new ArrayList<L>(3);

// DEFECTIVE (line 56) — called inside the same block location update loop
public synchronized void addLocation(L loc) {
    if (!locations.contains(loc)) {   // O(L) per call
        locations.add(loc);
    }
}

addLocation is called for each datanode UUID in blkLocs.getDatanodeUuids() for each block. With D datanodes/block and L existing locations per block: O(D × L). Across B blocks: O(B × D × L).

Fix

Dispatcher.java: Replace ArrayList<DBlock> with LinkedHashSet<DBlock> (preserves insertion order for deterministic iteration, O(1) contains):

// FIXED
private final Set<DBlock> srcBlocks = new LinkedHashSet<DBlock>();

MovedBlocks.java: Replace ArrayList<L> with LinkedHashSet<L>:

// FIXED
protected final Set<L> locations = new LinkedHashSet<L>(3);

Complexity

  • Before: O(B²) for block reporting during balance; O(B × D × L) for location updates
  • After: O(B) for block reporting; O(B × D) for location updates

Impact

HDFS Balancer is a background maintenance operation on large clusters. For a node with 100k blocks, this defect makes block reporting O(10^10) rather than O(10^5). Real-world balancer runs are visibly slow on large clusters; this is a documented operational pain point.