prepare-db AKS-fanout: surface file-level download progress in the BLAST Databases modal¶
Motivation¶
On the AKS-fanout prepare-db path (mode=aks), the BLAST Databases modal showed
Copying 0 / 10 shards · 0% for many minutes even while files were actively
landing in Storage. The progress callback only reported pod-level counts
(succeeded_pods / shard_count), and a pod is only marked succeeded once
all ~480 of its files finish. With 10 shards each doing a slow per-file scan
before/between downloads, no shard completes for a long time, so the SPA's
shard-progress fallback sat at 0 / 10 and 0%. The download was healthy
(blob count climbing steadily) but the UI gave the user no visible signal,
which read as "is it actually downloading?".
User-facing change¶
The modal now shows a moving per-file bar (e.g. 3723 / 4814 files · 77%)
plus a throughput-derived ETA during an AKS download/update, instead of a static
0 / 10 shards · 0%. No frontend change was required — BlastDbRow.tsx already
prefers per-file progress (copy_status.success) over shard progress when the
field is present; the backend now populates it.
In addition, the progress line now shows a live download speed (e.g.
· 42.3 MB/s) for the AKS path, computed from the total bytes landed in Storage
divided by elapsed time.
API / IaC diff summary¶
api/tasks/storage/prepare_db_via_aks.py- New
_count_staged_blobs(container, db_name)— best-effort(count, total_bytes)of blobs under<db>/viacontainer.list_blobs(name_starts_with=...). The byte sum reuses the same listing pass (blob.size), so it adds no extra Storage calls. ReturnsNoneon listing failure so the caller falls back to pod-level counts rather than reporting a wrong/zero file count. _on_job_progress(...)now addscopy_status.success = <count>andcopy_status.bytes_done = <total_bytes>to the copying-phase metadata when the listing succeeds. All existing fields (phase,mode,total_files,active_pods,succeeded_pods,failed_pods,shard_count) are unchanged;success/bytes_doneare additive/optional so the readiness contract (copy_status.phase == "completed") is untouched.web/src/components/cards/storage/blastDbProgress.ts- New
formatSpeed(bytesDone, elapsedSeconds)pure helper — rendersB/s→TB/swith a 5 s stability gate (returns""before that). web/src/components/cards/storage/BlastDbRow.tsx- Computes a monotonic-clamped
speedLabelfromcopy_status.bytes_doneand renders it in the copying progress line. web/src/api/blast.ts,web/src/components/cards/storage/useBlastDb.ts- Added optional
copy_status.bytes_done?: numberto the types. - No IaC change. The worker already reaches Storage via the private endpoint in
the platform VNet (same container client used by the post-Job
_poll_copy_completion), so no new network surface is opened — Storage stayspublicNetworkAccess: Disabled.
Validation evidence¶
uv run pytest -q api/tests/test_prepare_db_aks_task.py→ 9 passed, including two tests:test_on_job_progress_reports_file_level_success— assertscopy_status.success(3 blobs,core_ntx/decoy excluded) andcopy_status.bytes_done == 3500(summed sizes, decoy excluded).test_on_job_progress_falls_back_when_listing_fails— alist_blobsraise omits bothsuccessandbytes_done(no poisoned progress) and keeps pod-level fields.cd web && npm test -- --run src/components/cards/storage/blastDbProgress.test.ts→ 13 passed, including the newformatSpeedsuite (MB/s, GB/s, KB/s, B/s, stability gate, decimal rules).cd web && npm run build→ built clean (no type errors).uv run pytest -q api/tests/test_prepare_db_aks_route.py api/tests/test_blast_database_readiness.py api/tests/test_prepare_db_aks_manifest.py→ 46 passed (readiness contract + manifest guards unaffected).uv run ruff check api/tasks/storage/prepare_db_via_aks.py api/tests/test_prepare_db_aks_task.py→ All checks passed.