
Как OpenAI победила баг возрастом 18 лет: эпидемиология сбоев
OpenAI применила эпидемиологический подход к анализу core dump-файлов и обнаружила аппаратный сбой и 18-летний баг в GNU libunwind.
Как OpenAI победила баг возрастом 18 лет: эпидемиология сбоев
Что делать, если сервис периодически падает, воспроизвести ошибку невозможно, а каждая новая гипотеза немедленно опровергается? Именно с этой головоломкой столкнулась команда инженеров OpenAI несколько месяцев назад. История о том, как они справились — это не просто байка про баг: это урок по методологии отладки больших производственных систем.
Rockset: что это и зачем он нужен OpenAI
Для понимания контекста важно разобраться, что такое Rockset. Это облачная система для поиска и аналитики в реальном времени, которую OpenAI использует для множества внутренних задач, в том числе для синхронизации данных (Rockset была приобретена OpenAI в 2024 году).
Потоковые обновления применяются для поддержания актуального индекса базы знаний рабочего пространства, чтобы ChatGPT мог искать релевантную информацию при ответах на вопросы и выполнении действий.
Исполняющий слой Rockset написан на C++. Этот язык даёт низкоуровневый доступ к CPU — что хорошо для производительности, — но это также означает, что ошибки в приложении могут приводить к некорректному обращению с памятью и к segfault (нарушению сегментации).
Для отслеживания таких сбоев команда использует обработчик фатальных сигналов из библиотеки folly — он записывает стек вызовов при краше, а соответствующие core dump-файлы (снимки состояния программы в момент сбоя) загружаются в Azure Blob Storage для последующего анализа.
Симптомы: таинственный краш
Несколько месяцев назад в сервисе Rockset начали появляться странные сбои. В каждом случае обычная C++-функция, казалось, завершалась нормально — но затем передавала управление по «мусорному» адресу, и ядро останавливало программу, поскольку указатель инструкций больше не указывал на код.
Иногда в слоте адреса возврата на стеке оказывался NULL. Иногда регистр стека CPU (%rsp) был смещён на 8 байт — как будто его значение было декрементировано посреди нормального выполнения. В обоих случаях краш происходил именно при возврате из функции.
«Каждая гипотеза, которую мы (и ChatGPT) могли придумать, немедленно опровергалась — баг казался невозможным.» — команда инженеров OpenAI
То, что считалось одной проблемой, в итоге оказалось двумя никак не связанными между собой багами, обнаруженными одновременно по чистой случайности.
Подход «доктора» против подхода «эпидемиолога»
Инженеры сформулировали ключевой выбор методологии: можно расследовать каждый конкретный краш по отдельности — как врач обследует одного пациента. А можно взглянуть на всю совокупность сбоев и поискать паттерны, которые не видны при изучении единичного случая.
Начался ли баг с конкретного релиза? Коррелирует ли он с определённым типом железа (SKU — конфигурация CPU и сервера), регионом или версией ядра? Скрываются ли внутри того, что выглядит как один синдром, несколько различных кластеров?
Именно переход к «эпидемиологическому» мышлению стал решающим шагом.
Автоматический конвейер анализа: ChatGPT пишет скрипт
Предыдущие попытки автоматически найти все экземпляры проблемы провалились — команда пыталась использовать текстовый поиск по логам. Core dump-файлы содержат значительно больше информации, но изучать их вручную было невозможно в нужном масштабе. Поэтому было решено вложить усилия в построение конвейера для автоматического анализа дампов.
Инженеры попросили ChatGPT написать скрипт, который скачивал префикс каждого core-файла, извлекал значения регистров, фильтровал известные ложные срабатывания по логам и автоматически классифицировал краш как return-to-null (возврат на нулевой адрес), misaligned-stack (смещение стека) или other (иное).
Затем этот скрипт был запущен параллельно на всех production core dump-файлах Rockset за предыдущий год. Это стало переломным моментом: как только появился чистый датасет, корреляции проявились немедленно.
flowchart TD
A[Редкие крашы в Rockset] --> B[Ручной анализ core dumps не масштабируется]
B --> C[ChatGPT пишет скрипт классификации]
C --> D[Параллельный запуск на всех дампах за год]
D --> E{Два отдельных кластера сбоев}
E --> F[return-to-null: широко распределённые крашы]
E --> G[misaligned-stack: один регион, один хост]
F --> H[Баг в GNU libunwind — 18 лет]
G --> I[Аппаратный сбой на физическом сервере Azure]
Два разных кластера сбоев
То, что принималось за один странный баг, на самом деле оказалось двумя отдельными популяциями крашей.
Кластер 1: аппаратный сбой
Крашы типа misaligned-stack выглядели совершенно иначе. Все они происходили в одном регионе, имели чёткую дату начала и никогда не случались на узлах, работавших длительное время.
Несмотря на то что в них были задействованы несколько виртуальных машин Azure, паттерн напоминал поведение одного физического сервера с неисправным железом, который влиял на любую VM, попавшую на этот хост.
Речь шла о тихом аппаратном повреждении данных на одном хосте Azure: процессор просто неверно выполнял математические операции.
Кластер 2: баг возрастом 18 лет
Крашы типа return-to-null были распределены по множеству кластеров и географических регионов. Их частота недавно возросла, но не было ни чёткой даты начала, ни понятной инфраструктурной границы.
Инженеры обнаружили 18-летнее состояние гонки (race condition) в библиотеке GNU libunwind — широко используемой открытой библиотеке C++ для раскрутки стека при обработке исключений.
Сравнение двух кластеров
| Характеристика | Кластер 1: аппаратный сбой | Кластер 2: баг в libunwind |
|---|---|---|
| Тип краша | misaligned-stack | return-to-null |
| Распределение | Один регион, один хост | Много кластеров и регионов |
| Дата начала | Чёткая | Размытая |
| Долгожительство узлов | Только новые узлы | Любые |
| Причина | Неисправный CPU на физическом сервере | 18-летний race condition в GNU libunwind |
| Решение | Замена/изоляция хоста | Смена unwinder + upstream-патч |
Исправление и превентивные меры
Удаление неисправного хоста не является постоянным решением — оно не предотвращает повторение похожей проблемы в будущем. Тем не менее можно изменить программную часть так, чтобы при повторном появлении подобного сбоя его было легко обнаружить. Был улучшен обработчик фатальных сигналов: теперь он включает состояние регистров, что позволяет детектировать повторение только по логам — без необходимости в core dump.
OpenAI переключила Rockset с GNU libunwind на unwinder из libgcc в качестве немедленного решения, а инженер Натан Бронсон загрузил воспроизводящий пример и патч непосредственно в проект GNU libunwind на GitHub.
Поскольку GNU libunwind используется далеко за пределами OpenAI, другие организации, эксплуатирующие высоконагруженные C++-сервисы с интенсивной обработкой исключений, могут столкнуться с тем же скрытым багом.
# Пример команды для проверки используемого unwinder в Linux
ldd ./your_binary | grep unwind
# Если вы видите libunwind — стоит проверить версию и наличие патча
Почему это важно шире, чем один баг
Даже сильные инженерные команды могут застрять, принимая два несвязанных бага за один. И этот случай говорит столько же о методологии отладки, сколько о конкретном 18-летнем изъяне в GNU libunwind: популяционный анализ данных о сбоях преуспел там, где пошаговое расследование отдельных крашей не давало результата на протяжении месяцев.
Этот эпизод является конкретной иллюстрацией более широкого принципа отладки для инфраструктурных команд: анализ случай за случаем может провалиться даже при наличии сильных инженеров, тогда как автоматизированные конвейеры популяционного анализа данных способны выявить паттерны, которые невозможно увидеть в единичном инциденте.
Мышление эпидемиолога — не просто метафора. Это конкретная инженерная практика: сначала собрать данные о всей популяции событий, а уже потом формировать гипотезы.
Итоги
История с core dump-ами в Rockset — редкий пример подробного технического постмортема от крупной AI-компании. Она учит сразу нескольким вещам:
- Автоматизируй классификацию — ручной просмотр не масштабируется при сотнях дампов.
- Смотри на популяцию, а не на один образец — статистика выявляет то, что скрыто в единичных случаях.
- Не предполагай одну причину — несколько несвязанных проблем могут проявляться одновременно.
- Используй AI-инструменты — ChatGPT помог написать скрипт классификации, ускорив прорыв.
- Отдавай исправления в open source — патч для GNU libunwind теперь поможет всей индустрии.
Открытый баг в библиотеке возрастом 18 лет — напоминание о том, что даже самый стабильный и проверенный код несёт в себе скрытые угрозы, которые ждут своего часа в production-системах будущего.