Fix #1608: rebuild match graph from DB at startup, stop resurrecting deleted spot maps#1610
Open
JasonWildMe wants to merge 1 commit into
Open
Fix #1608: rebuild match graph from DB at startup, stop resurrecting deleted spot maps#1610JasonWildMe wants to merge 1 commit into
JasonWildMe wants to merge 1 commit into
Conversation
…deleted spot maps Deleted spot maps were reappearing as Modified-Groth match candidates in new scans, weeks later. Groth candidates come only from the in-memory GridManager.matchGraph, which was seeded at startup from the disk cache WEB-INF/MatchGraphCache.json (added in "Optimized Modified Groth"). That cache was written only at graceful shutdown and never invalidated on spot/encounter deletion, so a non-graceful restart (OOM/kill/crash) reloaded stale spotted entries and resurrected deleted spot maps until a clean shutdown overwrote it. Make the database authoritative and harden the surrounding paths: - StartupWildbook: always rebuild the match graph from the DB at startup (setMatchGraphReady(false) + createMatchGraph); drop the disk-cache read and the shutdown cache write. Scans stay gated (503) during the ~15-min rebuild. - GridManager: remove the now-unused cache machinery and unused imports; resetMatchGraphWithInitialCapacity is single-assignment (no transient null). - Shepherd: add commitDBTransactionWithStatus() (the existing commit swallows failures). EncounterRemoveSpots, SubmitSpotsAndImage and EncounterDelete now build the EncounterLite while managed and mutate the match graph only after a confirmed commit. - EncounterRemoveSpots and EncounterDelete refuse with 503 while the graph is rebuilding (guarded by useSpotPatternRecognition so non-spot installs aren't blocked), closing the delete-vs-rebuild race; the eager Shepherd PM is closed before the early return. - MatchGraphCreationThread: skip concurrently-deleted (null) encounters; real row errors abort the rebuild without marking the graph ready. - GrothMatchServlet: skip candidates with no spots on the scanned side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1608.
Problem
Deleted spot maps were reappearing as Modified-Groth match candidates in new scans, weeks after deletion (e.g. South African Carcharias taurus surfacing for Australian sharks).
Groth candidates come only from the in-memory
GridManager.matchGraph. Since "Optimized Modified Groth" (bf6ab9b6ca, 2026-02-20), that map is seeded at startup from the disk cacheWEB-INF/MatchGraphCache.json. The cache is written only at graceful shutdown (contextDestroyed → saveMatchGraph) and is never invalidated when spots/encounters are deleted. So a non-graceful restart (OOM /docker kill/ crash) skips the write, the next startup reloads the stale spotted entries, and deleted spot maps reappear as candidates — persisting across restarts until a clean shutdown finally overwrites the file. The DB was correct the whole time; only the matcher's in-memory view was stale.Fix
Make the database authoritative and harden the surrounding paths:
StartupWildbook— always rebuild the match graph from the DB at startup (setMatchGraphReady(false)+createMatchGraph()); drop the disk-cache read and the shutdown cache write. Scans stay gated (503 "match graph is still loading") during the ~15-min rebuild, as before on a cold start.GridManager— remove the now-unused cache machinery (cacheRead/cacheWrite/cacheToJSONObject/getCacheFilePath/CACHE_FILEPATH) and unused imports;resetMatchGraphWithInitialCapacityis single-assignment (no transientnullwindow).Shepherd— addcommitDBTransactionWithStatus()(the existingcommitDBTransaction()swallows commit failures). The three match-graph mutators build theEncounterLitewhile the object is still JDO-managed and update the graph only after a confirmed commit:EncounterRemoveSpots,SubmitSpotsAndImage,EncounterDelete.EncounterRemoveSpots/EncounterDelete— refuse with 503 while the graph is rebuilding, guarded byuseSpotPatternRecognitionso non-spot installs (where the graph is never built) aren't permanently blocked. This closes the delete-vs-rebuild race. The eagerly-openedShepherdPM is closed before the early return.MatchGraphCreationThread— skip concurrently-deleted (null) encounters; genuine row errors propagate and abort the rebuild without marking the graph ready (no silently-incomplete graph).GrothMatchServlet— skip candidates with no spots on the scanned side.Trade-off
This removes the startup match-graph disk cache, so startups again take ~15 min to rebuild the graph from the DB. Accepted because restarts are rare and the database must be the source of truth for the candidate set.
Verification
mvn compilepasses.codexacross 3 rounds to convergence (no Critical/Major findings).Known minor / deferred (pre-existing, not introduced here)
SubmitSpotsAndImagecloses the Shepherd thenfinallyrolls back a closed PM.Shepherd.getEncounter()collapses transient fetch errors tonull, so the rebuild could silently skip such a row.🤖 Generated with Claude Code