BLAST database listing performance + upgrade escape-hatch probe gating¶
Motivation¶
App Insights (production appi-elb-dashboard) showed a clear, data-backed
bottleneck and one benign-but-noisy failed request. Over a dogfood window:
GET /api/blast/databases— p50 9.0s, p95 21.7s.GET /api/blast/databases/check-updates— p95 26.6s.- Within one databases operation:
BlobClient.download_blobfired 629 times andContainerOperations.list_blob_flat_segmentaccumulated 423s — the signature of an N+1 overblast-dbblobs. GET /api/upgrade/escape-hatch— the only failed request (404 × 3), a benign probe against a deployment with no rollback snapshot yet.
Exceptions: 0. Error-level traces: 0. The codebase is otherwise healthy; these were the only actionable signals.
Root cause¶
api/services/storage/database_list.py::list_databasesdownloaded every volume's.njssidecar (core_nt.00.njs … core_nt.NN.njs) while keeping only the last lexicographic one. A large multi-volume DB therefore paid hundreds of wasted blob downloads per listing.api/routes/blast/databases.py::blast_databases_check_updatescalledstorage.data.list_databasesdirectly, bypassing the 300s-TTLdatabase_catalog_cachethatGET /api/blast/databasesalready uses — so it re-paid the full enumeration on every New Search load.web/src/pages/UpgradePage.tsxprobed/upgrade/escape-hatchon every refresh even whenrollback_targetwas empty, where the route correctly returns 404 (locked bytest_escape_hatch_404_without_snapshot).
User-facing change¶
- New Search / Database Builder load materially faster — the
.njswork drops from O(volumes) to O(databases) downloads, and check-updates no longer re-enumerates Storage on a warm cache. - No behaviour change to the returned catalogue payload: the "last volume wins"
.njscontent contract andfile_count(which counts.njsas a recognised BLAST extension) are preserved. - The Upgrade page no longer emits a 404 escape-hatch probe on never-upgraded deployments; escape-hatch / rollback-preflight are fetched only once a rollback snapshot exists.
API / IaC diff summary¶
- No API surface, response shape, or IaC change.
api/services/storage/database_list.py— record each base's.njsblob name during enumeration (last wins) and download exactly one per registered DB after the loop; a.njswhose base was filtered out (shards / staging) is never downloaded.api/routes/blast/databases.py—check-updatesnow callsdatabase_catalog_cache.list_databases_cachedinstead of the raw lister.web/src/pages/UpgradePage.tsx— gate the escape-hatch + rollback-preflight probes on a non-emptystatus.rollback_target.
Verified non-changes (App Insights false positives)¶
/api/aks/autostop/status(p95 7.5s) already has a two-tier L1+Redis cache;/api/monitor/acrand/api/monitor/aksalready usecached_snapshot. High p95 is cold-miss cost, not a missing cache — no change made.- Periodic reconcilers (
reconcile_auto_warmup,reconcile_public_https) already early-exit on no/disabled preferences; adding idle backoff would hurt warmup responsiveness — no change made. GET /api/monitor/sidecars/eventsInProc 776884ms is SSE stream lifetime, not latency — no change made.
Validation evidence¶
uv run pytest -q api/tests— 2902 passed, 3 skipped.- New regression tests in
api/tests/test_storage_data.py:test_list_databases_downloads_one_njs_per_multivolume_db(asserts exactly one.njsdownload for a 12-volume DB + "last wins" content) andtest_list_databases_skips_njs_for_filtered_base. uv run ruff checkon the changed backend files — clean.cd web && npx tsc --noEmit+npm run build— succeed.npx vitest run src/api/upgrade.test.ts— 16/16 passing.