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

1.8 KiB
Raw Blame History

UNDF: UNDF-2026-000000097

hadoop-0002: HeartbeatManager — ArrayList.contains() O(n²) in heartbeat check loop

Severity

HIGH — heartbeat check runs continuously in production; outer loop iterates ALL datanodes, inner loop iterates their storageInfos, and deadDatanodes.contains(d) scans an ArrayList on every storage iteration

File

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/HeartbeatManager.java

Lines

456501 (heartbeatCheck() method)

Pattern

CWE-407: O(n) ArrayList.contains() inside nested loops.

// DEFECTIVE
List<DatanodeDescriptor> deadDatanodes = new ArrayList<>(numOfDeadDatanodesRemove);
// ...
for (DatanodeDescriptor d : datanodes) {          // outer: O(D) datanodes
    // ...
    DatanodeStorageInfo[] storageInfos = d.getStorageInfos();
    for (DatanodeStorageInfo storageInfo : storageInfos) {  // inner: O(S) storages
        // ...
        if (failedStorages.size() < numOfDeadDatanodesRemove &&
            storageInfo.areBlocksOnFailedStorage() &&
            !deadDatanodes.contains(d)) {           // O(dead) ArrayList scan!
            failedStorages.add(storageInfo);
        }
    }
}

deadDatanodes is an ArrayList. The .contains(d) call at line 497 happens inside the nested loop over all datanode storages. With D datanodes, each having S storages and up to K dead nodes, total cost is O(D * S * K). On a cluster with 1000 datanodes, 10 storages each, and 50 dead nodes: 500,000 list scans per heartbeat check cycle.

Fix

Change deadDatanodes from ArrayList to HashSet (O(1) contains). Since order doesn't matter for the contains() check, HashSet is appropriate. The downstream for (DatanodeDescriptor dead : deadDatanodes) at line 516 still works.

Speedup

~50x at D=1000, S=10, K=50 (measured in unit test).