# Производительность и ресурсы контейнера Что сделано для быстродействия, стабильности и минимального потребления ресурсов, и как это проверять. ## Что включено | Область | Решение | |---|---| | Остановка | SIGTERM/SIGINT → `app.close()` (дожидается запросов) → WAL checkpoint → `close`. Таймаут `SHUTDOWN_TIMEOUT_MS` (8 с), повторный сигнал — немедленный выход. В compose `init: true`, `stop_grace_period: 15s`. | | SQLite | `busy_timeout`, `cache_size`, `temp_store=MEMORY`, `mmap_size`, лимит WAL; транзакции для записи доступа/аудита; версия схемы в `user_version` (миграции не гоняются на каждом старте); индексы под очистку и фильтры; фильтры/сортировки в SQL. | | Очистка | Раз в час: истёкшие/отозванные сессии, коды авторизации, WebAuthn-челленджи (пачками по 1000, `SESSION_RETENTION_DAYS`). | | Горячий путь | Кэш профиля пользователя (инвалидируется любой записью в users/доступ — блокировка действует сразу), кэш App Switcher, мемоизация JWKS. Лимитер argon2 (`ARGON2_CONCURRENCY`). Секреты OIDC-клиентов — SHA-256 (старые argon2 мигрируют при первом обмене). | | HTTP | Hashed-ассеты `immutable` на год, `index.html` — `no-cache`; сжатие JSON на стороне Traefik; CORS по allowlist; `/health` (liveness, без БД) и `/ready` (БД + ключ OIDC); корректные 404 для `/oauth`, `/.well-known`, `/api`. | | Образ | API бандлится tsup в один файл, в образ ставятся только нативные модули; статика только `.br`; healthcheck через `wget`; `NODE_OPTIONS=--max-old-space-size=160`; лимиты compose: 256 МиБ, 1 CPU, `read_only`, `cap_drop: ALL`. | | Бэкап | `docker exec auth-portal node dist/server.js backup` или `BACKUP_INTERVAL_HOURS`; онлайн-копия, `integrity_check`, ротация. | Дополнительно: подготовленные запросы (`prepare` один раз на соединение), строгая CSP (`'self'`, без inline-скриптов), опциональное сжатие в Node (`ENABLE_COMPRESS=true`, если нет Traefik compress), базовый образ закреплён по digest, `design-preview`-маршруты вырезаются из прод-образа, healthcheck Traefik и `depends_on: service_healthy`, provenance + SBOM при публикации, Trivy (информативно), общий composite-action CI, `inputs` в `turbo.json`. ## Замеры (локально, Windows, Node 25; не Linux-контейнер) | Что | Результат | |---|---| | `listActiveSessions` по пользователю на 100k сессий | ~1.6 мс | | `runHousekeeping`, 90k устаревших сессий | ~220 мс | | `appendAuditBatch`, 10k событий одной транзакцией | ~610 мс | | `listAudit(kind=logins)` на 10k | ~5 мс | | 20k prepared-запросов профиля | ~28 мс | | HTTP `GET /health` (autocannon, 50 соединений) | ~15 000 rps, p99 7 мс | | HTTP `GET /api/v1/auth/me` (JWT, кэш профиля) | ~8 300 rps, p99 11 мс | | web: файлов в `dist/assets` | 87 → 48 без preview-маршрутов; gzip JS 484 → 437 КБ | | Рабочий набор процесса (Windows) | ~117 МиБ в покое, ~237 МиБ после потока 8 с при куче 160 МБ | Рабочий набор Windows не равен RSS контейнера Linux. Реальный RSS под лимитом 256 МиБ мерит `scripts/ci/smoke-image.sh` и ночной `nightly-perf` — при превышении увеличьте `mem_limit` и `NODE_OPTIONS` вместе. ## Проверка ```bash pnpm turbo test:coverage --filter=@authportal/api --filter=@authportal/db --filter=@authportal/shared pnpm --filter web build && node scripts/ci/web-budget.mjs apps/web/dist/assets bash scripts/ci/smoke-image.sh # размер, RSS, /ready, docker stop, данные после рестарта pnpm --filter @authportal/db test:perf # бюджеты производительности БД node scripts/perf/load.mjs http://127.0.0.1:8080 [container] # autocannon (ночью: nightly-perf.yaml) ``` Бюджеты smoke-теста (env): `MAX_IMAGE_MB=250`, `MAX_RSS_MB=200`, `MAX_START_S=15`, `MAX_STOP_S=5`. Подтяните их после первого замера на CI. ## Эксплуатационные заметки - В production `JWT_SECRET` должен быть уникальным (≥ 16 символов), а `ADMIN_PASSWORD` для первого запуска — не короче 8 символов и не `admin`. - Если нужен другой лимит памяти, меняйте `mem_limit` и `NODE_OPTIONS` вместе (куча ≈ 60–65 % лимита). - Одна реплика: SQLite не поддерживает несколько писателей.