Caleb Rackliffe created CASSANDRA-21854:
-------------------------------------------

             Summary: SAI vector index corruption when an insert is interrupted 
at the new graph-insert permit wait
                 Key: CASSANDRA-21854
                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21854
             Project: Apache Cassandra
          Issue Type: Bug
          Components: Feature/SAI, Feature/Vector Search
            Reporter: Caleb Rackliffe
            Assignee: Sunil Ramchandra Pawar


h2. Summary

Commit 
[{{bf06dbd}}|https://github.com/cscotta/cassandra/commit/bf06dbdd9aafb6b1d4ef17f1e06ec2b720d40d2b]
 bounds concurrent SAI graph inserts with a semaphore. {{OnHeapGraph.add}} now 
registers the vector and ordinal before it waits for a permit. An interrupt 
during that wait corrupts the memtable index permanently.

{{{}postingsMap.putIfAbsent{}}}, the ordinal assignment, 
{{{}vectorValues.add{}}}, and {{postingsByOrdinal.put}} all run before 
{{{}graphInsertPermits.acquireThrowUncheckedOnInterrupt(1){}}}. The thrown 
{{UncheckedInterruptedException}} lands before the {{{}try{}}}block. No 
rollback runs, so the vector stays registered and the graph node never gets 
linked.
h2. Trigger conditions
 # A table has a Storage-Attached Index vector index on a memtable-backed index.
 # Concurrent inserts exceed {{{}MAX_CONCURRENT_GRAPH_INSERTS{}}}, or the 
memtable reports {{{}limitsConcurrentWritesTo{}}}.
 # A writer thread receives an interrupt while it waits for a graph-insert 
permit.

h2. Failure scenario

The interrupted writer throws {{UncheckedInterruptedException}} and abandons 
the insert. {{vectorValues}} and {{{}postingsByOrdinal{}}}keep the ordinal. 
{{graph.size()}} stays 0. A retry of the same row only updates the postings 
list. It never calls {{{}builder.addGraphNode{}}}, so nothing repairs the graph.

At flush time, {{OnHeapGraph.writeData}} asserts {{nextOrdinal == 
builder.getGraph().size()}} and fails. With assertions disabled, the index 
stays silent and vector searches return no results for that vector.
h2. Reproducer

Path: 
{{~/.local/share/cassandra-watch/reports/reproducers/bf06dbdd9aaf/InterruptPermitUnlinkedNodeRepro.java}}
 (executed against a full patched build with {{{}-ea{}}}).

Observed output on 
[{{bf06dbd}}|https://github.com/cscotta/cassandra/commit/bf06dbdd9aafb6b1d4ef17f1e06ec2b720d40d2b]:
 the interrupted {{add()}} throws. {{{}vectorValues.size()==1{}}}. 
{{{}graph.size()==0{}}}. The retry succeeds but does not link the node. Vector 
search returns 0 results. The flush invariant fails.

On parent 
[{{b40e292}}|https://github.com/cscotta/cassandra/commit/b40e29225c50c96f10c4836d68d37174204cddd6]
 the same interrupt path cannot occur. The parent has no blocking wait between 
registration and {{{}addGraphNode{}}}.
h2. Proposed resolution

Acquire the permit before the ordinal assignment and registration, or roll back 
the registration when {{acquireThrowUncheckedOnInterrupt}} throws. 
Alternatively, add a failure path that unregisters the ordinal and the postings 
entry.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

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

Reply via email to