Перейти до основного вмісту
OpenAI

22 січня 2026 р.

Інженерія

Масштабування PostgreSQL і підтримка 800 млн користувачів ChatGPT

Автор: Бохан Чжан, співробітник технічного підрозділу

Завантаження…

Упродовж багатьох років PostgreSQL був однією з ключових, хоча й прихованих від користувача, систем даних, що забезпечують роботу базових продуктів на кшталт ChatGPT і API OpenAI. Із стрімким зростанням нашої користувацької бази експоненційно зросли й вимоги до баз даних. Лише за останній рік навантаження на PostgreSQL збільшилося більш ніж у 10 разів і продовжує швидко зростати.

Наші зусилля з розвитку робочої інфраструктури для підтримки цього зростання привели до нового висновку: PostgreSQL можна масштабувати так, щоб надійно обслуговувати значно більші навантаження з переважанням читання, ніж багато хто вважав можливим раніше. Ця система (спочатку створена командою науковців Каліфорнійського університету в Берклі) дала нам змогу обслуговувати масивний глобальний трафік з одним первинним інстансом Azure PostgreSQL flexible server(відкривається у новому вікні) і майже 50 репліками для читання, розподіленими по кількох регіонах світу. Це історія про те, як ми масштабували PostgreSQL в OpenAI, щоб підтримувати мільйони запитів за секунду для 800 мільйонів користувачів завдяки ретельним оптимізаціям і надійній інженерії; ми також поділимося ключовими висновками, до яких дійшли в процесі.

Тріщини в нашому початковому дизайні

Після запуску ChatGPT трафік почав зростати з безпрецедентною швидкістю. Щоб упоратися з цим, ми оперативно впровадили масштабні оптимізації як на рівні програм, так і на рівні бази даних PostgreSQL, збільшили потужність шляхом розширення інстансів і масштабували систему горизонтально, додавши більше реплік для читання. Ця архітектура добре служила нам упродовж тривалого часу. Завдяки постійним удосконаленням вона й надалі забезпечує достатній запас міцності для подальшого зростання.

Може здатися несподіваним, що архітектура з єдиним первинним вузлом здатна витримувати навантаження масштабу OpenAI, однак реалізувати це на практиці непросто. Ми неодноразово стикалися з інцидентами рівня SEV, спричиненими перевантаженням Postgres, і зазвичай вони розвивалися за схожим сценарієм: проблема на вищому рівні призводила до різкого сплеску навантаження на базу даних — наприклад, масових промахів кешу через збій кешувального шару, напливу дорогих багатосторонніх JOIN-запитів, що насичували CPU, або «шторму» записів під час запуску нової функції. У міру зростання споживання ресурсів збільшується затримка виконання запитів, і запити починають завершуватися тайм-аутами. Повторні спроби ще більше підсилюють навантаження, запускаючи замкнене коло, здатне погіршити роботу всієї екосистеми ChatGPT та API.

Діаграма масштабування навантаження

Хоча PostgreSQL добре масштабується для наших навантажень із переважанням читання, у періоди інтенсивного запису ми все ще стикаємося з викликами. Значною мірою це зумовлено реалізацією багатоверсійного керування конкурентним доступом (MVCC) у PostgreSQL, яка робить його менш ефективним для навантажень з інтенсивним записом. Наприклад, коли запит оновлює кортеж або навіть одне поле, весь рядок копіюється для створення нової версії. За високих обсягів запису це призводить до суттєвого підсилення записів. Це також збільшує підсилення читання, оскільки запити змушені переглядати кілька версій кортежів (dead tuples), щоб отримати найактуальнішу. MVCC створює й додаткові труднощі, зокрема роздування таблиць і індексів, підвищені накладні витрати на обслуговування індексів і складне налаштування autovacuum. (Детальний розбір цих питань можна знайти в блозі, який я написав разом із проф. Енді Павло з Університету Карнегі — Меллона під назвою The Part of PostgreSQL We Hate the Most(відкривається у новому вікні), згаданому(відкривається у новому вікні) на сторінці PostgreSQL у Вікіпедії.)

Масштабування PostgreSQL до мільйонів QPS

Щоб пом’якшити ці обмеження та зменшити тиск на записи, ми мігрували — і продовжуємо мігрувати — шардовані (тобто такі, що можуть горизонтально розподілятися) навантаження з інтенсивним записом до шардованих систем, зокрема Azure Cosmos DB, паралельно оптимізуючи логіку застосунків для мінімізації зайвих записів. Крім того, ми більше не дозволяємо додавати нові таблиці до поточного розгортання PostgreSQL. Усі нові навантаження за замовчуванням спрямовуються до шардованих систем.

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

У наступних розділах ми детально розглянемо виклики, з якими зіткнулися, та масштабні оптимізації, які впровадили для їх подолання й запобігання майбутнім збоям, доводячи PostgreSQL до меж можливого та масштабуючи його до мільйонів запитів за секунду (QPS).

Зменшення навантаження на первинний вузол

Проблема: за наявності лише одного вузла запису архітектура з єдиним первинним вузлом не масштабується для операцій запису. Різкі сплески записів можуть швидко перевантажити первинний вузол і вплинути на роботу таких сервісів, як ChatGPT та наш API.

Рішення: ми мінімізуємо навантаження на первинний вузол настільки, наскільки це можливо — як з боку читання, так і з боку запису, — щоб забезпечити достатню ємність для обробки піків запису. Трафік читання за можливості виноситься на репліки. Втім, деякі запити на читання мають залишатися на первинному вузлі, оскільки вони є частиною транзакцій запису. Для таких випадків ми зосереджуємося на тому, щоб вони були ефективними, і уникаємо повільних запитів. Що стосується трафіку запису, ми мігрували шардовані навантаження з інтенсивним записом до шардованих систем, зокрема Azure Cosmos DB. Навантаження, які складніше шардовувати, але які все одно генерують великий обсяг записів, потребують більше часу на міграцію, і цей процес досі триває. Ми також активно оптимізували наші застосунки, щоб зменшити навантаження від записів: наприклад, усунули помилки, які спричиняли надлишкові записи, та впровадили відкладені записи там, де це доречно, щоб згладжувати піковий трафік. Крім того, під час заповнення полів таблиць ми застосовуємо суворі ліміти швидкості, щоб запобігти надмірному тиску на операції запису.

Оптимізація запитів

Проблема: ми виявили кілька надзвичайно «дорогих» запитів у PostgreSQL. У минулому раптові сплески обсягу таких запитів споживали значні ресурси CPU, уповільнюючи як ChatGPT, так і запити до API.

Рішення: кілька ресурсомістких запитів, зокрема тих, що об’єднують велику кількість таблиць, можуть суттєво деградувати або навіть зупинити роботу всього сервісу. Тому ми маємо постійно оптимізувати запити PostgreSQL, щоб вони залишалися ефективними й уникали поширених антипатернів Online Transaction Processing (OLTP). Наприклад, одного разу ми виявили надзвичайно затратний запит із JOIN для 12 таблиць — саме сплески цього запиту були причиною інцидентів SEV високої критичності в минулому. Складних багатотабличних запитів JOIN слід уникати за будь-якої можливості. Якщо ж об’єднання таблиць необхідні, ми дійшли висновку, що доцільно розбивати запит і переносити складну логіку JOIN на рівень програми. Багато з таких проблемних запитів генеруються фреймворками об’єктно-реляційного відображення (ORM), тож важливо уважно перевіряти SQL, який вони створюють, і переконуватися, що він поводиться очікувано. Також у PostgreSQL часто трапляються довготривалі запити в стані простою. Налаштування тайм-аутів, зокрема idle_in_transaction_session_timeout, є критично важливим, щоб запобігти блокуванню autovacuum.

Зменшення ризиків єдиної точки відмови

Проблема: якщо одна репліка для читання виходить з ладу, трафік можна перенаправити на інші репліки. Втім, залежність від єдиного вузла запису означає наявність єдиної точки відмови — у разі його збою страждає весь сервіс.

Рішення: більшість критично важливих запитів складаються лише з операцій читання. Щоб зменшити ризик єдиної точки відмови первинного вузла, ми винесли ці операції читання з вузла запису на репліки, гарантуючи, що такі запити продовжать обслуговуватися навіть у разі збою первинного. Хоча операції запису в такій ситуації все ще завершуються помилкою, вплив значно зменшується — це вже не інцидент рівня SEV0, адже читання залишається доступним.

Для зниження ризику відмов первинного вузла ми запускаємо його в режимі високої доступності (HA) з «гарячим» резервом — постійно синхронізованою реплікою, яка завжди готова перебрати на себе обслуговування трафіку. Якщо первинний вузол виходить з ладу або потребує відключення для обслуговування, ми можемо швидко підвищити резервний вузол, мінімізуючи простій. Команда Azure PostgreSQL виконала значний обсяг роботи, щоб забезпечити безпечні та надійні перемикання навіть за дуже високого навантаження. Щоб упоратися зі збоями реплік для читання, ми розгортаємо кілька реплік у кожному регіоні з достатнім запасом потужності, гарантуючи, що відмова однієї репліки не призведе до регіонального простою.

Ізоляція навантажень

Проблема: ми часто стикаємося з ситуаціями, коли окремі запити споживають непропорційно велику кількість ресурсів на інстансах PostgreSQL. Це може призводити до погіршення продуктивності інших навантажень, що працюють на тих самих інстансах. Наприклад, запуск нової функції може принести неефективні запити, які інтенсивно навантажують CPU PostgreSQL і сповільнюють роботу інших критично важливих можливостей.

Solution: To mitigate the “noisy neighbor” problem, we isolate workloads onto dedicated instances to ensure that sudden spikes in resource-intensive requests don’t impact other traffic. Specifically, we split requests into low-priority and high-priority tiers and route them to separate instances. This way, even if a low-priority workload becomes resource-intensive, it won’t degrade the performance of high-priority requests. We apply the same strategy across different products and services as well, so that activity from one product does not affect the performance or reliability of another.

Connection pooling

Challenge: Each instance has a maximum connection limit (5,000 in Azure PostgreSQL). It’s easy to run out of connections or accumulate too many idle ones. We’ve previously had incidents caused by connection storms that exhausted all available connections.

Solution: We deployed PgBouncer as a proxy layer to pool database connections. Running it in statement or transaction pooling mode allows us to efficiently reuse connections, greatly reducing the number of active client connections. This also cuts connection setup latency: in our benchmarks, the average connection time dropped from 50 milliseconds (ms) to 5 ms. Inter-region connections and requests can be expensive, so we co-locate the proxy, clients, and replicas in the same region to minimize network overhead and connection use time. Moreover, PgBouncer must be configured carefully. Settings like idle timeouts are critical to prevent connection exhaustion.

Діаграма проксі PostgreSQL

Кожна репліка для читання має власне розгортання в Kubernetes із кількома подами PgBouncer.Ми запускаємо кілька розгортань Kubernetes за одним Kubernetes Service, який балансує трафік між подами.

Caching

Challenge: A sudden spike in cache misses can trigger a surge of reads on the PostgreSQL database, saturating CPU and slowing user requests.

Solution: To reduce read pressure on PostgreSQL, we use a caching layer to serve most of the read traffic. However, when cache hit rates drop unexpectedly, the burst of cache misses can push a large volume of requests directly to PostgreSQL. This sudden increase in database reads consumes significant resources, slowing down the service. To prevent overload during cache-miss storms, we implement a cache locking (and leasing) mechanism so that only a single reader that misses on a particular key fetches the data from PostgreSQL. When multiple requests miss on the same cache key, only one request acquires the lock and proceeds to retrieve the data and repopulate the cache. All other requests wait for the cache to be updated rather than all hitting PostgreSQL at once. This significantly reduces redundant database reads and protects the system from cascading load spikes.

Scaling read replicas

Challenge: The primary streams Write Ahead Log (WAL) data to every read replica. As the number of replicas increases, the primary must ship WAL to more instances, increasing pressure on both network bandwidth and CPU. This causes higher and more unstable replica lag, which makes the system harder to scale reliably.

Solution: We operate nearly 50 read replicas across multiple geographic regions to minimize latency. However, with the current architecture, the primary must stream WAL to every replica. Although it currently scales well with very large instance types and high-network bandwidth, we can’t keep adding replicas indefinitely without eventually overloading the primary. To address this, we’re collaborating with the Azure PostgreSQL team on cascading replication(відкривається у новому вікні), where intermediate replicas relay WAL to downstream replicas. This approach allows us to scale to potentially over a hundred replicas without overwhelming the primary. However, it also introduces additional operational complexity, particularly around failover management. The feature is still in testing; we’ll ensure it’s robust and can fail over safely before rolling it out to production.

Діаграма каскадної реплікації PostgreSQL

Rate limit

Challenge: A sudden traffic spike on specific endpoints, a surge of expensive queries, or a retry storm can quickly exhaust critical resources such as CPU, I/O, and connections, which causes widespread service degradation.

Solution: We implemented rate-limiting across multiple layers—application, connection pooler, proxy, and query—to prevent sudden traffic spikes from overwhelming database instances and triggering cascading failures. It’s also crucial to avoid overly short retry intervals, which can trigger retry storms. We also enhanced the ORM layer to support rate limiting and when necessary, fully block specific query digests. This targeted form of load shedding enables rapid recovery from sudden surges of expensive queries.

Schema Management

Challenge: Even a small schema change, such as altering a column type, can trigger a full table rewrite(відкривається у новому вікні). We therefore apply schema changes cautiously—limiting them to lightweight operations and avoiding any that rewrite entire tables.

Solution: Only lightweight schema changes are permitted, such as adding or removing certain columns that do not trigger a full table rewrite. We enforce a strict 5-second timeout on schema changes. Creating and dropping indexes concurrently is allowed. Schema changes are restricted to existing tables. If a new feature requires additional tables, they must be in alternative sharded systems such as Azure CosmosDB rather than PostgreSQL. When backfilling a table field, we apply strict rate limits to prevent write spikes. Although this process can sometimes take over a week, it ensures stability and avoids any production impact.

Results and the road ahead

This effort demonstrates that with the right design and optimizations, Azure PostgreSQL can be scaled to handle the largest production workloads. PostgreSQL handles millions of QPS for read-heavy workloads, powering OpenAI’s most critical products like ChatGPT and the API platform. We added nearly 50 read replicas, while keeping replication lag near zero, maintained low-latency reads across geo-distributed regions, and built sufficient capacity headroom to support future growth.

This scaling works while still minimizing latency and improving reliability. We consistently deliver low double-digit millisecond p99 client-side latency and five-nines availability in production. And over the past 12 months, we’ve had only one SEV-0 PostgreSQL incident (it occurred during the viral launch(відкривається у новому вікні) of ChatGPT ImageGen, when write traffic suddenly surged by more than 10x as over 100 million new users signed up within a week.)

While we’re happy with how far PostgreSQL has taken us, we continue to push its limits to ensure we have sufficient runway for future growth. We’ve already migrated the shardable write-heavy workloads to our sharded systems like CosmosDB. The remaining write-heavy workloads are more challenging to shard—we’re actively migrating those as well to further offload writes from the PostgreSQL primary. We’re also working with Azure to enable cascading replication so we can safely scale to significantly more read replicas.

Looking ahead, we’ll continue to explore additional approaches to further scale, including sharded PostgreSQL or alternative distributed systems, as our infrastructure demands continue to grow.

Автор

Bohan Zhang

Подяки

Окрема подяка Джону Лі, Січенґу Лю, Чаомінь Ю та Ченлунґу Хао за внесок у підготовку цього матеріалу, а також усій команді, яка допомагала масштабувати PostgreSQL. Також дякуємо команді Azure PostgreSQL за плідне партнерство.