Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions posting/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,20 @@ func (l *List) Rollup(alloc *z.Allocator, readTs uint64) ([]*bpb.KV, error) {
kvs = append(kvs, kv)
}

// Retire removed parts at the parent's timestamp (+1 for finite readTs)
// so compaction preserves reads using the old parent.
for _, startUid := range l.plist.Splits {

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.

The crash-safety of this depends on a write-ordering invariant that's currently implicit. If one of these tombstones ever became durable before the new parent KV and the process died in between, the old parent would still be the latest visible version and would reference a part that now reads as empty. Worse, the next rollup would read that part as empty and persist the loss.

Today the ordering holds: the sort below puts the main key first (DefaultPrefix 0x00 sorts before ByteSplit 0x04), TxnWriter submits commits in call order, and badger's WAL replays a strict prefix, so a tombstone can't survive a crash without its parent. But nothing in the code says that order is load-bearing (the sort comment only mentions readability). Could you add a short comment here or on the sort noting that the main-list KV must be written before these tombstones?

if _, retained := out.parts[startUid]; retained {
continue
}
part, err := out.marshalPostingListPart(alloc, l.key, startUid, &pb.PostingList{})
if err != nil {
return nil, errors.Wrapf(err, "cannot marshal removed posting list part")
}
part.Version = kv.Version

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 works only because the retained-parts loop above shadows kv in its own scope, so the outer kv here is still the main-list KV with the +1 applied. That's fragile: a refactor of that loop could silently change which version these tombstones get. Suggest capturing parentVersion := kv.Version right after it's computed and using it here.

While you're at it, a line on why removed parts get the parent's +1 version while retained parts stay at out.newMinTs would help future readers: a reader at exactly newMinTs still resolves the old parent, which must still see the removed part's data.

kvs = append(kvs, part)
}

// Sort the KVs by their key so that the main part of the list is at the
// start of the list and all other parts appear in the order of their start UID.
sort.Slice(kvs, func(i, j int) bool {
Expand Down
Loading
Loading