perf(factory): тёплый пул процессов WeasyPrint вместо exec-per-doc #100

Merged
ant merged 2 commits from perf/factory-weasyprint-warm-pool into dev 2026-06-05 15:06:48 +00:00
Owner

Проблема

Генерация PDF в factory спавнила новый процесс weasyprint на каждый документ (exec в Services/Generator/index.ts). Медленные 3-5 сек — холодный старт Python: import всего стека (cffi/pydyf/tinycss2/Pango/cairo/HarfBuzz) + скан fontconfig. Сам рендер быстрый (<0.5 сек), но старт платился каждый раз — процесс умирал сразу после генерации.

Решение

Тёплый пул из N долгоживущих Python-процессов (WeasyPool / WeasyWorker):

  • import платится один раз за жизнь бэкенда, далее каждый документ = чистый рендер;
  • размер пула = env WEASY_POOL_SIZE (дефолт min(4, CPU)) → реальный параллелизм, одновременные генерации не сериализуются к одному процессу (FIFO-очередь ожидающих);
  • воркер живёт внутри процесса controller'а — без докера, миграций и отдельного сервиса;
  • питон из /venv (controller/Dockerfile), фоллбэк python3;
  • краш воркера самолечится: pending реджектится, следующий вызов поднимает процесс заново.

Протокол: в stdin воркера пишем <htmlPath>\t<pdfPath>, читаем OK / ERR <traceback>. PDF не гоним через пайп — обмениваемся путями к временным файлам.

Детерминизм

Сохранён: SOURCE_DATE_EPOCH=0 в env воркера. Заодно выровнял weasyPrintVersion 62.367 (синхрон с pip install WeasyPrint==67 в controller/Dockerfile — метадата producer'а документов больше не врёт о версии).

Эффект

10 одновременных генераций при пуле 4: ~3 волны по <0.5 сек (~1.5 сек у последнего) против ~30-50 сек в старой схеме (10× холодный старт).

Проверки

  • tsc --noEmit — чисто
  • unbuild — exit 0
  • smoke-тест петли воркера на реальном weasyprint 66.0: один горячий процесс отрендерил 2 дока подряд, кириллица ок, два рендера одного HTML дали идентичный sha256 (детерминизм подтверждён)

В playbooks менять нечего — прод тянет готовые coopback-образы, питон+weasyprint в /venv уже есть; dist пересобирается штатно (pnpm -F @coopenomics/factory build).

🤖 Generated with Claude Code

## Проблема Генерация PDF в `factory` спавнила новый процесс `weasyprint` на **каждый** документ (`exec` в `Services/Generator/index.ts`). Медленные 3-5 сек — холодный старт Python: import всего стека (cffi/pydyf/tinycss2/Pango/cairo/HarfBuzz) + скан fontconfig. Сам рендер быстрый (<0.5 сек), но старт платился каждый раз — процесс умирал сразу после генерации. ## Решение Тёплый пул из N долгоживущих Python-процессов (`WeasyPool` / `WeasyWorker`): - import платится **один раз** за жизнь бэкенда, далее каждый документ = чистый рендер; - размер пула = env `WEASY_POOL_SIZE` (дефолт `min(4, CPU)`) → **реальный параллелизм**, одновременные генерации не сериализуются к одному процессу (FIFO-очередь ожидающих); - воркер живёт внутри процесса controller'а — без докера, миграций и отдельного сервиса; - питон из `/venv` (controller/Dockerfile), фоллбэк `python3`; - краш воркера самолечится: `pending` реджектится, следующий вызов поднимает процесс заново. Протокол: в stdin воркера пишем `<htmlPath>\t<pdfPath>`, читаем `OK` / `ERR <traceback>`. PDF не гоним через пайп — обмениваемся путями к временным файлам. ## Детерминизм Сохранён: `SOURCE_DATE_EPOCH=0` в env воркера. Заодно выровнял `weasyPrintVersion` `62.3` → `67` (синхрон с `pip install WeasyPrint==67` в `controller/Dockerfile` — метадата producer'а документов больше не врёт о версии). ## Эффект 10 одновременных генераций при пуле 4: ~3 волны по <0.5 сек (~1.5 сек у последнего) против ~30-50 сек в старой схеме (10× холодный старт). ## Проверки - `tsc --noEmit` — чисто - `unbuild` — exit 0 - smoke-тест петли воркера на реальном weasyprint 66.0: один горячий процесс отрендерил 2 дока подряд, кириллица ок, два рендера одного HTML дали **идентичный sha256** (детерминизм подтверждён) В playbooks менять нечего — прод тянет готовые coopback-образы, питон+weasyprint в `/venv` уже есть; dist пересобирается штатно (`pnpm -F @coopenomics/factory build`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude added 1 commit 2026-06-05 14:15:53 +00:00
perf(factory): тёплый пул процессов WeasyPrint вместо exec-per-doc
Typecheck / controller (pull_request) Has been cancelled
Typecheck / desktop (pull_request) Has been cancelled
2244c4e95c
Генерация PDF спавнила новый процесс `weasyprint` на каждый документ через
`exec`. Медленные 3-5 сек — это холодный старт Python: import всего стека
(cffi/pydyf/tinycss2/Pango/cairo/HarfBuzz) + скан fontconfig. Сам рендер
быстрый (<0.5 сек), но старт платился каждый раз, т.к. процесс умирал сразу.

Держим пул из N долгоживущих Python-процессов горячими (WeasyPool/WeasyWorker):
import платится один раз за жизнь бэкенда, далее каждый документ = чистый
рендер. Размер пула = env WEASY_POOL_SIZE (дефолт min(4, CPU)) — даёт реальный
параллелизм, одновременные генерации не сериализуются к одному процессу.
Воркер живёт внутри процесса controller'а: без докера, миграций и отдельного
сервиса. Питон берётся из /venv (controller/Dockerfile), фоллбэк python3.

Детерминизм сохранён: SOURCE_DATE_EPOCH=0 в env воркера. Заодно выровнял
weasyPrintVersion 62.3 → 67 (синхрон с pip install в controller/Dockerfile —
метадата producer'а документов больше не врёт).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude added 1 commit 2026-06-05 14:30:24 +00:00
merge dev into perf/factory-weasyprint-warm-pool (PR #99 publish + др.)
Typecheck / controller (pull_request) Has been cancelled
Typecheck / desktop (pull_request) Has been cancelled
a2cf1d7bdf
ant merged commit 3a372fe801 into dev 2026-06-05 15:06:48 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: C9S/mono#100