Мониторинг мониторинга: как eZee validator молча падал 3.5 недели и никто не знал
19 мая 2026 года я сел разбирать бэклог задач — не потому что был повод, а потому что раз в несколько недель нужно смотреть на то, что обещал себе сделать но не сделал. Среди трёх задач в бэклоге оказалась: «проверить eZee data validator — кажется давно не запускался».
«Кажется давно» оказалось 3.5 недели. С конца апреля скрипт, который каждый час проверял целостность данных из eZee PMS по 16 виллам, молча падал с ошибкой в SQL-запросе. Cron исправно вызывал его. Системд фиксировал запуск. Скрипт загружался, делал один некорректный SELECT, получал ошибку — и тихо завершался с exit code 0.
Никакого алерта. Никакого повторного уведомления. Ни одного сообщения в рабочий чат за 3.5 недели. И в это время вилла 007 висела со статусом BLOCKED — мейнтенанс, о котором операционный менеджер не знал, потому что задача не дошла.
Это и есть silent failure. Не авария, которую видно сразу. Тихая поломка, которая сидит в системе и копит последствия.
За 5 месяцев работы с 18 агентами я встречал все три типа тихих сбоев. Громкий сбой — когда сервис падает с 500-ой ошибкой в прайм-тайм — это неприятно, но решаемо за час. Тихий сбой, который живёт 3.5 недели в критическом скрипте, не выдавая ни одного сигнала — это системная проблема, которую надо решать архитектурно.
Что такое eZee data validator и зачем он нужен
Solar Property управляет 16 активными виллами на Бали. Бронирования приходят через два OTA-канала — Airbnb и Booking.com — и агрегируются в eZee PMS (платформа Yanolja). У каждой виллы есть статус, маппинг комнат, тарифные ставки, данные гостей.
Проблема в том, что данные в PMS и данные в нашей базе PostgreSQL должны быть синхронизированы. PMS — источник правды для гостей и OTA-каналов. Наша база — источник правды для агентов: кто приедет, когда выезжает, что нужно подготовить, какие задачи создать команде уборщиков.
eZee data validator — скрипт, который раз в час:
- Забирает свежие данные из eZee через API
- Сравнивает с тем, что есть в PostgreSQL
- Находит расхождения: нулевые стоимости, несовпадающие каналы, двойные бронирования, виллы в BLOCKED без задачи
- Генерирует алерты в рабочий чат для тех расхождений, которые требуют действия человека
Без него агенты работают на потенциально устаревших данных. Без него вилла может висеть заблокированной — и операционный менеджер не знает об этом, потому что задача не создалась.
Где именно сломалось и почему никто не заметил
Ошибка оказалась банальной. В одном из SELECT-запросов скрипт обращался к полю, которое было переименовано во время обновления схемы базы данных в конце апреля. Поле было переименовано правильно — в самом коде валидатора, который читал данные для отображения. Но в подзапросе для фильтрации условий осталась старая ссылка.
Python поднимал исключение на этом запросе. Но обработчик исключений был написан так: «если ошибка — залогировать и продолжить следующий объект». Следующих объектов не было — это был первый запрос. Скрипт логировал ошибку в файл, считал что отработал, возвращал exit code 0.
Systemd видел exit code 0 — всё хорошо. Cron видел успешный запуск — всё хорошо. Лог файл рос. Кто-то мог открыть лог и увидеть там строку ошибки — но никто не открывал, потому что для этого нужна причина.
Три условия, при которых silent failure живёт долго
Анализируя этот инцидент, я выделил три условия, которые позволили ему просуществовать 3.5 недели:
Условие 1: exit code не отражает реальный результат. Скрипт завершался успешно по мнению системы, но не производил никакой работы. Exit code должен быть ненулевым при любом критическом сбое — не только при падении процесса, но и при провале бизнес-логики.
Условие 2: отсутствие content-check. Мониторинг проверял «скрипт запустился», а не «скрипт дал результат». Это принципиальная разница. Скрипт может запуститься и сразу упасть. Настоящий мониторинг — это проверка что за последний период что-то реально произошло: запись в базу, обновление метрики, изменение статуса.
Условие 3: нет watchdog'а на уровне результата. Даже если скрипт падает с ошибкой, у нас не было системы, которая замечает «последнее успешное обновление данных eZee было 3.5 недели назад — это аномально». Мониторинг был на уровне «сервис жив», а не на уровне «сервис делает что должен».
Что мы пропустили: вилла 007 и цена тишины
После того как я нашёл и поправил баг в SQL — одна строка кода, десять минут работы — прогнал скрипт вручную. Он отработал корректно и сразу выдал алерт: вилла 007, статус BLOCKED, мейнтенанс, задача не создана.
Это означало: вилла сидела со статусом обслуживания в PMS — без задачи для команды, без записи в нашей системе координации. Если бы за эти 3.5 недели пришла бронь на этот период — конфликт мог обнаружиться только на этапе заселения.
Создал задачу операционному менеджеру. Статус проверили, всё оказалось под контролем — мейнтенанс шёл и закрывался вручную. Но это была удача, а не система.
Цена тишины в данном случае оказалась небольшой — обошлось. Но тот же класс ошибки при другом стечении обстоятельств мог привести к конфликту бронирования, незаселению гостя или финансовому расхождению.
Ответ: новый watchdog и его архитектура
После исправления кода я не остановился на «ну теперь работает». Я сел проектировать watchdog для watchdog'а — отдельный сервис, который следит за тем что другие сервисы делают реальную работу, а не просто не падают.
Новый watchdog работает по следующей схеме:
Heartbeat-записи в базе данных
Каждый критический скрипт теперь в конце успешного выполнения пишет в таблицу service_heartbeats: имя сервиса, время последнего успешного запуска, количество обработанных объектов, хэш конфигурации. Это занимает 3 строки кода в каждом скрипте.
Watchdog каждые 15 минут смотрит на эту таблицу и проверяет: для каждого зарегистрированного сервиса — когда последний раз heartbeat? Если прошло больше ожидаемого интервала плюс 50% буфер — алерт. Для часового cron'а: если heartbeat старше 1.5 часов — что-то не так.
Content-check вместо process-check
Для eZee validator добавил дополнительную проверку: смотреть не только на heartbeat, но и на время последнего обновления данных в ezee_bookings. Если последняя запись обновлена более 2 часов назад — это сигнал, что синхронизация не идёт, даже если сам скрипт «запускается».
Weekly missed approvals отчёт
Параллельно с watchdog'ом добавил еженедельный отчёт по «пропущенным подтверждениям» — задачам, которые агенты создали для меня, но я не отреагировал больше 72 часов. Это другой класс слепого пятна: не «сервис сломан», а «я не заметил что что-то ждёт моего действия».
Отчёт приходит каждое воскресенье: список задач старше 3 дней без реакции с контекстом. Это принудительный аудит того, что я игнорировал — намеренно или нет.
Instagram inbox: другой тип слепого пятна
В тот же день, 19 мая, я закрыл ещё одну давно висящую задачу: подключить входящие сообщения Instagram Direct к единому инбоксу.
Ситуация была такая: у Solar Property есть Instagram-аккаунт, через который приходят вопросы по аренде вилл. Эти сообщения я читал только когда открывал приложение — то есть нерегулярно. Часть запросов терялась: человек написал, не получил ответа за день, нашёл другую виллу.
Это другой тип «тихой потери» — не технический сбой, а слепое пятно в архитектуре: канал существует, но не интегрирован в систему обработки.
Как работает интеграция Instagram DM
Meta предоставляет официальный Graph API для управления сообщениями Instagram Business Account. Процесс подключения:
- Instagram Business Account привязан к Facebook Page
- В Meta Developer Console создаётся приложение с разрешениями
instagram_manage_messagesиpages_messaging - Настраивается webhook endpoint на наш сервер — Meta отправляет на него POST-запрос при каждом новом входящем
- Webhook принимает событие, парсит
sender.idи текст сообщения, записывает в единую очередь - Агент продаж видит входящий из Instagram как обычный лид — без разницы, откуда он пришёл
Ответы уходят через POST на /v19.0/me/messages с recipient.id. Время ответа сократилось с «когда открою приложение» до нескольких секунд.
Почему это важно именно в туристической нише
Instagram в туристической аренде — часто первый канал контакта. Человек смотрит stories или ролики о виллах на Бали, видит что-то интересное, пишет в Direct: «а это доступно на такие-то даты?». Это горячий лид с минимальным friction — одно нажатие от просмотра до вопроса.
Если ответ приходит через 4 часа — лид холодный. Если через сутки — человек уже заехал в другую виллу. По данным SuperOffice, средняя компания отвечает на сообщения клиентов за 12 часов. Ответ в течение первых 5 минут повышает вероятность конверсии в 9 раз по сравнению с ответом через 10 минут.
Подключение Instagram DM к единому инбоксу — это не опция. Это гигиена.
После подключения Instagram DM к единому инбоксу все входящие из разных каналов — Telegram, Instagram, веб-форма — попадают в одну очередь. Агент продаж обрабатывает их по единой логике, без разницы с какой платформы пришёл запрос. История диалога сохраняется: если человек написал в Instagram, потом переключился в Telegram — агент видит весь контекст.
Как именно отлаживался инцидент: шаг за шагом
Для тех, кому интересна техническая сторона — разбор от момента обнаружения до полного исправления.
Шаг 1: Запустил скрипт вручную. Первые строки вывода показали: OperationalError: column 'booking_source_raw' does not exist. Поле переименовали в booking_channel в апрельском рефакторинге. Подзапрос не был обновлён.
Шаг 2: Проверил все файлы на наличие старого имени поля через grep. Нашёл ещё 2 места — в функции фильтрации и в условии сортировки. Поправил все три, запустил снова.
Шаг 3: Запустил с флагом --dry-run — скрипт прошёл до конца, вернул список аномалий. Вилла 007: статус BLOCKED, задача не создана. Ещё 2 виллы с небольшими расхождениями в стоимости — некритично.
Шаг 4: Добавил в скрипт явный sys.exit(1) при исключении в основном цикле вместо continue. Теперь при критической ошибке — ненулевой exit code, который systemd перехватит через OnFailure=.
Шаг 5: Добавил heartbeat-запись в конце успешного выполнения. Зарегистрировал сервис в watchdog-скрипте с ожидаемым интервалом.
Итого: 45 минут работы, из которых 30 — понять что произошло, 15 — исправить. Большую часть времени занял поиск всех мест с устаревшим именем поля. Команда grep -r booking_source_raw /opt/ решила бы это за секунды, если бы я догадался её запустить сначала, а не читать ошибки по одной.
RSS для Яндекс Дзен: третья закрытая дыра того же дня
Параллельно с watchdog и Instagram в тот же день я закрыл ещё одну задачу из бэклога: RSS-фид для Яндекс Дзен.
К тому моменту в блоге 4bos.ru было 97 статей. Дзен позволяет автоматически импортировать материалы через RSS, давая дополнительный охват без ручной переупаковки. Фид висел в бэклоге несколько недель.
Реализация: 20 строк Python, который читает список статей из базы данных и отдаёт стандартный RSS XML. Добавил endpoint /rss.xml в nginx, подключил к Дзен. 97 статей сразу появились в очереди на импорт.
Это типичный пример архитектурного слепого пятна (класс 3 из классификации выше): контент существует, канал существует, но они не соединены. Соединить — 20 минут. Упущенный охват без этого — месяцы.
Три задачи за один день. Не потому что был кризис. Потому что раз в несколько недель полезно смотреть не на то что горит, а на то что тихо не работает.
Три класса silent failures в автоматизации бизнеса
Разбирая эти два случая в один день, я сформулировал для себя типологию тихих сбоев. Это помогает превентивно проектировать мониторинг, а не реагировать постфактум.
Класс 1: Технический silent failure
Сервис работает, но делает не то. Скрипт запускается и падает с ошибкой, возвращая exit code 0. API отвечает 200, но возвращает пустой массив вместо данных. Cron выполняется, но таймаут истёк раньше полезной работы.
Детектируется через: content-check (ожидаемый результат появился?), heartbeat с счётчиком обработанных объектов, diff базы данных (данные обновлялись?)
Класс 2: Операционный silent failure
Задача создана, но не дошла до исполнителя. Алерт ушёл в неправильный канал. Уведомление пришло в 03:00 — человек проснулся, увидел пачку сообщений, часть пролистал как «ночное мусорное». Сообщение потерялось в шуме чата с высокой активностью.
Детектируется через: acknowledgement-флаг (задача прочитана?), timeout на реакцию (если за N часов нет ответа — повторный алерт), weekly missed approvals для системных задач.
Класс 3: Архитектурный silent failure
Канал существует, но не подключён к системе. Instagram DM читается вручную раз в день. Входящие из WhatsApp Business идут на отдельный номер без интеграции. Отзывы на Booking.com остаются без ответа, потому что нет автопарсинга.
Это не технический сбой — система работает. Но бизнес-возможность теряется каждый раз, когда кто-то пишет в неподключённый канал.
Детектируется через: регулярный аудит «откуда к нам могут написать» vs «где у нас есть интеграция». Раз в квартал — сравнить список, закрыть дыры.
Как выстроить мониторинг который не молчит
На основе всего описанного — практическая схема, которую я применяю в Solar Property:
Уровень 1: Process monitoring (есть у большинства)
Systemd или Docker следит что процесс живой. Если падает — автоперезапуск. Это необходимо, но недостаточно. Процесс может быть живым и бесполезным одновременно.
Уровень 2: Heartbeat monitoring (нужен всем)
Каждый сервис пишет в таблицу последнее время успешного выполнения. Watchdog проверяет что heartbeat свежий. Это ловит класс 1 — когда скрипт запускается, но падает до полезной работы.
Реализация: 5 строк кода в каждом скрипте + одна таблица в базе + один watchdog-скрипт на 30 строк. Суммарно — день работы один раз, постоянная защита навсегда.
Уровень 3: Content monitoring (нужен для бизнес-критичных сервисов)
Не «сервис написал heartbeat», а «ожидаемые данные появились». Для eZee validator: «в базе есть бронирования с датой обновления менее часа назад». Для агента продаж: «входящий лид за последние 24 часа был обработан». Для финансового агента: «транзакция за вчера записана».
Это требует понимания бизнес-логики, но именно это отличает настоящий мониторинг от формального.
Уровень 4: Missed approvals audit (для систем с агентами)
Если у вас система с агентами, которые создают задачи для людей — нужен периодический аудит «что ждёт реакции дольше нормы». Это ловит класс 2 — операционные silent failures, где задача создана, но потерялась.
Уровень 5: Channel audit (раз в квартал)
Список всех каналов, по которым к вам могут написать или через которые вы что-то публикуете. Для каждого — статус интеграции. Это ловит класс 3 — архитектурные дыры. Конкретный вопрос: «что сейчас теряется, потому что мы не смотрим в этот канал?»
Чек-лист для аудита тихих дыр
Практический список для самопроверки:
- Список всех cron-задач — для каждой: когда последний раз успешно выполнялась? Не «запускалась», а «дала результат».
- Список всех внешних сервисов — каждый представляет точку отказа. Есть ли алерт, если сервис перестанет отвечать?
- Список всех каналов коммуникации — email, Instagram DM, Telegram, WhatsApp, отзывы OTA. Для каждого: куда идут входящие, есть ли автоматическая обработка?
- Задачи старше 7 дней без движения — что-то застряло? Задача не дошла до исполнителя или исполнитель не отреагировал?
- Последнее обновление ключевых таблиц —
SELECT MAX(updated_at) FROM table. Если дата удивляет — стоит разобраться.
Этот аудит занимает 1-2 часа. Результат — список конкретных дыр, которые закрываются за один рабочий день. Без регулярного аудита они продолжают тихо существовать и накапливать последствия.
Итог: стоимость одного SQL-опечатки
Опечатка в имени поля SQL — это не катастрофа. Это один символ в одной строке кода. Прямой ущерб от этого конкретного инцидента оказался минимальным: вилла была в реальном мейнтенансе, который всё равно был под контролем вручную.
Но косвенный урок стоил больше. Система, в которой критический валидатор может молча умирать 3.5 недели — это не надёжная система. Независимо от того, что случится в следующий раз.
Инвестиция в исправление: один день. Итог: heartbeat-мониторинг для 7 ключевых скриптов, content-check для eZee-синхронизации, weekly missed approvals, интеграция Instagram DM. Плюс — осознание что «работает» и «делает что должно» — это разные вещи, и между ними нужен явный слой проверки.
Юрий Солар, основатель Solar Property: «Самая опасная автоматизация — та, которая выглядит работающей. Громкие сбои чинятся быстро. Тихие живут месяцами.»
Если вы строите автоматизацию для бизнеса — проверьте свои cron-скрипты прямо сейчас. Не «запускаются ли они», а «дают ли они ожидаемый результат». Для глубокого погружения в тему тихих отказов — читайте статью про 7 ботов с тихими отказами. Про архитектуру самовосстанавливающихся систем — в материале про self-healing боты.