Перейти к основному содержимому
megapromotingДавайте обсудим

Экспертиза · Обслуживание

Обслуживание — это знать, что форма сегодня отправляет, а не что сервер отвечает 200.

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

Уже построеноТри собственные реализации, все в репозитории этого сайта и все проверяемы сегодня: `src/app/api/health/contact/route.ts` (сонда доставки, сейчас отвечает 200 в live), `src/lib/lead-store.ts` (append-only журнал с ретенцией, применяемой при записи) и `scripts/verify-redesign.ts` (приемочная проверка, запущенная после публикации). Мы написали их потому, что 06.09.2026 собственная форма тихо упала: токен бота был отозван, endpoint возвращал 500, а запросы исчезали без следа. Это не история продаж — это коммит, который создал файлы выше.

«Сайт работает» — это утверждение о главной странице. Посетитель, который заполнил форму и получил ошибку, был остановлен не упавшей страницей, а каналом доставки, который тихо истёк. Разница между этими двумя вещами — это всё, что значит серьёзно сделанное обслуживание: мы следим не за тем, отвечает ли сервер, а за тем, доходит ли запрос от человека к человеку.

6 сентября 2026 именно это и произошло на этом сайте. Токен бота, который передавал запросы из формы команде, был отозван; интерфейс Telegram отвечал `401 Unauthorized`, наш маршрут возвращал 500, а посетитель видел «сообщение не удалось отправить». Запрос нигде не записывался. В журнале ошибок процесса было три реальных сбоя за примерно семь часов, прошедших с предыдущей публикации. Никто не смотрел.

Из этого вышли три части, которые мы теперь ставим в каждый проект, который обслуживаем. Append-only журнал, записываемый *до* попытки доставки, чтобы сломанный канал деградировал до «надо посмотреть в файл», а не до «запроса никогда не было». Зонд здоровья, который отвечает на один запрос, может ли лид сейчас дойти до человека. И проверка приёмки, которая запускается после каждой публикации и падает, если исчез канонический адрес, якорь вопросов или если в карте сайта появились адреса, которые нельзя открыть.

Остальное — скучная и проверяемая дисциплина: обновления с аудиторским шлюзом до касания production, автоматический перезапуск с порогом памяти и лимитом нестабильных перезапусков, ретенция, применяемая кодом, а не объявленная в политике, и IP-адреса, обрезанные до сетевого префикса, прежде чем быть записанными куда-либо.

Что входит

Работа, по составляющим

Зонд, проверяющий доставку, а не доступность

`GET /api/health/contact` отвечает 200, если лид может сейчас дойти до человека, и 503, если нет. Он отдельно проверяет две вещи: что токен канала уведомления всё ещё действителен (реальный запрос к поставщику, с timeout в 8 секунд) и что журнал запросов доступен для записи. Ответ состоит только из логических значений — никогда из токена, идентификатора канала или имени бота. Результат хранится в памяти 60 секунд, чтобы внешний зонд при частых запросах сам не превратился в трафик. Когда поставщик недоступен, поле становится `null`, а не `false`: «не знаю» и «недействительно» — разные состояния.

Журнал, записываемый до доставки, а не после

Каждому запросу присваивается идентификатор, и он записывается в append-only журнал, одна строка JSON на событие, *до* попытки уведомления. Вторая строка с тем же идентификатором сообщает, что произошло фактически: `delivered` или `failed`, с причиной, обрезанной до 300 символов. Каталог создаётся с правами `0700`, файлы — с `0600`, а путь намеренно выносится за пределы каталога версии, чтобы история пережила публикацию.

Проверка приёмки, запускаемая после публикации

Проверочный скрипт открывает каждую страницу продукта, группами по три, и падает, если отсутствует код 200, якорь вопросов, якорь примера или канонический адрес. Затем он проверяет карту сайта — чтобы в ней не было языковых вариантов, которые не открываются, и чтобы в ней был каждый продукт — и `robots.txt`, чтобы он не блокировал ресурсы, необходимые для рендеринга. Он также падает, если на странице снова появляется контент старого клиента. Это список вещей, которые уже однажды ломались.

Обновления с воротами, а не с надеждой

Скрипт публикации отказывается запускаться, если на сервере менее 500 MB свободной памяти, выполняет `npm audit --audit-level=high` и останавливается на уязвимостях высокого уровня, если вы явно не подтвердите, затем собирает с чистого состояния — `.next` и `node_modules` удалены, установка из файла блокировки. Если процесс не появляется как `online` после запуска, скрипт выводит последние 50 строк журнала и завершает работу с ошибкой, вместо того чтобы сообщать об успехе.

Перезапуск с порогами, а не наугад

Процесс автоматически перезапускается при более чем 500 MB памяти, с задержкой в 4 секунды между попытками, экспоненциальным увеличением задержки и остановкой после 10 нестабильных перезапусков — чтобы цикл падения стал видимым, а не молча съедал сервер. Время на завершение: 5 секунд, затем принудительное завершение. Журналы имеют дату и часовой пояс в одном потоке.

Дубликаты обрабатываются, а не считаются дважды

Один и тот же человек, одно и то же сообщение, дважды — двойной клик, перезагрузка страницы — раньше порождали два одинаковых уведомления на один запрос. Теперь отпечаток `sha256` пары адрес + сообщение хранится в памяти 10 минут; при второй отправке получается тот же идентификатор запроса и пометка `duplicate`, а уведомление не повторяется. Подавляются только дубликаты успешной доставки; неудачная может пройти снова.

Коды ошибок, которые говорят, что сломалось

400 за недопустимые данные, 405 за неверный метод, 500 за повреждённое тело запроса, 502 когда канал доставки ответил плохо, 503 когда отсутствуют креденциалы. Разница важна в 3 часа ночи: 502 означает «поставщик», 503 означает «наша конфигурация». Посетителю отдельно говорится: «мы получили запрос, но не смогли уведомить» — а не ложный успех.

Хранение, применённое кодом, а не обещанное в политике

Опубликованная заметка о конфиденциальности говорит, что запросы из формы хранятся 24 месяца. Код применяет ровно то же число: `LEAD_RETENTION_DAYS` по умолчанию 730, а файлы старше порога удаляются при каждой записи, без планировщика, о котором можно забыть. IP-адрес обрезается до сетевого префикса — `/24` для IPv4, `/48` для IPv6 — и читается последний хоп из заголовка, а не первый, потому что первый отправляет клиент.

Находим и то, на что никто не жаловался

Тот же проход по коду выявил две вещи, о которых не сообщил ни один пользователь: лимит в 10 запросов в минуту можно было полностью обойти, потому что читался первый элемент из заголовка перенаправленных адресов — тот, который контролирует клиент, — а маршрут для инициации телефонных звонков был открыт анонимно, за наш счёт и с нашего номера, хотя компонент, который его использовал, уже нигде не был установлен. Оба случая исправлены и проверены в production.

Как это выглядит

Путь, шаг за шагом.

01

Инвентарь путей, по которым запрос доходит до человека

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

02

Зонд и журнал, установленные до всего остального

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

03

Приёмочная проверка, написанная по реальным дефектам

Всё, что однажды сломалось, попадает в сценарий проверки, запускаемый после публикации. Мы не пишем тесты для гипотетических случаев; мы пишем для тех, которые уже стоили нам денег. Мы поставляем сценарий, а не только его результат — вы можете запустить его и сами.

04

Ритм обновления и шлюз перед production

Мы определяем, что обновляется автоматически, что проходит через ручную проверку и что не трогают без заранее объявленного окна. Шлюз включает аудит безопасности зависимостей и проверку ресурсов сервера, оба выполняются до выхода в production.

05

Передача с перечнем недочётов

В конце мы передаём процедуру публикации, процедуру отката, адреса здоровья и письменный список того, что не покрыто. На этом сайте, например, ветка истечения запроса к поставщику реализована и попадает в ту же обработку, что и ошибка сети, но сам abort в тесте не был вызван — так написано в журнале выполнения, а не во внутренней заметке.

1Sonda care verificălivrarea până la unom2jurnalul scrisînainte de încercare3verificarea deacceptare rulată dupăfiecare publicareTrei piese, toate în depozitul acestui site.
Путь, в 3 шага

Данные

К чему прикасаемся, где оно лежит и сколько остаётся

Вопросы, которые задаёт всякий, у кого есть ответственный за защиту данных, — заданы здесь раньше, чем их задаст он.

Журнал запросов
Имя, адрес электронной почты, телефон, компания, выбранная услуга, сообщение, страница, с которой отправлено, хост страницы-источника (только хост, не полный адрес) и сетевой префикс посетителя. JSON-файлы по дням, на собственном сервере, вне каталога версии. Срок: 24 месяца, применяется при записи.
Журналы процесса и сервера
Стандартный вывод и ошибки процесса, с датой и часовым поясом, плюс журналы веб-сервера. Здесь виден тихий сбой: три сбоя доставки из эталонного инцидента были в журнале ошибок, а не в каком-либо оповещении. Ротация и срок устанавливаются для каждого сервера; это технические данные, а не содержимое аккаунта.
Что никогда не выходит с сервера
Токены, идентификаторы канала и ключи поставщика. Служба здоровья отвечает исключительно логическими значениями, именно чтобы её мог вызвать внешний инструмент, не раскрывая ничего. То же правило и в уведомлении: сообщение содержит идентификатор запроса и страницу, а не креденциалы.
Статистика трафика
Измерение трафика проходит через инструмент аналитики, настроенный на проект, и активируется после согласия. Фактическая настройка срока хранения в аккаунте аналитики — это значение, которое мы читаем из аккаунта, а не предполагаем — реестр обработок этого сайта явно помечает его как элемент, который нужно уточнить, а не как установленный факт.
Реестр обработок
Каждый тип данных, затрагиваемый сайтом, имеет запись с целью, основанием, категориями и сроком, в версионируемом документе рядом с кодом. Когда код меняет срок, документ меняется в том же коммите — иначе политика и график говорят разные вещи, а ошибается обычно документ.

Случай

Форма, которая отправляла 500 вместо лидов, семь часов

Ситуация

Сайт-визитка с контактной формой, недавно опубликованный. Все страницы отвечают 200, панель зелёная, никто ни на что не жалуется. Единственный канал, через который запрос попадал в команду, — уведомление в приложении для сообщений.

Что мы построили

Токен бота уведомлений был отозван; интерфейс поставщика отвечал `401 Unauthorized`. Маршрут формы возвращал 500 и ничего не писал. Первым исправлением был не токен, а порядок операций: запрос теперь записывается в append-only журнал с собственным идентификатором *до* попытки доставки, а результат доставки добавляется как вторая строка. Поверх этого были внедрены публичная служба здоровья, проверяющая токен и возможность записи, отдельные коды ошибок для «поставщик ответил плохо» и «у нас нет креденциалов», тайм-аут 10 секунд на доставку и подавление дублей в окне 10 минут. Тот же проход по коду убрал и лимит запросов, который можно было обойти, и открытую без аутентификации ветку телефонных вызовов.

Что получилось

Служба здоровья теперь отвечает 200 с валидным токеном и записываемым журналом; её можно опрашивать из любого внешнего сервиса мониторинга, не раскрывая ничего. Тесты на production покрыли каждый код ответа — 400, 405, 500, 502, 503 — а две одинаковые отправки вернули один и тот же идентификатор запроса, вторая помечена как дубль, с двумя строками в журнале, а не четырьмя. Будущий сбой канала уведомлений больше не удаляет запрос: он остаётся в журнале с записанной причиной.

Чего этот случай не говорит

Служба здоровья говорит, что доставка сейчас возможна, а не что кто-то читает уведомления. Кто на них смотрит и через какое время — решение команды, а не функция кода. И ветка истечения запроса к поставщику, хотя и реализована, не была вызвана в тесте — она попадает в ту же обработку, что и ошибка сети, которая была протестирована; мы отмечаем её как неотработанную, а не как проверенную.

Вопросы

О чём нас спрашивают перед тем, как позвонить

Что именно вы мониторите?

Путь запроса до человека, а не доступность сервера. `GET /api/health/contact` проверяет одним запросом две вещи: что токен канала уведомлений по-прежнему действителен — через реальный вызов к поставщику, с timeout 8 секунд — и что журнал запросов можно записать. Отвечает 200, когда оба условия верны, и 503, когда нет. Вы можете вызвать его прямо сейчас на этом сайте: он публичный именно потому, что возвращает только логические значения.

Почему форма могла бы упасть так, чтобы никто этого не заметил?

Потому что видимая часть продолжает работать. Страница загружается, кнопка отвечает, сервер возвращает 200 на всех страницах — ломается только конечное звено, у которого нет интерфейса. На этом сайте токен bot для уведомлений был отозван: поставщик отвечал `401 Unauthorized`, маршрут возвращал 500, а запрос нигде не записывался. Три реальных сбоя примерно за семь часов, видимые только в журнале ошибок процесса. Поэтому журнал теперь записывается до отправки: даже если уведомление падает, запрос существует.

Что происходит, если уведомление не удается после того, как вы это исправили?

Запрос уже записан в журнал с собственным идентификатором, а вторая строка журнала говорит `failed` и указывает причину. Посетителю отвечают отдельно — «мы получили запрос, но не смогли уведомить» — а не ложным успехом. Код ответа различает причину: 502 означает, что поставщик ответил плохо, 503 — что у нас не хватает учетных данных. В 3 часа ночи разница между этими двумя вариантами решает, звонить ли кому-то или нет.

Вы автоматически обновляете зависимости?

Не без ворот. Публикация запускает `npm audit` на уровне `high` и останавливается на уязвимостях этого уровня, если продолжение не подтверждено явно; сначала она проверяет и доступную память сервера, с порогом 500 MB, потому что сборка, запущенная на тесном сервере, оставляет приложение остановленным. Сборка выполняется с чистого состояния: каталог сборки и зависимости удаляются, установка идет из файла блокировки. Что обновляется автоматически, а что проходит через проверку человеком, определяется по проекту, а не по умолчанию.

Как вы знаете, что публикация ничего не сломала?

По скрипту проверки, запускаемому после публикации, который открывает каждую страницу продукта и падает, если отсутствует код 200, якорь вопросов, якорь примера или канонический адрес. Затем он проверяет карту сайта — чтобы она содержала каждый продукт и не содержала языковых вариантов, которые нельзя открыть — и `robots.txt`, чтобы он не блокировал ресурсы рендеринга. Каждая проверка в списке соответствует чему-то, что уже ломалось однажды. Скрипт передается вместе с проектом.

Что вы делаете, когда приложение зависает или потребляет память?

Процесс автоматически перезапускается при превышении порога 500 MB, с задержкой 4 секунды между попытками и экспоненциальным увеличением, но останавливается после 10 нестабильных перезапусков за короткий промежуток. Это важно: петля падения, которая перезапускается бесконечно, выглядит здоровой на панели и молча потребляет сервер. Мы предпочитаем, чтобы процесс оставался остановленным и видимым.

Как долго вы храните данные из формы и кто может их читать?

24 месяца, применяемые кодом, а не только заявленные: порог — это переменная со значением по умолчанию 730 дней, а более старые файлы удаляются при каждой новой записи, без планировщика, о котором можно забыть. У каталога права `0700`, у файлов `0600`, а путь находится вне каталога версии, чтобы история пережила публикацию. IP-адрес не сохраняется целиком: он обрезается до `/24` для IPv4 и `/48` для IPv6, при этом читается последний hop из заголовка, а не первый — первый отправляет клиент и он ничего не значит.

Вы находите и проблемы, которые мы не жаловались?

Да, и обычно именно они самые дорогие. Та же проходка по коду, которая исправила форму, выявила две вещи, о которых никто не сообщал: лимит запросов можно было полностью обойти, потому что читался первый элемент из заголовка перенаправленных адресов — именно тот, который отправляет клиент; и маршрут, который инициировал телефонные звонки, был открыт анонимно, за наш счет, хотя компонент, который его использовал, был демонтирован. Оба случая были исправлены и проверены на production. Чего мы не можем обещать, так это что находим все; что мы можем обещать — что сообщаем и о том, что вы не просили.

Что не покрывает обслуживание?

Это не служба безопасности с круглосуточным мониторингом и не центр операций. Мы не гарантируем процент доступности без измерения, которое его подтверждает — и, чтобы было ясно, в данный момент мы не публикуем такую цифру для собственного сайта. Мы не берем на себя ответственность за стороннюю платформу, к которой у нас нет доступа. И мы не считаем страницу, отвечающую 200, доказательством того, что все работает: именно в этом и была проблема, с которой все, что написано выше, и началось.

На чём основаны утверждения выше (13 источников)
  1. Проверка доставки отвечает 200 в production: `{"ok":true,"telegram":{"configured":true,"credentialsValid":true},"leadJournal":{"writable":true}}`https://www.megapromoting.com/api/health/contact · 2026-09-06
  2. Активные заголовки безопасности на продакшене: HSTS `max-age=63072000; includeSubDomains; preload`, `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` с `microphone=(self)`https://www.megapromoting.com/servicii · 2026-09-06

11 из них — код и файлы из наших репозиториев. Мы не публикуем их название и строку: вместе, на одной странице, они слишком точно описали бы, как устроены системы, которые принадлежат не только нам. Мы разбираем их вместе с тобой, в репозитории, по запросу — проверка остаётся возможной, просто она проходит в разговоре.

Что вы хотели бы наладить?

Расскажите о своём процессе. Вместе определим, что стоит построить, что можно подключить и как проверим результат.

Давайте обсудим