Skip to content

Commit e9d8a33

Browse files
authored
fix(render-service): make parallel capture effective (#1042)
* fix(render-service): make parallel capture effective * fix(render-service): preserve adaptive workers and beginframe * fix(render-service): restore adaptive compose sizing
1 parent 4efd881 commit e9d8a33

10 files changed

Lines changed: 341 additions & 75 deletions

docker-compose.yml

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ services:
1515
- NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION=${NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION:-}
1616
- NEXT_PUBLIC_ENABLE_PPTX_IMPORT=${NEXT_PUBLIC_ENABLE_PPTX_IMPORT:-}
1717
ports:
18-
- "3000:3000"
18+
- '3000:3000'
1919
env_file:
2020
- .env.local
2121
environment:
@@ -49,7 +49,7 @@ services:
4949
# before using the stack outside a local trusted environment.
5050
- POSTGRES_PASSWORD=${PERSISTENCE_POSTGRES_PASSWORD:-openmaic-dev}
5151
healthcheck:
52-
test: ["CMD-SHELL", "pg_isready -U openmaic -d openmaic"]
52+
test: ['CMD-SHELL', 'pg_isready -U openmaic -d openmaic']
5353
interval: 5s
5454
timeout: 5s
5555
retries: 10
@@ -76,26 +76,35 @@ services:
7676
profiles:
7777
- video-export
7878
expose:
79-
- "9000"
79+
- '9000'
8080
# Required for the entrypoint's iptables egress lockdown. Without it the
8181
# service still boots, but logs a warning and does NOT block Chromium egress.
8282
cap_add:
8383
- NET_ADMIN
8484
environment:
8585
- PORT=9000
86-
# Use Chromium's HeadlessExperimental.beginFrame capture path instead of
87-
# the CPU-bound Page.captureScreenshot fallback. The default "software" GPU
88-
# mode (SwiftShader) silently force-enables screenshot capture, which pegs
89-
# all cores on CPU rasterization and caps throughput at ~10 fps. Selecting
90-
# "hardware" keeps beginFrame active — measured ~2x capture throughput
91-
# (~10 -> ~19 fps, 900-frame clip 100s -> 62s), roughly half the CPU, and
92-
# pixel-identical static frames (animation frames differ only in sub-pixel
93-
# edge antialiasing). No real GPU is required: the win is the faster capture
94-
# API, not hardware rasterization — beginFrame falls back gracefully if the
95-
# host lacks a GPU.
86+
# Keep the beginFrame-capable browser path available when producer decides
87+
# the cgroup has enough memory. Under this service's 4 GiB limit producer's
88+
# automatic low-memory profile safely selects screenshot capture + one
89+
# worker instead of multiplying Chromium for unvalidated 1080p/4K renders.
9690
- PRODUCER_BROWSER_GPU_MODE=hardware
97-
# Tune for the host; each concurrent render drives a full Chromium + FFmpeg.
98-
- RENDER_MAX_CONCURRENCY=2
91+
- PRODUCER_HEADLESS_SHELL_PATH=/usr/bin/chromium-headless-shell
92+
- RENDER_MAX_CONCURRENCY=1
93+
- RENDER_MAX_CONCURRENT_EXTRACTIONS=1
94+
# Producer's browser pool shares a Chromium compositor across capture
95+
# sessions. Keep it disabled so an operator who raises memory and opts into
96+
# parallel capture gets independent browsers; the 4 GiB default resolves to
97+
# one worker, so this does not multiply memory by itself.
98+
- PRODUCER_ENABLE_BROWSER_POOL=false
99+
# Leave producer workers unset: producer 0.7.60 applies its cgroup memory,
100+
# CPU, small-job, low-memory, and capture-cost guards. Operators may opt
101+
# into the measured 720p latency profile documented in the service README.
102+
# Long compositions still need CDP headroom even with one adaptive worker.
103+
- PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS=900000
104+
# OpenMAIC's long slide exports currently exhaust producer's 15s static
105+
# verification budget and disable dedup anyway. Skip that guaranteed-lost
106+
# startup cost until the verifier can share/scale its plan across workers.
107+
- HF_STATIC_DEDUP=false
99108
# In this default topology the app is exposed directly and does NOT set
100109
# TRUST_PROXY_HEADERS, so every caller collapses to one identity. A
101110
# per-identity limit would then throttle the whole deployment to a single
@@ -107,6 +116,9 @@ services:
107116
# heavy, and extraction is capped separately (RENDER_MAX_CONCURRENT_EXTRACTIONS).
108117
# Raise for higher RENDER_MAX_CONCURRENCY / larger classrooms.
109118
mem_limit: 4g
119+
# Chromium media/frame work can exceed Docker's 64 MiB default shared-memory
120+
# mount. This is a ceiling inside the same 4 GiB cgroup, not eager allocation.
121+
shm_size: 2gb
110122
networks:
111123
- render
112124
restart: unless-stopped

render-service/Dockerfile

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
# Isolated render service: Node 22 + system Chromium + FFmpeg.
1+
# Isolated render service: Node 22 + Chromium headless shell + FFmpeg.
22
#
33
# Debian (bookworm-slim), NOT Alpine: @hyperframes/producer drives Chromium via
44
# puppeteer, and Chromium + its shared libraries are far simpler to provision on
5-
# glibc/Debian than on musl/Alpine. We install the distro Chromium and point
6-
# puppeteer at it (PUPPETEER_EXECUTABLE_PATH) so no second browser is downloaded.
5+
# glibc/Debian than on musl/Alpine. Producer's beginFrame capture requires the
6+
# old headless shell binary; regular Chromium exposes the resolver path but then
7+
# rejects HeadlessExperimental.beginFrame and silently falls back to screenshots.
78
FROM node:22-bookworm-slim AS base
89

910
ENV PUPPETEER_SKIP_DOWNLOAD=true \
10-
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
11+
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-headless-shell \
1112
NODE_ENV=production
1213

13-
# chromium + ffmpeg + the fonts/libs a headless Chrome needs to render text and
14-
# composite frames. iptables lets the entrypoint lock down egress (block the
15-
# untrusted Chromium from reaching the app); ca-certificates covers TLS for any
16-
# asset the composition might still reference before lockdown.
14+
# chromium-headless-shell + ffmpeg + the fonts/libs a headless Chrome needs to
15+
# render text and composite frames. iptables lets the entrypoint lock down egress
16+
# (block the untrusted Chromium from reaching the app); ca-certificates covers
17+
# TLS for any asset the composition might still reference before lockdown.
1718
RUN apt-get update \
1819
&& apt-get install -y --no-install-recommends \
19-
chromium \
20+
chromium-headless-shell \
2021
ffmpeg \
2122
iptables \
2223
ca-certificates \
@@ -39,7 +40,7 @@ FROM base AS runner
3940
# Non-root: Chromium must run with --no-sandbox in a container, so drop privileges.
4041
RUN groupadd --system --gid 1001 render \
4142
&& useradd --system --uid 1001 --gid render --home-dir /app render \
42-
&& mkdir -p /tmp/openmaic-renders \
43+
&& mkdir -p /tmp/openmaic-renders /app/.cache \
4344
&& chown -R render:render /app /tmp/openmaic-renders
4445

4546
COPY --from=deps /app/node_modules ./node_modules
@@ -55,7 +56,12 @@ RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
5556
&& chmod +x /usr/local/bin/docker-entrypoint.sh
5657

5758
ENV PORT=9000 \
58-
PRODUCER_TMP_PROJECT_DIR=/tmp/openmaic-renders
59+
PRODUCER_TMP_PROJECT_DIR=/tmp/openmaic-renders \
60+
HOME=/app \
61+
XDG_CACHE_HOME=/app/.cache \
62+
PRODUCER_BROWSER_GPU_MODE=hardware \
63+
PRODUCER_HEADLESS_SHELL_PATH=/usr/bin/chromium-headless-shell \
64+
RENDER_REQUIRE_BEGINFRAME=false
5965

6066
# NOTE: we intentionally do NOT set `USER render` here. The container starts as
6167
# root so the entrypoint can install the iptables egress lockdown (needs

0 commit comments

Comments
 (0)