Стандартная (для многих) история. Отдаём какие‑то данные в реальном времени через server‑side web‑gRPC стримы. Цепочка: балансировщик, дальше Envoy с grpc‑web, дальше бэкенд.
Все проверки зелёные: TCP поднят, хендшейк проходит, /healthz отвечает 200, в графане/slack'e тишина. А фронтенд у клиентов замёрз. И узнали мы об этом от клиентов, а не от мониторинга.
Почему так выходит
Server‑side стрим — это не запрос‑ответ. Это канал, который сервер держит открытым и в который когда‑нибудь пушит сообщения. И вот тут дыра: сокет может быть жив, а данные — не идти. Ни один стандартный пробник этого не видит (или не видел на момент когда я писал свой чекер).
Как стрим умирает молча
— балансировщик прибил соединение по idle‑таймауту;
— Envoy при трансляции grpc‑web что‑то забуферизировал;
— у бэкенда отвалился апстрим‑фид: стрим открыт, слать нечего. Для любого health check это «здоровый» сервис;
— мой любимый случай: после деплоя кадры идут, но поле symbol в ответе пустое, формально поток есть, фактически — данных там нет.
HTTP 200 (да и любой другой ответ) не означает «ожидаемые данные идут». gRPC/TCP/UDP/etc health check тоже. Проверить это может только один пробник — настоящий клиент, который откроет стрим, дождётся данных, проверит ответ и его скорость.
Решение
Написал демона, который по расписанию:
Подгружает.proto на лету через proto‑loader — без кодогена: добавить стрим в мониторинг значит указать файл и метод в конфиге.
Открывает server‑side RPC как обычный клиент (TLS, auth‑метаданные — если надо).
Ждёт не один кадр, а N кадров (например 3) в бюджет времени — стрим, который выдал один кадр и завис, коварнее того, который не начался вовсе.
Валидирует каждый кадр: обязательные поля на месте, значения матчатся по точному значению или регулярке — потому что «кадры идут» и «кадры корректные» это разные аварии.
Отдаёт всё в Prometheus.
Каждый исход — отдельный лейбл: ok / timeout / insufficient_frames / validation_failed / error (с gRPC‑кодом). Плюс лейбл method на каждой метрике — панель «success rate по методам» собирается одним запросом:sum by (method) (rate(grpc_stream_check_total{result="ok"}[5m]))/sum by (method) (rate(grpc_stream_check_total[5m]))
Из полезных сигналов, которые вылезли на практике:
— время до первого кадра — лучший ранний звоночек. Каждый стрим, который у нас потом умирал, сначала становился медленным. Каждый.
— максимальный разрыв между кадрами — ловит «заикающийся» продюсер задолго до полной тишины.
— last_run_timestamp самого чекера — обязательный алерт. Мёртвый мониторинг выглядит ровно как парк здоровых стримов. Проверено на себе.
Проверить за минуту
Гонял такую штуку в проде около года на шести стриминговых методах — ловила то, что не видел никто. Недавно переписал начисто и выложил в опенсорс (Apache-2.0).
В репозитории демо‑сервер со встроенными режимами поломки — можно посмотреть, как чекер ловит каждый тип аварии, ничего не разворачивая:git clone https://github.com/youngpabl0/grpc-streams-checker.gitcd grpc-streams-checker && npm cinode examples/demo-server.js & # демо PriceStream на :50051cp streams.example.json streams.jsonnode src/index.js --once# {"stream":"price_stream","result":"ok","frames":3,"firstFrameMs":223,...}node examples/demo-server.js --silent # стрим открыт, кадров нет -> timeoutnode examples/demo-server.js --broken # пустые поля -> validation_failednode examples/demo-server.js --slow=2000 # тормозящий продюсер -> растёт inter_frame gap
Ключ ‑once возвращает ненулевой код выхода при любом фейле — можно втыкать в CI или cron как гейт. Внутри ещё: готовые алерт‑правила для Prometheus (в том числе «чекер сам умер»), манифесты для Kubernetes с пробами и ресурсами, 30+ тестов.
Паттерн переносится на любой долгоживущий push‑транспорт — WebSocket, SSE, консьюмеры очередей.
Суть одна: вопрос «а данные вообще идут, и они валидные?» должен быть метрикой с алертом.
Расскажите, как вы это решаете у себя — синтетическим потребителем, чем‑то в Envoy, или никак (тогда у меня для вас плохие новости)?
P. S. Конечно, хочется не одни и те же данные опрашивать, но это усложняет систему, а тут хотелось сделать максимально просто.