java-topology/defects/solr/unit/SplitShardSubSlicesContains.java

163 lines
6.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CWE-407 unit test: solr-003
* SplitShardCmd.cleanupAfterFailedSplit — subSlices.contains(s.getName()) (List)
* inside a loop over all collection slices — O(S × n).
*
* Slow path: List<String>.contains() — O(n) per slice.
* Fast path: HashSet<String>.contains() — O(1) per slice.
*
* Compile: javac -d . SplitShardSubSlicesContains.java
* Run: java -ea unit.SplitShardSubSlicesContains
*/
public class SplitShardSubSlicesContains {
static final class Slice {
final String name;
final String state;
Slice(String name, String state) {
this.name = name;
this.state = state;
}
String getName() { return name; }
}
// ---- Slow: List.contains O(n) per slice ----
static Map<String, String> slowCleanupStateUpdate(List<Slice> allSlices, List<String> subSlices) {
Map<String, String> propMap = new HashMap<>();
for (Slice s : allSlices) {
if (!subSlices.contains(s.getName())) { // O(n) per iteration
continue;
}
propMap.put(s.getName(), "CONSTRUCTION");
}
return propMap;
}
static long countSlowOps(List<Slice> allSlices, List<String> subSlices) {
long ops = 0;
for (Slice s : allSlices) {
// each contains() scan is O(n) — count all comparisons
for (String sub : subSlices) {
ops++;
if (sub.equals(s.getName())) break;
}
}
return ops;
}
// ---- Fast: HashSet.contains O(1) per slice ----
static Map<String, String> fastCleanupStateUpdate(List<Slice> allSlices, List<String> subSlices) {
Set<String> subSliceSet = new HashSet<>(subSlices);
Map<String, String> propMap = new HashMap<>();
for (Slice s : allSlices) {
if (!subSliceSet.contains(s.getName())) { // O(1)
continue;
}
propMap.put(s.getName(), "CONSTRUCTION");
}
return propMap;
}
static long countFastOps(List<Slice> allSlices, List<String> subSlices) {
long ops = subSlices.size(); // build set: O(n)
ops += allSlices.size(); // O(1) per lookup
return ops;
}
public static void main(String[] args) {
int passed = 0;
// ---- Test 1: correctness at small scale ----
{
List<Slice> allSlices = new ArrayList<>();
List<String> subSlices = new ArrayList<>();
// 20 total slices, 3 sub-shards from split
for (int i = 0; i < 20; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
subSlices.add("shard5_0");
subSlices.add("shard5_1");
subSlices.add("shard5_2");
// Add the sub-slices to allSlices too
for (String s : subSlices) allSlices.add(new Slice(s, "INACTIVE"));
Map<String, String> slow = slowCleanupStateUpdate(allSlices, subSlices);
Map<String, String> fast = fastCleanupStateUpdate(allSlices, subSlices);
assert slow.equals(fast) : "FAIL: slow=" + slow + " fast=" + fast;
assert slow.size() == 3 : "FAIL: expected 3 in propMap, got " + slow.size();
System.out.println("PASS test1: correctness — propMap size=" + fast.size());
passed++;
}
// ---- Test 2: no subslices found ----
{
List<Slice> allSlices = new ArrayList<>();
List<String> subSlices = new ArrayList<>();
for (int i = 0; i < 50; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
subSlices.add("nonexistent_0");
subSlices.add("nonexistent_1");
Map<String, String> slow = slowCleanupStateUpdate(allSlices, subSlices);
Map<String, String> fast = fastCleanupStateUpdate(allSlices, subSlices);
assert slow.isEmpty() && fast.isEmpty() : "FAIL: expected empty maps";
System.out.println("PASS test2: no subslices found");
passed++;
}
// ---- Test 3: operation count ratio >= 5x (theoretical max ~n for n subslices) ----
{
int S = 5000; // large collection with many shards
int n = 8; // MAX_NUM_SUB_SHARDS
List<Slice> allSlices = new ArrayList<>();
List<String> subSlices = new ArrayList<>();
for (int i = 0; i < S; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
for (int i = 0; i < n; i++) subSlices.add("newshard" + i);
// none overlap — worst case for slow: each contains() exhausts all n items
long slowOps = countSlowOps(allSlices, subSlices);
long fastOps = countFastOps(allSlices, subSlices);
double ratio = (double) slowOps / fastOps;
System.out.printf("PASS test3: S=%d n=%d slow=%d fast=%d ratio=%.1fx%n",
S, n, slowOps, fastOps, ratio);
assert ratio >= 5.0 : "FAIL: ratio " + ratio + " < 5x";
passed++;
}
// ---- Test 4: timing benchmark (larger subSlices list to amplify O(n) factor) ----
{
// Use a larger synthetic n (100 sub-shards) to show timing ratio clearly.
// The real cap is MAX_NUM_SUB_SHARDS=8 but the algorithmic structure is identical.
int S = 5000;
int n = 100;
List<Slice> allSlices = new ArrayList<>();
List<String> subSlices = new ArrayList<>();
for (int i = 0; i < S; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
for (int i = 0; i < n; i++) subSlices.add("newshard" + i);
int reps = 500;
long t0 = System.nanoTime();
for (int r = 0; r < reps; r++) slowCleanupStateUpdate(allSlices, subSlices);
long slowNs = System.nanoTime() - t0;
long t1 = System.nanoTime();
for (int r = 0; r < reps; r++) fastCleanupStateUpdate(allSlices, subSlices);
long fastNs = System.nanoTime() - t1;
double ratio = (double) slowNs / fastNs;
System.out.printf("PASS test4: timing S=%d n=%d slow=%.2fms fast=%.2fms ratio=%.1fx%n",
S, n, slowNs / 1e6 / reps, fastNs / 1e6 / reps, ratio);
assert ratio >= 10.0 : "FAIL: timing ratio " + ratio + " < 10x";
passed++;
}
System.out.println(passed + "/" + passed + " PASS");
}
}