Do not index deleted forts loaded from the database - #398
Conversation
|
The patch treats an evicting and not strictly deletion-safe cache as authoritative for formerly DB-backed endpoints, yielding silent omissions and inclusions. Its hot walker also adds per-match allocations and multipolygon-wide containment work that can defeat the stated large-geofence performance goal. Full review comments:
|
|
It was a design choice to do the geofence in the database for this - since we don’t nescessarily have all the records in memory. Can the query not just be optimised to avoid the particular issue? |
|
A quick google suggests these variations are worth trying in the first instance: SET @Fence = ST_GeomFromGeoJSON(?, 2, 0); SELECT ... or WITH candidates AS MATERIALIZED ( |
fortRtreeUpdatePokestopOnGet and fortRtreeUpdateGymOnGet indexed every fort they were handed. The load-by-id queries have no deleted filter and deletion leaves enabled and the quest fields intact, so a deleted row reaching the cache-miss path was added to the lookup cache and the fort tree as a live fort, and /api/pokestop/scan and /api/gym/scan would return it. The save path has always skipped deleted forts; match it on load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9b8bc04 to
01e503a
Compare
|
Thanks both. This ended up a lot smaller than it started. @Mygod all four were real, and I checked each rather than taking them on faith:
@jfberry you were right, and I had the cause wrong. I benchmarked both suggestions on MariaDB 11.8, 1M rows, a 2000 vertex fence, 628,560 bbox candidates:
Neither variant moves it, and Synthetic data in a container, so please read the ratio rather than the absolute times. |
This PR started as an in-memory rewrite of the geofence endpoints and has been reduced to the one bug it turned up along the way. The timeout it was chasing is fixed in #400 instead, on the database path, so nothing here depends on
fort_in_memory.Problem
fortRtreeUpdatePokestopOnGetandfortRtreeUpdateGymOnGetindex every fort they are handed. The load-by-id queries carry nodeletedfilter, and deleting a fort leavesenabledand the quest fields intact, so a deleted row arriving through the cache-miss path was added to the lookup cache and the fort tree as a live fort./api/pokestop/scanand/api/gym/scanwould then return it.The save path (
fortRtreeUpdatePokestopOnSaveintogenericUpdateFort) has always skipped deleted forts. The load path now matches it.Why the rest went away
The original approach walked the fort lookup index instead of querying the database, and measurement did not support it. Against 1,000,000 rows and a 2000-vertex fence, the in-memory walk and the database path spend most of their time in the same place: testing candidate points against the polygon. Moving that test out of SQL takes the query from 55 s to roughly 4 s, and the in-memory version saves only the second or so of query and transfer on top of that. The numbers are in #400.
That is not worth what it cost. Serving these endpoints from the index meant treating it as a census of the table, which it was not: the fort caches expire entries untouched for 25 to 27 hours and preload only runs at boot, so a region that stopped being scanned quietly dropped out of the answers. Keeping it complete meant holding every fort's index entry for the process lifetime, and the invariant had no test behind it. All of that to avoid a second of query time, on instances with
fort_in_memoryenabled and nowhere else.@jfberry's read was right, and the database is the correct place for this. #400 keeps it there.
Testing
TestDeletedFortsNotIndexedOnLoadcovers a deleted pokestop, a deleted gym, and a live pokestop that must still be indexed so the guard cannot pass by rejecting everything. It fails with either guard removed.go build -tags go_json ./...andgolangci-lint runare clean, and thedecoderand root suites pass.🤖 Generated with Claude Code