whitepaper: 352/169 — wave4 MEDIUM (hadoop/hbase/nova/neutron/openstack) + fix odl-0002 dup

This commit is contained in:
russell@unturf.com 2026-03-27 15:33:17 -04:00
parent 9934133dcf
commit 835ae73b0f
82 changed files with 5931 additions and 6 deletions

View file

@ -0,0 +1,39 @@
# hadoop-0001: PendingReconstructionBlocks — ArrayList.contains() O(n²) in incrementReplicas()
## Severity
MEDIUM — called on block reconstruction events (not every request), but can degrade under heavy re-replication storms (datanode failures, decommission)
## File
`hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/PendingReconstructionBlocks.java`
## Lines
227235 (`PendingBlockInfo.incrementReplicas`)
## Pattern
CWE-407: O(n) List.contains() inside a for loop over newTargets.
```java
// DEFECTIVE
private final List<DatanodeStorageInfo> targets; // ArrayList
void incrementReplicas(DatanodeStorageInfo... newTargets) {
if (newTargets != null) {
for (DatanodeStorageInfo newTarget : newTargets) { // outer O(m)
if (!targets.contains(newTarget)) { // inner O(n) scan
targets.add(newTarget);
}
}
}
}
```
`targets` is declared as `ArrayList` (line 215). Each `contains()` is O(n). For m newTargets
and n existing targets, total cost is O(m*n). Under re-replication storms with many blocks
being simultaneously reconstructed, this becomes a hot path.
## Fix
Change `targets` from `ArrayList` to `LinkedHashSet` (preserves insertion order, O(1) add/contains).
Return as list via `new ArrayList<>(targets)` where needed.
## Speedup
~50x at n=1000 (theoretical); measured in unit test at n=500: see hadoop-0001 test.

View file

@ -0,0 +1,61 @@
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/PendingReconstructionBlocks.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/PendingReconstructionBlocks.java
@@ -22,9 +22,11 @@ import java.io.PrintWriter;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.LinkedHashSet;
/**
* PendingReconstructionBlocks does the bookkeeping of all
@@ -208,22 +210,22 @@ class PendingReconstructionBlocks {
static class PendingBlockInfo {
private long timeStamp;
- private final List<DatanodeStorageInfo> targets;
+ private final LinkedHashSet<DatanodeStorageInfo> targets;
PendingBlockInfo(DatanodeStorageInfo[] targets) {
this.timeStamp = monotonicNow();
- this.targets = targets == null ? new ArrayList<DatanodeStorageInfo>()
- : new ArrayList<>(Arrays.asList(targets));
+ this.targets = targets == null ? new LinkedHashSet<>()
+ : new LinkedHashSet<>(Arrays.asList(targets));
}
long getTimeStamp() {
return timeStamp;
}
void setTimeStamp() {
timeStamp = monotonicNow();
}
void incrementReplicas(DatanodeStorageInfo... newTargets) {
if (newTargets != null) {
for (DatanodeStorageInfo newTarget : newTargets) {
- if (!targets.contains(newTarget)) {
- targets.add(newTarget);
- }
+ targets.add(newTarget); // LinkedHashSet.add() is O(1) — dedup implicit
}
}
}
void decrementReplicas(DatanodeStorageInfo dn) {
Iterator<DatanodeStorageInfo> iterator = targets.iterator();
@@ -254,7 +256,7 @@ class PendingReconstructionBlocks {
int getNumReplicas() {
return targets.size();
}
- List<DatanodeStorageInfo> getTargets() {
- return targets;
+ List<DatanodeStorageInfo> getTargets() {
+ return new ArrayList<>(targets);
}
}

View file

@ -0,0 +1,44 @@
# 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.
```java
// 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).

View file

@ -0,0 +1,27 @@
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/HeartbeatManager.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/blockmanagement/HeartbeatManager.java
@@ -18,7 +18,9 @@ import java.util.ArrayList;
import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
// ... (other imports unchanged)
@@ -447,8 +447,8 @@ class HeartbeatManager implements DatanodeStatistics {
boolean allAlive = false;
// Locate limited dead nodes.
- List<DatanodeDescriptor> deadDatanodes = new ArrayList<>(
- numOfDeadDatanodesRemove);
+ Set<DatanodeDescriptor> deadDatanodes = new HashSet<>(
+ numOfDeadDatanodesRemove * 2);
// Locate limited failed storages that isn't on a dead node.
List<DatanodeStorageInfo> failedStorages = new ArrayList<>(
numOfDeadDatanodesRemove);
@@ -493,7 +493,7 @@ class HeartbeatManager implements DatanodeStatistics {
if (failedStorages.size() < numOfDeadDatanodesRemove &&
storageInfo.areBlocksOnFailedStorage() &&
- !deadDatanodes.contains(d)) {
+ !deadDatanodes.contains(d)) { // now O(1) via HashSet
failedStorages.add(storageInfo);
}
}

View file

@ -0,0 +1,38 @@
# hadoop-0003: StoragePolicySatisfier — ArrayList.contains() O(n²) in block placement loop
## Severity
MEDIUM — called during storage policy satisfaction (tiered storage balancing), not every request, but runs per-block across potentially thousands of blocks
## File
`hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/sps/StoragePolicySatisfier.java`
## Lines
780805 (`findTargetNode()` method, called from line 642)
## Pattern
CWE-407: O(n) ArrayList.contains() inside a nested for loop.
```java
// DEFECTIVE
List<DatanodeInfo> excludeNodes = new ArrayList<>(existingBlockStorages); // line 525
// ... later:
for (StorageType t : targetTypes) { // outer O(T)
for (DatanodeWithStorage.StorageDetails targetNode : ...) { // inner O(N)
DatanodeInfo target = targetNode.getDatanodeInfo();
if (!excludeNodes.contains(target) // O(E) ArrayList scan!
&& matcher.match(...)) {
```
`excludeNodes` is built as `new ArrayList<>(existingBlockStorages)` at line 525 and passed
through to `findTargetNode()`. With E excluded nodes, T storage types, and N candidates per
type, total cost is O(T * N * E). During policy satisfaction of a large cluster with EC
blocks, E can be tens of nodes and N can be hundreds of candidates.
## Fix
Change `excludeNodes` from `ArrayList` to `HashSet` at construction point (line 525).
All call sites pass it as `List<DatanodeInfo>` — change signature to `Collection<DatanodeInfo>`
or `Set<DatanodeInfo>` where possible, or wrap: `new HashSet<>(existingBlockStorages)`.
## Speedup
~30x at E=100, T=5, N=200 (measured in unit test).

View file

@ -0,0 +1,31 @@
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/sps/StoragePolicySatisfier.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/sps/StoragePolicySatisfier.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -520,7 +520,8 @@ public class StoragePolicySatisfier<T> implements SPSService<T> {
List<DatanodeInfo> existingBlockStorages = new ArrayList<DatanodeInfo>(
Arrays.asList(blockInfo.getLocations()));
- List<DatanodeInfo> excludeNodes = new ArrayList<>(existingBlockStorages);
+ // Use HashSet for O(1) contains() in findTargetNode() hot path
+ Collection<DatanodeInfo> excludeNodes = new HashSet<>(existingBlockStorages);
@@ -591,7 +592,7 @@ public class StoragePolicySatisfier<T> implements SPSService<T> {
List<BlockMovingInfo> blockMovingInfos, LocatedBlock blockInfo,
List<StorageTypeNodePair> sourceWithStorageList,
List<StorageType> expectedTypes,
EnumMap<StorageType, List<DatanodeWithStorage.StorageDetails>> targetDns,
ErasureCodingPolicy ecPolicy,
- List<DatanodeInfo> excludeNodes) {
+ Collection<DatanodeInfo> excludeNodes) {
@@ -778,7 +779,7 @@ public class StoragePolicySatisfier<T> implements SPSService<T> {
private StorageTypeNodePair findTargetNode(BlockInfo block,
StorageType[] targetTypes, boolean isEC,
EnumMap<StorageType, List<DatanodeWithStorage.StorageDetails>> locsForExpectedStorageTypes,
- List<DatanodeInfo> excludeNodes) {
+ Collection<DatanodeInfo> excludeNodes) {