BorisovAI
Все публикации
Новая функцияtrend-analisisClaude Code

Молчаливый краш каждые восемь минут: как мы искали баг в конвейере тренд-анализа

Молчаливый краш каждые восемь минут: как мы искали баг в конвейере тренд-анализа

Работаю над Trend Analysis — системой, которая вытаскивает из кластеров событий настоящие тренды. Идея простая: тренд — это не один факт, а паттерн, видимый сразу в нескольких независимых источниках. Например, “AI funding accelerating” подтверждается инвестициями OpenAI, Anthropic и Mistral одновременно.

Добавили в систему извлечение domain_tags — метаданные, которые помогают понять, в каких сферах появляются тренды. Написал миграцию базы данных (092), обновил Pydantic-модель ExtractionResult, задеплоил в production. Всё выглядело хорошо.

Потом начался ад.

Pipeline рестартовался сам по себе каждые 8–10 минут. Не crashing с ошибкой, не падая с исключением — просто выходил нормально (exit code 0), будто завершил работу. PM2 считал это штатным поведением, счётчик restarts поднялся до 450. Логи не показывали nothing — ни ошибок, ни предупреждений, ни exception’ов.

Я начал добавлять debug-маркеры на критических этапах. “PHASE_DEBUG” перед главной стадией extraction. Ждал цикла за циклом. Маркер никогда не появлялся.

Потом заметил: логи говорят “Fact extraction done”, потом сразу — крах. Между фазой extraction и следующей стадией что-то умирало молча. Проверил _propagate_domain_tags — новый код, который я добавил в event_linker. Он вызывается после commit. Обёрнут в try/except. Не должно быть проблем.

Но потом я посмотрел на главный asyncio.gather() в функции main(). Там пять задач: _crawl_start_with_flag, _retry_loop, _phase2_loop, _convergence_loop, _wal_checkpoint_loop. И gather() без флага return_exceptions=True. Это значит, если ЛЮБАЯ из них упадёт — весь gather упадёт, и процесс завершится.

Но логов нет…

А потом вспомнил: я использую asyncio.create_task() для запуска _extract_facts_pipeline ВНУТРИ crawl_once(). Это отдельная задача, не добавленная в основной gather. Если она поднимает exception — в Python 3.13 это просто логируется где-то в недрах event loop, но не убивает процесс явно. Процесс выходит чисто, потому что задача закончилась (с ошибкой).

Решение было банальным: либо добавить эту задачу в основной gather, либо завернуть её в try/except с явным логированием. Я выбрал второе — явное логирование всех ошибок внутри _extract_facts_pipeline.

После fix pipeline работал стабильно. Uptime перевалил за 30 минут. Никаких рестартов.

Урок: когда Python молчит, ищи asyncio. Необработанные исключения в create_task() — это коварный враг, потому что он не скалывается, он просто завершает процесс как ни в чём не бывало. 😄

Метаданные

Session ID:
grouped_trend-analisis_20260418_1955
Branch:
fix/trend-coherence-scoring
Dev Joke
Если Java работает — не трогай. Если не работает — тоже не трогай, станет хуже.

Оцените материал

0/1000