mirror of https://github.com/apache/cassandra
Verify cursor compaction across two generations in the edge corpus
The differential harness compared single-generation outputs: both paths compact the same inputs once. Write-side corruption that only manifests when the NEXT merge re-reads the output — a bug class where a flag is decided after its byte is already written — was invisible to it. assertCursorMatchesIteratorAcrossGenerations runs the normal differential, then genuinely commits a cursor-path compaction (no restore — the live set becomes cursor-produced sstables) and runs the differential again over those outputs. Every edge-corpus scenario now uses it, so each scenario also proves cursor output is a correct INPUT to both pipelines.
This commit is contained in:
parent
85d05c30f7
commit
5d46e672e5
|
|
@ -183,6 +183,46 @@ public abstract class DifferentialCompactionTester extends CQLTester
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Differential at TWO generations: the normal differential first (gen 1), then the inputs
|
||||
* are genuinely compacted through the CURSOR path and the differential runs again over the
|
||||
* cursor-produced outputs (gen 2). Write-side corruption that only manifests when the next
|
||||
* merge re-reads the output — a bug class where a flag is decided after its byte is
|
||||
* already written, desyncing the FOLLOWING compaction rather than its own — fails gen 2
|
||||
* loudly here instead of surviving until production recompacts.
|
||||
*
|
||||
* Returns the GEN-1 iterator capture: scenario structural assertions target gen 1, whose
|
||||
* shape the scenario controls directly.
|
||||
*/
|
||||
protected CapturedOutput assertCursorMatchesIteratorAcrossGenerations(ColumnFamilyStore cfs,
|
||||
Set<String> byteDiffAllowlist) throws Exception
|
||||
{
|
||||
CapturedOutput gen1 = assertCursorMatchesIterator(cfs, byteDiffAllowlist);
|
||||
|
||||
long gcBefore = cfs.getDefaultGcBefore(FBUtilities.nowInSeconds());
|
||||
commitCompaction(cfs, cfs.getLiveSSTables(), true, gcBefore);
|
||||
if (cfs.getLiveSSTables().isEmpty())
|
||||
return gen1; // gen 1 purged everything; there are no gen-2 inputs
|
||||
|
||||
assertCursorMatchesIterator(cfs, byteDiffAllowlist);
|
||||
return gen1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits one compaction over the given inputs through the selected path WITHOUT restore:
|
||||
* the live set genuinely becomes the outputs. Used by the cross-generation rung so the
|
||||
* second differential reads cursor-produced sstables.
|
||||
*/
|
||||
protected void commitCompaction(ColumnFamilyStore cfs, Set<SSTableReader> inputs, boolean cursor, long gcBefore) throws Exception
|
||||
{
|
||||
DatabaseDescriptor.setCursorCompactionEnabled(cursor);
|
||||
if (cursor)
|
||||
assertCursorPathWillRun(cfs, inputs, gcBefore);
|
||||
LifecycleTransaction txn = cfs.getTracker().tryModify(inputs, OperationType.COMPACTION);
|
||||
assertNotNull("unable to mark inputs compacting for commit", txn);
|
||||
new CompactionTask(cfs, txn, gcBefore, false).execute(ActiveCompactionsTracker.NOOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one compaction path over the given input subset (non-participating live sstables
|
||||
* stay live and feed purge-overlap decisions), captures the outputs, and restores the live
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ import org.apache.cassandra.utils.ByteBufferUtil;
|
|||
* CURRENTLY SUPPORTED cursor compaction surface (see CursorCompactor.isSupported). Every
|
||||
* scenario here must run the cursor path for real — the harness fails loudly on silent
|
||||
* fallback. Scenarios for unsupported shapes belong in CursorSupportMatrixTest instead.
|
||||
*
|
||||
* Every scenario runs the differential at TWO generations (see
|
||||
* assertCursorMatchesIteratorAcrossGenerations): gen 2 re-compacts genuinely cursor-produced
|
||||
* outputs, so write-side corruption that only the NEXT merge can see fails here.
|
||||
*/
|
||||
public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTester
|
||||
{
|
||||
|
|
@ -61,7 +65,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Reversed clustering order changes on-disk ordering and bound comparisons. */
|
||||
|
|
@ -82,7 +86,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Multi-component clusterings: mixed types, shared prefixes, per-component bounds. */
|
||||
|
|
@ -109,7 +113,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Wide partition crossing column-index block boundaries (indexed RowIndexEntry path). */
|
||||
|
|
@ -131,7 +135,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,7 +161,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Overlapping range tombstones across sstables: boundary markers must merge identically. */
|
||||
|
|
@ -189,7 +193,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("DELETE FROM %s WHERE pk = 1 AND ck >= 20 AND ck < 30");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Frozen collections and tuples are single cells and inside the supported surface. */
|
||||
|
|
@ -216,7 +220,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** TTLs: live expiring cells and already-expired cells (expiry far from run boundaries). */
|
||||
|
|
@ -243,7 +247,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
|
||||
Thread.sleep(2000); // let the short TTLs expire well before the first run
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Same-timestamp conflicting writes: reconciliation must tie-break identically. */
|
||||
|
|
@ -268,7 +272,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?) USING TIMESTAMP 2000", 1L, ck, "tie" + ck);
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Newer partition deletion shadowing older data across several sstables. */
|
||||
|
|
@ -293,7 +297,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 3L, 0L, "alive-again");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Single-input compaction: pure rewrite, no merge. */
|
||||
|
|
@ -310,7 +314,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("DELETE FROM %s WHERE pk = 0 AND ck >= 2 AND ck < 6");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Many inputs: 8-way merge exercises the merge heap harder than the usual 2-4. */
|
||||
|
|
@ -332,7 +336,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Disjoint inputs: no overlapping partitions, pure concatenation. */
|
||||
|
|
@ -351,7 +355,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
flush();
|
||||
}
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** Empty (zero-length) values are valid and distinct from null; both must survive merge. */
|
||||
|
|
@ -374,7 +378,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("INSERT INTO %s (pk, ck, v1, v2) VALUES (?, ?, null, null)", 1L, ck);
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -413,7 +417,7 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 1L, 3L, "p1v3-overwrite");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** ASC counterpart of emptyClusteringValuesDescending: empty sorts BEFORE values on a
|
||||
|
|
@ -437,6 +441,6 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 1L, 3L, "p1v3-overwrite");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIterator(cfs, ALLOWLIST);
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue