104 lines
3.9 KiB
Java
104 lines
3.9 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* hbase-0001: DefaultStoreFileManager.getUnneededFiles() — ArrayList.contains() O(C)
|
|
* called inside a filter over all store files. Total: O(F * C).
|
|
*
|
|
* Root cause: HStore.filesCompacting = Lists.newArrayList() (ArrayList).
|
|
* Fix: build a local HashSet<HStoreFile> at start of getUnneededFiles() for O(1) lookup.
|
|
*
|
|
* Standalone unit test — no JUnit required.
|
|
* Compile: javac -d . HBaseStoreFileManagerTest.java
|
|
* Run: java -ea unit.HBaseStoreFileManagerTest
|
|
*/
|
|
public class HBaseStoreFileManagerTest {
|
|
|
|
static long slowOps;
|
|
static long fastOps;
|
|
|
|
/**
|
|
* Simulates getUnneededFiles() with ArrayList filesCompacting.
|
|
* For each of F store files, ArrayList.contains() scans C compacting files.
|
|
*/
|
|
static int slowGetUnneededFiles(int numStoreFiles, List<Integer> filesCompacting, long maxTs) {
|
|
slowOps = 0;
|
|
int unneededCount = 0;
|
|
// storeFiles.all — F files, first (F-1) are candidates (skip the last)
|
|
for (int i = 0; i < numStoreFiles - 1; i++) {
|
|
slowOps++; // stream entry
|
|
int fileId = i;
|
|
long fileTs = (long) fileId; // fileTs = id, so fileTs < maxTs when id < maxTs
|
|
if (fileTs < maxTs) {
|
|
for (Integer compacting : filesCompacting) { // ArrayList.contains() scan
|
|
slowOps++;
|
|
if (compacting.equals(fileId)) break;
|
|
}
|
|
if (!filesCompacting.contains(fileId)) {
|
|
unneededCount++;
|
|
}
|
|
}
|
|
}
|
|
return unneededCount;
|
|
}
|
|
|
|
/**
|
|
* Patched: build HashSet once at method entry — O(C) one time, then O(1) per lookup.
|
|
*/
|
|
static int fastGetUnneededFiles(int numStoreFiles, List<Integer> filesCompacting, long maxTs) {
|
|
fastOps = 0;
|
|
Set<Integer> compactingSet = new HashSet<>(filesCompacting); // O(C) once
|
|
fastOps += filesCompacting.size();
|
|
int unneededCount = 0;
|
|
for (int i = 0; i < numStoreFiles - 1; i++) {
|
|
fastOps++; // stream entry
|
|
int fileId = i;
|
|
long fileTs = (long) fileId;
|
|
if (fileTs < maxTs) {
|
|
fastOps++; // O(1) HashSet.contains()
|
|
if (!compactingSet.contains(fileId)) {
|
|
unneededCount++;
|
|
}
|
|
}
|
|
}
|
|
return unneededCount;
|
|
}
|
|
|
|
static void run(int F, int C, long maxTs, int expectedRatio) {
|
|
// Build filesCompacting: C files evenly distributed across F
|
|
List<Integer> filesCompacting = new ArrayList<>();
|
|
for (int i = 0; i < C; i++) {
|
|
filesCompacting.add(i * (F / Math.max(C, 1)));
|
|
}
|
|
|
|
int slowResult = slowGetUnneededFiles(F, filesCompacting, maxTs);
|
|
int fastResult = fastGetUnneededFiles(F, filesCompacting, maxTs);
|
|
|
|
boolean resultsMatch = (slowResult == fastResult);
|
|
boolean quadraticWorse = slowOps > fastOps * expectedRatio;
|
|
boolean pass = resultsMatch && quadraticWorse;
|
|
|
|
System.out.printf("F=%-4d C=%-4d maxTs=%-6d slow=%8d fast=%5d ratio=%6.1fx match=%b PASS=%b%n",
|
|
F, C, maxTs, slowOps, fastOps, (double) slowOps / fastOps, resultsMatch, pass);
|
|
|
|
if (!pass) {
|
|
throw new AssertionError(
|
|
"FAIL F=" + F + " C=" + C +
|
|
" resultsMatch=" + resultsMatch +
|
|
" slowOps=" + slowOps + " fastOps=" + fastOps +
|
|
" needed ratio>" + expectedRatio);
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== hbase-0001: DefaultStoreFileManager.getUnneededFiles() O(F*C) vs O(F+C) ===");
|
|
run(100, 10, 80, 3);
|
|
run(500, 50, 400, 20);
|
|
run(1000, 100, 900, 40);
|
|
System.out.println("3/3 PASS");
|
|
}
|
|
}
|