Michael Semb Wever created CASSANDRA-21781:
----------------------------------------------

             Summary: Replica filtering protection resurrects rows deleted by a 
range tombstone that spans a batch boundary
                 Key: CASSANDRA-21781
                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21781
             Project: Apache Cassandra
          Issue Type: Bug
          Components: Consistency/Coordination
            Reporter: Michael Semb Wever
            Assignee: Michael Semb Wever


A filtered read limited to one partition can return a row that a range 
tombstone deletes. It needs:
 * replicas that diverge: one replica holds a range tombstone, another holds an 
older row inside that range;
 * a newer live row inside the range, on the replica that holds the tombstone;
 * a single-partition read with a {{LIMIT}} or a page size, and a row filter 
({{{}ALLOW FILTERING{}}} or SAI), at a consistency level that uses replica 
filtering protection.

The read returns the deleted row when a batch boundary of replica filtering 
protection falls inside the range tombstone.  Unpaged reads without a {{LIMIT}} 
are not affected, and nor are reads spanning several partitions.
h3. Reproduction

Two nodes, RF 2, read repair off.  Each write goes to one replica only.
{code:java}
CREATE TABLE ks.t (k int, c int, v int, PRIMARY KEY (k, c)) WITH read_repair = 
'NONE';

-- node 2
INSERT INTO ks.t (k, c, v) VALUES (0, 0, 1) USING TIMESTAMP 1;
INSERT INTO ks.t (k, c, v) VALUES (0, 5, 1) USING TIMESTAMP 1;
-- node 1
INSERT INTO ks.t (k, c, v) VALUES (0, 0, 0) USING TIMESTAMP 2;
DELETE FROM ks.t USING TIMESTAMP 2 WHERE k = 0 AND c > 0 AND c <= 10;
INSERT INTO ks.t (k, c, v) VALUES (0, 1, 1) USING TIMESTAMP 3;
INSERT INTO ks.t (k, c, v) VALUES (0, 7, 1) USING TIMESTAMP 3;

-- either coordinator, CONSISTENCY ALL
SELECT c FROM ks.t WHERE k = 0 AND v = 1 LIMIT 2 ALLOW FILTERING;
{code}
Expected {{{}1, 7{}}}. Actual {{{}1, 5{}}}: row {{c = 5}} is deleted by the 
range tombstone at timestamp 2.
h3. Cause

CASSANDRA-20639 made replica filtering protection consume a limited 
single-partition read in batches of {{LIMIT}} rows, to avoid needless short 
read protection reads.  After each batch, {{populate()}} queues each replica's 
{{PartitionBuilder}} again, and the batch's protected partition drains the 
builder's {{contents}} queue.

When a batch ends inside a range tombstone, the open marker leaves with that 
batch.  The next batch for that replica holds only the close marker, so a stale 
row from another replica inside the range is no longer shadowed when the 
replicas' protected partitions merge.

In the reproduction, the first batch is {{c = 0, 1}} and ends inside the 
tombstone opened after {{{}c = 0{}}}.  Protection then drops {{{}c = 0{}}}, as 
node 1 holds {{{}v = 0{}}}, so the coordinator needs a second batch. That 
batch, {{{}c = 5, 7{}}}, has no open marker for node 1, and node 2's {{c = 5}} 
is returned.
h3. Fix

Track, per replica, whether a range tombstone is open in the merge listener.  
Keep consuming rows past {{LIMIT}} until every open tombstone has closed, so no 
batch boundary falls inside one.
{code:java}
while ((i < command.limits().count() || mergeListener.hasOpenMarker()) && 
currentRowIterator.hasNext())
{code}
A tombstone spanning many rows makes its batch that large.  Before 
CASSANDRA-20639 every limited read consumed whole partitions, so this is no 
worse than the earlier behaviour, and the cached-rows thresholds still apply.

An alternative keeps batches bounded: close the open marker at the end of a 
batch and reopen it at the start of the next, using boundary bounds at the last 
consumed clustering.  That needs a per-batch snapshot of the builder contents, 
since the batches share one queue; it is more code for a case that is uncommon.

The patch applies to {{cassandra-6.0}} and {{trunk}} with line offsets only.
h3. How it was found

Harry fuzz testing for CASSANDRA-11745 (paging by bytes) on 6.0 found filtered 
single-partition reads returning deleted rows.  Byte paging hit it more often 
than row paging, since its pages end at different rows.  (The same seed on 
cassandra-6.0 before CASSANDRA-11745 returned the deleted row with one-row 
pages.)



--
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