Skip to content

perf(api): cache mixdown renders - #311

Merged
thcp merged 3 commits into
mainfrom
perf/mixdown-render-cache
Jul 17, 2026
Merged

thcp merged 3 commits into
mainfrom
perf/mixdown-render-cache

Conversation

@thcp

@thcp thcp commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #290, PR-3E (the stretch item) of Phase 3.

  • _stream_ffmpeg optionally tees yielded chunks to a per-request temp file while streaming; a clean finish (finished and returncode == 0) atomically renames it into place as the cache entry and prunes the cache to a 20-file / 500 MB budget (oldest evicted first). Any failure or client disconnect unlinks the temp file instead -- a render the client didn't get in full can never become a cache hit for the next request. The other 3 _stream_ffmpeg call sites (stem region trim, stem-mp3, video mux) are untouched -- caching is opt-in via a new cache_path param, defaulting to None.
  • get_mixdown's cache key covers every render input: job_id, ext, stems, gains, region (start/end), and the live export_sample_rate setting -- so a mixer change, a region trim, or a Settings change always misses cleanly. Computed after the existing job/stem validation, so a deleted or not-yet-done job still 404s exactly as before instead of serving a stale entry from a job that no longer exists.
  • A cache hit returns a FileResponse directly -- no ffmpeg invocation, no subprocess spawn.
  • Bonus fix: cache/ (where CACHE_DIR defaults to for source runs, same pattern as jobs/) wasn't gitignored. Added it -- caught this because an earlier local test run before I isolated the cache dir in tests wrote real cache files into the repo.

Test plan

  • test_mixdown_cache_hit_skips_second_render -- two identical requests, second one doesn't invoke _stream_ffmpeg's ffmpeg subprocess at all, byte-identical response.
  • test_mixdown_cache_key_varies_with_params -- different gains / region produce distinct cache entries, no collisions.
  • test_mixdown_failed_render_leaves_no_cache_entry -- a corrupt input file (ffmpeg exits non-zero) leaves the cache dir empty, no stray temp file.
  • test_prune_mixdown_cache_bounds_file_count / test_prune_mixdown_cache_bounds_total_size -- direct unit tests of the prune bound, oldest-first eviction.
  • All new tests run against real ffmpeg locally (data/ffmpeg on PATH), not just the CI-gated stub path -- 222 passed with ffmpeg available, 207 passed / 15 skipped without (matches the existing skip-gating convention for the other ffmpeg-happy-path tests in this file).
  • ruff check / ruff format --check clean

Identical mixdown params re-ran the full ffmpeg graph on every request.
On a shared server, repeat downloads of the same export (a common case)
burned CPU for a pure function of the inputs.

_stream_ffmpeg optionally tees yielded chunks to a per-request temp file
as it streams; a clean finish atomically renames it into place as the
cache entry and prunes the cache to a 20-file / 500 MB budget (oldest
first). Any failure or client disconnect removes the temp file instead
-- a render the client didn't get in full never becomes a cache hit for
the next request.

get_mixdown's cache key covers every render input (job_id, ext, stems,
gains, region, and the live export sample rate setting), computed after
the existing job/stem validation so a deleted or not-ready job still
404s the same way it always has instead of serving a stale entry. A hit
returns a FileResponse with no ffmpeg invocation at all.

Also: cache/ (CACHE_DIR's default under the repo root for source runs,
same pattern as jobs/) wasn't gitignored -- added it alongside jobs/.
Comment thread app/api/stems.py Fixed
Comment thread tests/test_stems_api.py Fixed
Comment thread tests/test_stems_api.py Fixed
Comment thread tests/test_stems_api.py Fixed
Thales added 2 commits July 17, 2026 14:37
- _prune_mixdown_cache: log a debug line instead of silently swallowing
  a failed unlink, so a stuck cache entry leaves a trace.
- tests/test_stems_api.py: use "from app.api import stems as stems_mod"
  consistently instead of mixing it with "import app.api.stems as ...".
@thcp
thcp merged commit 679eb78 into main Jul 17, 2026
8 checks passed
@thcp
thcp deleted the perf/mixdown-render-cache branch July 17, 2026 13:51
@thcp thcp mentioned this pull request Jul 17, 2026
5 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exports re-encode on every request

1 participant