Skip to content

Fix #1608: rebuild match graph from DB at startup, stop resurrecting deleted spot maps#1610

Open
JasonWildMe wants to merge 1 commit into
mainfrom
fix/issue-1608-deleted-spotmaps
Open

Fix #1608: rebuild match graph from DB at startup, stop resurrecting deleted spot maps#1610
JasonWildMe wants to merge 1 commit into
mainfrom
fix/issue-1608-deleted-spotmaps

Conversation

@JasonWildMe

Copy link
Copy Markdown
Collaborator

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 cache WEB-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; resetMatchGraphWithInitialCapacity is single-assignment (no transient null window).
  • Shepherd — add commitDBTransactionWithStatus() (the existing commitDBTransaction() swallows commit failures). The three match-graph mutators build the EncounterLite while 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 by useSpotPatternRecognition so non-spot installs (where the graph is never built) aren't permanently blocked. This closes the delete-vs-rebuild race. The eagerly-opened Shepherd PM 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 compile passes.
  • Reviewed with codex across 3 rounds to convergence (no Critical/Major findings).

Known minor / deferred (pre-existing, not introduced here)

  • The delete/remove-spots servlets still report "success" even if the commit fails.
  • SubmitSpotsAndImage closes the Shepherd then finally rolls back a closed PM.
  • Shepherd.getEncounter() collapses transient fetch errors to null, so the rebuild could silently skip such a row.

🤖 Generated with Claude Code

…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>
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.

Deleted spot maps appear in match results of new matches

1 participant