Skip to content

Batch signed-URL lookup returns a partial map for a 4-member series on TEST_USER_1/prod #309

Closed

Description

@stevevanhooser

Surfaced by run 229 of test-cloud-api.yml after #308 merged. Three of the four (user × environment) cells pass; only TEST_USER_1 / prod fails, at the batch-signer efficiency assertion in test_members_survive_a_download_from_the_cloud:

AssertionError: a member fell back to per-uid getFileDetails.
Stats: signer_calls=1, uid_hits=2, uid_misses=2, failure_reason=''

last_map_size=2, last_map_uids=['41269720fdb9956d_40da7dec48b42117', '41269720fdb99570_c0d20060847e18de'].

What happened

The manifest and all 4 members uploaded and are on the server (that's #306, fixed in #308). All 4 members opened correctly on download — bytes match. What fails is the assertion that the batch signed-URL cache covers every uid in the scope: only 2 of 4 member uids came back in the batch map, so 2 members fell back to per-uid getFileDetails. Also emitted as a warning at src/ndi/cloud/filehandler.py:438:

The batch signed-URL lookup did not answer for scope "6aa931c04e21a03c972ae1c2/00065b84336133ad_4edab6a00cb0ac60/chunkdata.bin", so files there are being resolved one API call at a time. This still works, but for a large file series it is one call per member rather than one per series. Cause: the batch answered but did not name this uid. Reported once per scope.

Not a regression from #308

The download path and the batch-signer code (ndi/cloud/batch_signed_url.py, ndi/cloud/filehandler.py) weren't touched. But this is the first time a series document has ever successfully round-tripped on TEST_USER_1/prod — before #308, the manifest never reached the server (that's what run 227 caught), so this batch-signer behavior was hidden behind the upload gap.

Three of four (user × environment) cells produce a full map:

Cell Result last_map_size
User 1 / prod ❌ fail 2 (of 4)
User 1 / dev ✅ pass (presumed 4)
User 2 / prod ✅ pass (presumed 4)
User 2 / dev ✅ pass (presumed 4)

Two plausible causes

  1. Timing/indexing race. waitForAllBulkUploads returns when the pipeline reports done, but the batch signed-URL endpoint on prod's cluster may lag behind — indexing 4 uids into the series scope isn't guaranteed to be transactional with the bulk-upload completion signal on all environments. A retry after a beat would settle it, and only User 1 / prod is slow enough to expose it.
  2. Real gap on that account. A stale index, a per-account scope quirk, or a partial-response bug on the prod cluster specifically for User 1's datasets.

Next step

Re-run the failed job once to distinguish. If it passes on re-run, it's the timing case — likely fix on this side is a bounded retry in the batch signer's cache-population path, gated on stat coverage, before falling back to per-uid. If it fails again identically, it's an account/server issue and this issue tracks investigating the batch-signer scope query against the User 1 / prod cluster.

Refs

Activity

  1. stevevanhooser commented on Sep 15, 2026

    @stevevanhooser
    ContributorAuthor

    Retry passed. The failed User 1 / prod job passed on run_attempt: 2, ~37 minutes after the initial failure (attempt 1 finished 11:55:19Z, attempt 2 finished 12:31:54Z). Same commit (f1f195f), same test file, no code changes in between.

    Result: https://github.com/Waltham-Data-Science/NDI-python/actions/runs/34965617453/job/104380548545

    This makes hypothesis (1) — timing/indexing race between waitForAllBulkUploads returning and the batch signed-URL endpoint having all uids in its scope map — the leading cause. Not a proof (a second successful retry doesn't rule out an intermittent server-side gap), but it's inconsistent with a deterministic bug on the User 1 / prod account.

    Proposed direction

    A bounded, conservative retry in the batch signer's cache-population path — when the response's map covers strictly fewer uids than the request asked about, wait briefly and re-query the same scope once before falling back to per-uid getFileDetails. Concretely:

    • ndi/cloud/batch_signed_url.py::_fetch_scope: when the returned map's uid set is a strict subset of the uids the caller was interested in AND signer_calls == 1 for this scope, retry after (say) 500 ms once. Success moves on. Second miss records the failure_reason ("the batch answered but did not name this uid") and lets the per-uid fallback run as today.
    • Keep it opt-in in the design: only retry when the caller can name the expected uid set — the manifest-driven series-member path can, the general document scope cannot always. Series is where the cost of falling back is worst (28,000 API calls for a large series) and where the expected uids ARE known, so that's the case the retry addresses.
    • No retry loop, no exponential backoff — one bounded wait, one retry, then stand down. A slow server that never settles isn't ours to solve here; the test itself would still fail with the correct diagnostic.

    Not filing a PR yet — this warrants a look at whether the retry belongs in the cloud client, the batch signer, or filehandler.download_file_from_cloud, and whether the caller can always supply the expected-uid set at the shape the signer needs it. Marking the issue open for that design pass.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions