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