fix: handle non-flat row id vectors when replaying RTREE index appends and deletes from the WAL - #862
Merged
Maxxen merged 1 commit intoSep 7, 2026
Conversation
…s and deletes from the WAL
Maxxen
reviewed
Sep 2, 2026
| key_chunk.Reset(); | ||
| key_executor->ExecuteExpression(input, key_chunk.data[0]); | ||
| key_chunk.SetCardinality(count); | ||
| key_chunk.Flatten(); |
Member
There was a problem hiding this comment.
This doesn't really make sense to me - calling .Flatten() here should ensure the box_vec is a flat vector, making ConvertToEntries safe as is
Member
|
Hello! Thanks for digging into this and thanks for this PR! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #861.
Problem
If a database file is closed without checkpointing while its WAL contains inserts into a table with an
RTREEindex, then on the next open:INTERNAL Error: Operation requires a flat vector but a non-flat vector was encounteredCHECKPOINTfails with the same error, after which DuckDB reports the database as invalidatedIt doesn't appear to clear itself. Every subsequent open replays the same WAL and hits the same failure. The data itself seems intact —
SELECT count(*)returns every row and a read-only open reads everything — but the file can't checkpoint again until someone drops the index by hand.A single inserted row is enough to reproduce:
Root cause
When the WAL is replayed without the spatial extension loaded, the
RTREEindex cannot be bound, so DuckDB buffers the appends and deletes and applies them later after something binds the index.BoundIndex::ApplyBufferedReplaysslices the buffered chunk to build the row id vector:That produces a dictionary vector.
ConvertToEntriesread it withFlatVector::ValidityandFlatVector::GetData, which throw on anything non-flat.ART indexes may survive the same replay path because
ART::GenerateKeyVectorsgoes throughUnifiedVectorFormat, which is why the failure should be specific toRTREE.Fix
ConvertToEntriesreads the row ids throughUnifiedVectorFormat, indexing both the data and the validity mask through the selection vector. This coversInsertandDelete, which share the helper.key_chunk.SetCardinality(count)beforekey_chunk.Flatten()inInsertandDelete.DataChunk::Flatten()flattens up tosize(), which is 0 right afterReset(), so the flatten didn't do anything. I don't see a known trigger, since the key vector comes back flat on both paths, but the call was not doing what it reads like it should be doing.Test
test/sql/index/rtree_persistence_wal_append.testcovers both the append and the delete replay: create the index, write, restart, then index scan andCHECKPOINT. It fails before this change and passes after.Two notes on the test:
rtree_persistence_wal.testdidn't look like it was actually exercising WAL replay. The sqllogictest runner defaultscheckpoint_wal_sizeto 0 (TestConfiguration::GetCheckpointWALSize), so it checkpoints as soon as the WAL is non-empty and no.walfile survives therestart. The new test raisescheckpoint_thresholdafter every restart, which is what keeps the WAL alive into the next session. I left the existing test alone, but certainly they can be merged.rtree_index_dumpinstead of only counting table rows. Deleted rows are filtered out when the index scan fetches them from the table, so a table-level count passes whether or not the entries were actually removed from the index.Related
RTreeIndex::Append. Its stack crashes insideRTree::SplitNode/SortEntriesByRowIdon the ordinaryLocalTableStorage::AppendToIndexespath, which points at concurrent tree mutation rather than vector layout.