Skip to content

CASSANDRA-21607: Fix reads and compaction of retained complex cells - #5065

Open
1fanwang wants to merge 7 commits into
apache:trunkfrom
1fanwang:fix-cassandra-21607
Open

CASSANDRA-21607: Fix reads and compaction of retained complex cells#5065
1fanwang wants to merge 7 commits into
apache:trunkfrom
1fanwang:fix-cassandra-21607

Conversation

@1fanwang

@1fanwang 1fanwang commented Aug 26, 2026

Copy link
Copy Markdown

Reading or compacting a table fails when a dropped non-frozen collection still has cells newer than the drop timestamp. Both paths reach the same rows and throw:

Cannot invoke "org.apache.cassandra.db.rows.ComplexColumnData$Builder.newColumn(...)" because "this.complexBuilder" is null

Read response serialization and compaction share a row merger that sizes its complex-column state from the current schema. After the collection is dropped, that schema reports no complex columns, but older SSTables can still return newer cells that must survive. The merger now creates complex state only when those cells arrive. Separately, a wildcard column filter answered its fetch check with an unconditional yes while reporting a fixed set of fetched columns. A read whose filter was built before the drop therefore deserialized cells it could not then serialize. The filter now answers from the set it was built with, so those cells are skipped as rows are read rather than filtered out afterwards.

The regression creates real SSTables through CQL and flushes, then drops the map column. Native-protocol reads succeed before and after compaction, a command built before the drop no longer fetches the dropped column, and dropped-only rows do not consume forward, reverse, or paged read limits. Re-adding the map exposes both retained cells.

Jira: https://issues.apache.org/jira/browse/CASSANDRA-21607

Testing

JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.compaction.differential.DroppedColumnDifferentialCompactionTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.ReadCommandTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.ReadResponseTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.PartitionRangeReadTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.SinglePartitionSliceCommandTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.rows.RowsTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.rows.RowsMergingTest
JAVA_HOME=$(/usr/libexec/java_home -v 17) ant testsome \
  -Dtest.name=org.apache.cassandra.db.rows.UnfilteredRowIteratorsMergeTest
JAVA_HOME=$(/usr/libexec/java_home -v 21) ant testsome \
  -Dtest.name=org.apache.cassandra.cql3.validation.operations.AlterTest
JAVA_HOME=$(/usr/libexec/java_home -v 21) ant testsome \
  -Dtest.name=org.apache.cassandra.cql3.validation.entities.CollectionsTest
JAVA_HOME=$(/usr/libexec/java_home -v 21) ant testsome \
  -Dtest.name=org.apache.cassandra.cql3.validation.entities.SecondaryIndexTest
Raw logs
Before:
Testcase: complexCellsNewerThanDropRetained(...) Caused an ERROR
java.lang.NullPointerException: Cannot invoke "org.apache.cassandra.db.rows.ComplexColumnData$Builder.newColumn(org.apache.cassandra.schema.ColumnMetadata)" because "this.complexBuilder" is null
    at org.apache.cassandra.db.rows.Row$Merger$ColumnDataReducer.getReduced(Row.java:919)

Testcase: complexCellsNewerThanDropRetained(...) Caused an ERROR
java.lang.IllegalStateException: [m] is not a subset of []
    at org.apache.cassandra.db.Columns$Serializer.encodeBitmap(Columns.java:618)
Testcase: droppedComplexCellsDoNotConsumeReadLimit(...) Caused an ERROR
Cassandra failure during read query at consistency LOCAL_ONE (1 responses were required but only 0 replica responded, 1 failed)
BUILD FAILED

After:
DroppedColumnDifferentialCompactionTest: Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
ReadCommandTest: Tests run: 29, Failures: 0, Errors: 0, Skipped: 0
ReadResponseTest: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
PartitionRangeReadTest: Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
SinglePartitionSliceCommandTest: Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
RowsTest: Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
RowsMergingTest: Tests run: 11, Failures: 0, Errors: 0, Skipped: 0
UnfilteredRowIteratorsMergeTest: Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
AlterTest: Tests run: 46, Failures: 0, Errors: 0, Skipped: 0
CollectionsTest: Tests run: 58, Failures: 0, Errors: 0, Skipped: 0
SecondaryIndexTest: Tests run: 61, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESSFUL

@frankgh frankgh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not sufficient. You still won't be able to read the table after the compaction occurred. In your test after line 113, if you select * from the table, it will also produce an error.

Comment thread src/java/org/apache/cassandra/db/rows/Row.java Outdated
@1fanwang
1fanwang force-pushed the fix-cassandra-21607 branch from 7abfb6e to 51a909f Compare August 26, 2026 17:55
@rustyrazorblade

Copy link
Copy Markdown
Contributor

This is not sufficient. You still won't be able to read the table after the compaction occurred. In your test after line 113, if you select * from the table, it will also produce an error.

I added a minimal shell script to the issue which demonstrates this.

@1fanwang 1fanwang changed the title CASSANDRA-21607: Fix compaction of retained complex cells CASSANDRA-21607: Fix reads and compaction of retained complex cells Aug 26, 2026
@1fanwang
1fanwang requested a review from frankgh August 26, 2026 18:41
@frankgh

frankgh commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

This is what I'm seeing with your patch, after running the steps mentioned in the JIRA:

cqlsh> CREATE KEYSPACE npe WITH replication = {'class':'SimpleStrategy','replication_factor':1};
cqlsh> CREATE TABLE npe.t (pk bigint, ck bigint, v1 bigint, m map<text,bigint>, PRIMARY KEY (pk, ck));
cqlsh> UPDATE npe.t USING TIMESTAMP 4102444800000000 SET m = m + {'a':1} WHERE pk=0 AND ck=0;
cqlsh> UPDATE npe.t USING TIMESTAMP 4102444800000001 SET m = m + {'a':2} WHERE pk=0 AND ck=0;
cqlsh> ALTER TABLE npe.t DROP m;
cqlsh> SELECT * FROM npe.t ;
ReadFailure: Error from server: code=1300 [Replica(s) failed to execute read] message="Operation failed - received 0 responses and 1 failures: UNKNOWN from localhost/127.0.0.1:7000" info={'consistency': 'ONE', 'required_responses': 1, 'received_responses': 0, 'failures': 1, 'error_code_map': {'127.0.0.1': '0x0000'}}
cqlsh> 

and the stacktrace of the error is:

ERROR [SharedPool-Worker-2] 2026-08-26T18:43:57,258 JVMStabilityInspector.java:75 - Exception in thread Thread[SharedPool-Worker-2,10,SharedPool]
java.lang.AssertionError: m
	at org.apache.cassandra.db.rows.UnfilteredSerializer.serializeColumnData(UnfilteredSerializer.java:266)
	at org.apache.cassandra.utils.btree.BTree.applyValue(BTree.java:1856)
	at org.apache.cassandra.utils.btree.BTree.applyLeaf(BTree.java:1864)
	at org.apache.cassandra.utils.btree.BTree.apply(BTree.java:1879)
	at org.apache.cassandra.db.rows.BTreeRow.apply(BTreeRow.java:226)
	at org.apache.cassandra.db.rows.UnfilteredSerializer.serializeRowBody(UnfilteredSerializer.java:252)
	at org.apache.cassandra.db.rows.UnfilteredSerializer.serialize(UnfilteredSerializer.java:216)
	at org.apache.cassandra.db.rows.UnfilteredSerializer.serialize(UnfilteredSerializer.java:148)
	at org.apache.cassandra.db.rows.UnfilteredSerializer.serialize(UnfilteredSerializer.java:136)
	at org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.serializeWithoutKey(UnfilteredRowIteratorSerializer.java:168)
	at org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.serialize(UnfilteredRowIteratorSerializer.java:127)
	at org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.serialize(UnfilteredRowIteratorSerializer.java:108)
	at org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.serialize(UnfilteredRowIteratorSerializer.java:96)
	at org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.serialize(UnfilteredRowIteratorSerializer.java:89)
	at org.apache.cassandra.db.partitions.UnfilteredPartitionIterators$Serializer.serialize(UnfilteredPartitionIterators.java:349)
	at org.apache.cassandra.db.ReadResponse$LocalDataResponse.build(ReadResponse.java:262)
	at org.apache.cassandra.db.ReadResponse$LocalDataResponse.<init>(ReadResponse.java:237)
	at org.apache.cassandra.db.ReadResponse.createDataResponse(ReadResponse.java:58)
	at org.apache.cassandra.db.ReadCommand.createResponse(ReadCommand.java:453)
	at org.apache.cassandra.service.StorageProxy$LocalReadRunnable.runMayThrow(StorageProxy.java:2756)
	at org.apache.cassandra.service.StorageProxy$DroppableRunnable.run(StorageProxy.java:3155)
	at org.apache.cassandra.concurrent.ExecutionFailure$2.run(ExecutionFailure.java:168)
	at org.apache.cassandra.concurrent.SEPWorker.run(SEPWorker.java:192)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:829)
	at org.apache.cassandra.concurrent.CassandraThread.run(CassandraThread.java:97)

Iterator compaction can encounter complex cells newer than a column drop even when the current schema has no complex columns.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix-cassandra-21607 branch from cac75a1 to 40e5a0c Compare August 27, 2026 05:37
@frankgh

frankgh commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

I have some suggestions so we don't have to filter, but I will only get to this tomorrow.

fetches() answered true for every column, including ones dropped after
the filter was built, while fetchedColumns() returned the fixed set it
was built with. A read crossing a schema change then deserialized cells
it could not serialize:

  IllegalStateException: [m] is not a subset of []

Skipping them here lets ReadCommand stop filtering.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix-cassandra-21607 branch from 194a872 to 9b96953 Compare August 31, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants