1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
---
title: "OpenAI fait évoluer PostgreSQL pour 800 millions d'utilisateurs avec un seul writer (et sans sharding)"
date: 2026-03-11T20:00:00+01:00
draft: false
slug: "openai-postgresql-800-millions-utilisateurs"
slug_en: "openai-postgresql-800m-users-single-writer"
description: "OpenAI dessert 800M utilisateurs de ChatGPT avec un seul PostgreSQL primaire et ~50 réplicas. Pas de sharding, pas de microservices. La simplicité calculée l'emporte sur l'ingénierie excessive."
tags: ["postgresql", "évolutivité", "infrastructure", "openai", "bases-de-données"]
categories: ["opinion"]

translation:
  hash: ""
  last_translated: ""
  notes: |
    - "ñapa": means "hack/kludge/quick fix". Not strongly derogatory, implies something done the easy way instead of properly.
    - "dicho en cristiano": "in plain language". No religious connotation.
    - "barra del bar": "bar counter" — metaphor for casual conversation setting.
    - "chapuza": "bodge/kludge". Quick-and-dirty approach.
    - "baqueteado": "battle-hardened" / "well-worn from experience". Positive connotation despite sounding rough.
    - "buen rollito": "good vibes" / "feel-good factor". Casual, positive.
---

Chaque fois qu’un article est publié sur l’infrastructure d’une grande entreprise, la moitié des commentaires sur Hacker News disent quelque chose du style : “Bien sûr, ils utilisent Kubernetes avec 47 microservices et une base de données distribuée avec leur propre protocole de consensus”. Et quand on découvre que non, ils utilisent simplement PostgreSQL avec un seul primary et beaucoup de discipline, un silence gênant s’installe.

C’est exactement ce qui vient de se produire avec OpenAI.

Les chiffres que personne n’attendait

Bohan Zhang, ingénieur en infrastructure chez OpenAI, a partagé les détails sur la façon dont ils font évoluer PostgreSQL pour ChatGPT. Les chiffres clés :

  • 800 millions d’utilisateurs
  • Un seul PostgreSQL primary (writer) hébergé sur Azure
  • ~50 read replicas
  • Des millions de requêtes par seconde
  • p99 entre 10 et 19ms
  • 99,999% de disponibilité
  • Un seul incident SEV-0 en un an (causé par le lancement viral d’ImageGen, qui a ajouté 100 millions de nouveaux utilisateurs en une semaine)

Relisez ça. Un. Seul. Writer. Pour 800 millions d’utilisateurs.

“Mais ils devraient faire du sharding”

Non. Et la raison est brutalement pragmatique.

Faire du sharding avec PostgreSQL aurait impliqué de modifier des centaines de points d’accès dans l’application. Chaque requête qui suppose que toutes les données sont dans une base unique — ce qui représente presque toutes les requêtes — aurait dû être réécrite pour savoir dans quel shard réside chaque élément.

Le coût pour une telle migration ? Des mois de travail d’ingénierie, des bugs partout, et une période de transition où il faut maintenir deux systèmes.

Qu’ont-ils fait à la place ? Ils ont identifié les writes les plus lourds et les ont migrés vers Cosmos DB. Non pas parce que Cosmos DB est meilleur que PostgreSQL, mais simplement parce que ces workloads spécifiques étaient mieux adaptés à un modèle documentaire. Tout le reste — la majorité de la logique métier — est resté sur PostgreSQL.

En clair : ils ont isolé le problème et l’ont résolu sans tout compliquer. De la chirurgie précise à l’aide d’un scalpel, pas un massacre à la tronçonneuse.

PgBouncer : de 50ms à 5ms par connexion

L’une des premières limitations qu’ils ont rencontrées était la latence des connexions. PostgreSQL crée un processus pour chaque nouvelle connexion. Avec des milliers de connexions simultanées provenant de centaines de pods d’application, le coût en temps pour établir ces connexions atteignait 50ms avant même l’exécution d’une requête.

La solution : utiliser PgBouncer comme connection pooler. Ce dernier maintient un pool de connexions déjà établies et les réutilise. Résultat : la latence des connexions est passée à 5ms. Une réduction de 90%, simplement en ajustant un élément de plomberie.

Ce n’est pas une technologie récente. PgBouncer existe depuis plus de 15 ans et est utilisé en production dans des entreprises de toutes tailles. Mais là encore : un outil éprouvé et en apparence banal résout un problème dans l’une des applications les plus utilisées au monde.

L’ORM qui faisait des joins sur 12 tables

C’est mon anecdote préférée. Parce que je l’ai vue partout : chez mes étudiants, dans des startups, dans des entreprises, même dans des banques.

L’ORM générait des requêtes avec des joins sur 12 tables. Non pas parce que quelqu’un avait choisi de les concevoir ainsi, mais parce que les modèles étaient reliés entre eux, et l’ORM suivait ces relations jusqu’au bout — sans discernement.

La solution n’a pas été de changer d’ORM ou d’écrire soi-même toutes les requêtes en SQL. Ils ont simplement déplacé la logique dans l’application. Plutôt que de demander à PostgreSQL de traiter un join monstre, ils ont découpé la tâche en plusieurs petites requêtes simples et les ont rassemblées dans le code.

Est-ce moins élégant ? Oui. Est-ce plus rapide ? Énormément. Car PostgreSQL peut optimiser des requêtes simples bien plus efficacement qu’un join entre 12 tables avec des conditions croisées. Et vous pouvez mettre en cache les résultats partiels pour les réutiliser.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-- AVANT : l'ORM génère ceci
SELECT u.*, p.*, s.*, t.*, ...
FROM users u
JOIN profiles p ON ...
JOIN settings s ON ...
JOIN teams t ON ...
JOIN ... -- 12 tables liées
WHERE u.id = $1;

-- APRÈS : requêtes séparées, logique dans l'application
SELECT * FROM users WHERE id = $1;
SELECT * FROM profiles WHERE user_id = $1;
-- plus simple à mettre en cache, paralléliser et à déboguer

Chaque requête individuelle est triviale. Le query planner de PostgreSQL les exécute en microsecondes. Et si l’une échoue ou ralentit, vous savez exactement laquelle est en cause.

Les protections invisibles

Ce qui est vraiment impressionnant dans l’article de Bohan Zhang, ce ne sont pas les chiffres impressionnants, mais les petites protections qui empêchent tout de s’écrouler :

idle_in_transaction_session_timeout

Si une transaction reste ouverte sans rien faire, PostgreSQL la termine après un délai configurable. Pourquoi est-ce important ? Parce qu’une transaction ouverte bloque le autovacuum. Sans autovacuum, les tables se gonflent, les index se détériorent, et votre base de données ralentit progressivement.

C’est comme laisser le réfrigérateur ouvert. Ça ne pose pas problème les premières minutes. Mais si vous oubliez la porte toute la nuit, le lendemain, tout est à température ambiante.

Modifications de schéma avec un délai de 5 secondes

Lorsque vous exécutez un ALTER TABLE dans PostgreSQL, vous avez besoin d’un lock sur la table. Si des transactions longues sont en cours, ce lock attend. Pendant ce temps, toutes les nouvelles requêtes sont bloquées. Une modification de schéma qui prend 200ms peut paralyser votre système si une vieille transaction ne se termine pas.

La solution d’OpenAI : SET lock_timeout = '5s'. Si la modification ne parvient pas à obtenir le lock en 5 secondes, elle échoue. Mieux vaut un échec rapide suivi d’une nouvelle tentative, plutôt que de bloquer tout le système.

Limitation de débit en 4 couches

Pas une. Pas deux. Quatre couches de limitation de débit :

  1. Edge/CDN — filtre le trafic abusif avant qu’il n’atteigne l’application
  2. API gateway — impose des limites par utilisateur/ou clé API
  3. Application — limites par type d’opération
  4. Base de données — limites de connexion et temps d’exécution des requêtes

Chaque couche capture ce que la précédente laisse passer. Une défense en profondeur. La même philosophie en couches que j’applique pour les défenses contre les hallucinations, mais adaptée à l’infrastructure.

Isolation des workloads selon leur priorité

Toutes les requêtes ne se valent pas. Une requête pour “afficher le chat de l’utilisateur” est critique — si elle échoue, l’utilisateur reçoit une erreur. Une requête pour “générer un rapport analytique” est importante, mais peut attendre 30 secondes.

OpenAI dirige les requêtes en fonction de leur priorité vers différentes read replicas. Les réplicas de haute priorité ont moins de charge et répondent plus rapidement. Les réplicas de basse priorité sont plus chargées, car leur lenteur n’affecte pas directement l’expérience utilisateur.

C’est du bon sens, mais cela nécessite une discipline stricte. Il faut classifier chaque requête, configurer les routages, et résister à la tentation de tout envoyer à la réplique rapide “juste pour une requête de plus”.

Les backfills qui prennent des semaines

Quand vous avez besoin de remplir une nouvelle colonne pour 800 millions d’utilisateurs, vous ne pouvez pas simplement exécuter un UPDATE users SET column = calculated_value. Cela bloquerait la table, saturerait le disque et pourrait faire tomber le primary.

Chez OpenAI, les backfills sont réalisés avec une limitation stricte du débit. Des semaines entières.

Ça semble horrible ? C’est tout le contraire. C’est la décision d’une équipe qui comprend que la vitesse d’un backfill est bien moins importante que la stabilité du système. Mieux vaut prendre 3 semaines sans que personne ne le remarque, que 3 heures suivies d’un incident critique au milieu de la nuit.

La réplication en cascade à venir

Actuellement, ils ont ~50 réplicas directement connectées au primary. Chaque réplique consomme une connexion de réplication et de la bande passante du primary. Avec 50, c’est gérable. Avec plus de 100, cela deviendrait un problème.

La solution en développement : réplication en cascade. Des réplicas qui se répètent depuis d’autres réplicas, et non directement depuis le primary. Une structure en arbre plutôt qu’en étoile. Ainsi, le primary envoie des données à 5-10 réplicas de premier niveau, qui elles-mêmes les transmettent aux autres.

C’est la même idée que BitTorrent : au lieu que tout le monde télécharge à partir du même serveur, les nœuds partagent les données entre eux. Ça marche pour les films piratés… ça marche aussi pour les segments WAL.

La leçon que personne ne veut entendre

Notre industrie est accro à l’over-engineering. Chaque semaine, une nouvelle base de données promet de résoudre des problèmes que la majorité des entreprises n’ont même pas. Et chaque semaine, des équipes d’ingénieurs adoptent ces technologies parce qu’elles “évoluent mieux” ou sont “plus modernes”, sans se demander si PostgreSQL avec un peu de discipline serait suffisant.

OpenAI — l’entreprise qui façonne l’avenir de l’IA, avec l’un des produits à la croissance la plus rapide de l’histoire — utilise PostgreSQL. Avec un seul primary. Pas de sharding. Pas de bases de données distribuées exotiques.

Ils utilisent PgBouncer (2007). Des lecteurs répliqués (concept des années 90). Un connection pooling (aussi vieux que les bases relationnelles). Et des limites de débit (inventées avant la naissance de beaucoup d’entre nous).

La magie n’est pas dans la technologie. Elle est dans leur discipline :

  • Des requêtes simples au lieu de joins complexes
  • Des délais agressifs au lieu d’attentes illimitées
  • L’isolation des workloads au lieu de tout centraliser
  • Migrer uniquement ce qui doit l’être, au lieu de tout réécrire

Pour votre prochain standup

La prochaine fois que quelqu’un dans votre équipe propose de migrer vers une base distribuée, ou de shardear PostgreSQL, ou d’introduire une file d’attente entre l’API et la base “parce que cela ne va pas évoluer”, montrez-lui ces chiffres.

800 millions d’utilisateurs. Un primary. p99 entre 10 et 19ms. 99,999% de disponibilité.

Et posez-lui la question : “Est-ce vraiment PostgreSQL qui ne peut pas évoluer ? Ou est-ce que nos requêtes sont un assemblage bancal ?”

Parce que bien souvent, c’est la seconde option.


Source : Inside the Postgres Setup Powering 800M ChatGPT Users — Bohan Zhang, OpenAI. Si vous ne devez lire qu’un seul article sur l’infrastructure cette année, que ce soit celui-ci.