mirror of https://github.com/apache/cassandra
Pin open-ended range tombstones; fix wall-clock flake in the harness
Open-ended (single-sided) range tombstones were covered only probabilistically by the randomized soak. Two deterministic scenarios now pin them: markers open to TOP and from BOTTOM, nested open ranges, a partition containing only an open range tombstone, resurrection inside open-deleted ranges, interleaving with bounded tombstones, and a DESC variant where the bound direction inverts in on-disk order. Unbounded bounds are zero-component clustering prefixes — the same empty-prefix comparison region as the reversed-column finding. Separately, the materialized-view suite exposed a harness hole: JsonTransformer computes its "expired" fields from the wall clock, ignoring the nowInSec parameter the harness fixes for deterministic rendering. The two paths' captures run seconds apart, so a localExpirationTime falling between them renders expired=false on one side and expired=true on the other over byte-identical sstables — materialized-view expired-liveness rows sit permanently on that boundary because their expiration is the write second. The harness now normalizes the flag out of the dump before comparing; it is derived from expires_at, which is still compared. (Upstream observation for the eventual ticket: sstabledump's "expired" ignores the tool's own time parameter.)
This commit is contained in:
parent
5e9bc61d22
commit
277c4059d3
|
|
@ -380,7 +380,14 @@ public abstract class DifferentialCompactionTester extends CQLTester
|
|||
JsonTransformer.toJsonLines(scanner, Util.iterToStream(scanner), true, false,
|
||||
sstable.metadata(), DUMP_NOW_SEC, baos);
|
||||
}
|
||||
String json = baos.toString(StandardCharsets.UTF_8);
|
||||
// JsonTransformer's "expired" fields are computed from WALL CLOCK
|
||||
// (currentTimeMillis), ignoring the fixed nowInSec passed above — so byte-identical
|
||||
// outputs can render differently when a localExpirationTime falls between the two
|
||||
// paths' captures, which run seconds apart (materialized-view expired-liveness rows
|
||||
// sit permanently on that boundary: their expiration IS the write second). The flag
|
||||
// is derived from expires_at, which is still compared, so normalize it out.
|
||||
String json = baos.toString(StandardCharsets.UTF_8)
|
||||
.replaceAll("\"expired\"\\s*:\\s*(true|false)", "\"expired\":\"normalized\"");
|
||||
|
||||
// 3. stats spot-check summary
|
||||
StatsMetadata stats = sstable.getSSTableMetadata();
|
||||
|
|
|
|||
|
|
@ -613,6 +613,67 @@ public class EdgeCaseDifferentialCompactionTest extends DifferentialCompactionTe
|
|||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* OPEN-ENDED (single-sided) range tombstones: DELETE with only a lower or upper
|
||||
* clustering bound produces markers whose other side is the unbounded partition edge —
|
||||
* zero-component TOP/BOTTOM bounds, the same empty-prefix region bound-kind comparisons
|
||||
* and covered-clustering stats exercise. Open RTs nest with each other, overlap bounded
|
||||
* RTs and rows across sstables, and one partition is open-RT-only.
|
||||
* Previously covered only probabilistically by the widened soak.
|
||||
*/
|
||||
@Test
|
||||
public void openEndedRangeTombstones() throws Exception
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk bigint, ck bigint, v text, PRIMARY KEY (pk, ck)) " +
|
||||
"WITH gc_grace_seconds = 864000");
|
||||
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
||||
cfs.disableAutoCompaction();
|
||||
|
||||
for (long pk = 1; pk <= 2; pk++)
|
||||
for (long ck = 0; ck < 30; ck++)
|
||||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", pk, ck, "v" + ck);
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck >= ? AND ck < ?", 1L, 10L, 20L); // bounded, for interleave
|
||||
flush();
|
||||
|
||||
// open-ended deletes: up to TOP, down from BOTTOM, nested opens, and an RT-only partition
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck >= ?", 1L, 25L);
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck > ?", 1L, 27L); // nests inside the >= 25 open range
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck <= ?", 2L, 4L);
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck >= ?", 3L, 0L); // partition with ONLY an open RT
|
||||
flush();
|
||||
|
||||
// resurrection inside open-deleted ranges with newer timestamps
|
||||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 1L, 26L, "resurrected");
|
||||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 2L, 2L, "resurrected");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/** DESC counterpart: single-sided bounds invert in on-disk clustering order, so the
|
||||
* open edge swaps between TOP and BOTTOM relative to the CQL bound direction. */
|
||||
@Test
|
||||
public void openEndedRangeTombstonesDescending() throws Exception
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk bigint, ck bigint, v text, PRIMARY KEY (pk, ck)) " +
|
||||
"WITH CLUSTERING ORDER BY (ck DESC) AND gc_grace_seconds = 864000");
|
||||
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
||||
cfs.disableAutoCompaction();
|
||||
|
||||
for (long ck = 0; ck < 30; ck++)
|
||||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 1L, ck, "v" + ck);
|
||||
flush();
|
||||
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck >= ?", 1L, 25L);
|
||||
execute("DELETE FROM %s WHERE pk = ? AND ck <= ?", 1L, 4L);
|
||||
flush();
|
||||
|
||||
execute("INSERT INTO %s (pk, ck, v) VALUES (?, ?, ?)", 1L, 27L, "resurrected");
|
||||
flush();
|
||||
|
||||
assertCursorMatchesIteratorAcrossGenerations(cfs, ALLOWLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* ODD superset size at the subset-encoding mode boundary: with 71 columns, the encoder
|
||||
* and decoder must agree on present-index vs missing-index
|
||||
|
|
|
|||
Loading…
Reference in New Issue