Skip to content

perf(engine): content-addressed extraction cache for video frames#433

Closed
jrusso1020 wants to merge 1 commit intoperf/producer-segment-scope-hdr-preflightfrom
perf/producer-extraction-cache
Closed

perf(engine): content-addressed extraction cache for video frames#433
jrusso1020 wants to merge 1 commit intoperf/producer-segment-scope-hdr-preflightfrom
perf/producer-extraction-cache

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented Apr 23, 2026

What

Adds a content-addressed cache for pre-extracted video frames. When extractCacheDir is configured, extractAllVideoFrames skips the Phase 3 ffmpeg extract on inputs whose (source, window, fps, format) tuple has been seen before.

Opt-in via EngineConfig.extractCacheDir or HYPERFRAMES_EXTRACT_CACHE_DIR. Disabled by default.

Why

PR 3 of the 5-PR stack from hyperframes-notes/producer-render-architecture-review-2026-04-21.md. The doc calls this the biggest wall-clock win for iteration workflows:

Extraction cache does not exist. If the same video appears in two consecutive renders with the same window, we extract it twice.

Typical impact: the second render of an unchanged composition skips Phase 3 entirely, so videoExtractBreakdown.extractMs drops to ~0 (just an ffprobe on the source) and cacheHits equals the video count. This is visible in the RenderPerfSummary from PR 1.

How

Key layout (extractionCache.ts)

  • v1-<sha256-prefix-32> — schema-version prefix + 128-bit hash of path | mtime-ms | size | mediaStart | duration | fps | format.
  • Content hashing of the source file is deliberately avoided (hashing hundreds of MB on every render defeats the purpose); mtime+size is a good proxy for "the same file on disk."
  • The version prefix means a future format change (PR 4's WebP swap) invalidates old entries without collision — PR 4 will bump it to v2.

Completeness sentinel

  • Each entry dir gets .hf-complete written after a successful extraction.
  • Cache hit requires the sentinel — partial/aborted writes never serve as a hit.
  • Concurrent renders missing the same key both extract into the same dir, both touch the sentinel; last-writer-wins is fine because the key is content-addressed.

Lifecycle

  • New ExtractedFrames.ownedByLookup?: boolean field. When false (cache-owned paths), FrameLookupTable.cleanup() skips rmSync so the cache dir survives the render.
  • Cache hits: no ffmpeg spawn at all; ExtractedFrames built from the cache entry's frame files + the source's ffprobe metadata.
  • Cache misses: extract directly into the cache entry dir (via a new outputDirOverride param on extractVideoFramesRange), then mark complete. The per-render work/video-frames/<id>/ dir isn't created on the cache path.

Counters

  • New ExtractionPhaseBreakdown.cacheHits / cacheMisses counters flow through RenderPerfSummary.videoExtractBreakdown.

Known limitations (deliberate for v1)

  • No eviction. Cache grows until the user clears it. Follow-up PR can add size-capped LRU.
  • HDR/VFR-preflighted inputs always miss. The preflight converted file lives in the per-render work/ dir (different path+mtime per render), so its cache key differs across runs. An optimization for later; the common SDR+CFR case is covered.
  • HTTP downloads always miss. Each download resolves to a fresh tmp path. Keying on URL instead of download path is also a follow-up.

Test plan

  • 17 unit tests in extractionCache.test.ts — key determinism, schema-version prefix, per-field invalidation (path / mtime / size / window / fps / format), float stability, source probing, sentinel presence/absence, format-mismatch miss, multi-frame hit.
  • 2 integration tests in videoFrameExtractor.test.ts (skipped when ffmpeg unavailable):
    • Extract same source twice, second call has cacheHits=1, wall time under 2s, identical frame count.
    • Changing fps (or any keyed field) causes a second miss and new cache entry.
  • Full engine suite: 338 tests pass (was 319).
  • Typecheck clean in engine + producer.
  • Lint + format clean.

Stack

Built on #432 (segment-scope HDR preflight) which is built on #430 (phase-level instrumentation). Each PR in the stack adds to RenderPerfSummary so improvements are measurable.

Follow-ups (remaining PRs in the stack)

  1. WebP as the single extraction format (will bump CACHE_SCHEMA_VERSION to 2)
  2. Hardware-accelerated SDR decode gated on !hasAlpha and segment-length floor

Copy link
Copy Markdown
Collaborator Author

jrusso1020 commented Apr 23, 2026

@jrusso1020
Copy link
Copy Markdown
Collaborator Author

Closed: rebuilt as #446 off current main with #437's cleanups (FRAME_FILENAME_PREFIX constant, Phase 2 metadata reuse, unified cache key) rolled in. See hyperframes-notes/perf-stack-rebuild-plan-2026-04-23.md

@jrusso1020 jrusso1020 closed this Apr 23, 2026
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.

1 participant