-- AZ-505: Leaflet covering index on `tiles` keyed by location_hash. -- -- Forward migration: -- 1. Create `tiles_leaflet_path` covering index over (location_hash, -- captured_at DESC, updated_at DESC, id DESC) with INCLUDE (file_path, source). -- The leading column matches the equality predicate used by the AZ-505 -- Leaflet hot path (`SELECT file_path FROM tiles WHERE location_hash = $1 -- ORDER BY captured_at DESC, updated_at DESC, id DESC LIMIT 1`); the INCLUDE -- columns make that exact projection an index-only scan once VACUUM ANALYZE -- has set the visibility map. -- 2. Drop the lightweight `idx_tiles_location_hash` introduced by migration -- 014 — it is superseded because equality lookups by `location_hash` use -- the leading column of the new covering index. -- -- Back-migration (manual): -- DROP INDEX IF EXISTS tiles_leaflet_path; -- CREATE INDEX IF NOT EXISTS idx_tiles_location_hash ON tiles (location_hash); -- -- INCLUDE columns are intentionally narrow (`file_path, source`). The richer -- inventory endpoint legitimately requires extra columns that are NOT in the -- INCLUDE list (`id, captured_at, flight_id, image_type, tile_size_meters, -- tile_size_pixels, location_hash`); inventory queries therefore trigger a -- bounded heap fetch, which is acceptable per the AZ-505 NFR-Perf-2 budget -- (≤ 1000 ms p95 / 2500 tiles). See AZ-505 Risk 1 in the task spec. -- -- Lock window: this migration runs inside DbUp's per-script transaction, which -- is incompatible with `CREATE INDEX CONCURRENTLY`. On a populated `tiles` -- table the `CREATE INDEX` takes an `ACCESS SHARE` + `SHARE` lock on the table -- for the duration of the build, blocking writes. Schedule deploys to a -- low-traffic window or pre-build the index out-of-band before running this -- migration. See AZ-505 Risk 2. BEGIN; CREATE INDEX IF NOT EXISTS tiles_leaflet_path ON tiles (location_hash, captured_at DESC, updated_at DESC, id DESC) INCLUDE (file_path, source); DROP INDEX IF EXISTS idx_tiles_location_hash; COMMIT;