Skip to content

fix: persist the index dirty marker - #1281

Open
mfleisch wants to merge 1 commit into
nitrite:mainfrom
mfleisch:pr/index-dirty-marker
Open

fix: persist the index dirty marker#1281
mfleisch wants to merge 1 commit into
nitrite:mainfrom
mfleisch:pr/index-dirty-marker

Conversation

@mfleisch

@mfleisch mfleisch commented Aug 21, 2026

Copy link
Copy Markdown

Hi! We ran into this while tracking down why some of our users' databases rebuilt their
indexes on every start, and it looks like a real bug rather than something intentional.

markDirty() reads the IndexMeta out of the index meta map, flips isDirty on it, and
stops there:

private void markDirty(Fields fields, boolean dirty) {
    IndexMeta meta = indexMetaMap.get(fields);
    if (meta != null && meta.getIndexDescriptor() != null) {
        meta.getIsDirty().set(dirty);
    }
}

Nothing puts the instance back, so the store is never told the entry changed and the new
value is not guaranteed to be written. Both beginIndexing() and endIndexing() go
through this method, so neither direction of the marker is reliably durable.

That bites in two ways:

  • An index left half built by a crash can come back up reading as clean, because the
    true never made it to disk. Nothing rebuilds it and queries quietly use an incomplete
    index.
  • A completed index can stay marked dirty, when the true did reach disk through an
    unrelated commit of the same page and the following false did not. Then every open
    rebuilds the index again on the first write. This is the one we actually hit.

The fix is to put the meta back after flipping the flag. It is crash-recovery state, so
both directions have to survive a restart.

Test

IndexDirtyMarkerDurabilityTest in the MVStore adapter drives beginIndexing() /
endIndexing() directly, closes the database and reopens it, then asserts what the marker
reads as. Sitting in org.dizitart.no2.collection.operation so it can reach the
package-private methods. The "started, then restarted" case fails on current main and
passes with the fix; the "completed" case passes either way and is there to pin the
complementary expectation.

Happy to move the test somewhere else if you would rather not have a test in that package.

Summary by CodeRabbit

  • Bug Fixes

    • Improved index recovery state handling so indexing status is preserved across database restarts.
    • Completed indexing now reliably clears the recovery marker.
  • Tests

    • Added coverage confirming that interrupted indexing remains marked after restart.
    • Added coverage confirming that completed indexing is restored as clean after restart.

markDirty() flips isDirty on the IndexMeta that get() returned and never puts
it back, so nothing tells the store the entry changed and the new value is not
guaranteed to be written. Both beginIndexing() and endIndexing() go through
it, so the marker is unreliable in both directions: an index left half built
by a crash can come back reading as clean, and a completed one can stay marked
dirty and be rebuilt on the first write after every open.

Put the IndexMeta back after flipping the flag.
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7dfbca2-0658-438e-8d7f-051af5e59f80

📥 Commits

Reviewing files that changed from the base of the PR and between ab91339 and d09159b.

📒 Files selected for processing (2)
  • nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/collection/operation/IndexDirtyMarkerDurabilityTest.java
  • nitrite/src/main/java/org/dizitart/no2/collection/operation/IndexManager.java

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

IndexManager.markDirty now persists updated index metadata. A new MVStore test creates an indexed database and verifies that dirty and clean marker states survive database restarts.

Changes

Index dirty marker durability

Layer / File(s) Summary
Persist index dirty metadata
nitrite/src/main/java/org/dizitart/no2/collection/operation/IndexManager.java
markDirty writes the updated IndexMeta back to indexMetaMap.
Verify restart state
nitrite-mvstore-adapter/src/test/java/org/dizitart/no2/collection/operation/IndexDirtyMarkerDurabilityTest.java
The tests create a committed index, reopen the database, and verify that incomplete indexing remains dirty while completed indexing is clean.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to d0915

This is a localized fix that persists index dirty-state changes and adds regression coverage; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 clearly and concisely describes the main change: persisting the index dirty marker.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant