Стремительное масштабирование онлайн-хранилища для обслуживания более 1 миллиарда пользователей ChatGPT

SEO.png?w=1600&h=900&fit=fill

Как мы адаптировали нашу платформу хранения приложений Habitat на Python для управления беспрецедентным ростом.

Авторы: Джон Ли (Jon Lee), Чаомин Ю (Chaomin Yu) и Бен Рис (Ben Ries), сотрудники технического отдела

Каждый продукт OpenAI зависит от быстрого и надежного доступа к данным — будь то вход пользователя в систему, проверка настроек Codex или начало нового разговора в ChatGPT. Для каждого из этих действий может потребоваться множество раздельных поисков данных, прежде чем продукт сможет дать ответ. Если эти запросы работают медленно, продукт кажется медленным. Если эти запросы завершаются с ошибкой, продукт полностью перестает работать.

Habitat — это платформа онлайн-хранилища, созданная нами для того, чтобы продукты OpenAI могли быстро и надежно получать доступ к необходимой информации. В настоящее время Habitat обрабатывает более 70 миллионов запросов каждую секунду, поддерживая продукты, которыми еженедельно пользуются более 1 миллиарда человек почти в 40 географических регионах. Два года назад Habitat начиналась как простая клиентская библиотека на Python, подключенная к единой базе данных. Сегодня это сложная распределенная система, обслуживающая более 500 петабайт данных.

Рисунок 01 · Что такое Habitat?

Онлайн-платформа хранения

Habitat — это платформа онлайн-хранилища, созданная нами для того, чтобы продукты OpenAI могли быстро и надежно получать доступ к необходимой информации.

  • Запрос
  • Ответ
  • Изменения (CDC)

Клиенты

Платформа онлайн-хранилища

Ресурсы хранения

  • ChatGPT
  • API
  • Codex
  • Внутренние сервисы
  • И многое другое

Habitat

  • КэшированиеКэши
  • Правила ACLАвторизация
  • Размещение и локализация данныхЛокализация данных
  • ШифрованиеБезопасность данных
  • ИзоляцияМногопоточность
  • Ограничение частоты запросовФормирование запросов
  • МаршрутизацияПоиск схемы · Локализация данных
  • Azure Cosmos DBОнлайн-хранилище
  • NanobaseОнлайн-хранилище
  • ValkeyКэши
  • Хранилище BLOB-объектовРесурсы хранения
  • CDC-сервисыПерехват измененных данных
  • Databricks
  • Rockset
  • Kafka
  • И многое другое

Создание и эксплуатация инфраструктуры такого масштаба — задача непростая, но в то же время не сказать чтобы сверхсложная. Нашу ситуацию уникальной делал беспрецедентный темп, с которым нам приходилось масштабироваться для поддержки ошеломляющего роста числа пользователей и спроса на продукты, одновременно выстраивая зрелую платформу. Обычно системные инженеры проектируют системы с расчетом на 10-кратное масштабирование и надеются, что это проработает несколько лет, пока они готовятся к следующему скачку в 10 раз. В нашем случае мы росли более чем в 10 раз год к году на протяжении последних трех лет. В результате создание и эксплуатация Habitat представляли собой череду тактических решений и последовательных шагов: глубокое понимание каждого компонента на самом низком уровне, чтобы выжать максимум из существующего стека, и одновременное предотвращение дефицита вычислительных ресурсов и емкости хранилищ, чтобы выиграть время для фундаментальных инвестиций.

  • 70М+

    запросов в секунду

  • 1B+

    пользователей каждую неделю

  • 500 ПБ+

    данных

По мере роста OpenAI рос и Habitat: сначала став достаточно надежным для критически важного продуктового трафика, затем ускорившись для пользователей по всему миру и, наконец, научившись уверенно работать в огромных масштабах. Эта публикация открывает серию из двух частей о том, как мы масштабировали онлайн-хранилище. В этой статье мы расскажем, как эволюционировал Habitat, почему мы превратили его из библиотеки в сервис и как нам удалось превратить сервис, написанный на не самом распространенном для серверных стеков языке — Python — в надежный слой платформы хранения.

В будущей публикации мы подробно расскажем о том, как мы обеспечили надежность многопоточности в большом масштабе, о нашей многоуровневой стратегии оптимизации производительности чтения и о том, как мы расширили партнерство с Azure Cosmos DB для надежной обработки беспрецедентного спроса.

Что такое Habitat?

Habitat начинался с простейшей идеи: инженерам по продукту не нужно думать об управлении базами данных. Habitat появился в середине 2024 года как небольшая библиотека на Python, которая взаимодействовала с основным сервером ChatGPT. Она поддерживала небольшой набор операций, которые под капотом проецировались на базу данных приложения — Azure Cosmos DB.

Задача библиотеки заключалась в том, чтобы предоставить продуктовым командам простой способ сохранения и извлечения данных без необходимости вдаваться в основные технические детали. Habitat брала на себя всю необходимую работу: определяла, с каким типом данных идет работа, откуда они должны поступать (или куда отправляться), разрешен ли запрос, и так далее.

Инженерам продуктов не нужно было беспокоиться о поиске схем, маршрутизации, авторизации, шифровании, сериализации, формировании запросов и пуле соединений. Им даже не нужно было задумываться о том, откуда поступают данные: из Azure Cosmos DB, кэшей или других типов хранилищ.

Рисунок 02 · Сервис Habitat

Упрощенный поток запросов в Habitat

Отделив логику хранения в автономный сервис, мы создали единую точку контроля для развертываний, наблюдаемости и улучшений платформы.

  • Запрос
  • Ответ

Клиент

OpenAI

Azure Cosmos DB

  • Клиентский SDK Habitat
  • envoy
  • habitat-serviceпроцесс 1
  • habitat-serviceпроцесс 2
  • habitat-serviceпроцесс 3
  • habitat-envoy
  • habitat-cosmos-db-us0
  • habitat-cosmos-db-us1
  • habitat-cosmos-db-eu0

Эта библиотека на Python работала отлично, и Habitat быстро завоевал популярность среди инженеров OpenAI, несмотря на отсутствие централизованных призывов отказываться от самообслуживаемых Postgres и Azure Cosmos DB.

По мере изменения потребностей продуктов разработчикам было даже легко добавлять в общую библиотеку поддержку таких функций, как клиентское кэширование, сжатие или шифрование.

Создание сервиса для лучшей поддержки нескольких сложных продуктов

К середине 2025 года возможности Habitat в качестве клиентской реализации исчерпали себя. По мере того как слой Habitat усложнялся, а количество сервисов OpenAI росло, внесение обратно совместимых изменений в протокол стало невыполнимой задачей.

В одном из случаев мы хотели снизить радиус поражения при сбое в отдельном регионе для наших самых критически важных наборов данных, перенеся их на набор регионально распределенных учетных записей Azure Cosmos DB. Для этого изменения потребовалось внедрить дополнительную логику маршрутизации в клиент (отключенную за переключателем функций), убедиться, что она распространилась на всех клиентов, а затем включить этот переключатель.

Координация развертываний в десятках сервисов и работа с каждой командой по их внедрению заняли дни. Перед включением мы поняли, что хотим добавить теневое копирование (shadowing), чтобы убедиться в правильности логики шардирования. На это ушло еще пара дней. Исправление ошибки в том, что мы посчитали некорректным? Еще пара дней. В конце концов мы были готовы включить переключатель, но одна из команд по не связанных с этим причинам откатила свой сервис до прежнего клиента с багами, вызвав тот самый сбой, которого мы так старались избежать.

Изменения в клиентской библиотеке требовали сложной координации между десятками сервисов — процесс, который оказывался все более хрупким, неэффективным и подверженным сбоям в работе. Чтобы сократить этот операционный разброс при будущих развертываниях, мы решили выделить Habitat в отдельный сервис.

Отделив логику хранения в автономный сервис, мы создали единую точку контроля для развертываний, наблюдаемости и улучшений платформы. Вместо управления разрозненными обновлениями мы могли внедрять улучшения централизованно, обеспечивая мгновенную пользу для каждого продукта OpenAI.

Централизованный сервис также дает нам единую точку контроля для обеспечения максимального уровня безопасности данных и конфиденциальности. Сервис Habitat — это место, где мы можем централизованно применять политики контроля доступа, вести аудит-логирование и ограничивать доступ к базовым ресурсам хранения, таким как Azure Cosmos DB. Habitat играет критически важную роль в защите пользовательских данных и предотвращении несанкционированного доступа со стороны внешних, внутренних и агентных субъектов.

Запуск сервиса на Python в большом масштабе

Мы знали, что нам нужен сервис, но пока не хотели уходить с Python, несмотря на дополнительные издержки Python как сервиса. Использование Python для высокопроизводительного сервиса увеличивало сетевую задержку и добавляло существенные затраты на масштабирование ЦП и памяти по сравнению с выполнением локальной библиотеки. Более того, мы понимали, что неэффективность Python будет неприемлема при 100-кратном масштабировании, из-за чего последующее переписывание кода практически неизбежно.

Тем не менее мы рассматривали это как стратегическое накопление технического долга. Нашей главной задачей тогда была не оптимизация затрат или ресурсов, а разблокировка разработчиков продуктов и достижение стабильности платформы. Согласившись на компромиссы в производительности сервиса на Python в краткосрочной перспективе, мы смогли расставить приоритеты для более насущных задач, создать наши основные API и выстроить надежную инфраструктуру.

Мы также сделали расчет на то, что быстрое развитие наших собственных моделей для кодинга упростит технический путь в будущем. Мы сделали ставку на то, что к моменту, когда потребуется полноценный уход с Python, Codex и GPT сделают эту миграцию выполнимой. Эта ставка в итоге оказалась верной.

Запуск Habitat в качестве сервиса на Python был бы субоптимальным с точки зрения производительности, но это был необходимый выбор. Python позволяет нам двигаться быстро, но это не значит, что мы могли пренебречь осторожностью и смириться со значительным ухудшением задержек. Когда средний запрос пользователя приводит к сотням вызовов базы данных, самый медленный вызов базы данных — это то, что чувствует пользователь. Мы обнаружили, что главная сложность при запуске сервиса на Python в таком масштабе заключается в управлении этими хвостовыми задержками (tail latencies).

Отслеживание задержки asyncio

Asyncio помогает Python параллельно выполнять рабочие нагрузки, ограниченные операциями ввода-вывода (I/O-bound), но не помогает обойти GIL Python и обеспечить параллелизм на уровне ЦП. Помимо проксирования запросов с интенсивным вводом-выводом, Habitat выполняет множество ресурсоемких для ЦП задач и фоновых процессов: маршрутизацию, сжатие, шифрование, подсчет контрольных сумм, проверку работоспособности нижестоящих систем (health checking), теневое копирование запросов и хеджирование.

При таком количестве ресурсоемких для ЦП задач и фоновых процессов в нашем сервисе задержка планирования asyncio может легко доминировать в хвостовой задержке запросов. До настройки для первог запуска сервиса мы замечали в трассировках запросов с перцентилем p99 и выше, что, хотя нижестоящее хранилище отвечало быстро, запросы часто зависали в ожидании перепланирования ответственной сопрограммы для разбора ответа.

Рисунок 03 · Отслеживание задержки asyncio

Конкурентность не равна параллелизму ЦП

Python asyncio позволяет обрабатывать запросы параллельно (конкурентно), но в один момент времени на потоке ЦП выполняется только один запрос. Это сильно влияет на задержки запросов, когда требуется выполнить много вычислений на ЦП.

Обработка запроса/ответа ЦПСетевое чтение/запись в PythonОжидание Cosmos

Низкая нагрузка на ЦП

Короткие шаги Python; ожидание ввода-вывода перекрывается

Высокая нагрузка на ЦП

Длинные шаги Python заставляют готовые ответы ждать
Иллюстративное время0.0 / 40 иллюстративных единиц

Разрабатывая сервисы на Python в OpenAI, мы обнаружили, что помимо измерения стандартных метрик использования и насыщения памяти, ЦП, сети и диска критически важно также отслеживать цикл asyncio и степень его загруженности, а затем соответствующим образом производить настройку.

Периодически планируя фоновые задачи и фиксируя дельту между ожидаемым и фактическим временем выполнения, мы можем эмпирически измерять задержку планирования цикла событий в реальном времени. При высокой нагрузке и большом количестве ресурсоемких задач даже умеренное число одновременных запросов на один процесс способно вызывать значительный джиттер планирования — вплоть до сотен миллисекунд, а в некоторых крайних случаях и нескольких секунд.

В результате мы вынуждены поддерживать конфигурацию, при которой каждый процесс обрабатывает лишь небольшое количество одновременных запросов, вместо этого масштабируя количество рабочих процессов Python до огромных масштабов.

Сокращение хвостовой задержки в конфигурациях флажков функций

При первоначальном запуске сервиса в ходе профилирования ЦП работающего сервиса мы обнаружили одну из главных причин высокой задержки asyncio (и, как следствие, высоких хвостовых задержек): периодический парсинг JSON-файлов наших конфигураций флажков функций (feature flags) с помощью Statsig (инструмента для управления флажками функций, который также используется для проведения A/B-тестирования и т. д.).

По умолчанию Statsig была настроена на опрос обновленных конфигураций каждую минуту без джиттера, причем конфигурация включала каждое правило для продакшена по каждому сервису. Кроме того, ранее было принято архитектурное решение запускать до 8 процессов Python на один под для повышения утилизации ЦП и снижения задержек. В совокупности это означало, что каждую минуту в каждом поде наступал момент, когда все его рабочие процессы приостанавливали обработку текущих запросов и вместо этого тратили ресурсы ЦП на разбор гигантского файла конфигурации.

Проблема решилась довольно просто, как только профилирование ЦП помогло нам выявить первопричину: развернуть меньшую целевую конфигурацию, увеличить интервал обновления и добавить немного джиттера в подобные фоновые задачи.

Балансировка нагрузки и управление пулами соединений

Для поддержания низкой задержки asyncio также критически важна качественная балансировка нагрузки по запросам между серверными процессами; пулинг соединений без соответствующей настройки может дать противоположный эффект.

При клиентском пулинге соединений один клиентский процесс, выполняющий много параллельных запросов, может установить лишь горстку соединений с сервером и, как следствие, отправить всю нагрузку всего лишь на несколько процессов. До настройки нашей балансировки нагрузки в сервисе наблюдался огромный разброс утилизации: некоторые концевые процессы обслуживали в 5–10 раз больше параллельных запросов, чем в среднем по системе.

Мы обнаружили это во время случайного инцидента, когда, несмотря на остановку клиента, перегружавшего часть нашего сервиса, подмножество процессов оставалось в деградировавшем состоянии еще долгое время после прекращения пикового трафика. Более того, мы заметили, что в этих процессах происходила лавинообразная деградация: они получали все больше запросов, пока мы их не перезапускали. Как только под перегружался, срабатывало некое поведение, перенаправляющее на него дополнительный трафик. С таким классом сбоев некоторые из наших коллег были хорошо знакомы по прошлой работе: метастабильный сбой.

Мы заподозрили в проблеме пул соединений и проверили это предположение, ограничив максимальную продолжительность повторного использования соединений, что действительно сдержало деградацию и подтвердило правильность нашего направления расследования. Дальнейший анализ показал, что TCPConnector в библиотеке aiohttp для Python по умолчанию использует повторное использование соединений по принципу LIFO (последним пришел — первым вышел): для следующего запроса выбирается соединение, которое было возвращено в пул последним. Обычно это разумное поведение по умолчанию: повторное использование недавних соединений позволяет избыточным соединениям, созданным для обработки пикового трафика, завершиться по таймауту простоя, снижая накладные расходы на поддержание лишних соединений. В нашем же случае это привело к метастабильному сбою. Во время всплеска запросов соединения, направлявшиеся к более медленным перегруженным серверам, возвращались в пул с задержкой и поэтому чаще выбирались для последующих запросов, постепенно концентрируя все больший объем трафика на и без того испытывающих трудности подах. Исправление пула соединений на использование FIFO (первым пришел — первым ушел) разорвало эту петлю обратной связи и даже уменьшило дисперсию запросов в установившемся режиме.

Рисунок 04A · Клиентский пулинг соединений

LIFO направляет новые задачи обратно на медленный процесс

После всплеска запросов более медленные серверы возвращают соединения в пул последними. Алгоритм LIFO стимулирует концентрацию дополнительной работы на тех же медленных серверах.

Первоначальный всплеск достигает A, B и более медленного процесса C.

Рисунок 04B · Клиентский пулинг соединений

FIFO разрывает петлю обратной связи повторного использования соединений

FIFO поддерживает больше активных соединений после всплеска, но справедливо распределяет рабочую нагрузку между всеми серверами.

Первоначальный всплеск достигает A, B и более медленного процесса C.

Сегодня мы в основном полагаемся на Istio и Envoy в плане обеспечения пулинга соединений и более совершенных стратегий балансировки с учетом нагрузки на серверы во всей инфраструктуре OpenAI, что позволяет полностью избежать этой проблемы.

Предотвращение перегрузки нижестоящих ресурсов

Одним из побочных эффектов настройки низкой задержки asyncio и наличия огромного количества процессов Python становится высокая вероятность перегрузить нижестоящие зависимости огромным количеством соединений (явление, известное как «эффект лавины» или thundering herd).

Обычное ежедневное развертывание — если не настроено на медленное выполнение — может вызывать значительную нагрузку на ЦП из-за циклического переподключения. Либо утечка соединений способна вывести из строя сеть, перенасытив шлюз NAT. Это нередкие проблемы и для других сервисов, но порог их возникновения существенно снижается при наличии на порядок большего числа процессов, что часто приводит к насыщению сетевых ресурсов, справляться с которыми клиенты не планировали в установившемся режиме, рассчитывая лишь на чистую пропускную способность.

Мы также полагаемся на Envoy для максимизации мультиплексирования соединений (fan-in). Мы используем его для обновления HTTP/1-соединений Python до HTTP/2, чтобы задействовать мультиплексирование, объединять эти соединения в пулы и продлевать время их жизни. Кроме того, Envoy предоставляет нам централизованную точку для реализации ограничений частоты запросов (rate limits) и предохранителей (circuit breakers), которые были бы менее эффективны внутри каждого отдельного независимого процесса Python.

Рисунок 05 · Концентрация соединений (fan-in)

Те же запросы, меньше соединений

Пулинг соединений и мультиплексирование HTTP/2 помогают снизить нагрузку соединений на нижестоящие системы.

ЗапросОтветПростаивающее keep-alive

Почему Habitat делает меньше

Одна из причин, по которой нам удалось масштабировать Python до такого уровня, заключается в ограниченном API Habitat, который позволяет прогнозировать стоимость запросов. Вместо того чтобы разрешать клиентам составлять произвольные SQL-запросы, которые могли бы приводить к сканированию больших таблиц или соединениям (JOIN) по множеству таблиц, Habitat предоставляет простой NoSQL API. Отсутствие мощного API — это осознанный компромисс в дизайне Habitat.

Мы стремимся оптимизировать систему под простые, предсказуемые запросы с постоянным объемом работы. По нашему опыту, такие системы значительно проще масштабировать, в них сложнее ошибиться или неправильно их использовать. Запросы с непредсказуемым коэффициентом ветвления (fanout) оперативны опасны: они усложняют изоляцию, балансировку нагрузки и порождают скачки задержек, которые трудно масштабировать как для самого сервиса, так и для его клиентов.

До перехода на Habitat и Azure Cosmos DB большая часть оперативных данных OpenAI хранилась в Postgres. В то время было легко проверять все изменения запросов и схем, чтобы убедиться в их корректности и работе по проиндексированным данным перед отправкой в продакшен. По мере роста команды и продуктов это быстро стало неуправляемым и часто приводило к сбоям, когда один тяжелый новый запрос на горячем пути выводил базу данных из строя.

Проблема здесь кроется в дисбалансе стоимости: писать SQL-запросы, которые выполняются тяжело и дорого, дешево и просто. В Habitat мы избегаем этого и делаем дорогие запросы предельно очевидными на стороне клиента. Здесь нет безграничных запросов, способных перегрузить Habitat, а сложные соединения и обходы графов требуют от продуктовых команд выполнения определенной сложной работы, что в целом помогает оптимизировать архитектуру в сторону большей эффективности.

Habitat предоставляет NoSQL API, смоделированный вокруг определяемых клиентом типов объектов и ребер (edges), вдохновленный TAO. Клиенты заранее определяют объекты, ребра и то, как они связаны друг с другом, но не содержимое каждого типа. Полученные связи напоминают граф, однако сам Habitat не поддерживает типичные запросы обхода графа за исключением запросов к прямым ребрам конкретного объекта.

Мы секционируем этот граф таким образом, чтобы каждый объект и соответствующие ему ребра размещались совместно в разделе на уровне хранилища, однако мы не предпринимаем целенаправленных усилий на уровне базы данных для совместного размещения объектов и удаленных объектов, на которые указывают их ребра. В результате модель легко поддается секционированию для горизонтального масштабирования, но обходы графов неэффективны, поскольку любой конкретный переход между объектами может потребовать выборки из двух совершенно разных учетных записей Azure Cosmos DB, хранящихся в разных регионах.

Для клиентов с более сложными потребностями в запросах мы действительно предоставляем автономное вторичное представление Habitat, предоставляемое через Rockset. Мы используем CDC (извлечение измененных данных) для потоковой передачи изменений из оперативного хранилища в изолированные инстансы Rockset практически в реальном времени. Каждая команда клиентов несет ответственность за масштабирование собственного инстанса Rockset под свои сложные задачи запросов.

Такое предоставление Rockset создает дополнительную сложность для наших клиентов, но мы считаем это правильным компромиссом на данный момент: делать простые запросы стандартными по умолчанию, предоставляя лазейку (escape hatch) для тех, кому нужны сложные запросы. Такой дизайн изолирует наше онлайн-хранилище от аналитических и поисковых нагрузок с преобладанием операций чтения.

Миграция с Python на Rust

Отсрочка переписывания сервиса на Python на целый год позволила нам сосредоточиться на более срочных и важных задачах в период гиперроста. По мере взросления платформы и продолжения ускоренного роста, а также учитывая, что наш сервис является вторым по величине по количеству ядер в OpenAI (и четвертым по объему использования Envoy), наконец настало время отказаться от Python. На пике своего развития Python помогал нам обрабатывать более 20 миллионов запросов каждую секунду.

Во втором квартале 2026 года силами всего 2 инженеров при поддержке Codex и GPT‑5.5 мы смогли переписать весь сервис на Rust. Новый сервис на Rust теперь обрабатывает 95% наших продакшн-запросов; в ближайшие недели мы полностью откажемся от Python. Наши данные показывают, что сервис на Rust в 6 раз эффективнее по использованию ЦП и в 15 раз эффективнее по памяти по сравнению с версией на Python, демонстрируя при этом значительно более низкие средние и хвостовые задержки. Мы планируем поделиться новыми выводами в одной из будущих публикаций в блоге.

Оптимизация нашего уровня базы данных — Azure Cosmos DB

Сервис на Python (а теперь и на Rust) — это лишь один из аспектов Habitat. Во второй части этой серии статей, объясняющей, как мы быстро масштабировали наше онлайн-хранилище для обслуживания более 1 миллиарда пользователей ChatGPT, мы поговорим о слое хранения и о том, как Habitat обслуживает более 500 петабайт и свыше 70 миллионов запросов каждую секунду.

Если вы хотите работать над OLTP-системами предельного масштаба и вам интересна подобная инженерия, обратите внимание на эту открытую вакансию в нашей команде.

Автор

Джон Ли (Jon Lee), Чаомин Ю (Chaomin Yu), Бен Рис (Ben Ries)

Полный текст статьи читайте на OpenAI