Преминаване към основното съдържание
OpenAI

22 януари 2026 г.

Инженерство

Мащабиране на PostgreSQL да обслужва 800 мил. ChatGPT потребители

От Бохан Жанг, член на техническия екип

Зареждане…

В продължение на години PostgreSQL е една от най-критичните, работещи задкулисно системи за данни, които захранват основни продукти като ChatGPT и OpenAI API. Тъй като потребителите ни се увеличават бързо, изискванията към нашите бази данни също се увеличават лавинообразно. През изминалата година натоварването на PostgreSQL скочи над 10 пъти и продължава да расте бързо.

Нашите усилия да развием производствената си инфраструктура, за да поддържаме този растеж, ни дадоха ново прозрение: PostgreSQL може да се мащабира, за да поддържа надеждно много по-големи натоварвания с преобладаващо четене, отколкото мнозина по-рано смятаха за възможно. Системата (първоначално създадена от екип учени в Калифорнийския университет в Бъркли) ни позволи да поддържаме огромен глобален трафик с един основен гъвкав сървър инстанция Azure PostgreSQL(отваря се в нов прозорец) и почти 50 реплики за четене, разпределени в множество региони по целия свят. Това е историята за това как мащабирахме PostgreSQL в OpenAI, за да поддържаме милиони заявки в секунда за 800 милиона потребители чрез стриктни оптимизации и солидно инженерство. Ще разгледаме и ключовите изводи, които научихме в този процес.

Пукнатини в първоначалния ни дизайн

След стартирането на ChatGPT, трафикът расте с безпрецедентна скорост. За да го поддържаме, ние бързо приложихме обширни оптимизации както на ниво приложение, така и на ниво база данни PostgreSQL, увеличихме мащаба чрез разширяване на размера на инстанцията и добавихме повече реплики за четене. Тази архитектура ни служи добре от дълго време. С непрекъснати подобрения, тя продължава да осигурява достатъчно възможности за бъдещ растеж.

Може да изглежда изненадващо, че архитектура с един основен възел може да отговори на изискванията на мащаба на OpenAI, но на практика постигането ѝ не е лесно. Имахме няколко инцидента (SEV), причинени от претоварване на Postgres, и те често следват един и същ модел: проблем нагоре по веригата причинява внезапен скок в натоварването на базата данни, като например масови пропуски в кеша вследствие на срив на кеширащия слой, рязко увеличение на скъпи многостепенни JOIN операции, натоварващи процесора до максимум, или буря от записи при пускане на нова функционалност. С увеличаването на използваните ресурси, забавянето на заявките нараства и те започват да изтичат. Повторните опити допълнително увеличават натоварването, задействайки порочен цикъл с потенциал да влоши цялостните услуги на ChatGPT и API.

Диаграма за мащабиране на натоварването

Въпреки че PostgreSQL се мащабира добре за нашите натоварвания с преобладаващо четене, все още срещаме предизвикателства по време на периоди на интензивен трафик на запис. Това до голяма степен се дължи на реализацията на многоверсионния контрол на конкурентността (MVCC) в PostgreSQL, което го прави по-малко ефективен при натоварвания с интензивни записи. Например, когато заявка актуализира кортеж или дори едно поле, целият ред се копира, за да се създаде нова версия. При големи натоварвания при запис, това води до значително усилване на записите. Това също увеличава усилването при четене, тъй като заявките трябва да сканират през множество версии на кортежи (мъртви кортежи), за да извлекат най-новата версия. MVCC въвежда допълнителни предизвикателства, като раздуване на таблици и индекси, увеличени разходи за поддръжка на индекси и сложно настройване на автоматично почистване на базата данни. (Можете да намерите задълбочен анализ на тези въпроси в блог, който написах с проф. Анди Павло от Carnegie Mellon University, озаглавен The Part of PostgreSQL We Hate the Most(отваря се в нов прозорец), цитиран(отваря се в нов прозорец) в страницата на PostgreSQL в Wikipedia.)

Увеличаване на капацитета на PostgreSQL до милиони заявки в секунда

За да смекчим тези ограничения и намалим натоварването при запис, ние сме мигрирали и продължаваме да мигрираме шардирани (т.е. работни натоварвания, които могат да се разделят хоризонтално), интензивни за запис системи към шардирани решения като Azure Cosmos DB, като оптимизираме логиката на приложението, за да минимизираме ненужните записи. Също така вече не позволяваме добавянето на нови таблици към текущото внедряване на PostgreSQL. Новите работни натоварвания по подразбиране се насочват към шардираните системи.

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

В следващите раздели ще се задълбочим в предизвикателствата, пред които се изправихме, и в обширните оптимизации, които внедрихме, за да ги адресираме и да предотвратим бъдещи прекъсвания, като изведем PostgreSQL до пределите му и го мащабираме до милиони заявки в секунда (QPS).

Намаляване на натоварването на основния сървър

Предизвикателство: При наличие на един единствен записващ, конфигурацията с единствена основна инстанция не може да мащабира записите. Силните пикове при запис могат бързо да претоварят основния сървър и да повлияят на услуги като ChatGPT и нашия API.

Решение: Минимизиране на натоварването върху основната инстанция доколкото е възможно – както при четене, така и при запис – за да се гарантира, че тя има достатъчно капацитет да обработва пикове на записите. Трафикът за четене се пренасочва към реплики, когато това е възможно. Въпреки това някои заявки за четене трябва да останат на основния сървър, защото са част от транзакции за запис. За тях се фокусираме върху осигуряването на ефективност и избягването на бавни заявки. За трафика за запис мигрирахме работни натоварвания, които могат да бъдат шардирани и са силно натоварени с операции за запис, към шардирани системи като Azure CosmosDB. Натоварванията, които са по-трудни за шардиране, но все пак генерират голям обем записи, отнемат повече време за мигриране и този процес все още продължава. Също така активно оптимизирахме нашите приложения, за да намалим натоварването от записи; например, поправихме грешки в приложенията, които водеха до излишни записи, и въведохме отложени записи, където е уместно, за да смекчим пиковете в трафика. Освен това, при попълване на полета в таблица, налагаме строги ограничения на скоростта, за да предотвратим прекомерен натиск върху записите.

Оптимизация на заявки

Предизвикателство: Идентифицирахме няколко скъпи заявки в PostgreSQL. В миналото внезапни пикове в обема на тези заявки водеха до високо натоварване на CPU, което забавяше както ChatGPT, така и API заявките.

Решение: Няколко скъпи заявки, като тези, които обединяват много таблици, могат значително да влошат или дори да сринат цялата услуга. Трябва непрекъснато да оптимизираме заявките в PostgreSQL, за да гарантираме тяхната ефективност и да избягваме често срещаните антишаблони в онлайн обработката на транзакции (OLTP). Например, веднъж идентифицирахме изключително скъпа заявка, която обединяваше 12 таблици, като пиковете в тази заявка бяха отговорни за предишни инциденти с висока тежест. Трябва да избягваме сложни обединения на множество таблици, когато е възможно. Ако са необходими обединения, се научихме да обмисляме разбиване на заявката и вместо това да преместим сложната логика на обединенията в приложния слой. Много от тези проблемни заявки се генерират от рамки за обектно-релационно картографиране (ORM), затова е важно внимателно да прегледате генерирания от тях SQL и да се уверите, че той действа според очакванията. Също така е обичайно да се срещат дълго изпълнявани неактивни заявки в PostgreSQL. Конфигурирането на таймаути като idle_in_transaction_session_timeout е от съществено значение, за да се предотврати блокирането на автоматично почистване.

Смекчаване на единична точка на отказ

Предизвикателство: Ако реплика за четене спре да работи, трафикът все още може да бъде пренасочен към други реплики. Въпреки това, разчитането на единствен записващ означава наличие на единична точка на отказ. Ако той се повреди, цялата услуга ще бъде засегната.

Решение: Повечето критични заявки включват само четене. За да смекчим единичната точка на отказ в основния сървър, прехвърлихме тези операции за четене от записващия към реплики, като гарантирахме, че тези заявки могат да продължат да се обслужват дори ако основният сървър спре да работи. Въпреки че операциите за запис все още ще се провалят, въздействието е намалено; вече не е SEV0, тъй като операциите за четене остават достъпни.

За да смекчим отказите на основната инстанция, изпълняваме основната база в режим на висока наличност с „горещ“ резервен сървър – постоянно синхронизирана реплика, която винаги е готова да поеме обработката на трафика. Ако основният сървър спре да работи или трябва да бъде изведен офлайн за поддръжка, можем бързо да активираме резервния, за да сведем до минимум прекъсването на услугата. Екипът на Azure PostgreSQL извърши значителна работа, за да гарантира, че тези превключвания при отказ остават безопасни и надеждни дори при много високо натоварване. За да се справим с отказите на репликите за четене, внедряваме множество реплики във всеки регион с достатъчен резерв от капацитет, като гарантираме, че отказът на една реплика не води до регионално прекъсване.

Изолиране на натоварванията

Предизвикателство: Често се сблъскваме със ситуации, в които определени заявки консумират непропорционално количество ресурси в PostgreSQL инстанции. Това може да доведе до влошаване на производителността за други работни натоварвания, изпълнявани на същите инстанции. Например, стартирането на нова функция може да въведе неефективни заявки, които силно натоварват процесора на 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, която разпределя трафика между подовете.

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 за тяхното силно партньорство.