Стоит сразу сказать: подход, о котором пойдёт речь, довольно специфичный и подойдёт далеко не всем. Но, как показала практика, в нём есть смысл. Да, у моего пет‑проекта — мессенджера Pulse — есть автоматические тесты, которые регулярно ходят прямо в прод.
Они авторизуются под настоящими аккаунтами, создают реальные сообщения, проходят через тот же бэк, базу, вебсокет и остальные компоненты, которыми пользуются обычные пользователи (коих немного, отсюда автотестовые и появились).

Звучит как начало статьи «как не надо делать». Но я как раз считаю, что такие тесты нужны.
Потому что зелёный CI отвечает на один вопрос:
работает ли код в окружении, которое мы подготовили для тестирования?
А после выкладки меня интересует немного другой:
работает ли прямо сейчас тот продукт, который получает реальный пользователь?
Это не одно и то же.
Можно иметь идеально зелёный pipeline и одновременно сломанный production: неправильную конфигурацию nginx, недоступный внешний сервис, ошибку миграции, проблемы с сетевым стеком, права на каталог, сертификат, неверный environment variable или десяток других вещей, которые unit‑ и integration‑тесты никогда не увидят.
Поэтому в какой‑то момент у Pulse появился отдельный pulse-smoke.
И недавно он довольно эффектно напомнил мне, что production smoke — это тоже настоящий production traffic. Только понял я это, когда решил посмотреть статистику своего же мессенджера.
Зачем вообще тестировать production
Для начала важное уточнение.
Речь не о том, чтобы запускать весь test suite на живой пользовательской базе.
Я не предлагаю делать:
go test ./... --database=production
и надеяться на лучшее. Ни в коем случае так не делайте, если жизнь ваша и вашего продукта дорога.
Production smoke должен отвечать только на несколько критических вопросов.
Например:
можно ли авторизоваться;
работает ли основной API;
устанавливается ли realtime‑соединение;
можно ли отправить сообщение;
доходит ли оно до второго пользователя;
корректно ли обновляются состояния;
открываются ли основные сценарии, зависящие сразу от нескольких компонентов системы.
То есть это не проверка всех corner cases.
Это проверка того, что критический пользовательский путь действительно жив после деплоя.
Условно:
Клиент A | | login v Backend | | message v PostgreSQL | | realtime event v WebSocket | v Клиент B
Если на любом участке этой цепочки прод вдруг где‑то по какой‑то мифической или не очень причине собран неправильно, смок должен это увидеть.
Именно поэтому запускать такой тест только в тестовой среде для меня было недостаточно.
Тест доказывает работоспособность теста, не более.
А вот продакшн смок доказывает что проверенные критические сценарии в данный момент работаю на продовой среде, что мне кажется все таки более показательно и информативно.
Почему для этого используются настоящие аккаунты
В Пульсе для смоков есть отдельные пользователи.
Сейчас это аккаунты вида:
Smoke Monitor A ... Smoke Monitor B ...
Один изображает первого участника сценария, второй — такого же второго участника.
Это позволяет проверить не абстрактный HTTP 200 OK, а сценарий целиком.
Условно:
Пользак A получает рабочую сессию.
Пользак A отправляет сообщение.
Бек его сохраняет.
Пользак B получает событие.
Состояния сходятся.
Смок убеждается, что ожидаемый результат действительно появился.
Созданные данные очищаются.
То есть если здоровительный ендпоинт отвечает:
{"status":"ok"}
а сообщения фактически перестали доставляться — обычная проверка /health будет счастлива.
А вот мониторы — нет.
Для мессенджера это особенно важно. У него слишком много вещей находятся между «сервер отвечает» и «пользователь действительно получил сообщение»:
HTTP Database Authorization Conversation membership WebSocket Realtime routing Delivery state Read state Media Client-visible state
Каждая часть отдельно может выглядеть здоровой, в то время как пользовательский сценарий — может радостно сдохнуть по какой‑либо причине.
И всё прекрасно работало
Результаты прогонов складываются на status.pulsehq.ru — я каждый день вижу, какой production‑сценарий прошёл или упал.
Smoke запускался. Ошибки обнаруживались. После деплоя можно было быстро понять, что прод действительно успешно пережил обновление. В общем, всё было хорошо и прекрасно...
Пока однажды мне не захотелось посмотреть, как растёт Пульс. У меня маленький проект, поэтому никакой огромной аналитической платформы пока нет и врятли предвидится в ближайшее время, поэтому источником моей истины выступает продовый PostgreSQL.
Первый запрос был очень простой:
SELECT count(*) FROM messages;
Результат:
7381
Неплохо. Потом начал смотреть глубже. За последние 30 дней:
2235 сообщений
За семь дней:
1052
За сутки:
152
Я уже начал радоваться. Потом посмотрел отправителей.
И среди лидеров обнаружили:
Smoke Monitor A Smoke Monitor B
Причём далеко не в самом низу списка.
Каждое пятое сообщение оказалось написано тестами
После этого пришлось пересчитать статистику уже нормально.
Взял период с 11 августа до начала октября. Получил:
Всего новых message rows: 2736 Из них production smoke: 581 Удалены обычными пользователями: 7 Чистые пользовательские: 2148
То есть примерно 21% новых строк в messages за этот период создали автоматические production smoke‑тесты.
Каждое пятое. Это был довольно хороший момент для переоценки фразы:
«у нас столько‑то сообщений».
Нет. У нас столько‑то строк в таблице messages. Это несколько другая метрика.
Но дальше стало ещё интереснее
В Pulse удаление сообщения — soft delete: строка остаётся в таблице, а deleted_at получает значение:
deleted_at IS NOT NULL
Поэтому было решено посмотреть, сколько сообщений удаляют пользователи. За последние 30 дней оказалось:
379 deleted messages
Выглядело любопытно. Неужели пользователи настолько активно редактируют историю?Проверили авторов.
Оказалось, production smoke объясняет 373 из этих 379 удалений. То есть около 98%. Причина довольно простая. Smoke создаёт реальное состояние, проверяет его, а затем выполняет cleanup.С пользовательской точки зрения тестовые сообщения исчезли. С точки зрения PostgreSQL они всё ещё существовали как soft‑deleted записи.
Получился отличный пример того, почему:
cleanup != absence of analytical footprint
Система была очищена для пользователя. Но не для аналитика.
Smoke не ломал продукт. Он ломал понимание продукта
Это важное различие. Сами тесты работали правильно. Они не засоряли обычным пользователям список диалогов и не превращали Messenger в свалку тестовых сообщений. Проблема появилась только тогда, когда я стал задавать базе продуктовые вопросы.
Например:
SELECT count(*) FROM messages;
На самом деле означает:
сколько строк существует в
messages?
Но мне очень хочется прочитать ответ как:
сколько сообщений написали пользователи?
Это уже моя ошибка.
А запрос:
SELECT count(*) FROM messages WHERE deleted_at IS NOT NULL;
отвечает:
сколько сообщений находится в удалённом состоянии?
но совсем не обязательно:
сколько сообщений решили удалить обычные пользователи?
В проде живут не только пользователи.
Там живут ещё:
технические аккаунты;
мониторинг;
синтетический трафик;
миграции;
фоновые процессы;
автоматические проверки.
И всё это может выглядеть абсолютно настоящим. Потому что оно и есть настоящее.
А затем smoke начал портить DAU и MAU
После сообщений я решил посмотреть аудиторию.
Сырая статистика показала:
DAU: 6 WAU: 14 MAU: 22
Для небольшого проекта цифры вполне приятные. Но мы то уже помним — два наших Smoke Monitor регулярно заходят в production.
Следовательно, они тоже:
active users
С точки зрения базы всё правильно. После их исключения акков получилось:
DAU: 4 WAU: 12 MAU: 20
И это уже была реальная продуктовая статистика. Похожая ситуация произошла с уникальными отправителями. За последние 30 дней база показывала:
15 senders
После исключения smoke:
13
За последние семь дней:
10 -> 8
За сутки:
4 -> 2
Последняя цифра особенно хорошо показывает проблему маленьких продуктов.
Если у вас миллион DAU, два смок пользователя ничего визуально не меняют. Если DAU равен шести — они составляют треть аудитории.
Как smoke можно увидеть даже без знания его имени
После этого расследование стало довольно забавным. Группировка сообщений по минутам.
Видится примерно такую картину:
2026-10-04 04:12 Smoke Monitor A 13 messages 2026-10-03 04:12 Smoke Monitor A 13 messages 2026-10-02 04:12 Smoke Monitor A 13 messages 2026-10-01 04:11 Smoke Monitor A 13 messages 2026-09-30 04:10 Smoke Monitor A 13 messages ...
Очень дисциплинированный пользователь, едрит мадри его в качель...
Каждый день приходит примерно в четыре утра, быстро отправляет двенадцать‑тринадцать сообщений и исчезает. Retention мечты. Конечно, это был наш монитор, послушный и надежный как швейцарские часы:D
И именно здесь мне стало понятно, что синтетический трафик — это, пожалуй, полноценный класс production‑данных. Его нельзя считать ошибкой или мусором.
Его нужно уметь классифицировать, определять и отделять. Примерно как мух от котлет...
Что оказалось неправильно в подходе
Сам production smoke я после этого выключать не захотел.
Наоборот.
Я по‑прежнему считаю его полезным. Ошибка была скорее в другом: я хорошо отделил тестового пользователя логически, но почти никак не отделили его аналитически.
Имя:
Smoke Monitor A 20260917
понятно человеку. Для базы это просто обычная строка username. В итоге аналитический запрос начинает выглядеть так:
WHERE username NOT LIKE 'Smoke Monitor %'
Работает? — Да!.
Красиво? — Не очень...
Если завтра аккаунт назовут (гипотетические коллеги, например?):
Production Probe A
аналитика неожиданно снова «вырастет». Намного правильнее иметь явную принадлежность аккаунта.
Например:
account_type = human account_type = synthetic account_type = service
или хотя бы:
is_synthetic = true
Тогда продуктовый запрос становится честнее:
WHERE is_synthetic = false
А мониторинг при этом может совершенно спокойно продолжать жить в проде.
Synthetic traffic должен быть first‑class citizen
Это, пожалуй, главный вывод всей истории. Если в проде система регулярно генерирует синтетический трафик, это не временный костыль. Это часть продуктовой архитектуры. Значит, ей нужны нормальные свойства, о чем многие часто забывают. Да и я напоролся на эти же грабли, потому что спешил. Итого, что должно быть:
Отдельная identity. Смок конечно не должен работать от имени случайного реального пользователя.
Явная классификация — аналитика должна понимать разницу между человеком и синтетикой.
Ограниченный scope — техюзверь должен иметь ровно те возможности, которые нужны для проверки.
Предсказуемый cleanup — Причём надо понимать разницу между:
user-visible cleanup
и
physical database cleanup
Отдельные метрики. Но на самом деле такую активность тоже интересно считать.
Например:
smoke runs smoke success rate scenario latency created synthetic messages cleanup failures last successful run
Просто эти цифры не должны смешиваться с продуктовыми.
Удалять ли тестовые записи физически?
Первой реакцией может быть:
ну так делайте hard delete после теста, проблема решена.
Но мне это решение не очень нравится. Во‑первых, жесткое уаление само по себе может обойти тот сценарий, который мы как раз и пытаемся проверять. Во‑вторых, след успешного смока иногда полезен для расследования.
Если ночью что‑то сломалось, возможность посмотреть:
какие данные создал тест что успело сохраниться на каком состоянии остановился сценарий
может быть полезнее идеально стерильной таблицы.
В‑третьих, тестовые данные перестают быть проблемой, если система знает, что они тестовые.
Поэтому сейчас мне ближе подход:
не прятать synthetic traffic, а корректно его маркировать
Тогда можно иметь обе картины.
Операционную:
в production прошло 7388 message events
и продуктовую:
пользователи создали N реальных сообщений
Обе метрики правильные. Они просто отвечают на разные вопросы.
Ещё одна неожиданность: историческая аналитика тоже врёт
Пока мы разбирались со смоками, обнаружился ещё один класс данных.
В один день система показывала огромный всплеск:
613 сообщений 28 отправителей 39 диалогов
На первый взгляд — отличный день.
Почти вирусный рост.
Но у большого количества этих сообщений timestamp оказался абсолютно одинаковым до микросекунды.
Это была миграция старой истории. Когда то давно. Я уже сам не помню, что за миграция это была. Получается, что даже после удаления синтетического трафика нельзя автоматически считать:
GROUP BY created_at::date
графиком реальной активности пользователей. Иногда created_at отвечает на вопрос:
когда эта строка появилась в нынешней модели данных?
а не:
когда человек совершил действие?
Короче в итоге получено несколько классов событий:
human activity synthetic production activity historical import service activity
И внезапно простая таблица сообщений стала выглядеть гораздо интереснее.
Что теперь считается «реальным сообщением»
Для текущей аналитики Pulse правило получилось примерно таким:
visible message + non-synthetic sender + non-migration event
В SQL минимальная версия сейчас выглядит примерно так:
SELECT count(*) FROM messages m JOIN users u ON u.id = m.from_user WHERE m.deleted_at IS NULL AND u.is_synthetic = false;
Поля is_synthetic у нас на момент расследования ещё не было — именно расследование и показало, зачем оно нужно.
До этого приходилось исключать известные smoke identities отдельно. В целом как говорится «и так сойдет» и работать можно, но если подобное условие начинает появляться в каждом аналитическом запросе, значит классификация должна переехать в модель данных.
Значит ли это, что продуктовы смоки — плохая идея?
Для меня — нет. Наоборот, после этой истории я ещё больше уверен, что он нужен. Они поймали уже немало вещей, которые невозможно нормально доказать одним CI. Потому что прод — это не только бинарник.
Production — это:
код + конфигурация + база + миграции + reverse proxy + TLS + сетевые зависимости + внешние сервисы + runtime state
Можно протестировать каждый компонент отдельно и всё равно получить неработающий продукт в сборе. Production smoke проверяет именно сборку. Но за это приходится платить. Synthetic traffic становится частью реальности production.
И если делать вид, что его не существует, он обязательно всплывёт где‑нибудь ещё. У нас он всплыл в продуктовой аналитике.
У кого‑то может всплыть:
в billing;
в rate limits;
в fraud detection;
в рекомендациях;
в push‑уведомлениях;
в retention;
в audit;
в storage metrics.
Чем серьёзнее и сложнее система, тем важнее отделять тестовый трафик от человеческого не по соглашению, а на уровне модели.
Самая забавная часть
Всё расследование началось вообще не с проблемы. Я просто хотел порадоваться росту своего мессенджера и пытался найти интересные цифры. Все таки 11 августа последний раз данные снимал! Посмотрел:
7381 сообщений
Потом:
1052 за неделю
Потом:
379 удалённых за месяц
А через некоторое время сидел и вычислял, какая часть моего DAU вообще не является людьми. В результате статистика стала немного скромнее. Зато намного честнее. И автотесты в проде я оставил и буду дальше их развивать. Но это уже другая история...
Подводя итог — А что думаете вы? Нужны ли в проде smoke‑тесты, которые создают настоящие данные, если эти данные изолированы от пользовательских сценариев? Или production должен оставаться полностью свободным от синтетики?
Весь описанный цирк происходит внутри моего пет‑проекта — Pulse Messenger. Автотесты продолжают работать, а после этого расследования у них, похоже, скоро появится ещё и нормальный признак синтетического идентити.
Chemist_modeler
IMHO, отличный пример на тему "если гора не идет к Магомету, то Магомет идет к горе". У Вас банально нет ресурсов, чтобы сделать тестовую среду полным аналогом продуктовой. Соответственно, Вы сделали продуктовую среду полным аналогом тестовой.
Не буду судить с позиций высоких профессионалов, коим я в этой области не являюсь, но с точки зрения здравого смысла - решение видится вполне нормальным. Появятся ресурсы на отдельную тестовую среду - будет другой разговор. Но это не 13 и не 130 живых пользователей за месяц.
Удачи!
qubs993 Автор
Спасибо! Пожалуй действительно так, тестовый стенд у меня чисто локально на винде, а в проде все на линуксе, в другой конфигурации с другими ТТХ, в общем возможно, когда-нибудь будет идентичный тестовый стенд....