РАЗРАБОТКА

Pinterest нашла «CPU-зомби» и убрала сбои в ML-кластерах

Падение успешности ML-задач у Pinterest превышало 25%: инженеры нашли «CPU-зомби» в базовом образе и сняли скрытое узкое место в Kubernetes.

✍️ Редакция iTech News | 15.05.2026 | ⏱ 5 мин | 👁 3 | Источник: InfoQ
Pinterest нашла «CPU-зомби» и убрала сбои в ML-кластерах

У Pinterest часть обучающих ML-задач на внутренней платформе PinCompute теряла больше 25% успешных запусков, хотя общая загрузка процессоров выглядела вполне прилично. Проблему вызвал не экзотический баг в Ray и не перегруженный Kubernetes-кластер, а забытый агент Amazon ECS, который вообще не использовался, но успевал оставлять после себя тысячи «CPU-зомби». Для команд, которые гоняют обучение моделей в Kubernetes, история неприятно поучительная: дефицит CPU может годами прятаться за красивыми дашбордами.

О разборе инцидента сообщает InfoQ со ссылкой на рассказ инженерной команды Pinterest. Речь идет о PinCompute, Kubernetes-платформе, на которой компания выполняет больше половины своей офлайн-нагрузки по машинному обучению. Каждый месяц там поднимаются десятки тысяч кластеров Ray, и именно на этой инфраструктуре начали всплывать странные сбои: сетевые проблемы, падение training jobs и резеты ENA, сетевого адаптера AWS. На верхнем уровне картина была обманчиво спокойной: агрегированная CPU-утилизация не кричала о пожаре, поэтому первичный поиск причины почти неизбежно вел не туда.

Дальше начинается та часть истории, которую обычно любят SRE и боятся менеджеры. Когда общие метрики не объяснили происходящее, инженеры спустились на уровень отдельных ядер и посмотрели статистику через mpstat. Там и обнаружился реальный источник боли: некоторые ядра периодически уходили в 100% system CPU на несколько секунд. Это особенно плохо для узлов, где эти же ядра обслуживают сетевые прерывания ENA. Если ядро занято чем угодно, кроме обработки этих событий, NAPI poll thread драйвера недополучает процессорное время. А дальше срабатывает встроенный механизм самовосстановления: если подтверждения передачи пакетов задерживаются дольше пяти секунд, ENA делает reset. Для распределенных Ray-задач этого уже достаточно, чтобы потерять связность и уронить job.

Чтобы понять, кто именно съедает ядро, команда запустила циклический сбор perf-трейсов с интервалом в две минуты на протяжении 12 часов, пока воспроизводился сбой. Затем данные визуализировали в Flamescope, инструменте Netflix для анализа профилей во времени. Такой подход позволил не просто увидеть «где-то бывают спайки», а приблизить ровно те моменты, когда происходили сетевые сбросы. В профилях всплыл неожиданный подозреваемый: kubelet, который обычно ест меньше 1% CPU, в пиках доходил примерно до 6,5%. Большая часть времени при этом уходила в kernel-функцию mem_cgroup_nr_lru_pages. На языке прикладной эксплуатации это звучит так: не приложение сходило с ума, а системный слой тратил процессор на учет памяти в cgroup-структурах.

Финальная развязка еще менее романтична, чем поиск. Источник проблемы оказался в AWS Deep Learning AMI, базовом образе, который использовался на узлах. В нем по умолчанию был включен агент Amazon ECS. Pinterest этот агент не использовала, но он crashloop-ился и при каждом рестарте оставлял после себя утекшие memory cgroup. Команда назвала их «зомби», и это не журналистское украшение: при примерно 240 реально используемых memcg в системе накопилось почти 70 тысяч мусорных. Из-за этого kubelet на каждой синхронизации cgroup-статистики проходил по раздутому списку и мог монопольно занимать одно ядро на несколько секунд. Вот вам и дефицит CPU на фоне «нормальной» средней загрузки: суммарно процессоры вроде не забиты, а одно конкретное ядро регулярно уходит в системный ад и валит сетевой стек.

Исправление, как это часто бывает, оказалось гораздо проще расследования. Pinterest отключила systemd-юнит ECS-агента в базовом образе и перезагрузила затронутые машины, чтобы очистить накопившиеся cgroup. После этого количество memory cgroup стабилизировалось, а ENA reset прекратились. С практической точки зрения здесь важно не только само решение, но и то, насколько оно не похоже на типичный список гипотез в таких инцидентах. Если смотреть только на приложение, можно неделями крутиться вокруг Ray, Kubernetes-сетей, CNI, лимитов, noisy neighbors и любых других привычных подозреваемых. В реальности корнем проблемы оказался лишний userspace-демон из дефолтного образа, который тихо загрязнял kernel state.

Для отрасли это хороший холодный душ. Инфраструктурные абстракции сделали жизнь удобнее, но они же отлично маскируют дефекты на стыке образа, оркестратора и ядра. Особенно в ML-инфраструктуре, где дорого стоят не только простои, но и нестабильность повторяемости экспериментов. Если training jobs падают не из-за кода модели, а из-за редких сетевых сбоев под нагрузкой, команда теряет не только compute-бюджет, но и доверие к самой платформе. И да, это тот случай, когда «мы же используем стандартный образ» не снижает риск, а создает его. Дефолты в облаке полезны ровно до первого инцидента, который они же и подбрасывают.

Отдельно показательно, какие инструменты Pinterest считает полезными после этого кейса. Команда прямо указывает на ценность непрерывного профилирования в продакшене, привязанного ко времени. В материале упоминаются gProfiler, который Pinterest разворачивает вместе с Intel, а также eBPF-платформы вроде Parca и Grafana Pyroscope. Смысл понятен: когда сбой плавающий и короткий, ручной сбор perf может сработать один раз, но как операционная модель он дорог и хрупок. Непрерывное профилирование дает шанс заметить, что дефицит CPU рождается не в бизнес-логике и не в контейнере пользователя, а в системной обвязке, которую обычно никто не трогает до аварии.

Для русскоязычных команд вывод здесь довольно приземленный. Если у вас Kubernetes держит ML, data processing или любую другую тяжелую пакетную нагрузку, проверять нужно не только манифесты и autoscaling, но и состав базовых образов, активные systemd-сервисы, фоновых агентов и хвосты от предыдущих платформенных решений. Средние метрики по ноде легко врут, когда дефицит CPU локализован в одном ядре и длится секунды, а этого уже хватает, чтобы обрушить чувствительный компонент вроде сетевого драйвера. Следующий заметный класс сбоев в больших кластерах, скорее всего, тоже окажется не «магией Kubernetes», а скучной ошибкой в дефолтной конфигурации, которую никто не считал частью продукта, пока она не остановила продакшен.

Поделиться: Telegram X LinkedIn