Nadav Har'El created CASSANDRA-21733:
----------------------------------------

             Summary: Reads fail after ALTER TABLE DROP ... USING TIMESTAMP 
older than the column's data
                 Key: CASSANDRA-21733
                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21733
             Project: Apache Cassandra
          Issue Type: Bug
          Components: CQL/Interpreter, Legacy/Local Write-Read Paths
            Reporter: Nadav Har'El


*Affects versions:*{color:#616161} 4.1.12, 5.0.9, 6.0-alpha2 (tested on the 
official Docker images); probably all versions since 4.0 or earlier{color}

When a regular column is dropped with {{{}ALTER TABLE ... DROP col USING 
TIMESTAMP t{}}}, and the column still holds cells with timestamps newer than 
{{{}t{}}}, reads of that table fail. The coordinator returns a ReadFailure to 
the client, and the replica logs an IllegalStateException while serializing the 
read response.

Reproducer (single node, cqlsh):
{code:sql}
CREATE KEYSPACE ks WITH replication = {'class': 'SimpleStrategy', 
'replication_factor': 1};
CREATE TABLE ks.t (a int PRIMARY KEY, c int, f int);
INSERT INTO ks.t (a, c, f) VALUES (1, 2, 3);
ALTER TABLE ks.t DROP f USING TIMESTAMP 20000;
SELECT * FROM ks.t;
{code}
The {{SELECT}} fails:
{noformat}
ReadFailure: Error from server: code=1300 [Replica(s) failed to execute read] 
message="Operation failed - received 0 responses and 1 failures: UNKNOWN from 
/127.0.0.1:7000"
{noformat}
and the node logs:
{noformat}
java.lang.RuntimeException: java.lang.IllegalStateException: [c, f] is not a 
subset of [c]
at 
org.apache.cassandra.service.StorageProxy$DroppableRunnable.run(StorageProxy.java:3159)
...
Caused by: java.lang.IllegalStateException: [c, f] is not a subset of [c]
at org.apache.cassandra.db.Columns$Serializer.encodeBitmap(Columns.java:618)
at org.apache.cassandra.db.Columns$Serializer.serializeSubset(Columns.java:548)
at 
org.apache.cassandra.db.rows.UnfilteredSerializer.serializeRowBody(UnfilteredSerializer.java:242)
...
at 
org.apache.cassandra.db.ReadResponse$LocalDataResponse.build(ReadResponse.java:262)
at org.apache.cassandra.db.ReadCommand.createResponse(ReadCommand.java:453)
at 
org.apache.cassandra.service.StorageProxy$LocalReadRunnable.runMayThrow(StorageProxy.java:2756)
{noformat}
(Stack trace from 6.0-alpha2; 4.1 and 5.0 fail the same way.)

Observations:
 * The same sequence without {{USING TIMESTAMP}} (so the drop time is newer 
than the data) works correctly.
 * The INSERT doesn't need an explicit timestamp. Any data newer than the drop 
timestamp triggers the bug. In practice that can happen with clock skew, 
client-provided timestamps, or a schema restored with explicit {{DROP ... USING 
TIMESTAMP}} statements, like the ones DESCRIBE and snapshots' schema.cql emit 
for dropped columns.
 * The table doesn't need another regular column. With {{{}CREATE TABLE ks.t (a 
int PRIMARY KEY, f int){}}}, the error becomes {{{}[f] is not a subset of 
[]{}}}.
 * Range scans ({{{}SELECT * FROM ks.t{}}}) fail for every table we tried. 
Single-partition reads ({{{}WHERE a = 1{}}}) also fail when the table has a 
clustering key, e.g. {{{}PRIMARY KEY (a, b){}}}, but succeed in the example 
above without one.
 * The error is in serializing the read response. The row still contains the 
dropped column {{{}f{}}}, apparently because its cells are newer than the drop 
time, but the serialization header's column set doesn't include it.

*Why the existing unit tests don't catch this:*

The unit tests that drop a column {{USING TIMESTAMP}} older than its data don't 
detect this bug:
 * {{{}AlterTest.testDropWithTimestamp{}}}, {{testDropStaticWithTimestamp}} and 
{{testDropMultipleWithTimestamp}} re-ADD the dropped column before reading the 
table, so they never read a table whose dropped column still has newer data.
 * {{CreateLikeTest.testCopyAfterAlterTable}} does exactly that: it runs 
{{ALTER TABLE ... DROP f USING TIMESTAMP 20000}} after writing {{f}} with 
current timestamps, and then reads the table. It passes because CQLTester's 
{{execute()}} runs queries through {{{}QueryProcessor.executeInternal(){}}}, 
which reads locally without building a serialized {{{}ReadResponse{}}}, and 
that serialization is where the bug is. The same test fails when its statements 
are sent through the native protocol (e.g. with {{{}executeNet(){}}}). We found 
this while translating CreateLikeTest to ScyllaDB's Python-based CQL tests, 
which run against a real Cassandra node through the Python driver.


Related: CASSANDRA-21607 reports a similar scenario, a dropped non-frozen 
collection that still holds cells newer than the drop time, which causes a 
NullPointerException in reads and compaction. This issue is the same situation 
for a simple (non-collection) column. It fails differently, in read-response 
serialization, but may have the same root cause.

 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to