Skip to content

fix(posting): set minTs for empty rollups, drop replayed commits belo… - #9863

Open
shiva-istari wants to merge 1 commit into
mainfrom
shiva/minTs
Open

shiva-istari wants to merge 1 commit into
mainfrom
shiva/minTs

Conversation

@shiva-istari

@shiva-istari shiva-istari commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

…w minTs

Fixes #9856

When a delete empties a posting list, the incremental rollup rewrites the key as a BitEmptyPosting marker at commitTs+1. ReadPostingList did not set List.minTs for that marker (it does for BitCompletePosting), so:

  • on a cache miss MemoryLayer.ReadData, which reads the key at math.MaxUint64, served the empty list to every read ts, including transactions that started before the delete; and
  • ToBackupPostingList versioned the emptied key at 0, so restoring a full plus an incremental backup kept the older, non-empty version.

Set minTs = item.Version() for BitEmptyPosting as well. The line was dropped in ee99a7a (#9237) and the bug first shipped in v24.1.0.

With minTs set, a commit replayed from the Raft log after a restart can reach updateItemInCache with commitTs <= minTs of the cached list, because the rollup that already contains it is versioned newest commit + 1. Folding it in again puts a delta below minTs into the mutable layer, and if it is a delete-all every read the entry serves fails with "deleteBelowTs must be greater than the minTs in the list" until the entry is evicted. Drop the cache entry in that case instead; the next read rebuilds it from disk. This path was already reachable for complete rollups before this change.

Tests: posting/mvcc_test.go gains the reporter's snapshot-read test and a replay test for both rollup kinds;
systest/integration2/rollup_empty_list_test.go reproduces the earlier-start-ts read and the backup/restore case end to end.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • Bug Fixes
    • Preserved historical relationship data for transactions started before deletions, even after list rollups.
    • Improved handling of empty relationship lists across snapshot reads.
    • Ensured deleted relationships remain removed after backup and restore operations.
    • Prevented replayed updates from corrupting cached relationship-list results.

@shiva-istari
shiva-istari requested a review from a team as a code owner October 21, 2026 05:36
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1b51016d-e2c1-4f16-8f5b-592ce3ce3415
📥 Commits

Reviewing files that changed from the base of the PR and between 59e3f50 and 9c7d5fa.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a5eaa79b-7323-43f2-bdc9-608660db2fdb
📥 Commits

Reviewing files that changed from the base of the PR and between 830bc4d and 59e3f50.

📒 Files selected for processing: 3
  • posting/mvcc.go
  • posting/mvcc_test.go
  • systest/integration2/rollup_empty_list_test.go

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Empty posting list rollups now record the marker version, and cached updates discard commits at or below the list’s minimum timestamp. MVCC and integration tests cover snapshot reads, cached replay, query results, and backup restore.

Changes: Empty posting list rollup

Layer / File(s) Summary
Track empty-marker versions and guard cached replay
posting/mvcc.go
ReadPostingList records the empty marker’s version in minTs. updateItemInCache removes the cached list and stops when a commit timestamp is at or below minTs.
Verify MVCC snapshots and cached replay
posting/mvcc_test.go
Tests cover reads around an empty rollup and replayed delete-all commits for empty and re-added posting lists.
Verify query snapshots and backup restore
systest/integration2/rollup_empty_list_test.go
Integration tests cover earlier-start-timestamp queries across rollup and restoration after full and incremental backups.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: matthewmcneely

Merge Risk: ⚪ Minimal · up to 59e3f

This change keeps deletions that empty a posting list correct across rollups. Snapshots taken before the delete still see the earlier values. Backup and restore no longer bring deleted values back. Replayed older commits no longer corrupt cached lists. No concrete defect was found, so the change looks ready to merge.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title accurately identifies the main fix: setting minTs for empty rollups and dropping replayed commits below that threshold.
Linked Issues check Passed The changes satisfy the coding requirements in [#9856]. ReadPostingList sets List.minTs to item.Version() for BitEmptyPosting, so reads before the delete can use older deltas while reads at or…
Out of Scope Changes check Passed All production changes and tests are connected to [#9856]. The cache invalidation change supports replay safety for rolled-up posting lists. The unit and integration tests provide regression coverage …
Full details: Linked Issues check

Explanation

The changes satisfy the coding requirements in [#9856]. ReadPostingList sets List.minTs to item.Version() for BitEmptyPosting, so reads before the delete can use older deltas while reads at or after the marker see an empty list. MemoryLayer.updateItemInCache drops cached lists when a replayed commit has commitTs &lt;= minTs, which prevents the cache error and allows disk rebuild. The MVCC tests cover earlier snapshots and replayed commits for empty and complete rollups. The integration tests cover UID edges, count(), has() filters, and incremental backup/restore behavior.

Full details: Out of Scope Changes check

Explanation

All production changes and tests are connected to [#9856]. The cache invalidation change supports replay safety for rolled-up posting lists. The unit and integration tests provide regression coverage for the corrected snapshot and backup/restore behavior. No unrelated changes are shown in the reviewed diff.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@blacksmith-sh

This comment has been minimized.

…w minTs

When a delete empties a posting list, the incremental rollup rewrites the
key as a BitEmptyPosting marker at commitTs+1. ReadPostingList did not set
List.minTs for that marker (it does for BitCompletePosting), so:

- on a cache miss MemoryLayer.ReadData, which reads the key at
  math.MaxUint64, served the empty list to every read ts, including
  transactions that started before the delete; and
- ToBackupPostingList versioned the emptied key at 0, so restoring a full
  plus an incremental backup kept the older, non-empty version.

Set minTs = item.Version() for BitEmptyPosting as well. The line was
dropped in ee99a7a (#9237) and the bug first shipped in v24.1.0.

With minTs set, a commit replayed from the Raft log after a restart can
reach updateItemInCache with commitTs <= minTs of the cached list, because
the rollup that already contains it is versioned newest commit + 1.
Folding it in again puts a delta below minTs into the mutable layer, and
if it is a delete-all every read the entry serves fails with
"deleteBelowTs must be greater than the minTs in the list" until the entry
is evicted. Drop the cache entry in that case instead; the next read
rebuilds it from disk. This path was already reachable for complete
rollups before this change.

Tests: posting/mvcc_test.go gains the reporter's snapshot-read test and a
replay test for both rollup kinds;
systest/integration2/rollup_empty_list_test.go reproduces the
earlier-start-ts read and the backup/restore case end to end.

Fixes #9856
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

A delete that empties a posting list leaks into earlier transactions, and is undone by a restore, once the list is rolled up (since v24.1.0)

1 participant