Skip to content

bug-report test: document not found after cleanup() + re-insert of the same primary key - #8948

Open
kilbot wants to merge 1 commit into
pubkey:masterfrom
kilbot:bug/resurrection-stale-query
Open

bug-report test: document not found after cleanup() + re-insert of the same primary key#8948
kilbot wants to merge 1 commit into
pubkey:masterfrom
kilbot:bug/resurrection-stale-query

Conversation

@kilbot

@kilbot kilbot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Filled in the bug-report template, per its instructions.

The sequence is: insert → remove() → cleanup(0) → insert the same primary key again. The second insert succeeds, but findOne() returns null for the document that was just written. Closing and reopening the database shows the document was stored correctly — the live instance is what's wrong. A fresh find() with a selector also misses the document while count() returns 1, so it isn't query-cache staleness.

Fails on current master with both DEFAULT_STORAGE=memory and DEFAULT_STORAGE=dexie, and with eventReduce: false too:

AssertionError [ERR_ASSERTION]: findOne() must return the document that was just inserted

While digging we noticed the conflict-retry for inserting a previously-deleted key (reInserts in rx-storage-helper.ts) emits its change event with previousDocumentData: null even though the write carried the tombstone as previous — that might be a useful place to start looking. Possibly related: #7984 (bulk revival of soft-deleted docs, SQLite).

Context: our production app keeps hitting a class of malformed-JSON errors in the premium OPFS storage, and delete → re-insert of the same primary key turned out to be the trigger. This test is the smallest storage-independent piece we could distill from it. The OPFS-specific side (index files growing by one row per delete → re-insert cycle) I'll file separately in rxdb-premium-issues with its own reproduction.

Finally — an apology for the earlier AI-heavy submissions here and in rxdb-premium-issues (#8841, premium PR 26). I'm honestly too busy to write these investigations up myself, so I lean on AI tooling; all I knew first-hand was that I kept getting this class of malformed-JSON bugs without knowing where they came from. I've tried to keep this one to exactly what the template asks for: a failing test.

@kilbot

kilbot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Traced it further, in case it saves you time: the DocumentCache's anti-downgrade guard (the out-of-order protection from #8609 in doc-cache.ts) rejects the re-inserted document because its revision chain restarts at height 1, which is "older" than the cached tombstone's height 2 — and cleanup emits no events, so the cache never learns the tombstone was purged. That's why findOne(pk) (which reads through the cache) returns null while a selector-less find() and count() are correct.

Evicting deleted-latest entries from cacheItemByDocId after a cleanup pass fixes this test on memory and dexie, with cleanup/rx-query/rx-collection/event-reduce/doc-cache/reactive/rx-document suites staying green. One caveat: under multiInstance only the instance that ran the cleanup evicts its own cache. Happy to push that as a commit here if you want it — otherwise the failing test stands on its own.

kilbot added a commit to wcpos/monorepo that referenced this pull request Aug 13, 2026
The census lifecycle cycled insert -> claim -> remove -> re-insert of the
same queryKey every ~15 minutes. Re-inserting a soft-deleted primary key is
the resurrection pattern that leaked index rows in the premium storage
(patched at the storage layer in #1164) and still trips an unfixed rxdb-core
cache bug after cleanup purges the tombstone (pubkey/rxdb#8948). Removing
the delete -> re-insert cycle removes the trigger entirely and cuts write
churn on the highest-churn collection.

A terminal 'idle' status replaces deletion: the runner's three remove()
sites become markIdle CAS transitions (same replaceIfCurrent contract as
markFailed), the maintenance-lane seeder wakes idle states when their census
cache entry goes stale (claimNew only for genuinely-absent keys, preserving
exactly-once claims), and readRunnable naturally excludes idle. Schema bumps
2 -> 3 (enum widening changes the schema hash) with a passthrough migration.

Mutation-checked: dropping the success->idle transition fails the runner +
maintenance tests; dropping the v3 migration fails the schema test.
Implemented by Codex from a written spec; reviewed line-by-line, suite and
mutation checks re-run independently.

Claude-Session: https://claude.ai/code/session_01VRdA9wbToqQXCvH1JFDBNH
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