Skip to content

GET /v1/tracks/{id}/stems exposes no file size, forcing an N+1 inspectTrack fan-out #999

Description

@dylanjeffers

Summary

GET /v1/tracks/{id}/stems returns no file size, so every consumer that needs sizes fans out one inspectTrack call per stem. Two independent consumers do this today, and one of them turns it into a hard failure mode.

Current response shape (api/v1_track_stems.go, TrackStem):

id, parent_id, category, cid, user_id, blocknumber, orig_filename

Consumers paying the N+1

  1. ClientuseFileSizes in ContestStemsCard.tsx / track-page DownloadSection.tsx requests sizes for [trackId, ...stems] purely to render the size column.
  2. Archiver (AudiusProject/pedalboard, apps/archiver) — calls sdk.tracks.inspectTrack per stem to compute disk reservation before downloading.

For an 11-stem contest track that's 11 extra round-trips per consumer, per view.

Why it's worse than a perf nit

In the archiver the sizing pass is all-or-nothing:

const fileSizes = await Promise.all(stems.map(inspectFileSize))
// inspectFileSize throws when inspection.data?.size is falsy

A single stem with unavailable size metadata fails the entire archive before any download starts. Filed separately against the archiver, but the root cause is that size has to be fetched per-stem from a second service at all.

Caveat on the fix

This is not a simple SELECT addition. There is no file-size column on tracks — only orig_file_cid / orig_filename. Byte size currently lives on the content node and is what inspectTrack goes and fetches. So options are roughly:

  1. Denormalize size at index time — persist the original blob size on the track row when the upload is indexed, then include it in the stems response (and probably the track response too). Biggest payoff, needs an indexer change and a backfill.
  2. Batch endpoint — accept multiple track ids in a single inspect/size call so consumers make one round-trip instead of N. Smaller change, keeps size authoritative on the content node.
  3. Leave as-is and make consumers resilient — at minimum the archiver should not fail an entire archive on one missing size.

Option 3 should happen regardless. Raising this to decide whether 1 or 2 is worth doing.

Context: surfaced while investigating the 2026-07-28 contest stem-download outage.

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