Кубернетес не спасёт ваш пайплайн, он просто автоматизирует его падение. Отказоустойчивость AI-сервиса: как сделать, чтобы инференс не лёг вместе с одной нодой, начинается ровно там, где заканчивается вера в автомагию оркестраторов.
«Просто заверните модель в Docker и задеплойте в кластер» — это мантра для пет-проджектов. Обычный stateless веб-сервер при краше рестартится за миллисекунды. Тяжёлая нейросеть грузит десятки гигабайт весов в VRAM полминуты или дольше. За это время очередь входящих запросов забивается, таймауты истекают, глупый балансировщик продолжает сыпать трафик на еле живые поды, и реплики начинают ложиться одна за другой по эффекту домино. И вот у вас лежат все GPU, бизнес теряет деньги, а в логах сплошной хаос.
Точки отказа: анатомия катастрофы AI-пайплайна
Инференс не висит в вакууме. Любой AI-продукт в проде — это распределённая система, где каждый компонент может и хочет убить соседний. Разберём критичные ребра, на которых всё ломается:
- GPU-нода. Видеокарта может перегреться, словить аппаратную ошибку шины (Xid error) или тихо уйти в OOM из-за фрагментации памяти. Оркестратор смотрит на метрики CPU и RAM ноды, а GPU для него — чёрный ящик. Контейнер жив, процесс висит, видеокарта не отвечает.
- Очередь сообщений. Инференс затормозил из-за всплеска трафика, брокер накопил миллион картинок или тяжелых эмбеддингов, упёрся в лимиты памяти и лёг. Отвалился не только AI-компонент, но и вся шина данных предприятия.
- Feature store и БД эмбеддингов. Модель может отрабатывать за 50 миллисекунд. Но перед инференсом пайплайн идёт в фичестор за историческим контекстом пользователя. Если база векторов начинает отдавать ответы за секунду, весь пайплайн встаёт колом, ожидая I/O.
- Сеть. Жирные тензоры и батчи гоняются между узлами, забивая канал. Если сеть моргнула во время распределённого инференса (tensor parallel), весь процесс рассыпается в труху.
Иллюзия health-checks и реальность GPU-таймаутов
Базовые проверки жизнеспособности (Liveness и Readiness пробы) в контексте машинного обучения — это зачастую ложь. Стандартный подход, когда балансировщик дёргает HTTP-эндпоинт /health, не работает. Веб-сервер, обёртывающий вашу модель (будь то FastAPI или Triton), радостно отдаст 200 OK, даже если инференс-движок намертво повис внутри CUDA-ядра. HTTP-поток жив, вычислительный поток мёртв.
Вам нужны честные health-checks для GPU. Сервис обязан регулярно опрашивать состояние VRAM и утилизацию через NVML (NVIDIA Management Library). Ещё лучше — раз в несколько секунд прогонять через модель легковесный dummy-запрос (тензор из нулей). Только успешный прогон такого тензора гарантирует, что модель реально способна выполнять инференс. Если dummy-запрос упал или завис — нода помечается как больная, и трафик с неё убирается мгновенно.
Что делать при OOM? Единственный правильный путь — немедленно убить процесс. Fail-fast. Никаких попыток очистить кэш PyTorch, никаких костылей с ручным вызовом сборщика мусора. Память фрагментирована, стейт повреждён. Завершайте процесс с фатальной ошибкой и позволяйте оркестратору поднять чистую реплику.
Здесь же всплывает понятие таймаут-бюджета. Запрос от клиента гуляет по системе: API Gateway, очередь, препроцессинг, инференс. Установите жёсткий дедлайн на весь жизненный цикл запроса, например, 500 миллисекунд. Если на 450-й миллисекунде батч только дошёл до GPU-модели — безжалостно дропайте его. Нет никакого смысла тратить драгоценное время видеокарты на генерацию ответа, если клиент уже закрыл соединение по своему таймауту. Вычислять протухшие запросы — значит греть воздух за счёт бизнеса.
Ситуация усложняется, если вы используете динамическое батчирование (Dynamic Batching) на уровне сервера вроде Triton или vLLM. Движок намеренно задерживает первый пришедший запрос на несколько миллисекунд, ожидая соседей, чтобы сформировать оптимальную матрицу и максимизировать пропускную способность (throughput). Это классический конфликт: throughput требует задержек, а latency требует скорости. Если ваш таймаут-бюджет слишком агрессивный, вы начнёте дропать запросы прямо из батча до того, как он отправится на вычисление. Архитектору приходится балансировать: окно батчирования должно быть строго меньше минимального клиентского таймаута за вычетом сетевых задержек.
Паттерны маршрутизации: Circuit Breaker и ретраи без штормов
Как защитить живые ноды, когда соседние начинают умирать? Стандартный Round-Robin балансировщик выступает в роли соучастника убийства. Если одна реплика начинает тупить, она накапливает запросы. Балансировщик сыплет в неё новые, усугубляя ситуацию.
Внедряйте паттерн Circuit Breaker (предохранитель). Если конкретная реплика инференса не отвечает вовремя три раза подряд — рубильник размыкается. Трафик на эту ноду полностью прекращается на заданное время, давая ей возможность разгрести бэклог или спокойно умереть и перезапуститься. После паузы балансировщик переходит в состояние half-open, пуская один тестовый запрос. Прошёл успешно — нода возвращается в пул. Упал — снова в изолятор.
А что делает клиент, не получив ответа? Шлёт запрос снова. Если кластер задыхается, тысяча клиентов, одновременно повторяющих запросы каждую секунду, организуют вам retry storm и вобьют последний гвоздь в крышку гроба системы. Любые ретраи к тяжелым AI-сервисам обязаны делаться с экспоненциальной задержкой и рандомизацией (jitter). Первая попытка через секунду, вторая через две с половиной, третья через пять, плюс-минус случайный разброс. Это размазывает пиковую нагрузку и даёт инфраструктуре шанс выжить.
Graceful Degradation: когда тупой фолбэк лучше идеальной 503 ошибки
Истинная отказоустойчивость стоит неприлично дорого. Полная High Availability означает, что вы удваиваете парк железа. В облаке это ещё можно пережить, балансируя спотовыми инстансами. Но если вы выкатываете edge-инференс в закрытом контуре на железе заказчика, где стоят физические серверы с H100 или A100, дублирование пробьёт дыру в бюджете любого энтерпрайза. Вы не можете просто сказать: «купите ещё четыре сервера за двести тысяч долларов на случай, если первые четыре упадут».
Здесь на сцену выходит концепция graceful degradation — элегантной деградации сервиса.
Задайте себе вопрос: нужно ли бизнесу идеальное качество предсказаний каждую секунду времени? Почти всегда — нет. Если кластер с тяжёлой нейросетью перегружен или потерял половину нод, система не должна сыпать 503 Service Unavailable. Она должна прозрачно для пользователя переключиться на легковесный фолбэк.
Это может быть сильно квантованная версия модели, которая влезает в CPU. Это может быть классический градиентный бустинг вместо трансформера. Это может быть даже банальный набор захардкоженных эвристик и регулярных выражений.
Да, бизнес-метрики на эти 15 минут аварии просядут. Конверсия рекомендательной ленты упадёт на 3%, качество распознавания текста снизится. Но процесс не остановится. Пользователь получит результат, конвейер на заводе не встанет, транзакция пройдёт скоринг. Трейд-офф между качеством ML-предсказания и доступностью системы в промышленной эксплуатации всегда решается в пользу доступности. Если ради выживания нужно на время поглупеть — система должна поглупеть.
RTO, RPO и Chaos Engineering в машинном обучении
Отказоустойчивость в моменте — это гигиена. Настоящая проверка архитектуры начинается на уровне Disaster Recovery. В классическом бэкенде бэкапят базы данных. В MLOps-инфраструктуре бэкапят датасеты, веса моделей, артефакты пайплайнов и критичные срезы фичесторов.
Посчитайте ваш RTO (Recovery Time Objective) для AI-сервиса. Если сгорит стойка с серверами, сколько времени займёт поднятие системы с нуля на резервной площадке? Для LLM или массивных ансамблей CV-моделей счёт идёт на часы. Модель нужно скачать из хранилища, распаковать, загрузить в VRAM, прогреть кэши и скомпилировать графы вычислений (например, через TensorRT). Если у вас нет автоматизированного конвейера cold start, резервная площадка будет подниматься до завтрашнего утра.
А что с RPO (Recovery Point Objective)? Потерять транзакции пользователей за сутки — катастрофа. Для модели машинного обучения потерять логи инференса за сутки — неприятность, которая мешает сбору аналитики для дообучения, но никак не влияет на работу сервиса в моменте. Инфраструктура должна чётко разделять критический путь (сам инференс) и асинхронный сбор телеметрии. Если хранилище логов отвалилось, инференс должен продолжать работать, просто отбрасывая данные аналитики.
Вы не знаете, насколько устойчива ваша система, пока не выдернете оптический кабель из коммутатора. Прогон DR-учений и Chaos Engineering — не модная игрушка корпораций, а суровая необходимость.
Убейте брокер сообщений под пиковой нагрузкой и посмотрите, как поведут себя продюсеры. Погасите ноду с базой векторных эмбеддингов во время массового поиска. Заблокируйте доступ к стораджу с весами в момент скейлинга новых подов. Система обязана выжить или переключиться на деградированный режим без вмешательства дежурного инженера.
Железо ломается всегда, особенно под постоянной 100% утилизацией, характерной для ML-нагрузок. Архитектура, в которой смерть одной видеокарты рушит весь скоринг кредиток или процессинг видеопотока — это архитектура дилетантов. Закладывайте отказ в самый фундамент дизайна пайплайна, режьте таймауты без малейшей жалости и всегда держите наготове тупой CPU-фолбэк. Только так можно гарантировать, что инференс переживёт отказ оборудования, а вы сможете спать по ночам.