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
- Client —
useFileSizes in ContestStemsCard.tsx / track-page DownloadSection.tsx requests sizes for [trackId, ...stems] purely to render the size column.
- 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:
- 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.
- 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.
- 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.
Summary
GET /v1/tracks/{id}/stemsreturns no file size, so every consumer that needs sizes fans out oneinspectTrackcall 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):Consumers paying the N+1
useFileSizesinContestStemsCard.tsx/ track-pageDownloadSection.tsxrequests sizes for[trackId, ...stems]purely to render the size column.AudiusProject/pedalboard,apps/archiver) — callssdk.tracks.inspectTrackper 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:
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— onlyorig_file_cid/orig_filename. Byte size currently lives on the content node and is whatinspectTrackgoes and fetches. So options are roughly: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.