docs(generator): развилка фикса задачи 20 решена после ревью постановки
- Зачем:
- адверсарное ревью показало: генератор не обрабатывает SIGTERM
(python — PID 1, docker compose stop ведёт к SIGKILL посреди batch),
а вариант «ждать записи generator_batch_history» гонку не закрывает.
- Что:
- направление фикса: минимальный SIGTERM-хендлер с дозаписью batch,
альтернативы отклонены с доводами, bounded live mode вне скоупа.
- критерии дополнены: красный сценарий обязан ловиться, стабильность
обосновывается структурно, запрещены вероятностные смягчения.
- в диагноз внесены механизм обрыва и различие device/geo vs location.
- Проверка:
- чтение постановки: все находки ревью закрыты решением или доводом.
This commit is contained in:
+50
-12
@@ -51,34 +51,72 @@ location/device/geo -> DDS строит `dds.event` через `LEFT JOIN locati
|
||||
Судьба гипотез:
|
||||
|
||||
1. Запасная ветка рождения — **опровергнута**: ветка уже хранит донора
|
||||
(`generation.py:194-209`, комментарий «Запасная ветка тоже восстановима»)
|
||||
(`generation.py:196-209`, комментарий «Запасная ветка тоже восстановима»)
|
||||
и громко падает на неполных locations. Перепроверено координатором по коду.
|
||||
2. Недетерминизм live — **подтверждена частично**: решает не сид, а настенный
|
||||
момент остановки процесса и какие топики успели дописаться.
|
||||
3. Проверочный SQL — **опровергнута в формулировке**: поля не «легитимно
|
||||
различаются», SQL маскирует пропущенный location под «смену фактуры».
|
||||
|
||||
## Направление фикса (из диагноза)
|
||||
Уточнение механизма обрыва (адверсарное ревью постановки, 2026-07-12;
|
||||
перепроверено координатором по коду):
|
||||
|
||||
1. **Основное:** сделать остановку live управляемой — завершать после полного
|
||||
batch/tick и flush всех топиков (bounded live mode или ожидание записи
|
||||
`generator_batch_history` с полными sent-счётчиками).
|
||||
- Генератор не обрабатывает SIGTERM: ловится только `KeyboardInterrupt`
|
||||
(`service.py:175`), а `docker compose stop` шлёт именно SIGTERM. В compose
|
||||
у сервиса generator нет `init:`/`stop_signal:`, python работает PID 1 —
|
||||
SIGTERM игнорируется, и через grace-период прилетает SIGKILL: жёсткий
|
||||
обрыв посреди batch, без `finally` и без flush.
|
||||
- Почему отставание device/geo не валит гейт, а location валит: device/geo
|
||||
привязаны к `click_id` (одно значение на визит) — при пропуске весь визит
|
||||
однородно NULL, массив уникальных значений длины 1, проверка проходит;
|
||||
location привязан к `event_id` — частичный пропуск даёт смешанный массив
|
||||
(реальное значение + NULL) и «смену фактуры». Догонять device/geo фикс
|
||||
не обязан — важна граница batch.
|
||||
|
||||
## Направление фикса (развилка решена после ревью постановки 2026-07-12)
|
||||
|
||||
1. **Основное: корректная обработка SIGTERM в генераторе.** Минимальный
|
||||
хендлер: по SIGTERM выставить `_running = False`, дать текущему tick'у
|
||||
дописаться (все четыре топика + flush + запись batch history), затем
|
||||
штатный `finally`/`stop()`. Runtime-check после `docker compose stop`
|
||||
дожидается фактического завершения контейнера. Так генератор завершается
|
||||
только на границе batch — гонка снята по построению, а не вероятностно.
|
||||
- **Отклонено: «ожидание записи `generator_batch_history` перед stop»
|
||||
как самостоятельный фикс** — запись history подтверждает только
|
||||
ПРОШЕДШИЙ batch (`service.py:586-603`, пишется после цикла публикации);
|
||||
следующий batch к моменту stop уже может быть в полёте, и SIGKILL
|
||||
оборвёт его так же. Довод — ревью постановки, перепроверен по коду.
|
||||
- Bounded live mode (генератор сам останавливается по лимиту) — более
|
||||
тяжёлая альтернатива; в скоуп не входит, возвращаться к ней только
|
||||
если SIGTERM-хендлера окажется недостаточно (с доводом в задаче).
|
||||
2. **Дополнительно:** precheck в seam-check на непарные
|
||||
browser/location/device/geo live-строки — чтобы ошибка называла реальную
|
||||
причину, а не «фактура поменялась».
|
||||
3. Просто увеличить sleep перед stop — отклонено: снижает вероятность,
|
||||
но гонку не убирает.
|
||||
причину, а не «фактура поменялась». Precheck не подменяет основную
|
||||
проверку: настоящая смена фактуры внутри `click_id` обязана падать
|
||||
как и раньше.
|
||||
3. **Отклонены любые вероятностные смягчения**, а не только «увеличить
|
||||
sleep»: settle-sleep перед прогоном, рост `WAIT_LIVE_ROWS`/`LIVE_SECONDS`,
|
||||
сужение окна `GEN_LIVE_CHECK_MINUTES` и прочие способы снизить
|
||||
вероятность — гонку они не убирают и фиксом не считаются.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Причина расхождения 8/19 найдена и названа (код, не догадка) —
|
||||
см. «Диагноз» выше.
|
||||
- [ ] Остановка live в runtime-check управляемая: batch дописывается во все
|
||||
топики целиком до остановки (направление фикса, пункт 1).
|
||||
- [ ] Генератор корректно завершается по SIGTERM: текущий batch дописывается
|
||||
во все четыре топика целиком (flush + запись history), потом процесс
|
||||
выходит; runtime-check дожидается фактической остановки контейнера
|
||||
(направление фикса, пункт 1).
|
||||
- [ ] Seam-check различает «непарные live-строки» и «смена фактуры»:
|
||||
precheck называет реальную причину (пункт 2).
|
||||
- [ ] `make generated-history-runtime-check` стабилен: N подряд прогонов
|
||||
зелёные (N >= 3), зафиксировано в задаче.
|
||||
- [ ] Красный сценарий по-прежнему ловится: настоящая смена per-event
|
||||
фактуры внутри `click_id` (инъекция в тесте или контролируемое искажение
|
||||
данных) валит гейт с прежним сообщением — precheck и фикс не сделали
|
||||
проверку мягче.
|
||||
- [ ] Стабильность обоснована структурно (гонка снята по построению:
|
||||
завершение только на границе batch), а не статистикой прогонов;
|
||||
`make generated-history-runtime-check` — N подряд зелёных (N >= 3)
|
||||
как дымовая проверка поверх этого довода, зафиксировано в задаче.
|
||||
|
||||
## Blocked by
|
||||
|
||||
|
||||
Reference in New Issue
Block a user