207 lines
8.3 KiB
Java
207 lines
8.3 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* yugabyte-0001: GetXReplStreamsForTable std::find on table_id list inside per-table loop
|
||
*
|
||
* Models xrepl_catalog_manager.cc:
|
||
* GetXReplStreamsForTable: for each stream, std::find on table_id repeated field
|
||
* DropXClusterStreamsOfTables: calls GetXReplStreamsForTable in a loop over table_ids
|
||
*
|
||
* Total defective: O(D × M × T) where D=dropped tables, M=streams, T=tables per stream
|
||
* Total fixed: O(M × T) — one pass + O(1) hash lookup
|
||
*
|
||
* Compile: javac -d . YugabyteTest.java
|
||
* Run: java unit.YugabyteTest
|
||
*/
|
||
public class YugabyteTest {
|
||
|
||
static class CDCStreamInfo {
|
||
final String streamId;
|
||
final List<String> tableIds; // backed by protobuf RepeatedPtrField (array)
|
||
boolean started_deleting = false;
|
||
|
||
CDCStreamInfo(String streamId, List<String> tableIds) {
|
||
this.streamId = streamId;
|
||
this.tableIds = tableIds;
|
||
}
|
||
}
|
||
|
||
// ---- DEFECTIVE: O(D × M × T) ----
|
||
static List<String> getXReplStreamsForTable_defective(
|
||
String tableId,
|
||
List<CDCStreamInfo> cdcStreamMap,
|
||
int[] comparisonCount) {
|
||
List<String> streams = new ArrayList<>();
|
||
for (CDCStreamInfo stream : cdcStreamMap) { // O(M)
|
||
if (stream.started_deleting) continue;
|
||
// std::find on table_id list — O(T) linear scan
|
||
boolean found = false;
|
||
for (String tid : stream.tableIds) { // O(T)
|
||
comparisonCount[0]++;
|
||
if (tid.equals(tableId)) { found = true; break; }
|
||
}
|
||
if (found) streams.add(stream.streamId);
|
||
}
|
||
return streams;
|
||
}
|
||
|
||
static List<String> dropXClusterStreamsOfTables_defective(
|
||
Set<String> tableIds,
|
||
List<CDCStreamInfo> cdcStreamMap,
|
||
int[] comparisonCount) {
|
||
List<String> affectedStreams = new ArrayList<>();
|
||
for (String tid : tableIds) { // O(D)
|
||
affectedStreams.addAll(
|
||
getXReplStreamsForTable_defective(tid, cdcStreamMap, comparisonCount));
|
||
}
|
||
return affectedStreams;
|
||
}
|
||
|
||
// ---- FIXED: single pass O(M × T) with O(1) hash lookup ----
|
||
static List<String> dropXClusterStreamsOfTables_fixed(
|
||
Set<String> tableIds,
|
||
List<CDCStreamInfo> cdcStreamMap,
|
||
int[] comparisonCount) {
|
||
List<String> affectedStreams = new ArrayList<>();
|
||
|
||
for (CDCStreamInfo stream : cdcStreamMap) { // O(M) — single pass
|
||
if (stream.started_deleting) continue;
|
||
for (String tid : stream.tableIds) { // O(T) per stream
|
||
comparisonCount[0]++;
|
||
if (tableIds.contains(tid)) { // O(1) hash set lookup
|
||
affectedStreams.add(stream.streamId);
|
||
break; // found one match, don't add stream twice
|
||
}
|
||
}
|
||
}
|
||
return affectedStreams;
|
||
}
|
||
|
||
// Build a test CDC stream map
|
||
static List<CDCStreamInfo> buildStreamMap(int M, int T, int totalTables) {
|
||
List<CDCStreamInfo> map = new ArrayList<>();
|
||
for (int i = 0; i < M; i++) {
|
||
List<String> tables = new ArrayList<>();
|
||
for (int j = 0; j < T; j++) {
|
||
tables.add("table-" + ((i * T + j) % totalTables));
|
||
}
|
||
map.add(new CDCStreamInfo("stream-" + i, tables));
|
||
}
|
||
return map;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
int pass = 0, fail = 0;
|
||
|
||
// -- Test 1: correctness — small scenario
|
||
{
|
||
List<CDCStreamInfo> streams = new ArrayList<>();
|
||
streams.add(new CDCStreamInfo("s1", Arrays.asList("t1", "t2", "t3")));
|
||
streams.add(new CDCStreamInfo("s2", Arrays.asList("t2", "t4")));
|
||
streams.add(new CDCStreamInfo("s3", Arrays.asList("t5", "t6")));
|
||
|
||
Set<String> toDrop = new HashSet<>(Arrays.asList("t2", "t5"));
|
||
|
||
int[] cmpDef = {0}, cmpFix = {0};
|
||
List<String> resultDef = dropXClusterStreamsOfTables_defective(toDrop, streams, cmpDef);
|
||
List<String> resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streams, cmpFix);
|
||
|
||
Collections.sort(resultDef);
|
||
Collections.sort(resultFix);
|
||
|
||
if (resultDef.equals(resultFix)) {
|
||
System.out.printf("PASS test1: correctness — both=%s%n", resultDef);
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test1: defective=%s fixed=%s%n", resultDef, resultFix);
|
||
fail++;
|
||
}
|
||
}
|
||
|
||
// -- Test 2: complexity comparison
|
||
{
|
||
int D = 50; // tables being dropped
|
||
int M = 100; // CDC streams
|
||
int T = 20; // tables per stream
|
||
int totalTables = 200;
|
||
|
||
List<CDCStreamInfo> streamMap = buildStreamMap(M, T, totalTables);
|
||
|
||
// Drop first D tables
|
||
Set<String> toDrop = new HashSet<>();
|
||
for (int i = 0; i < D; i++) toDrop.add("table-" + i);
|
||
|
||
int[] cmpDef = {0}, cmpFix = {0};
|
||
List<String> resultDef = dropXClusterStreamsOfTables_defective(toDrop, streamMap, cmpDef);
|
||
List<String> resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streamMap, cmpFix);
|
||
|
||
System.out.printf("test2: defective comparisons=%d fixed comparisons=%d (D=%d M=%d T=%d)%n",
|
||
cmpDef[0], cmpFix[0], D, M, T);
|
||
|
||
// Defective adds a stream once per matching dropped table (may have duplicates);
|
||
// fixed adds each stream at most once. Compare as sets of unique stream IDs.
|
||
Set<String> setDef = new HashSet<>(resultDef);
|
||
Set<String> setFix = new HashSet<>(resultFix);
|
||
|
||
if (setDef.equals(setFix) && cmpFix[0] < cmpDef[0]) {
|
||
System.out.println("PASS test2: fixed is more efficient and produces same (unique) result");
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test2: match=%b efficient=%b%n",
|
||
setDef.equals(setFix), cmpFix[0] < cmpDef[0]);
|
||
fail++;
|
||
}
|
||
|
||
// Show expected complexity
|
||
System.out.printf(" Expected defective ~O(D×M×T)=%d, actual=%d%n", D * M * T, cmpDef[0]);
|
||
System.out.printf(" Expected fixed ~O(M×T)=%d, actual=%d%n", M * T, cmpFix[0]);
|
||
}
|
||
|
||
// -- Test 3: deleted streams are excluded
|
||
{
|
||
CDCStreamInfo deletingStream = new CDCStreamInfo("s-deleting", Arrays.asList("t1", "t2"));
|
||
deletingStream.started_deleting = true;
|
||
|
||
List<CDCStreamInfo> streams = new ArrayList<>();
|
||
streams.add(deletingStream);
|
||
streams.add(new CDCStreamInfo("s-active", Arrays.asList("t1")));
|
||
|
||
Set<String> toDrop = new HashSet<>(Collections.singletonList("t1"));
|
||
|
||
int[] cmpDef = {0}, cmpFix = {0};
|
||
List<String> resultDef = dropXClusterStreamsOfTables_defective(toDrop, streams, cmpDef);
|
||
List<String> resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streams, cmpFix);
|
||
|
||
Collections.sort(resultDef);
|
||
Collections.sort(resultFix);
|
||
|
||
if (resultDef.equals(resultFix) && resultFix.equals(Collections.singletonList("s-active"))) {
|
||
System.out.printf("PASS test3: deleting stream excluded — result=%s%n", resultFix);
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test3: def=%s fix=%s%n", resultDef, resultFix);
|
||
fail++;
|
||
}
|
||
}
|
||
|
||
// -- Test 4: empty table_ids set
|
||
{
|
||
List<CDCStreamInfo> streams = buildStreamMap(10, 5, 50);
|
||
int[] c1 = {0}, c2 = {0};
|
||
List<String> r1 = dropXClusterStreamsOfTables_defective(new HashSet<>(), streams, c1);
|
||
List<String> r2 = dropXClusterStreamsOfTables_fixed(new HashSet<>(), streams, c2);
|
||
if (r1.isEmpty() && r2.isEmpty()) {
|
||
System.out.println("PASS test4: empty input — both return empty");
|
||
pass++;
|
||
} else {
|
||
System.out.printf("FAIL test4: def=%s fix=%s%n", r1, r2);
|
||
fail++;
|
||
}
|
||
}
|
||
|
||
System.out.printf("%nResults: %d passed, %d failed%n", pass, fail);
|
||
if (fail > 0) System.exit(1);
|
||
}
|
||
}
|