Skip to content

Shared caches can hold uncommitted state after a rollback #33982

Description

@manerow

Summary

Several paths write to shared caches, or run external effects, while a transaction is still open. After a rollback, what they wrote or did stays. These were found while fixing the nested hard delete ordering (#33981) and are not fixed there.

Findings

  • Reads inside an open transaction fill shared caches. A read inside a transaction sees that transaction's uncommitted rows. On a cache miss, the following write what they saw straight into shared caches:

    • the Guava L1 loader, and the Redis L2 putBase it calls;
    • the "not found" markers written by find and findByName when the database has no row;
    • the read-bundle puts.

    After a rollback those entries stay: up to 3600 s in L2, and 30 s for the markers. The loader's write-epoch check does not catch this case, because the epoch moved before the load started.

  • Subclass postDelete overrides run external effects before the outer commit. When the delete is nested:

    • WorkflowDefinitionRepository.postDelete deletes the Flowable definition;
    • TableRepository.postDelete starts the async profiler purge (its javadoc says it runs after the commit);
    • ingestion pipeline deletes call the pipeline service.
  • Peer nodes delete a fresh "not found" marker. A delete publishes a cache invalidation, and every other node's NotFoundCache.invalidate deletes the shared marker just after the deleting node set it. This weakens fix(cache): seal hard-delete stale-read window via NotFoundCache marker #28197 on multi-node deployments.

  • Redis round trips inside a transaction. cleanupFlushBody calls invalidate() inside the delete transaction, contrary to flushInOneTransaction's javadoc. Fixes 33860: evict Redis L2 after commit, not inside the write transaction #33866 made the L2 half wait for the commit; the in-process half and the pub/sub are still inline.

  • Wrong javadoc. executeInTransaction says a boundary opened on Entity.getJdbi() does not bind the on-demand DAOs. jdbi 3.37 does bind them, through threadHandleSupplier.

  • Reindex still queries the negative cache. EntityCacheBypass does not cover NotFoundCache, so a reindex still pays a Redis GET per lookup.

Suggested direction

Writes to shared caches made while a unit of work is open should either wait for its commit (PostCommitActionQueue) or be skipped. External effects in delete hooks should be queued the same way.

Activity

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

Metadata

Metadata

Assignees

Labels

backendbugSomething isn't working

Type

Projects

  • Status
    No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions