9.2 KiB
Итог расследования production-инцидента 2026-06-07
Документ фиксирует результат расследования нестабильности рабочего контура AWatch-rus перед пилотной демонстрацией. Все значения ниже обезличены: реальные IP-адреса, hostname, учетные записи, домены и приватные пути не приводятся.
Краткий вывод
Причина инцидента была не в ClickHouse и не в портале. Узкое место находилось в
цепочке ActivityWatch Server -> aw-worktime-api-rust -> worktime reports.
aw-worktime-api-rust при построении отчетов запрашивал слишком большой объем
events из bucket aw-worktime-sessions_<HOST> через ActivityWatch HTTP API.
На рабочей SQLite-базе это приводило к холодным запросам с таймаутами,
росту memory/CPU у aw-server-rust, зависанию прогрева отчетов и периодическим
таймаутам executive API в портале.
Наблюдаемые симптомы
/portal/api/healthмог оставаться зеленым за счет кэша, но/portal/api/reports?role=executiveпериодически уходил в долгий ответ или таймаут.aw-worktime-api-rustписал ошибки построения отчетов на запросах вида/buckets/aw-worktime-sessions_<HOST>/events?limit=5000.aw-server-rustнакапливал высокое потребление памяти и CPU, после чего даже metadata-запросы к bucket могли отвечать медленно.- У file-operation telemetry наблюдался рост
sendFailuresв health-событиях: это указывало на сбои HTTP-доставки событий наблюдения во время перегруза, а не на потерю или копирование самих файлов. aw-worktime-autohealиaw-worktime-ui-bridgeв исходной оркестрации были чувствительны к placeholder-host, если runtime env не был применен.- Operations View показывал legacy
rdp_window/rdp_afkкак критические источники, хотя фактический Rust-путь сбораworktime_sessionsиwatcher_afkбыл свежим.
Что было исключено
- ClickHouse: контейнер был доступен, health-check после повторного запуска проходил успешно, данные читались.
- Nginx/gateway: локальные portal endpoints отвечали.
- Ролевая модель портала: smoke-тесты подтвердили серверные запреты.
- Отсутствие данных 1C: не являлось причиной таймаута worktime reports.
Корневая причина
- Верхний лимит
AW_WORKTIME_EVENTS_LIMITфактически не позволял задать малое production-значение: нижняя граница была1000, а код дополнительно ограничивал запрос к ActivityWatch значением до5000. - Worktime reports фильтровали дневной диапазон уже после получения событий, поэтому холодный запрос зависел от стоимости чтения большого bucket через AW HTTP API.
- Несколько timer/service-компонентов могли одновременно прогревать отчеты или запускать health/autoheal, усиливая нагрузку.
- Legacy RDP freshness checks не учитывали, что текущий промышленный путь уже
идет через Rust-сбор
aw-worktime-sessions_<HOST>и локальный watcher AFK.
Внесенные исправления
- В
worktime-apiразрешен меньший безопасный лимит events:AW_WORKTIME_EVENTS_LIMITтеперь ограничивается диапазоном100..50000. - Production baseline установлен на:
AW_WORKTIME_EVENTS_LIMIT=250;AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS=6;AW_WORKTIME_EVENTS_CACHE_TTL_SECONDS=300;AW_WORKTIME_REPORT_CACHE_TTL_SECONDS=300;AW_WORKTIME_REPORT_STALE_TTL_SECONDS=3600.
- Legacy
rdp_windowиrdp_afkбольше не создают критический источник Operations View, если свежиеworktime_sessionsиwatcher_afkуже подтверждают рабочий Rust-путь. aw-worktime-autoheal.serviceиaw-worktime-ui-bridge.serviceтеперь берутAW_WORKTIME_HOSTиз/etc/activitywatch/aw-server.env, а не из hardcoded placeholder.- Таймеры worktime-цепочки разведены по более щадящему графику:
aw-worktime-autoheal.timer: 15 минут;aw-worktime-prewarm.timer: 15 минут;aw-worktime-ui-bridge.timer: 5 минут;aw-rus-healthd.timer: 5 минут.
- Gateway orchestration
detmir-auto.timerпереведен на 30 минут, а service source зафиксирован на Rust binary с bounded timeouts. - File-operation collectors проверены отдельно: активного роста
sendFailuresпосле стабилизации сервера не было, локальные очереди были пустыми. Для снятия ложного аварийного хвоста перезапущены только file-operation collectors в активных пользовательских сессиях и восстановлены штатнымиActivityWatch Launch [...]задачами.
Проверка после исправления
Подтверждено на рабочем контуре:
- failed systemd units:
0; - ClickHouse container health:
healthy; /portal/api/health:ok=true;/reports/worktime/today:200;/reports/worktime/management:200;- full prewarm: все URL завершились
200безbuild-error; node scripts/detmir-pilot-demo-smoke.mjs: OK;node scripts/detmir-portal-tabs-smoke.mjs: OK;- role gates: executive/manager/security/forensics ограничения подтверждены.
- file-operation telemetry после перезапуска: по одному collector-процессу на
активную сессию,
queueDepth=0,sendFailures=0, свежие события приходят в bucketaw-file-operations_<HOST>. - collector-guard до исправления не контролировал наличие Rust
file-operation collector в активных сессиях: после принудительной остановки
он продолжал писать
status=ok actions=0. Исправление добавило проверкуfileOperationsPresence: если в активной сессии есть Rust DLP/browser collector, но нет Rust file-operation collector, guard запускает штатныеActivityWatch Launch [...]задачи. - Исправление collector-guard проверено fault-injection: один file-operation
collector был принудительно остановлен, следующий цикл guard выполнил
recovery (
actions>0), после чегоmissingSessions=[],queueDepth=0,sendFailures=0.
Локальные проверки кода:
cargo fmt --all --check;cargo test -p worktime-api;cargo clippy -p worktime-api --all-targets -- -D warnings;cargo build --release -p worktime-api;git diff --check.
Остаточные наблюдения
- Исторические
sendFailuresв старых health-событиях остаются в bucket как архивная телеметрия. Текущее состояние после перезапуска collectors чистое: ростsendFailuresне подтвержден. - ActivityWatch SQLite остается чувствительным к широким историческим
events-запросам. Для production-демо и пилота надо держать bounded limits, кэш и предварительный прогрев. - Реальные runtime значения должны оставаться в private inventory/env, а не в tracked repository files.
Итоговое решение
Инцидент закрыт как operational performance regression в worktime reporting chain. Исправление принято в код, оркестрацию и установочный пакет. Пилотный контур можно показывать при выполненном преддемо-прогреве и зеленом smoke.