Skip to content

perf: RealVideoProcessor blocks the event loop on result-cache disk I/O #1227

Description

@groupthinking

Problem

RealVideoProcessor performs its result-cache disk I/O inline inside async def methods. Every call runs blocking filesystem and JSON work directly on the event loop thread, stalling all concurrently-served requests for the duration.

Method Blocking calls (on main)
_load_from_cache (real_video_processor.py:73-96) Path.exists(), Path.stat(), open(), json.load()
_save_to_cache (real_video_processor.py:98-115) open(), json.dump()

The payloads are full video analyses (metadata + transcript + AI analysis), so json.load/json.dump are not trivial — serialization cost scales with transcript length. _load_from_cache sits on the hot path of every process_video() call, so the stall is paid on both cache hits and misses.

_load_from_cache additionally splits exists()stat()open() into three separate filesystem calls, leaving a TOCTOU window where the entry can be evicted between the existence check and the read.

Reachability evidence

Production entrypoint is youtube_extension.main:app (Dockerfile:93CMD python -m uvicorn youtube_extension.main:app ...).

main.py:192              app.include_router(api_v1_router)
backend/api/v1/router.py:678   POST endpoint process_video_v1
  -> router.py:693         await video_processing_service.process_video_basic(...)
  -> services/video_processing_service.py:132   processor = self.get_video_processor()
  -> services/video_processing_service.py:84    self.video_processor_factory.create_processor("auto")
  -> containers/service_container.py:257        get_video_processor(processor_type)
  -> backend/video_processor_factory.py:74      return RealVideoProcessor()
  -> services/video_processing_service.py:137   await processor.process_video(video_url)
  -> services/real_video_processor.py:145/274   _load_from_cache / _save_to_cache

RealVideoProcessor is selected by video_processor_factory.get_video_processor() in two reachable ways:

  1. Explicit configurationVIDEO_PROCESSOR_TYPE=real (video_processor_factory.py:44-47). This is a supported deployment knob, wired as a container env var in scripts/archive/service.yaml:61.
  2. Automatic fallback — if constructing EnhancedVideoProcessor raises, the factory falls through to RealVideoProcessor (video_processor_factory.py:66-74). This is the degraded-mode path, i.e. exactly when the service is already unhealthy and least able to absorb event-loop stalls.

Note: backend/real_api_endpoints.py also calls this processor, but it is not currently mounted by main.py and is therefore excluded from this reachability claim.

Acceptance criteria

  • _load_from_cache and _save_to_cache perform no blocking filesystem or JSON work on the event loop thread.
  • The exists/stat/open/json.load sequence is performed in a single off-loop hop, closing the TOCTOU window.
  • Observable behaviour is unchanged: caching disabled → None; missing file → None; entry aged >= 24h → None; corrupt JSON → None (warning logged, no raise); fresh entry → payload with cached=True and cache_age_hours.
  • _save_to_cache continues to strip cached / cache_age_hours before writing.
  • Pre-existing tests in tests/unit/test_real_processors.py pass without modification.
  • New tests prove the blocking work runs off the event loop thread, and fail against the pre-change code.
  • New tests assert on thread identity, not wall-clock timing (deterministic under CI load).
  • ruff check / ruff format output at parity with main for the touched files.

Tracking context

  • Defect class: blocking disk + JSON I/O executed inline on the asyncio event loop.
  • Component: youtube_extension.backend.services.real_video_processor.
  • Reachability: reachable on the POST /api/v1/process-video path whenever RealVideoProcessor is selected — either explicitly via VIDEO_PROCESSOR_TYPE=real, or as the degraded-mode fallback when EnhancedVideoProcessor fails to construct. RealVideoProcessor is the configured-or-fallback processor, not the default.
  • Risk if unfixed: every concurrent request stalls for the duration of the cache read/write; worst impact lands in the fallback path, when the service is already unhealthy.
  • Blast radius of the fix: 2 files, no dependency change, no observable behavior change; TOCTOU window narrowed, not eliminated; no schema/format change.
  • Current validation: governance, test, trivy, and coverage are green on fd5a6b2c5918a5a23ebfb5746c88788babbe4238; CodeRabbit review is still queued.

Campaign sequencing

This is item 1 of 10 in a sequenced performance campaign against event-loop-blocking calls in this service. Remaining items target cloud_ai provider image reads, intelligent_cache Redis fan-out, database_optimizer SQLite connects, and deployment_manager subprocess verification.

Activity

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions