Прескокни до главната содржина
OpenAI

22 јануари 2026 г.

Инженерство

Проширување на PostgreSQL за 800 милиони корисници на ChatGPT

Од Бохан Жанг, член на техничкиот тим

Се вчитува...

Со години, PostgreSQL е еден од најважните, позадински системи за податоци што ги напојуваат основните производи како што се ChatGPT и OpenAI API. Со брзиот пораст на нашата корисничка база, побарувањата за нашите бази на податоци експоненцијално се зголемуваат. Во текот на изминатата година, оптоварувањето на нашиот PostgreSQL се зголеми за повеќе од 10 пати и продолжува брзо да расте.

Нашите напори да ја унапредиме нашата продукциската инфраструктура за да го одржиме овој раст открија нов увид: PostgreSQL може да се прошири за сигурно да поддржи многу поголеми оптоварувања со доминантни читања отколку што многумина претходно мислеа дека е можно. Системот (првично создаден од тим на научници на Универзитетот во Калифорнија, Беркли) ни овозможи да поддржиме огромен глобален сообраќај со една примарна Azure PostgreSQL flexible server instance(се отвора во нов прозорец) и речиси 50 реплики за читање распоредени низ повеќе региони на глобално ниво. Ова е приказната за тоа како го проширивме PostgreSQL во OpenAI за да поддржиме милиони пребарувања во секунда за 800 милиони корисници преку ригорозни оптимизации и солиден инженеринг; ќе ги опфатиме и клучните заклучоци што ги научивме попатно.

Пукнатини во нашиот првичен дизајн

По лансирањето на ChatGPT, сообраќајот порасна со невидена брзина. За да го поддржиме, брзо имплементиравме обемни оптимизации и на ниво на апликацијата и на ниво на базата на податоци PostgreSQL, ја проширивме нагоре со зголемување на големината на инстанцата и ја проширивме нанадвор со додавање повеќе реплики за читање. Оваа архитектура ни беше од голема корист долго време. Со тековните подобрувања, таа продолжува да обезбедува доволно простор за идниот раст.

Можеби звучи изненадувачки што архитектура со еден примарен елемент може да ги задоволи барањата со големина на OpenAI; сепак, да се направи ова да функционира во практика не е едноставно. Видовме неколку SEV предизвикани од преоптоварување на Postgres, и тие често го следат истиот образец: проблем нагоре по синџирот предизвикува ненадеен скок во оптоварувањето на базата на податоци, како што се масовни промашувања на кеш-меморијата поради дефект во слојот за кеширање, нагло зголемување на скапи повеќенасочни спојувања што го заситуваат процесорот или „наплив на записи“ при лансирање на нова функционалност. Како што расте искористеноста на ресурсите, латентноста на барањата се зголемува и времето на отчитување на барањата почнува да истекува. Повторните обиди дополнително го зголемуваат оптоварувањето, предизвикувајќи маѓепсан круг што може да ги деградира сите услуги на ChatGPT и API.

Дијаграм за зголемување на оптоварувањето

Иако PostgreSQL добро се проширува за нашите работни оптоварувања со доминантно читање, сепак се соочуваме со предизвици за време на периоди на интензивен сообраќај на запишување. Ова во голема мера се должи на имплементацијата на PostgreSQL за контрола на конкурентност со повеќе верзии (MVCC), што го прави помалку ефикасен за работни оптоварувања со многу запишувања. На пример, кога барање ажурира торка или дури и едно поле, целиот ред се копира за да се создаде нова верзија. При големи оптоварувања на пишување, ова резултира со значително засилување на запишуваето. Исто така, го зголемува засилувањето на читањето, бидејќи барањата мора да прегледаат повеќе верзии на записи (неактивни записи) за да го добијат најновиот запис. MVCC воведува дополнителни предизвици како што се надуеност на табели и индекси, зголемени трошоци за одржување на индексите и сложено подесување на autovacuum. (Можеш да најдеш детална анализа на овие прашања во блог што го напишав со проф. Енди Павло на Универзитетот Карнеги Мелон, наречен Делот од PostgreSQL што најмногу го мразиме(се отвора во нов прозорец), цитиран(се отвора во нов прозорец) на страницата на Википедија за PostgreSQL.)

Проширување на PostgreSQL до милиони QPS

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

Иако нашата инфраструктура се развиваше, PostgreSQL остана неподелен, со една примарна инстанца која ги опслужува сите записи. Главната причина е дека поделбата на постојните работни оптоварувања на апликацијата би била многу сложена и одзема многу време, барајќи промени на стотици крајни точки на апликацијата и потенцијално би можела да трае со месеци, дури и години. Бидејќи нашите оптоварувања се претежно ориентирани кон читање и бидејќи имплементиравме обемни оптимизации, тековната архитектура сè уште обезбедува доволно простор за поддршка на континуиран раст на сообраќајот. Иако не ја исклучуваме можноста за поделување на PostgreSQL во иднина, тоа не е приоритет во блиска иднина со оглед на тоа дека имаме доволно простор за тековниот и идниот раст.

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

Намалување на оптоварувањето на примарната единица

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

Решение: Го минимизираме оптоварувањето на примарната единица што е можно повеќе - и читањата и запишувањата - за да обезбедиме дека има доволен капацитет да се справи со скоковите во запишувањето. Сообраќајот за читање се пренасочува кон реплики секогаш ккога е можно. Сепак, некои барања за читање мора да останат на примарната единица бидејќи се дел од трансакции за запишување. За нив, се фокусираме на тоа да бидат ефикасни и да избегнуваат бавни пребарувања. За сообраќајот за запишување, ги мигриравме работните оптоварувања што можат да се поделат и се интензивни за запишување во поделени системи како што е Azure CosmosDB. Работните оптоварувања што потешко се делат но сепак генерираат голем обем на запишување, бараат повеќе време за миграција, и тој процес сè уште трае. Исто така, агресивно ги оптимизиравме нашите апликации за да го намалиме оптоварувањето при запишување; на пример, ги поправивме грешките во апликациите што предизвикуваа непотребни запишувања и воведовме одложени запишувања, онаму каде што е соодветно, за да ги ублажиме скоковите во сообраќајот. Дополнително, при пополнување на полињата во табелата, применуваме строги ограничувања на брзината за да спречиме прекумерен притисок при запишување.

Оптимизација на пребарувања

Предизвик: Идентификувавме неколку скапи пребарувања во PostgreSQL. Во минатото, ненадејните скокови во обемот на овие барања би трошеле големи количини на CPU, забавувајќи ги и ChatGPT и API пребарувањата.

Решение: Неколку скапи пребарувања, како што се оние што спојуваат многу табели, можат значително да ја влошат, дури и да ја прекинат целата услуга. Треба континуирано да ги оптимизираме PostgreSQL пребарувањата за да се осигураме дека се ефикасни и да избегнеме вообичаени анти-шеми за Online Transaction Processing (OLTP). На пример, еднаш идентификувавме исклучително скапо пребарување што спојуваше 12 табели, при што скоковите во ова пребарување беа одговорни за минатите сериозни SEV. Треба да избегнуваме сложени спојувања на повеќе табели секогаш кога е можно. Ако се неопходни спојувања, научивме да размислуваме за разложување на пребарувањето и сложената логика за спојување да ја преместиме во апликацискиот слој. Голем број од овие проблематични пребарувања се генерирани од рамки за Object-Relational Mapping (ORM), па затоа е важно внимателно да се прегледа SQL што го создаваат и да се осигура дека се однесува како што се очекува. Исто така, вообичаено е да се најдат долготрајни неактивни пребарувања во PostgreSQL. Конфигурирањето на временските ограничувања како idle_in_transaction_session_timeout е од суштинско значење за да се спречи блокирањето на autovacuum.

Ублажување на ризикот од единечна точка на дефект

Предизвик: Ако реплика за читање престане да функционира, сообраќајот сè уште може да се пренасочи кон други реплики. Сепак, потпирањето на само еден запишувач значи дека имате само една точка на дефект - ако таа точка падне, целата услуга ќе биде засегната.

Решение: Најкритичните пребарувања најчесто вклучуваат само читање на податоци. За да ја ублажиме единствената точка на дефект во примарната единица, ги отстранивме тие читања од запишувачот на реплики, осигурувајќи дека тие пребарувања можат да продолжат да се опслужуваат дури и ако падне примарната единица. Иако операциите за запишување сè уште би паѓале, влијанието е намалено; веќе не е SEV0 бидејќи читањата остануваат достапни.

За да ги ублажиме дефектите на примарната единица, ја активираме примарната единица во режим на висока достапност (HA) со hot standby, континуирано синхронизирана реплика што секогаш е подготвена да го преземе опслужувањето на сообраќајот. Ако примарниот систем падне или треба да се исклучи за одржување, можеме брзо да го активираме систем за да го минимизираме времето на прекин. Тимот на Azure PostgreSQL заврши значајна работа за да обезбеди овие префрлувања да останат безбедни и сигурни дури и при многу големо оптоварување. За да се справиме со дефектите на репликата за читање, распоредуваме повеќе реплики во секој регион со доволна резерва на капацитет, осигурувајќи дека дефектот на една реплика не предизвикува регионален прекин.

Изолација на оптоварувањето

Предизвик: Често се соочуваме со ситуации каде што одредени барања трошат непропорционално многу ресурси на PostgreSQL инстанците. Ова може да доведе до намалување на перформансите за други задачи кои се извршуваат на истите инстанци. На пример, воведувањето нова функција може да предизвика неефикасни пребарувања кои значително го оптоваруваат процесорот на PostgreSQL, забавувајќи ги барањата за други важни функции.

Решение: За да го ублажиме проблемот со „бучен сосед“, ги изолираме оптоварувањата на наменски инстанци за да осигуриме дека ненадејните скокови во пребарувањата што интензивно користат ресурси не влијаат на останатиот сообраќај. Поконкретно, пребарувањата ги делиме на нивоа со низок и висок приоритет и ги насочуваме кон одделни инстанци. На овој начин, дури и ако оптоварување со низок приоритет почне да користи многу ресурси, тоа нема да ја намали ефикасноста на пребарувањата со висок приоритет. Ја применуваме истата стратегија низ различни производи и услуги, за активноста од еден производ да не влијае на перформансите или доверливоста на друг.

Групирање на конекции

Предизвик: Секоја инстанца има максимален број на конекции (5.000 во Azure PostgreSQL). Лесно е да останеш без конекции или да натрупаш премногу неактивни конекции. Претходно имавме инциденти предизвикани од налети на конекции кои ги исцрпија сите достапни врски.

Решение: Го имплементиравме PgBouncer како прокси-слој за да ги групираме конекциите со базата на податоци. Извршувањето во режим на групирање изјави или трансакции ни овозможува ефикасно повторно да ги користиме конекциите, значително намалувајќи го бројот на активни клиентски конекции. Ова, исто така, ја намалува латентноста при воспоставување на конекцијата: според нашите тестови, просечното време на поврзување се намали од 50 милисекунди (ms) на 5 милисекунди (ms). Меѓурегионалните поврзувања и барања може да бидат скапи, па затоа ги ставаме на заедничка локација проксито, клиентите и репликите во истиот регион за да го намалиме мрежното општо користење и времето на користење на конекцијата. Покрај тоа, PgBouncer мора внимателно да се конфигурира. Поставките како времето на неактивност се критични за да се спречи исцрпување на конекциите.

Дијаграм на прокси за postgreSQL

Секоја реплика за читање има своја Kubernetes примена која извршува повеќе PgBouncer подови. Извршуваме повеќе Kubernetes распоредувања зад истиот Kubernetes сервис, кој го балансира сообраќајот низ подови.

Кеширање

Предизвик: Ненадеен скок во промашувања на кеш-меморијата може да предизвика нагло зголемување на читањата на базата на податоци PostgreSQL, што го заситува процесорот и ги забавува корисничките пребарувања.

Решение: За да го намалиме притисокот од читање на PostgreSQL, користиме слој за кеширање за да опслужиме најголем дел од сообраќајот за читање. Сепак, кога стапките на погодоци во кешот неочекувано ќе паднат, налетот на промашувања во кеш-меморијата може да предизвика голем обем пребарувања да се насочат директно кон PostgreSQL. Ова ненадејно зголемување на читањата од базата на податоци троши значителни ресурси и ја забавува услугата. За да спречиме преоптоварување за време на налети од промашување на кеш-меморијата, имплементираме механизам за заклучување (и изнајмување) на кеш-меморијата, така што само еден читач кој промашува одреден клуч ги презема податоците од PostgreSQL. Кога повеќе пребарувања промашуваат на истиот клуч на кеш-меморијата, само едно пребарување го добива клучот и продолжува да ги презема податоците и повторно да ја пополнува кеѓ-меморијата. Сите други барања чекаат кеш-меморијата да се ажурира, наместо сите одеднаш да го оптоварат PostgreSQL. Ова значително ги намалува непотребните читања од базата на податоци и го штити системот од каскадни скокови на оптоварување.

Проширување на реплики за читање

Предизвик: Примарната единица стримува податоци од Write Ahead Log (WAL) до секоја реплика за читање. Како што се зголемува бројот на реплики, примарната единицат мора да испорачува WAL до повеќе инстанци, зголемувајќи го оптоварувањето и на мрежниот пропусен опсег и на процесорот. Ова предизвикува поголемо и понестабилно доцнење на репликите, што го отежнува доверливото проширување на системот.

Решение: Ние управуваме со речиси 50 реплики за читање низ повеќе географски региони за да ја намалиме латентноста. Сепак, со тековната архитектура, главниот сервер мора да го пренесува WAL до секоја реплика. Иако моментално добро се проширува со многу големи типови инстанци и висок мрежен пропусен опсег, не можеме бесконечно да додаваме реплики без на крај да ја преоптовариме примарната единица. За да го решиме ова, соработуваме со тимот за Azure PostgreSQL на каскадна репликација(се отвора во нов прозорец), каде што посредничките реплики го пренесуваат WAL до низводните реплики. Овој пристап ни овозможува да се прошириме до потенцијално над сто реплики без да ја преоптовариме примарната единица. Сепак, тоа исто така воведува дополнителна оперативна сложеност, особено во врска со управувањето со префрлање. Функцијата сè уште е во тестирање; ќе се погрижиме да биде стабилна и да може безбедно да се префрли на резервен режим пред да ја воведеме во продукција.

postgreSQL дијаграм на каскадна репликација

Ограничување на стапката

Предизвик: Ненадејно зголемување на сообраќајот на одредени крајни точки, нагло зголемување на скапи пребарувања или бура од повторни обиди може брзо да ги исцрпи критичните ресурси како што се процесорот, I/O и конекциите, што предизвикува широко распространето влошување на услугата.

Решение: Имплементиравме ограничување на стапката низ повеќе слоеви - апликација, групирач за конекции, прокси и пребарување - за да спречиме ненадејни скокови на сообраќајот кои би можеле да ги преоптоварат инстанците на базата на податоци и да предизвикаат каскадни откажувања. Исто така, важно е да се избегнуваат премногу кратки интервали за повторен обид, кои можат да предизвикаат налети од повторни обиди. Исто така, го подобривме ORM слојот за да поддржува ограничување на брзината и, кога е потребно, целосно да блокира специфични резимеа на пребарувања. Оваа насочена форма на намалување на оптоварувањето овозможува брзо закрепнување од ненадејни скокови на скапи пребарувања.

Управување со шема

Предизвик: Дури и мала промена на шемата, како што е менување на типот на колона, може да предизвика целосно препишување на табелата(се отвора во нов прозорец). Затоа ги применуваме промените на шемата внимателно, ограничувајќи ги на лесни операции и избегнувајќи ги оние што препишуваат цели табели.

Решение: Дозволени се само лесни промени на шема, како што се додавање или отстранување на одредени колони кои не предизвикуваат целосно препишување на табелата. Имаме строг рок од 5 секунди за промени на шемите. Креирањето и бришењето индекси истовремено е дозволено. Промените на шемата се ограничени само на постојните табели. Ако нова функција бара дополнителни табели, тие треба да бидат во алтернативни поделени системи како што е Azure CosmosDB, наместо PostgreSQL. Кога се врши пополнување на поле во табела, применуваме строги ограничувања на брзината за да спречиме нагли зголемувања на запишувањата. Иако овој процес понекогаш може да трае повеќе од една недела, тој обезбедува стабилност и избегнува какво било влијание врз продукцијата.

Резултати и правецот во кој се движиме

Овој напор покажува дека со соодветен дизајн и оптимизации, Azure PostgreSQL може да се прошири за да ги поднесе најголемите продукциски оптоварувања. PostgreSQL обработува милиони QPS за оптоварувања со многу читање, овозможувајќи ги најважните производи на OpenAI како што се ChatGPT и API платформата. Додадовме речиси 50 реплики за читање, додека ја одржувавме латентноста на репликацијата речиси на нула, одржувавме читања со ниска латентност низ географски распределени региони и изградивме доволно резервен капацитет за поддршка на идниот раст.

Ова проширување функционира, додека ја минимизира латентноста и ја подобрува доверливоста. Ние доследно испорачуваме p99 латентност на клиентската страна во ниски двоцифрени милисекунди и достапност од 99,999 % во продукција. И во текот на изминатите 12 месеци, имавме само еден SEV-0 инцидент со PostgreSQL (се случи за време на виралното лансирање(се отвора во нов прозорец) на ChatGPT ImageGen, кога сообраќајот за запишување одеднаш се зголеми за повеќе од 10 пати бидејќи над 100 милиони нови корисници се регистрираа во рок од една седмица.)

Иако сме задоволни со тоа колку далеку нè однесе PostgreSQL, продолжуваме да ги поместуваме неговите граници за да обезбедиме доволно простор за идниот развој. Веќе ги мигриравме работните оптоварувања што можат да се делат и се интензивни за запишување на нашите системи со делови како CosmosDB. Преостанатите работни задачи со интензивно запишување се потешки за делење - активно ги мигрираме и нив за дополнително да го отстраниме запишувањето од примарниот PostgreSQL. Исто така, соработуваме со Azure за да овозможиме каскадна репликација, со што безбедно ќе се прошириме на значително повеќе реплики за читање.

Гледајќи нанапред, ќе продолжиме да истражуваме дополнителни пристапи за понатамошно проширување, вклучувајќи поделен PostgreSQL или алтернативни дистрибуирани системи, бидејќи нашите инфраструктурни барања продолжуваат да растат.

Автор

Bohan Zhang

Признанија

Посебна благодарност до Jon Lee, Sicheng Liu, Chaomin Yu и Chenglong Hao, кои придонесоа за овој пост, и до целиот тим што помогна во зголемувањето на PostgreSQL. Исто така, би сакале да се заблагодариме на тимот на Azure PostgreSQL за нивното силно партнерство.