Мониторинг мониторинга: как 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 — скрипт, который раз в час:

  1. Забирает свежие данные из eZee через API
  2. Сравнивает с тем, что есть в PostgreSQL
  3. Находит расхождения: нулевые стоимости, несовпадающие каналы, двойные бронирования, виллы в BLOCKED без задачи
  4. Генерирует алерты в рабочий чат для тех расхождений, которые требуют действия человека

Без него агенты работают на потенциально устаревших данных. Без него вилла может висеть заблокированной — и операционный менеджер не знает об этом, потому что задача не создалась.

Где именно сломалось и почему никто не заметил

Ошибка оказалась банальной. В одном из 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. Процесс подключения:

  1. Instagram Business Account привязан к Facebook Page
  2. В Meta Developer Console создаётся приложение с разрешениями instagram_manage_messages и pages_messaging
  3. Настраивается webhook endpoint на наш сервер — Meta отправляет на него POST-запрос при каждом новом входящем
  4. Webhook принимает событие, парсит sender.id и текст сообщения, записывает в единую очередь
  5. Агент продаж видит входящий из 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 боты.

Частые вопросы

Как автоматически следить за здоровьем cron-скриптов?
Три уровня: во-первых, exit-коды — скрипт при ошибке должен возвращать ненулевой exit code, который cron может перехватить через MAILTO или systemd OnFailure. Во-вторых, heartbeat-запись в базу данных: скрипт пишет последнее время успешного выполнения, watchdog раз в час проверяет что heartbeat свежий. В-третьих, content-check: не «скрипт завершился», а «скрипт дал ожидаемый результат». Для eZee validator это означало проверку что за последние 15 минут хотя бы одна бронь обновилась.
Что такое silent failure в автоматизации бизнеса?
Silent failure — это когда система продолжает «работать» снаружи (cron запускается, сервис отвечает на пинги), но внутри давно сломана и не выдаёт правильного результата. Опаснее громкого сбоя: громкий сбой замечают и чинят. Тихий сбой может длиться неделями — пока кто-то не проверит данные вручную или не возникнет следствие в виде реальной проблемы.
Как подключить Instagram Direct Messages к своей системе автоматизации?
Через официальный Meta Graph API: создаёте приложение в Meta Developer Console, запрашиваете разрешения instagram_manage_messages и pages_messaging, настраиваете webhook на свой endpoint, проходите App Review. Сообщения приходят как webhook-события с полем sender.id и message.text. Ответить можно через POST /v19.0/me/messages с recipient.id. Важно: требуется Instagram Business Account, подключённый к Facebook Page.
Сколько слоёв мониторинга нужно для автоматизации малого бизнеса?
Три слоя достаточно для начала: сервисный (сервис жив, порт слушает — systemd/Docker health check), операционный (ключевые метрики в норме — например, бронирования синхронизировались за последние 30 минут), бизнес-метрики (показатели, которые человек может проверить — входящие лиды обработаны, финансовые данные обновлены). Ошибка большинства — только первый слой. Это как проверять, что сервер включён, но не проверять что приложение на нём что-то делает.
Как eZee PMS интегрируется с системой автоматизации управления виллами?
eZee PMS (Yanolja) предоставляет официальный API для чтения и записи данных бронирований, гостей и номеров. Для Solar Property настроена синхронизация каждые 15 минут: скрипт забирает дельту изменений через API, пишет в PostgreSQL, где данные доступны всем агентам. Ключевые поля: статус брони, даты заезда/выезда, канал (Airbnb/Booking.com/direct), стоимость, контактные данные гостя.

Читайте также

Подписаться на блог в Telegram

Читайте свежие кейсы об AI-автоматизации, системной архитектуре и масштабировании бизнеса.

Подписаться