You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by "Alex Petrov (Jira)" <ji...@apache.org> on 2026/04/26 14:47:00 UTC
[jira] [Updated] (CASSANDRA-21339) CursorBasedCompaction: expiring cells, same timestamp, same ldt, different ttl:
[ https://issues.apache.org/jira/browse/CASSANDRA-21339?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Alex Petrov updated CASSANDRA-21339:
------------------------------------
Description:
Expiring cells, same timestamp, same LDT, different TTL:
* {{Cells.resolveRegular}}: lower-TTL cell wins (Cells.java:124)
* {{CursorCompactor}}: no TTL check — falls to COMPARE => smaller value wins
{code}
SSTable-1: timestamp=100, nowInSec=T+1000, ttl=200 => LDT=T+1200, value='aaa'
SSTable-2: timestamp=100, nowInSec=T+1001, ttl=199 => LDT=T+1200, value='zzz'
Both cells share the same LDT => LDT check passes through in both paths.
Expected regular: 'zzz'/ttl=199 (lower TTL wins: Cells.java:124)
Expected cursor: 'aaa'/ttl=200 (no TTL check; falls to COMPARE => smaller value)
[regular] winner='zzz' ttl=199 ldt=1777215342 (expected=1777215342)
[cursor ] winner='aaa' ttl=200 ldt=1777215342 (expected=1777215342)
{code}
{code}
public class CellReconciliationTiebreakTest extends CQLTester
{
private static final String OUT = "/tmp/tiebreak_results.txt";
private boolean originalCursorEnabled;
@Before
public void saveCursorSetting() throws IOException
{
originalCursorEnabled = DatabaseDescriptor.cursorCompactionEnabled();
Files.deleteIfExists(Paths.get(OUT));
}
@After
public void restoreCursorSetting()
{
DatabaseDescriptor.setCursorCompactionEnabled(originalCursorEnabled);
}
private static void out(String line)
{
try {Files.write(Paths.get(OUT), (line + "\n").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);}
catch (IOException e) { throw new RuntimeException(e); }
}
@Test
public void runCase2ExpiringCellTTLTiebreak() throws Throwable
{
long realNow = FBUtilities.nowInSeconds();
for (boolean cursor : new boolean[]{ false, true })
{
DatabaseDescriptor.setCursorCompactionEnabled(cursor);
String ks = createKeyspace("CREATE KEYSPACE %s WITH replication = " +
"{'class':'SimpleStrategy','replication_factor':1}" +
" AND durable_writes = false");
String tbl = createTable(ks, "CREATE TABLE %s (pk int, ck int, c1 text, PRIMARY KEY (pk, ck))");
execute("USE " + ks);
ColumnFamilyStore cfs = Keyspace.open(ks).getColumnFamilyStore(tbl);
cfs.disableAutoCompaction();
// RowUpdateBuilder(metadata, nowInSec, timestamp, ttl, pk)
// actual cell LDT = nowInSec + ttl
// SSTable-1: LDT = (realNow+1000) + 200 = realNow+1200, value='aaa' (higher TTL, lower value)
new RowUpdateBuilder(cfs.metadata(), realNow + 1000L, 100L, 200, 1)
.clustering(1)
.add("c1", "aaa")
.build()
.applyUnsafe();
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
// SSTable-2: LDT = (realNow+1001) + 199 = realNow+1200, value='zzz' (lower TTL, higher value)
new RowUpdateBuilder(cfs.metadata(), realNow + 1001L, 100L, 199, 1)
.clustering(1)
.add("c1", "zzz")
.build()
.applyUnsafe();
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
cfs.forceMajorCompaction();
Cell<?> cell = firstCell(cfs);
String value = cell.column().type.getString(cell.buffer());
int ttl = cell.ttl();
long ldt = cell.localDeletionTime();
out(" [" + mode(cursor) + "] winner='" + value + "' ttl=" + ttl
+ " ldt=" + ldt + " (expected=" + (realNow + 1200) + ")");
}
out("");
}
// -------------------------------------------------------------------------
private static String mode(boolean cursor) { return cursor ? "cursor " : "regular"; }
private static Cell<?> firstCell(ColumnFamilyStore cfs)
{
SSTableReader sstable = cfs.getLiveSSTables().iterator().next();
UnfilteredRowIterator partition = sstable.getScanner().next();
Unfiltered unfiltered = partition.next();
return ((Row) unfiltered).cells().iterator().next();
}
}
{code}
was:
Expiring cells, same timestamp, same LDT, different TTL:
* {Cells.resolveRegular}: lower-TTL cell wins (Cells.java:124)
* {CursorCompactor}: no TTL check — falls to COMPARE => smaller value wins
{code}
SSTable-1: timestamp=100, nowInSec=T+1000, ttl=200 => LDT=T+1200, value='aaa'
SSTable-2: timestamp=100, nowInSec=T+1001, ttl=199 => LDT=T+1200, value='zzz'
Both cells share the same LDT => LDT check passes through in both paths.
Expected regular: 'zzz'/ttl=199 (lower TTL wins: Cells.java:124)
Expected cursor: 'aaa'/ttl=200 (no TTL check; falls to COMPARE => smaller value)
[regular] winner='zzz' ttl=199 ldt=1777215342 (expected=1777215342)
[cursor ] winner='aaa' ttl=200 ldt=1777215342 (expected=1777215342)
{code}
{code}
public class CellReconciliationTiebreakTest extends CQLTester
{
private static final String OUT = "/tmp/tiebreak_results.txt";
private boolean originalCursorEnabled;
@Before
public void saveCursorSetting() throws IOException
{
originalCursorEnabled = DatabaseDescriptor.cursorCompactionEnabled();
Files.deleteIfExists(Paths.get(OUT));
}
@After
public void restoreCursorSetting()
{
DatabaseDescriptor.setCursorCompactionEnabled(originalCursorEnabled);
}
private static void out(String line)
{
try {Files.write(Paths.get(OUT), (line + "\n").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);}
catch (IOException e) { throw new RuntimeException(e); }
}
@Test
public void runCase2ExpiringCellTTLTiebreak() throws Throwable
{
long realNow = FBUtilities.nowInSeconds();
for (boolean cursor : new boolean[]{ false, true })
{
DatabaseDescriptor.setCursorCompactionEnabled(cursor);
String ks = createKeyspace("CREATE KEYSPACE %s WITH replication = " +
"{'class':'SimpleStrategy','replication_factor':1}" +
" AND durable_writes = false");
String tbl = createTable(ks, "CREATE TABLE %s (pk int, ck int, c1 text, PRIMARY KEY (pk, ck))");
execute("USE " + ks);
ColumnFamilyStore cfs = Keyspace.open(ks).getColumnFamilyStore(tbl);
cfs.disableAutoCompaction();
// RowUpdateBuilder(metadata, nowInSec, timestamp, ttl, pk)
// actual cell LDT = nowInSec + ttl
// SSTable-1: LDT = (realNow+1000) + 200 = realNow+1200, value='aaa' (higher TTL, lower value)
new RowUpdateBuilder(cfs.metadata(), realNow + 1000L, 100L, 200, 1)
.clustering(1)
.add("c1", "aaa")
.build()
.applyUnsafe();
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
// SSTable-2: LDT = (realNow+1001) + 199 = realNow+1200, value='zzz' (lower TTL, higher value)
new RowUpdateBuilder(cfs.metadata(), realNow + 1001L, 100L, 199, 1)
.clustering(1)
.add("c1", "zzz")
.build()
.applyUnsafe();
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
cfs.forceMajorCompaction();
Cell<?> cell = firstCell(cfs);
String value = cell.column().type.getString(cell.buffer());
int ttl = cell.ttl();
long ldt = cell.localDeletionTime();
out(" [" + mode(cursor) + "] winner='" + value + "' ttl=" + ttl
+ " ldt=" + ldt + " (expected=" + (realNow + 1200) + ")");
}
out("");
}
// -------------------------------------------------------------------------
private static String mode(boolean cursor) { return cursor ? "cursor " : "regular"; }
private static Cell<?> firstCell(ColumnFamilyStore cfs)
{
SSTableReader sstable = cfs.getLiveSSTables().iterator().next();
UnfilteredRowIterator partition = sstable.getScanner().next();
Unfiltered unfiltered = partition.next();
return ((Row) unfiltered).cells().iterator().next();
}
}
{code}
> CursorBasedCompaction: expiring cells, same timestamp, same ldt, different ttl:
> -------------------------------------------------------------------------------
>
> Key: CASSANDRA-21339
> URL: https://issues.apache.org/jira/browse/CASSANDRA-21339
> Project: Apache Cassandra
> Issue Type: Bug
> Reporter: Alex Petrov
> Priority: Normal
>
> Expiring cells, same timestamp, same LDT, different TTL:
> * {{Cells.resolveRegular}}: lower-TTL cell wins (Cells.java:124)
> * {{CursorCompactor}}: no TTL check — falls to COMPARE => smaller value wins
> {code}
> SSTable-1: timestamp=100, nowInSec=T+1000, ttl=200 => LDT=T+1200, value='aaa'
> SSTable-2: timestamp=100, nowInSec=T+1001, ttl=199 => LDT=T+1200, value='zzz'
> Both cells share the same LDT => LDT check passes through in both paths.
> Expected regular: 'zzz'/ttl=199 (lower TTL wins: Cells.java:124)
> Expected cursor: 'aaa'/ttl=200 (no TTL check; falls to COMPARE => smaller value)
> [regular] winner='zzz' ttl=199 ldt=1777215342 (expected=1777215342)
> [cursor ] winner='aaa' ttl=200 ldt=1777215342 (expected=1777215342)
> {code}
> {code}
> public class CellReconciliationTiebreakTest extends CQLTester
> {
> private static final String OUT = "/tmp/tiebreak_results.txt";
> private boolean originalCursorEnabled;
> @Before
> public void saveCursorSetting() throws IOException
> {
> originalCursorEnabled = DatabaseDescriptor.cursorCompactionEnabled();
> Files.deleteIfExists(Paths.get(OUT));
> }
> @After
> public void restoreCursorSetting()
> {
> DatabaseDescriptor.setCursorCompactionEnabled(originalCursorEnabled);
> }
> private static void out(String line)
> {
> try {Files.write(Paths.get(OUT), (line + "\n").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);}
> catch (IOException e) { throw new RuntimeException(e); }
> }
> @Test
> public void runCase2ExpiringCellTTLTiebreak() throws Throwable
> {
> long realNow = FBUtilities.nowInSeconds();
> for (boolean cursor : new boolean[]{ false, true })
> {
> DatabaseDescriptor.setCursorCompactionEnabled(cursor);
> String ks = createKeyspace("CREATE KEYSPACE %s WITH replication = " +
> "{'class':'SimpleStrategy','replication_factor':1}" +
> " AND durable_writes = false");
> String tbl = createTable(ks, "CREATE TABLE %s (pk int, ck int, c1 text, PRIMARY KEY (pk, ck))");
> execute("USE " + ks);
> ColumnFamilyStore cfs = Keyspace.open(ks).getColumnFamilyStore(tbl);
> cfs.disableAutoCompaction();
> // RowUpdateBuilder(metadata, nowInSec, timestamp, ttl, pk)
> // actual cell LDT = nowInSec + ttl
> // SSTable-1: LDT = (realNow+1000) + 200 = realNow+1200, value='aaa' (higher TTL, lower value)
> new RowUpdateBuilder(cfs.metadata(), realNow + 1000L, 100L, 200, 1)
> .clustering(1)
> .add("c1", "aaa")
> .build()
> .applyUnsafe();
> cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
> // SSTable-2: LDT = (realNow+1001) + 199 = realNow+1200, value='zzz' (lower TTL, higher value)
> new RowUpdateBuilder(cfs.metadata(), realNow + 1001L, 100L, 199, 1)
> .clustering(1)
> .add("c1", "zzz")
> .build()
> .applyUnsafe();
> cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
> cfs.forceMajorCompaction();
> Cell<?> cell = firstCell(cfs);
> String value = cell.column().type.getString(cell.buffer());
> int ttl = cell.ttl();
> long ldt = cell.localDeletionTime();
> out(" [" + mode(cursor) + "] winner='" + value + "' ttl=" + ttl
> + " ldt=" + ldt + " (expected=" + (realNow + 1200) + ")");
> }
> out("");
> }
> // -------------------------------------------------------------------------
> private static String mode(boolean cursor) { return cursor ? "cursor " : "regular"; }
> private static Cell<?> firstCell(ColumnFamilyStore cfs)
> {
> SSTableReader sstable = cfs.getLiveSSTables().iterator().next();
> UnfilteredRowIterator partition = sstable.getScanner().next();
> Unfiltered unfiltered = partition.next();
> return ((Row) unfiltered).cells().iterator().next();
> }
> }
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org