PostgreSQL на 800 млн користувачів: що OpenAI зробили правильно
22 січня 2026 року OpenAI запостили цікаву статтю «Scaling PostgreSQL to power 800 million ChatGPT users» — вона цінна не «магією масштабу», а тим, що майже кожна проблема там знайома будь-якій команді, у якої швидко виростає навантаження на базу даних. Якщо читати по діагоналі: у них не було «однієї чарівної оптимізації». Використали багато різних «запобіжників», які по черзі перекривали типові проблеми.
OpenAI зізнаються, що після запуску ChatGPT трафік ріс «нереально» швидко. За рік навантаження на базу даних виросло більш ніж у 10 разів, і при цьому вони змогли залишитися на архітектурі з одним primary і багатьма read-репліками. А якщо казати точніше, то один primary Azure PostgreSQL Flexible Server і майже 50 реплік на різних регіонах.
Розгляньмо, які техніки вони застосовували, і які проблеми команда розробників вирішувала. А також дізнаємось, звідки історично пішли такі підходи та які ризики там приховані. Наприкінці статті буде чекліст для покращення роботи вашої бази даних. Буде дуже цікаво і пізнавально.
Контекст: «проблеми» в початковому дизайні
OpenAI чесно описують типову еволюцію:
- після запуску ChatGPT трафік росте неймовірно швидко;
- команда робить швидкі оптимізації в аплікейшені та в Postgres, вертикально скейлить інстанси й додає read-replica;
- далі вилітають класичні проблеми:
- дорогі запити;
- спайки write запитів;
- оверлоад конекшенів;
- cache-miss проблема;
- primary як single point of failure;
- реплікації починають навантажувати primary;
- ретраї на запис і оновлення ще більше тиснуть на write інстанс;
- навіть DDL стає небезпекою.

Найважливіше з цього всього, що вони не почали мігрувати на іншу БД. Лише системно знімали пікове навантаження, ізолювали ризики, і вчилися швидко відрізати погані патерни.
1. «Зняти тиск з primary»: менше читати й менше писати туди, де один writer
Яку проблему вони вирішували
У них — single-primary-архітектура: один writer на весь прод. Це нормально для read-heavy систем, але погано переживає write спайки. Один невдалий реліз або backfill і primary просто перевантажувався. Вони прямо кажуть: «heavy write spikes can quickly overload the primary».
Що конкретно зробили
- Зняли максимально по можливості read-запити з writer-ноди та віддали їх реплікам.
- Write-heavy, shardable навантаження, тобто те, що реально горизонтально партиціюється, почали виносити в окремі БД на кшталт Azure Cosmos DB.
- Оптимізували сам аплікейшен, щоб не писати зайвого:
- прибирали дубльовані або зайві записи;
- додали lazy writes, де можливо, для згладжування піків.
- Для backfill-полів зробили жорсткі rate limits, щоб не створювати цілу купу primary-апдейтів.
- І ще одне дуже цікаве правило: у поточній Postgres-реалізації вони більше не дозволяють додавати нові таблиці. Нові ворклоади «за замовчуванням» йдуть у шардовані системи.
Де так роблять зазвичай і навіщо
Це стандартна стратегія. Postgres залишаємо для транзакційного ядра. А write-hot дані, наприклад: події, логи, великі потоки дрібних апдейтів. Простіше винести в:
- шардовані/партиціоновані NoSQL;
- event store;
- або хоча б окремий кластер Postgres із партиціями.
Техніка «lazy writes» — теж типова. Запис не «прямо зараз», а пізніше (або батчами), якщо бізнес-кейси дозволяють.
Хто першим так почав робити
Як ідея — це ще з епохи ранніх web-гігантів. Ядро даних в реляційній БД, масові записи — в інше сховище. У сучасному світі ця техніка дуже поширена. Десятиліттями high-traffic сервіси на кшталт соцмереж, маркетплейсів або стрімінг-платформ користуються цією технікою.
Ризики та що може піти не так
- Неправильний partition key та отримуєте hot partition. Як результат, ви просто переносите біль з реляційної БД у нереляційну.
- Консистентність і source of truth: якщо частина стану в Postgres, частина в Cosmos (NoSQL) — потрібно чітко знати, де «правдиві» дані, і як робиться reconciliation.
- Семантика консистентності. OLTP використовує транзакції та джоіни, а шардована система — ні. Доведеться змінити модель читання даних.
- Складність експлуатації: дві бази, два світи, два типи інцидентів.
- Lazy writes можуть зламати очікування продукту. Те, що «має бути видно одразу», раптом стане «видно через N секунд».
2. Оптимізація запитів: «OLTP анти-патерни», 12-табличний join і ORM-генерований жах
Яку проблему вони вирішували
OpenAI описують дуже життєву історію, де кілька дорогих запитів при спайках з’їдали CPU і уповільнювали API. В одному випадку вони знайшли запит, який джоінив 12 таблиць — і сплески саме цього запиту приводили до high-severity інцидентів. Паралельно вони згадують іншу «тиху» проблему Postgres. Це довгі idle-транзакції, які блокують autovacuum і роздувають таблиці.
Що конкретно зробили
- Оптимізували SQL-запити і прибрали складні multi-table JOINʼи там, де можна.
- Якщо JOIN потрібен — розбивали запит на декілька і переносили частину логіки в application layer. Це дуже цікава техніка, спершу виглядає взагалі як антипатерн.
- Окремо почали перевіряти SQL, який згенерований ORM. Бо саме там часто народжується катастрофа.
- Скоригували значення
idle_in_transaction_session_timeout, що відповідає за таймаути на idle-in-transaction, щоб ці транзакції не блокували autovacuum.
Де так роблять зазвичай і навіщо
Це класика для всіх high-QPS OLTP-системах. Потрібно розуміти, що Postgres чудовий, але не завжди витягує джойни по великій кількості таблиць в проді. Також ORM полегшує життя, але інколи перетворює простий запит на N+1 проблему.
Окрема тема — «idle in transaction». Postgres може тримати транзакцію відкритою, блокувати vacuum або просто заважати іншим сесіям. Сам параметр idle_in_transaction_session_timeout офіційно робить рівно те, що написано в назві: вбиває сесію, що «зависла» всередині транзакції.
Ризики і що може піти не так
- Занадто короткі таймаути можуть блокувати інші сценарії. До прикладу: міграції, довгі адмінські джоби, вигруження репортів. Для цього потрібно створювати окремі ролі та окремі конекшени.
- Перенесення джоіна в аплікейшен-рівень може легко привести до наступних проблем: створення N+1 проблеми, додаткове навантаження на аплікейшен, більше шансів на race conditions та інше.
- Лікування проблем без профілювання поверне вас у ту ж точку через місяць.
3. Single point of failure: зробити так, щоб падіння primary не вбивало читання
Яку проблему вони вирішували
У single-primary найболючіше місце це writer. Якщо падає read-репліка, то нічого страшного не відбувається, є інші репліки. Якщо впав primary — ми не можемо нічого писати, а інколи навіть і частина read-запитів падає.
Що конкретно вони зробили
- Винесли майже всі критичні read-операції на репліки, щоб при падінні primary-сервіс міг читати. Так, writes впадуть, але це вже не SEV0, бо продукт частково живий.
- Primary запустили в HA (high availability) mode з «hot» standby read-реплікою, яка постійно синхронізована і готова до промоуту при аварії чи плановому даунтаймі. Дорого, але дуже надійно.
- Для захисту від outage read-операцій вони зробили кілька реплік у кожному регіоні, щоб одна смерть репліки не робила регіональний outage.
Де так роблять зазвичай і навіщо
Це базовий патерн: degrade gracefully. Якщо ви можете зробити продукт read-only на якийсь час замість повного outage, це вже перемога. HA з hot standby — класика для managed Postgres.
Ризики і що може піти не так
- Якщо читати з реплік, то треба завжди пам’ятати про eventual consistency і replication lag.
- Failover:
- можливий split brain;
- можливі несподівані latency або проблеми з конекшеном після promotion;
- потрібен контроль за тим, хто зараз writer.
- Запас по capacity легко з’їдається і коштує грошей. Але альтернатива — платити репутацією.
4. Workload isolation або як прибрати «noisy neighbor»
Яку проблему вони вирішували
Сценарій знайомий: виходить нова фіча і в ній неідеальний запит. Під навантаженням з’їдає CPU, і страждають інші критичні фічі. OpenAI пояснюють, як прибрати noisy neighbor. Вони ізолюють навантаження на окремі інстанси, розділяють запити на high-priority та low-priority і маршрутизують їх на різні інстанси.
Що конкретно зробили
- Розділили запити на high-priority і low-priority та перенаправили їх на різні інстанси в залежності від приорітету. Тоді дешевий або експериментальний функціонал не валить основний трафік.
- Аналогічно розділили і самі сервіси, щоб активність одного не «топила» інші.
Де так роблять зазвичай і навіщо
У великих системах це дуже поширено: окремі кластери та репліки для аналітики, бекофісу та експериментів.
Ризики і що може піти не так
- Потрібно вміти правильно класифікувати трафік. Це дуже відповідально. Помилились — і critical-запит поїхав у low-priority.
- Підтримка кількох «пулів» інстансів одночасно ускладнює конфігурацію, алерти та capacity planning.
5. Connection pooling: PgBouncer як «запобіжник» від connection storm
Яку проблему вони вирішували
В Azure PostgreSQL є ліміт підключень, і OpenAI прямо називають число: 5000 конекшенів на інстанс. Вони мали інциденти, де під час спайків конекшени дуже швидко з’їдались.
Що конкретно зробили
- Поставили PgBouncer як proxy layer для пулу конекшенів до нод баз даних.
- Використали statement та transaction pooling, щоб ефективно реюзати серверні конекшени й зменшити кількість активних клієнтських підключень. Як результат середній час встановлення конекшену впав з ~50ms до ~5ms.
- Окрема практична деталь: колокація (proxy, клієнти і репліки в одному регіоні), бо міжрегіональні конекшени «дорогі».
- PgBouncer був підлаштований правильно, особливо idle timeouts, це дозволило дуже сильно зменшити кількість конекшенів.
- До кожної read-репліки додали свій Kubernetes deployment з кількома PgBouncer pods, за баланс відповідає Kubernetes Service.

Де так роблять зазвичай і навіщо
PgBouncer — стандарт для:
- Навантажень з великою кількістю коротких запитів.
- Середовищ, де конекшен-ліміт є — bottleneck.
Ризики і що може піти не так
- Transaction/statement pooling ламає частину можливостей Postgres: prepared statements, session state, temp tables тощо. Офіційні PgBouncer доки прямо кажуть, що transaction pooling «ламає очікування клієнта» за дизайном, і показують мапу сумісності фіч.
- PgBouncer стає single point of failure. Потрібно виносити його в HA mode і налаштувати нормальні алерти.
6. Caching і cache-miss storm
Яку проблему вони вирішували
Вони описують класичну проблему: падає hit rate кеша (або ламається кеш-шар) і як результат дуже різко зростає читання з Postgres. CPU починає зростати і запити дуже уповільнюються.
Що конкретно зробили
Додали cache locking / leasing. Тобто якщо робиться багато запитів по одному ключу і його немає в кешу, то лише один іде в Postgres та оновлює кеш. Усі інші чекають на запис в кешу не DDoS’ять базу. Тобто вибудовується черга за доступом даних з кешу.
Де так роблять зазвичай і навіщо
У всіх системах з «гарячими ключами»: профілі, популярні сторінки, конфіги, права доступу, feature flags.
Хто популяризував «leasing»
Це дуже популярний та добре описаний дизайн. Перші, хто зізнався у використанні, це Facebook. Можна знайти їх роботу під назвою «Scaling Memcache at Facebook».
Ризики і що може піти не так
- Якщо «той один» запит завис — ви блокуєте всіх, хто чекає (потрібні таймаути, fallback).
- Очікування на lock додає latency (хоча часто це краще, ніж валити Postgres).
- Якщо ключ дуже гарячий — ви створюєте «чергу очікування». Іноді це ок, а іноді призводить до деградації системи. Тому потрібно вирішити, що робити при деградації: чекати, віддавати stale або відмовляти.
- Якщо ви неправильно реалізували lease/lock — можна отримати ще гірший результат (deadlocks у кеш-шарі, вічні локи, stampede при unlock).
7. Масштабування read-репліки: cascading replication
Яку проблему вони вирішували
OpenAI тримають майже 50 read replicas по світу, щоб зменшити latency.
Але в такій архітектурі primary мусить стрімити WAL на кожну репліку. Чим більше реплік — тим більше навантаження на network і CPU primary, і тим більшим стає replica lag. Вони чесно кажуть: «ми не можемо додавати репліки нескінченно».
Що конкретно зробили і продовжують робити
Разом з Azure Postgres командою тестують cascading replication, де проміжні репліки ретранслюють WAL вниз по ланцюжку. Так можна вирости до сотень реплік без перегріву primary. Але вони одразу попереджають: це додає операційної складності, особливо з failover management, тому фіча ще в тестуванні.

Де так роблять зазвичай і навіщо
Каскадну реплікацію часто використовують, коли:
- треба багато реплік;
- є дорогий міжрегіональний трафік, тобто глобальні системи;
- primary не має стільки мережевої потужності, щоб надавати дані на всі репліки.
Postgres-документація прямо описує, що cascading replication зменшує кількість прямих підключень до primary і може економити міжсайтний bandwidth.
Ризики і що може піти не так
- Складніша діагностика lag і failover стає складнішим. Потрібно розуміти, хто upstream, хто downstream, як взагалі треба перебудовувати дерево.
- Якщо Intermediate-репліка «погано почувається», це вплине на все її піддерево. Тому потрібна чітка матриця: хто від кого реплікується і що робимо при відмові середньої ланки.
- Спостережуваність: lag може «розмазатися» по всьому ланцюжку.
8. Rate limiting + блокування query digests
Яку проблему вони вирішували
Вони описують SEV-сценарії своєї системи:
- раптовий спайк на endpoint;
- спайк дорогих запитів;
- retry storm і як наслідок — CPU та I/O конекшени закінчилися;
- деградація всієї системи.
Що конкретно зробили
- Rate limiting на кількох шарах: application, connection pooler, proxy, query.
- Контроль retry policy: «надто короткі інтервали» можуть запустити retry storm.
- Додали це навіть до ORM. Підтримка rate limiting і, коли треба, можливість повністю блокувати конкретні query digests (тобто точково «відрубати» саме токсичний клас запитів).
- Вони називають це targeted load shedding: швидко скинути найгірше, щоб система відновилась.
Де так роблять зазвичай і навіщо
У зрілих великих системах rate limit та load shedding існують на:
- API gateway;
- сервісах;
- воркерах.
Добре пояснення різниці rate-limiting vs load shedding як концептів можна знайти у матеріалі «The Pragmatic Engineer про resiliency».
Ризики і що може піти не так
- Можна випадково відключити бізнес-критичний трафік.
- Якщо ліміт стоїть лише на вході, а ретраї живуть у воркерах — шторм все одно доїде до бази.
- Блокування по digest це сильний важіль. Але потрібні процеси, щоб не відрізати «живий» бізнес-трафік.
9. Schema management: «DDL тільки легкий», ніяких нових таблиць
Яку проблему вони вирішували
OpenAI підсвітили те, що багато команд вчать лише через біль: навіть маленька зміна схеми (наприклад, ALTER COLUMN TYPE) може запустити full table rewrite.
А rewrite на великій таблиці — це години I/O, replication lag, ризик довгих lock’ів і загальна деградація продакшен бази даних.
Що конкретно вони зробили
- Дозволяють лише lightweight schema changes, що не тригерять rewrite.
- Ввели
5-секундний timeout на schema changes (тобто DDL не має права «висіти»). CREATE/DROP INDEX CONCURRENTLY— дозволено (це зменшує блокування записів у продакшені).- Схема змінюється тільки на існуючих таблицях. Нові таблиці для нових фіч — не в цей Postgres, а в шардовані системи (CosmosDB).
- Backfill поля може зайняти «понад тиждень», але він жорстко rate-limited, щоб не створити write spike.
Де так роблять зазвичай і навіщо
У високонавантажених OLTP-системах DDL це дуже важлива річ. Тому, щоб не було інцидентів, ви або:
- робите zero-downtime міграції (через «expand/contract», shadow columns);
- робите жорсткі правила, як у OpenAI.
Ризики і що може піти не так
- Навіть «легкий» DDL часто бере сильні локи за замовчуванням (Postgres доки прямо кажуть про
ACCESS EXCLUSIVEдля багатьох підформ). - Відмова від нових таблиць у Postgres це сильне архітектурне обмеження, яке змушує команду думати про межі домену.
- Людський фактор: хтось запустить «невинний ALTER», який насправді rewrite. Саме тому правила мають перевірятись автоматизовано.
Чому в них взагалі так болить write-heavy навантаження
OpenAI окремо пояснюють, що Postgres під heavy writes страждає через MVCC. При апдейті навіть одного поля копіюється весь рядок. Як результат зростає write amplification та з’являються dead tuples, які в свою чергу збільшують read amplification.
Якщо хочеться продивитись більш детально, то вони самі посилаються на текст «The Part of PostgreSQL We Hate the Most» від Bohan Zhang та Andy Pavlo.
Результат, який вони отримали
- за 12 місяців їхній Postgres load виріс більш ніж у 10 разів;
- вони тримають single primary + ~50 read replicas, lag «майже нуль», читають з низькою латентністю у різних регіонах;
- заявляють low double-digit ms для p99 client-side latency та five-nines availability;
- і за рік був лише один SEV-0 інцидент, який стався під час вірусного запуску ImageGen, коли write traffic стрибнув більше 10x і за тиждень прийшло 100+ млн нових користувачів.
Тобто це не історія, де знайшли один трюк і все почало магічно працювати. Це історія, де поступово і цілеспрямовано прибирали 9 різних класів проблем. Можливо, було б простіше одразу робити правильний вибір архітектури і використовувати CosmosDB. А можливо й ні.
Цікавим залишається те, що при виникненні проблем вони обрали шлях для покращення системи і використання найкращих практик. Замість того, щоб шукати інші бази даних і робити міграції даних. І дотримання цих правил дозволило досягти видатних результатів. Сподіваюсь, було цікаво :)
Чекліст: що з цього можна використати у себе на проєкті
Можна впровадити вже завтра
- Увімкнути й підібрати
idle_in_transaction_session_timeout(і винести міграції та адмін-джоби в окремі ролі/конекшени). - Зробити практику SQL-рев’ю для ORM: хоча б на топ-N запитів по CPU та latency.
- Додати rate limit на найболючіші ендпоінти.
- Ввести правило: «backfill’и тільки з rate limit».
Зрілість і дисципліна
- Додати PgBouncer.
- Додати workload isolation (high/low priority, окремі інстанси/репліки).
Важко, але дуже окупається на масштабі
- Політика schema changes: тільки «легкі» DDL, timeouts, CONCURRENTLY, автоматичні перевірки, чи буде rewrite таблиці.
- Винос write-heavy навантажень у шардовані системи (або хоча б в окремий кластер або партицію), плюс боротьба з redundant writes та lazy writes.
- Cascading replication, якщо реплік реально багато і primary стає важко від WAL-стрімінгу.
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.
13 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів