wave10b/c: 465/212 hudi/iceberg/scylladb/yugabyte/foundationdb

This commit is contained in:
russell@unturf.com 2026-03-27 17:12:25 -04:00
parent f7fa333977
commit 70702dff5c
38 changed files with 2073 additions and 5 deletions

View file

@ -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
```

View 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);
}
}