whitepaper: 352/169 — wave4 MEDIUM (hadoop/hbase/nova/neutron/openstack) + fix odl-0002 dup
This commit is contained in:
parent
9934133dcf
commit
835ae73b0f
82 changed files with 5931 additions and 6 deletions
39
defects/hadoop/patch/hadoop-0001-ticket.md
Normal file
39
defects/hadoop/patch/hadoop-0001-ticket.md
Normal 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
|
||||
227–235 (`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.
|
||||
61
defects/hadoop/patch/hadoop-0001.patch
Normal file
61
defects/hadoop/patch/hadoop-0001.patch
Normal 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);
|
||||
}
|
||||
}
|
||||
44
defects/hadoop/patch/hadoop-0002-ticket.md
Normal file
44
defects/hadoop/patch/hadoop-0002-ticket.md
Normal 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
|
||||
456–501 (`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).
|
||||
27
defects/hadoop/patch/hadoop-0002.patch
Normal file
27
defects/hadoop/patch/hadoop-0002.patch
Normal 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);
|
||||
}
|
||||
}
|
||||
38
defects/hadoop/patch/hadoop-0003-ticket.md
Normal file
38
defects/hadoop/patch/hadoop-0003-ticket.md
Normal 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
|
||||
780–805 (`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).
|
||||
31
defects/hadoop/patch/hadoop-0003.patch
Normal file
31
defects/hadoop/patch/hadoop-0003.patch
Normal 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) {
|
||||
Loading…
Add table
Add a link
Reference in a new issue