メインコンテンツにスキップ
OpenAI

2026年1月22日

エンジニアリング

PostgreSQL を拡張して8億人の ChatGPT ユーザーに対応

Bohan Zhang 氏、Technical Staff メンバー

読み込んでいます...

長年にわたり、PostgreSQL は ChatGPT や OpenAIのAPI などの中核製品を支える、最も重要な基盤データシステムの一つです。私たちのユーザーベースが急速に拡大するにつれて、データベースへの要求も指数関数的に増加しています。この1年で、当社の PostgreSQL の負荷は10倍以上増加しており、現在も急速に増え続けています。

この成長を維持するために当社の本番インフラストラクチャを強化する中で、新たな知見が得られました。それは、PostgreSQL は、これまで考えられていたよりもはるかに大きな読み取り中心のワークロードを確実にサポートできるように拡張できるということです。このシステム(当初はカリフォルニア大学バークレー校の科学者チームによって作成)により、単一のプライマリAzure PostgreSQL フレキシブル サーバー インスタンス(新しいウィンドウで開く)と、世界中の複数のリージョンに分散された約50のリードレプリカを使用して、大規模なグローバルトラフィックをサポートできるようになりました。これは、OpenAI において PostgreSQL をどのように拡張させ、厳密な最適化と確固たるエンジニアリングを通じて8億人のユーザー向けに毎秒数百万件のクエリをサポートするに至った物語です。また、その過程で得た重要な知見についても解説します。

当初の設計の欠陥

ChatGPT の公開後、トラフィックはこれまでにない速度で増加しました。これを支えるため、アプリケーション層と PostgreSQL データベース層の両方で広範な最適化を迅速に実装し、インスタンスサイズを増やしてスケールアップし、さらにリードレプリカを追加してスケールアウトしました。このアーキテクチャは長年にわたり良好に機能してきました。継続的な改善により、将来の成長に向けて十分な余裕が確保され続けています。

単一のプライマリアーキテクチャが OpenAI の規模の要求を満たすことができるというのは驚くべきことのように思えるかもしれませんが、実際にこれを実現するのは簡単ではありません。Postgres の過負荷による SEV を複数確認しており、その多くは同じパターンに従っています。つまり、上流の問題がデータベース負荷の急激な増加を引き起こすのです。例えば、キャッシュ層の障害による広範なキャッシュミス、CPU を飽和させる高コストなマルチウェイ結合の急増、あるいは新機能リリースに伴う書き込み集中などが挙げられます。リソース使用率が上昇すると、クエリの遅延が増加し、リクエストがタイムアウトし始めます。再試行によって負荷がさらに増大し、悪循環を引き起こし、ChatGPT と API サービス全体の性能を低下させる可能性があります。

負荷スケーリング図

PostgreSQL は読み込みが多いワークロードに対しては高いスケーラビリティを発揮しますが、書き込みトラフィックが多い状況では依然として課題があります。これは主に、PostgreSQL のマルチバージョン同時実行制御(MVCC)の実装に起因します。この仕組みにより、書き込みが多いワークロードでは効率が低下します。例えば、クエリがタプル、あるいは単一のフィールドのみを更新する場合でも、新しいバージョンを作成するために行全体がコピーされます。書き込み負荷が高い状況では、その結果として大きな書き込み増幅が発生します。また、クエリが最新のデータを取得するために複数のタプルバージョンをスキャンしなければならないため、読み取り増幅も増大します。MVCC はさらに、テーブルやインデックスの肥大化、インデックスメンテナンスのオーバーヘッド増加、複雑な autovacuum のチューニングといった課題をもたらします。(これらの問題の詳細については、私がカーネギーメロン大学の Andy Pavlo 教授と共同執筆し、PostgreSQL の Wikipedia ページにも引用(新しいウィンドウで開く)されているブログ「The Part of PostgreSQL We Hate the Most(新しいウィンドウで開く)」をご参照ください。)

PostgreSQL を数百万の QPS に拡張

これらの制限を緩和し書き込み負荷を軽減するため、シャード化可能な(水平分割可能な)書き込み集約型ワークロードを Azure Cosmos DB などのシャード化システムへ移行し続け、アプリケーションロジックを最適化し不要な書き込みを最小限に抑えています。また、現行の PostgreSQL デプロイメントへの新規テーブル追加は許可していません。新しいワークロードはデフォルトでシャードシステムに設定されます。

インフラが進化してきたにもかかわらず、PostgreSQL はシャーディングされず、単一のプライマリインスタンスがすべての書き込みを処理しています。主な理由は、既存のアプリケーションのワークロードをシャーディングすることは非常に複雑で時間がかかり、数百のアプリケーションエンドポイントの変更が必要となり、数か月、場合によっては数年かかる可能性があるためです。当社のワークロードは主に読み取り中心であり、広範な最適化を実装しているため、現在のアーキテクチャでも、継続的なトラフィック増加を支えるのに十分な余力があります。将来的に PostgreSQL のシャーディングを行う可能性を排除しているわけではありませんが、現在および今後の成長に対して十分な余裕があるため、短期的な優先事項ではありません。

以下のセクションでは、直面した課題と、それらに対処し将来の障害を防止するために実施した大規模な最適化について掘り下げます。PostgreSQL を限界まで押し上げ、毎秒数百万クエリ(QPS)へのスケーリングを実現しました。

プライマリの負荷を軽減

課題:ライターが1つのみのため、単一プライマリ構成では書き込みを拡張できません。書き込みの急増が発生すると、プライマリノードが瞬時に過負荷状態となり、ChatGPT や API などのサービスに影響を及ぼします。

解決策:書き込みの急増に対応できる十分な容量を確保するため、プライマリノードへの負荷(読み取り・書き込み両方)を可能な限り最小化します。読み取りトラフィックは可能な限りレプリカにオフロードします。ただし、一部の読み取りクエリは書き込みトランザクションの一部であるため、プライマリに残す必要があります。それらについては、効率性を確保し低速のクエリを回避することに注力します。書き込みトラフィックに関しては、シャード化可能で書き込み負荷の高いワークロードを、Azure CosmosDB などのシャード化されたシステムに移行しました。シャーディングが難しいのに書き込み量が多いワークロードは移行に時間がかかり、そのプロセスは現在も進行中です。また、書き込み負荷を減らすためにアプリケーションを積極的に最適化しました。たとえば、冗長な書き込みを引き起こしていたアプリケーションのバグを修正し、適切な場合には遅延書き込みを導入して、トラフィックの急増を緩和しました。さらに、テーブルフィールドをバックフィルする際には、過剰な書き込み負荷を防ぐために厳格なレート制限を設けています。

クエリの最適化

課題:PostgreSQL でコストのかかるクエリをいくつか特定しました。過去には、これらのクエリの処理量が急増すると大量の CPU リソースを消費し、ChatGPT と API リクエストの両方の速度が低下していました。

解決策:多くのテーブルを結合するような高コストなクエリが少数存在するだけで、サービス全体のパフォーマンスが著しく低下したり、最悪の場合は停止することがあります。PostgreSQL クエリを継続的に最適化し、効率を確保して一般的なオンライントランザクション処理(OLTP)のアンチパターンを回避する必要があります。たとえば、12個のテーブルを結合する非常にコストのかかるクエリを特定したことがあります。このクエリの急増が、過去に重大度の高い SEV の原因となっていました。可能な限り、複雑なマルチテーブル結合は避けるべきです。結合が必要な場合は、クエリを分解することを検討し、複雑な結合ロジックは代わりにアプリケーション層へ移すようにしました。これらの問題のあるクエリの多くはオブジェクトリレーショナルマッピング(ORM)フレームワークによって生成されるため、生成された SQL を注意深く確認し、期待通りに動作することを確認することが重要です。PostgreSQL では、長時間アイドル状態のクエリが見つかることも一般的です。idle_in_transaction_session_timeout のようなタイムアウトを設定することは、autovacuum のブロックを防ぐために不可欠です。

単一障害点の軽減

課題:読み取りレプリカがダウンした場合でも、トラフィックは他のレプリカにルーティングされます。ただし、単一のライターに依存するということは、単一障害点が存在することを意味します。つまり、そのライターがダウンすると、サービス全体が影響を受けます。

解決策:最も重要なリクエストには読み取りクエリのみが関係します。プライマリにおける単一障害点を軽減するために、これらの読み取りをライターからレプリカにオフロードし、プライマリがダウンした場合でもこれらのリクエストが引き続き処理されるようにしました。書き込み操作は引き続き失敗しますが、影響は軽減されています。読み取りは利用可能なままのため、SEV0 ではなくなります。

プライマリの障害を軽減するために、プライマリは高可用性(HA)モードでホットスタンバイと共に運用しています。ホットスタンバイは常に同期されているレプリカで、いつでもトラフィックの処理を引き継ぐ準備ができています。プライマリがダウンしたり、メンテナンスのためにオフラインにする必要がある場合は、スタンバイをすぐに昇格させてダウンタイムを最小限に抑えることができます。Azure PostgreSQL チームは、非常に高い負荷がかかってもこれらのフェイルオーバーの安全性と信頼性を確保するために多大な努力を払ってきました。リードレプリカの障害に対処するため、各リージョンに十分な余裕を持たせて複数のレプリカを配置し、単一のレプリカ障害がリージョン全体の障害につながらないようにしています。

ワークロードの分離

課題:PostgreSQL インスタンスにおいて、特定のリクエストがリソースを過剰に消費する状況が頻繁に発生します。これにより、同じインスタンス上で実行されている他のワークロードのパフォーマンスが低下する可能性があります。たとえば、新しい機能の導入により、PostgreSQL の CPU を多く消費する非効率なクエリが発生し、他の重要な機能のリクエストが遅くなることがあります。

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 プロキシ図

各リードレプリカは、複数の PgBouncer ポッドを実行する独自の Kubernetes デプロイメントを保持しています。同じ 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

謝辞

Jon Lee、Sicheng Liu、Chaomin Yu、Chenglong Hao、この記事に貢献してくださった方々、そして PostgreSQL のスケールアップを支援してくださったチーム全体に特別な感謝を申し上げます。また、強力なパートナーシップを築いてくださった Azure PostgreSQL チームにも感謝申し上げます。