wave10b/c: 465/212 hudi/iceberg/scylladb/yugabyte/foundationdb
This commit is contained in:
parent
f7fa333977
commit
70702dff5c
38 changed files with 2073 additions and 5 deletions
|
|
@ -0,0 +1,79 @@
|
|||
# foundationdb-0001 — canLaunchSrc: std::count nested inside O(S×R) double loop
|
||||
|
||||
**Project:** FoundationDB
|
||||
**File:** `fdbserver/datadistributor/DDRelocationQueue.actor.cpp`
|
||||
**Function:** `canLaunchSrc`
|
||||
**Severity:** MEDIUM
|
||||
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
|
||||
|
||||
## Defect
|
||||
|
||||
`canLaunchSrc` checks whether a data relocation can be launched without overloading
|
||||
source servers. The outer loop iterates over `relocation.src` (team size, S = 3–5
|
||||
storage servers). The inner loop iterates over `cancellableRelocations` (the queue of
|
||||
in-flight cancellable moves, R entries). Inside the inner loop, `std::count` performs
|
||||
an O(S') linear scan of each cancellable relocation's `src` vector (also of size ~S'):
|
||||
|
||||
```cpp
|
||||
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
|
||||
auto busyCopy = busymap[relocation.src[i]];
|
||||
for (int j = 0; j < cancellableRelocations.size(); j++) { // O(R)
|
||||
auto& servers = cancellableRelocations[j].src;
|
||||
if (std::count(servers.begin(), servers.end(), relocation.src[i])) // O(S')
|
||||
busyCopy.removeWork(...);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Total: **O(S × R × S')** where R grows with the number of concurrent relocations.
|
||||
In a large cluster undergoing rebalancing, R can reach hundreds or thousands,
|
||||
making this O(n²) in the relocation queue depth.
|
||||
|
||||
## Fix
|
||||
|
||||
Pre-build an `unordered_set<UID>` for each cancellable relocation's `src` servers
|
||||
before the outer loop (or use a flat `unordered_map<UID, work_to_cancel>` indexed
|
||||
by server). The inner loop then becomes an O(1) map lookup.
|
||||
|
||||
```cpp
|
||||
// Build per-server cancellable work map once
|
||||
std::unordered_map<UID, std::vector<int>> cancellable_by_server;
|
||||
for (int j = 0; j < cancellableRelocations.size(); j++) {
|
||||
for (const auto& uid : cancellableRelocations[j].src) {
|
||||
cancellable_by_server[uid].push_back(j);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < relocation.src.size(); i++) {
|
||||
auto busyCopy = busymap[relocation.src[i]];
|
||||
// O(1) map lookup instead of O(R * S') double scan
|
||||
auto it = cancellable_by_server.find(relocation.src[i]);
|
||||
if (it != cancellable_by_server.end()) {
|
||||
for (int j : it->second) {
|
||||
busyCopy.removeWork(cancellableRelocations[j].priority,
|
||||
cancellableRelocations[j].workFactor);
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Complexity: **O(S + R×S' + S)** = **O(R×S')** build + **O(S)** lookup, dominated by
|
||||
the one-time build. This removes the multiplicative factor between S and the inner
|
||||
O(R×S') scan.
|
||||
|
||||
## Impact
|
||||
|
||||
During cluster rebalancing or after storage server failures, `canLaunchSrc` is called
|
||||
for every candidate relocation in the queue (line 1090, inside a hot loop). As the
|
||||
queue grows, each call degrades from O(R) to O(S×R×S') causing compounding slowdown
|
||||
in data distribution scheduling decisions.
|
||||
|
||||
## Location
|
||||
|
||||
```
|
||||
fdbserver/datadistributor/DDRelocationQueue.actor.cpp
|
||||
canLaunchSrc() lines 436–477
|
||||
call site line 1090
|
||||
```
|
||||
239
defects/foundationdb/unit/FoundationdbTest.java
Normal file
239
defects/foundationdb/unit/FoundationdbTest.java
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* foundationdb-0001: canLaunchSrc std::count nested loop O(S × R × S')
|
||||
*
|
||||
* Models the data structures in DDRelocationQueue.actor.cpp:
|
||||
* outer loop: relocation.src (S servers)
|
||||
* inner loop: cancellableRelocations (R entries)
|
||||
* innermost: std::count on servers vector (S')
|
||||
*
|
||||
* Compile: javac -d . FoundationdbTest.java
|
||||
* Run: java unit.FoundationdbTest
|
||||
*/
|
||||
public class FoundationdbTest {
|
||||
|
||||
// Simulate a UID (storage server ID)
|
||||
static class UID {
|
||||
final long v;
|
||||
UID(long v) { this.v = v; }
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof UID && ((UID)o).v == v;
|
||||
}
|
||||
@Override public int hashCode() { return Long.hashCode(v); }
|
||||
@Override public String toString() { return "UID(" + v + ")"; }
|
||||
}
|
||||
|
||||
// Simulate RelocateData.src (list of source server UIDs)
|
||||
static class RelocateData {
|
||||
final List<UID> src;
|
||||
final int priority;
|
||||
final int workFactor;
|
||||
RelocateData(List<UID> src, int priority, int workFactor) {
|
||||
this.src = src; this.priority = priority; this.workFactor = workFactor;
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate Busyness.addWork / removeWork
|
||||
static class Busyness {
|
||||
int load = 0;
|
||||
Busyness(int load) { this.load = load; }
|
||||
Busyness copy() { return new Busyness(load); }
|
||||
void removeWork(int priority, int workFactor) { load = Math.max(0, load - workFactor); }
|
||||
boolean canLaunch(int priority, int workFactor) { return load + workFactor <= 100; }
|
||||
}
|
||||
|
||||
// ---- DEFECTIVE implementation (O(S × R × S')) ----
|
||||
static boolean canLaunchSrc_defective(
|
||||
RelocateData relocation,
|
||||
Map<UID, Busyness> busymap,
|
||||
List<RelocateData> cancellableRelocations,
|
||||
int[] comparisonCount) {
|
||||
int workFactor = 10;
|
||||
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
|
||||
|
||||
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
|
||||
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
|
||||
for (int j = 0; j < cancellableRelocations.size(); j++) { // O(R)
|
||||
List<UID> servers = cancellableRelocations.get(j).src;
|
||||
// std::count equivalent — O(S') linear scan
|
||||
boolean found = false;
|
||||
for (UID uid : servers) { // O(S')
|
||||
comparisonCount[0]++;
|
||||
if (uid.equals(relocation.src.get(i))) { found = true; break; }
|
||||
}
|
||||
if (found) {
|
||||
busyCopy.removeWork(cancellableRelocations.get(j).priority,
|
||||
cancellableRelocations.get(j).workFactor);
|
||||
}
|
||||
}
|
||||
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
|
||||
--neededServers;
|
||||
if (neededServers == 0) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- FIXED implementation: pre-build map, O(R×S' + S) ----
|
||||
static boolean canLaunchSrc_fixed(
|
||||
RelocateData relocation,
|
||||
Map<UID, Busyness> busymap,
|
||||
List<RelocateData> cancellableRelocations,
|
||||
int[] comparisonCount) {
|
||||
int workFactor = 10;
|
||||
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
|
||||
|
||||
// Build inverted index: UID -> list of cancellable relocation indices O(R×S')
|
||||
Map<UID, List<Integer>> cancellableByServer = new HashMap<>();
|
||||
for (int j = 0; j < cancellableRelocations.size(); j++) {
|
||||
for (UID uid : cancellableRelocations.get(j).src) {
|
||||
comparisonCount[0]++;
|
||||
cancellableByServer.computeIfAbsent(uid, k -> new ArrayList<>()).add(j);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
|
||||
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
|
||||
List<Integer> indices = cancellableByServer.get(relocation.src.get(i));
|
||||
if (indices != null) {
|
||||
for (int j : indices) {
|
||||
busyCopy.removeWork(cancellableRelocations.get(j).priority,
|
||||
cancellableRelocations.get(j).workFactor);
|
||||
}
|
||||
}
|
||||
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
|
||||
--neededServers;
|
||||
if (neededServers == 0) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build a test scenario
|
||||
static Map<UID, Busyness> buildBusymap(List<UID> allServers) {
|
||||
Map<UID, Busyness> map = new HashMap<>();
|
||||
for (UID uid : allServers) map.put(uid, new Busyness(50));
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int pass = 0, fail = 0;
|
||||
|
||||
// -- Test 1: correctness — small scenario
|
||||
{
|
||||
List<UID> allServers = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) allServers.add(new UID(i));
|
||||
|
||||
// The relocation being considered
|
||||
RelocateData candidate = new RelocateData(
|
||||
Arrays.asList(allServers.get(0), allServers.get(1), allServers.get(2)),
|
||||
100, 10);
|
||||
|
||||
// Some cancellable in-flight moves that involve the same servers
|
||||
List<RelocateData> cancellable = new ArrayList<>();
|
||||
cancellable.add(new RelocateData(Arrays.asList(allServers.get(0), allServers.get(3)), 50, 30));
|
||||
cancellable.add(new RelocateData(Arrays.asList(allServers.get(1), allServers.get(4)), 50, 30));
|
||||
|
||||
Map<UID, Busyness> busymap = buildBusymap(allServers);
|
||||
|
||||
int[] cmpDefective = {0};
|
||||
int[] cmpFixed = {0};
|
||||
boolean resultDef = canLaunchSrc_defective(candidate, busymap, cancellable, cmpDefective);
|
||||
boolean resultFix = canLaunchSrc_fixed(candidate, busymap, cancellable, cmpFixed);
|
||||
|
||||
if (resultDef == resultFix) {
|
||||
System.out.printf("PASS test1: correctness — defective=%b fixed=%b%n", resultDef, resultFix);
|
||||
pass++;
|
||||
} else {
|
||||
System.out.printf("FAIL test1: defective=%b fixed=%b%n", resultDef, resultFix);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Test 2: complexity — large R queue demonstrates O(S×R×S') vs O(R×S' + S)
|
||||
// Use servers at max load so canLaunch always fails, forcing the full loop
|
||||
{
|
||||
int S = 3; // team size (relocation.src)
|
||||
int R = 500; // queue depth (cancellable relocations)
|
||||
int S2 = 3; // src team size of each cancellable relocation
|
||||
|
||||
// Need enough servers: 3 (candidate) + R*S2 (cancellable) = 3 + 500*3 = 1503
|
||||
List<UID> servers = new ArrayList<>();
|
||||
for (int i = 0; i < 3000; i++) servers.add(new UID(i));
|
||||
|
||||
RelocateData candidate = new RelocateData(
|
||||
servers.subList(0, S), 100, 10);
|
||||
|
||||
// cancellable relocations — use disjoint servers so no work is removed
|
||||
// and servers stay busy, forcing all S iterations to complete
|
||||
List<RelocateData> cancellable = new ArrayList<>();
|
||||
for (int j = 0; j < R; j++) {
|
||||
int base = 100 + j * S2; // disjoint from candidate servers (0,1,2)
|
||||
cancellable.add(new RelocateData(
|
||||
servers.subList(base, base + S2), 50, 5));
|
||||
}
|
||||
|
||||
// Overload the source servers so canLaunch never returns true early
|
||||
Map<UID, Busyness> busymapDef = new HashMap<>();
|
||||
Map<UID, Busyness> busymapFix = new HashMap<>();
|
||||
for (UID uid : servers) {
|
||||
busymapDef.put(uid, new Busyness(95)); // high load — can't launch
|
||||
busymapFix.put(uid, new Busyness(95));
|
||||
}
|
||||
|
||||
int[] cmpDefective = {0};
|
||||
int[] cmpFixed = {0};
|
||||
|
||||
boolean resDef = canLaunchSrc_defective(candidate, busymapDef, cancellable, cmpDefective);
|
||||
boolean resFix = canLaunchSrc_fixed(candidate, busymapFix, cancellable, cmpFixed);
|
||||
|
||||
System.out.printf("test2: defective comparisons=%d fixed comparisons=%d (S=%d R=%d S2=%d)%n",
|
||||
cmpDefective[0], cmpFixed[0], S, R, S2);
|
||||
System.out.printf("test2: defective result=%b fixed result=%b%n", resDef, resFix);
|
||||
|
||||
// Fixed must produce same result with fewer comparisons (no S multiplier)
|
||||
// Defective: S * R * S2 = 3 * 500 * 3 = 4500 comparisons
|
||||
// Fixed: R * S2 (build) = 500 * 3 = 1500 comparisons (no outer S factor)
|
||||
boolean sameResult = (resDef == resFix);
|
||||
boolean moreEfficient = cmpFixed[0] < cmpDefective[0];
|
||||
if (sameResult && moreEfficient) {
|
||||
System.out.println("PASS test2: fixed is more efficient and produces same result");
|
||||
pass++;
|
||||
} else {
|
||||
System.out.printf("FAIL test2: sameResult=%b moreEfficient=%b%n", sameResult, moreEfficient);
|
||||
fail++;
|
||||
}
|
||||
|
||||
int expectedDefective = S * R * S2;
|
||||
int expectedFixed = R * S2; // build phase dominates
|
||||
System.out.printf(" Expected defective ~O(S×R×S')=%d, actual=%d%n", expectedDefective, cmpDefective[0]);
|
||||
System.out.printf(" Expected fixed ~O(R×S')=%d, actual=%d%n", expectedFixed, cmpFixed[0]);
|
||||
}
|
||||
|
||||
// -- Test 3: no cancellable relocations — both return same result
|
||||
{
|
||||
List<UID> servers = new ArrayList<>();
|
||||
for (int i = 0; i < 3; i++) servers.add(new UID(i));
|
||||
RelocateData candidate = new RelocateData(servers, 100, 5);
|
||||
Map<UID, Busyness> busymap = buildBusymap(servers);
|
||||
|
||||
int[] c1 = {0}, c2 = {0};
|
||||
boolean r1 = canLaunchSrc_defective(candidate, busymap, new ArrayList<>(), c1);
|
||||
boolean r2 = canLaunchSrc_fixed(candidate, busymap, new ArrayList<>(), c2);
|
||||
|
||||
if (r1 == r2) {
|
||||
System.out.printf("PASS test3: empty cancellable — both=%b%n", r1);
|
||||
pass++;
|
||||
} else {
|
||||
System.out.printf("FAIL test3: mismatch on empty cancellable%n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.printf("%nResults: %d passed, %d failed%n", pass, fail);
|
||||
if (fail > 0) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue