Vai al contenuto principale
OpenAI

11 settembre 2026

Ingegneria

Scalare rapidamente lo storage online per oltre 1 miliardo di utenti ChatGPT

Come abbiamo adattato Habitat, la nostra piattaforma di storage applicativo in Python, a una crescita senza precedenti.

Di Jon Lee, Chaomin Yu e Ben Ries, membri dello staff tecnico

Caricamento in corso...

Ogni prodotto OpenAI dipende da un accesso rapido e affidabile ai dati, che si tratti di effettuare l'accesso, controllare le impostazioni di Codex o iniziare una nuova conversazione in ChatGPT. Ognuna di queste azioni può richiedere numerose ricerche separate nei dati prima che il prodotto possa rispondere. Se queste richieste sono lente, il prodotto appare lento. Se queste richieste non riescono, il prodotto smette completamente di funzionare.

Habitat è la piattaforma di storage online che abbiamo creato affinché i prodotti OpenAI possano accedere rapidamente e in modo affidabile alle informazioni necessarie. Habitat gestisce ora oltre 70 milioni di richieste al secondo e supporta prodotti usati ogni settimana da più di 1 miliardo di persone in quasi 40 regioni geografiche. Habitat è stato lanciato inizialmente per supportare i GPT durante DevDay 2023, come semplice libreria Python lato client collegata a un unico database. Oggi è un complesso sistema distribuito che gestisce oltre 500 petabyte di dati.

Figura 01 · Cos'è Habitat?

Piattaforma di storage online

Habitat è la piattaforma di storage online che abbiamo creato affinché i prodotti OpenAI possano accedere rapidamente e in modo affidabile alle informazioni necessarie.

  • Richiesta
  • Risposta
  • Modifiche (CDC)

Client

Piattaforma di storage online

Risorse di storage

  • ChatGPT
  • API
  • Codex
  • Servizi interni
  • E altro ancora

Habitat

  • CachingCache
  • Policy ACLAutorizzazione
  • Posizionamento e residenza dei datiResidenza dei dati
  • CrittografiaSicurezza dei dati
  • IsolamentoMulti-tenancy
  • Limitazione della frequenzaDefinizione delle richieste
  • InstradamentoRicerca dello schema · Residenza dei dati
  • Azure Cosmos DBStorage online
  • NanobaseStorage online
  • ValkeyCache
  • Storage BLOBRisorse di storage
Servizi CDCChange Data Capture
  • Databricks
  • Rockset
  • Kafka
  • E altro ancora

Creare e gestire un'infrastruttura di queste dimensioni non è un'impresa da poco, ma neppure particolarmente difficile. A rendere unica la nostra situazione è stata la velocità senza precedenti con cui abbiamo dovuto scalare per sostenere l'enorme crescita degli utenti e la domanda di prodotti, costruendo al contempo una piattaforma matura. Spesso gli ingegneri di sistema progettano per una scala 10 volte maggiore e sperano che regga per alcuni anni mentre preparano il successivo aumento di 10 volte. Nel nostro caso, negli ultimi tre anni siamo cresciuti di oltre 10 volte su base annua. Di conseguenza, creare e gestire Habitat ha richiesto una sequenza di decisioni tattiche: comprendere ogni componente al livello più profondo per sfruttare al massimo lo stack esistente, evitando carenze di capacità di storage e calcolo per guadagnare tempo da dedicare agli investimenti fondamentali.

  • 70 mln+

    richieste al secondo

  • 1 mld+

    persone ogni settimana

  • 500 PB+

    dati

Con la crescita di OpenAI, anche Habitat ha dovuto crescere: prima diventando abbastanza affidabile per il traffico mission-critical dei prodotti, poi abbastanza veloce per gli utenti globali e infine capace di operare con agilità su vastissima scala. Questo articolo è il primo di una serie in due parti su come abbiamo scalato lo storage online. In questo articolo illustreremo l'evoluzione di Habitat, perché lo abbiamo trasformato da libreria a servizio e come abbiamo portato un servizio scritto in un linguaggio insolito per questo ambito, Python, a diventare un livello affidabile della piattaforma di storage.

In un prossimo articolo spiegheremo nel dettaglio come abbiamo garantito l'affidabilità multi-tenant su larga scala, la nostra strategia a più livelli per ottimizzare le prestazioni di lettura e come abbiamo ampliato la collaborazione con Azure Cosmos DB per gestire in modo affidabile una domanda senza precedenti.

Cos'è Habitat?

Habitat nasce da un'idea semplice: gli ingegneri di prodotto non dovrebbero doversi occupare della gestione dei database. Habitat è stato lanciato inizialmente per supportare i GPT durante DevDay 2023, come piccola libreria Python che interagiva con il server principale di ChatGPT. Supportava un insieme limitato di operazioni che, dietro le quinte, venivano associate all'applicazione database Azure Cosmos DB.

La libreria offriva ai team di prodotto un modo semplice per archiviare e recuperare dati senza dover padroneggiare i dettagli sottostanti. Habitat si occupava del lavoro necessario: determinare il tipo di dati coinvolti, la loro origine o destinazione, se la richiesta fosse consentita e così via.

Gli ingegneri di prodotto non dovevano preoccuparsi di ricerca dello schema, instradamento, autorizzazione, crittografia, serializzazione, definizione delle richieste e pooling delle connessioni. Non dovevano neppure considerare la provenienza dei dati: Azure Cosmos DB, cache o altri tipi di storage.

Figura 02 · Servizio Habitat

Flusso semplificato delle richieste Habitat

Separando la logica di storage in un servizio autonomo, abbiamo creato un unico punto di controllo per distribuzioni, osservabilità e miglioramenti della piattaforma.

  • Richiesta
  • Risposta

Client

OpenAI

Azure Cosmos DB

SDK client di Habitat
envoy
  • habitat-serviceprocesso 1
  • habitat-serviceprocesso 2
  • habitat-serviceprocesso 3
habitat-envoy
  • habitat-cosmos-db-us0
  • habitat-cosmos-db-us1
  • habitat-cosmos-db-eu0

Questa libreria Python funzionava bene e Habitat è stato adottato rapidamente dagli ingegneri di prodotto di OpenAI, pur senza un'iniziativa centrale coordinata per abbandonare l'uso self-service di Postgres e Azure Cosmos DB.

Con l'evoluzione delle esigenze dei prodotti, gli sviluppatori potevano facilmente aggiungere alla libreria condivisa il supporto per funzionalità quali caching lato client, compressione o crittografia.

Creare un servizio per supportare meglio prodotti molteplici e complessi

A metà del 2025, Habitat aveva raggiunto i limiti della sua implementazione lato client. Con l'aumento della complessità del livello Habitat e del numero di servizi OpenAI, le modifiche ai protocolli compatibili con le versioni precedenti erano diventate impraticabili.

In un caso, volevamo ridurre l'impatto di un'eventuale interruzione in una singola regione sui nostri set di dati più critici, migrandoli verso un insieme di account Azure Cosmos DB distribuiti a livello regionale. La modifica richiedeva di introdurre nel client ulteriore logica di instradamento, disattivata tramite un flag di funzionalità, distribuirla a tutti i client e infine attivare il flag.

Coordinare le distribuzioni tra decine di servizi e collaborare con ogni team per completarle ha richiesto giorni. Prima di attivare la funzione, ci siamo resi conto che volevamo introdurre lo shadowing per verificare la correttezza della logica di sharding. La distribuzione ha richiesto un altro paio di giorni. Correggere un bug in qualcosa che avevamo scoperto essere errato? Un altro paio di giorni. Alla fine eravamo pronti ad attivare il flag, ma uno dei team, per motivi estranei, ha ripristinato nel proprio servizio una versione precedente del client contenente un bug, causando proprio l'interruzione che avevamo lavorato tanto per evitare.

Le modifiche alla libreria client richiedevano un coordinamento complesso tra decine di servizi, un processo sempre più fragile, inefficiente e soggetto a problemi operativi. Per ridurre questa dispersione operativa nelle distribuzioni future, abbiamo deciso di trasformare Habitat in un servizio autonomo.

Separando la logica di storage in un servizio autonomo, abbiamo creato un unico punto di controllo per distribuzioni, osservabilità e miglioramenti della piattaforma. Anziché gestire aggiornamenti frammentati, potevamo implementare i miglioramenti centralmente, offrendo vantaggi immediati a ogni prodotto OpenAI.

Un servizio centralizzato ci offre inoltre un unico punto di controllo per garantire i più solidi meccanismi di sicurezza e privacy dei dati. Il servizio Habitat ci consente di applicare centralmente le policy di controllo degli accessi, registrare i log di audit e limitare l'accesso alle risorse di storage sottostanti, come Azure Cosmos DB. Habitat svolge un ruolo fondamentale nella protezione dei dati degli utenti e nella prevenzione degli accessi non autorizzati da parte di soggetti esterni, interni e agenti.

Avviare un servizio Python su larga scala

Sapevamo che ci serviva un servizio, ma non volevamo ancora abbandonare Python, nonostante i costi aggiuntivi comportati dal suo utilizzo come servizio. Usare Python per un servizio ad alta capacità di elaborazione aumentava la latenza di rete e comportava notevoli costi di scalabilità per CPU e memoria rispetto all'esecuzione come libreria locale. Inoltre, sapevamo che le inefficienze di Python non sarebbero state accettabili su una scala 100 volte maggiore, rendendo quasi certa una futura riscrittura.

Tuttavia, consideravamo questa scelta un'assunzione strategica di debito tecnico. Il nostro obiettivo principale non era ottimizzare costi o risorse, bensì consentire agli sviluppatori di prodotto di procedere e rendere stabile la piattaforma. Accettando nel breve termine i compromessi prestazionali di un servizio Python, abbiamo potuto dare priorità alle sfide più immediate, definire le API principali e costruire un'infrastruttura solida.

Abbiamo anche scommesso, in modo ponderato, che i rapidi progressi dei nostri modelli di programmazione avrebbero semplificato il percorso tecnico futuro. Abbiamo scommesso che, quando fosse diventata necessaria una migrazione completa da Python, Codex e GPT l'avrebbero resa possibile. Alla fine, la scommessa si è rivelata vincente.

Eseguire Habitat come servizio Python non sarebbe stato ottimale in termini di prestazioni, ma era una scelta necessaria. Python ci permette di procedere rapidamente, ma non per questo potevamo ignorare i rischi e accettare latenze nettamente peggiori. Quando una richiesta utente media genera centinaia di chiamate al database, l'utente percepisce il tempo della chiamata più lenta. Abbiamo constatato che la sfida principale nell'eseguire un servizio Python su questa scala è gestire le latenze di coda.

Monitorare il ritardo di asyncio

Asyncio consente a Python di eseguire contemporaneamente carichi di lavoro vincolati dall'I/O, ma non permette di aggirare il GIL di Python né di ottenere parallelismo sulla CPU. Oltre a inoltrare richieste con un uso intensivo dell'I/O, Habitat gestisce molte attività in background e ad alto uso di CPU: instradamento, compressione, crittografia, checksum, controlli sullo stato dei servizi downstream, shadowing e hedging delle richieste.

Con così tanti carichi ad alto uso di CPU e attività in background nel servizio, il ritardo di pianificazione di asyncio può facilmente dominare la latenza di coda delle richieste. Prima dell'ottimizzazione per il lancio iniziale, nelle tracce delle richieste con latenza p99 o superiore osservavamo che, sebbene lo storage downstream rispondesse rapidamente, le richieste spesso si bloccavano in attesa che la coroutine responsabile venisse ripianificata per analizzare la risposta.

Figura 03 · Monitorare il ritardo di asyncio

La concorrenza non è parallelismo della CPU

Asyncio di Python consente di elaborare le richieste contemporaneamente, ma sul thread della CPU ne viene eseguita solo una alla volta. Ciò incide notevolmente sulle latenze delle richieste quando la CPU deve svolgere molto lavoro.

Elaborazione CPU di richieste/risposteLettura/scrittura di rete PythonAttesa di Cosmos

Carico CPU ridotto

Brevi passaggi Python; le attese I/O si sovrappongono

Carico CPU elevato

Lunghi passaggi Python fanno attendere le risposte pronte

0.0 / 40 unità illustrative

Per i servizi Python di OpenAI, oltre a misurare le normali metriche di utilizzo e saturazione di memoria, CPU, rete e disco, è essenziale monitorare anche il ciclo asyncio e il suo livello di occupazione, quindi ottimizzare di conseguenza.

Pianificando periodicamente attività in background e registrando la differenza tra il momento di esecuzione previsto e quello effettivo, possiamo misurare empiricamente e in tempo reale il ritardo di pianificazione del ciclo di eventi. Con un utilizzo elevato e molte attività costose, bastano anche poche richieste simultanee per processo per produrre notevoli variazioni nella pianificazione, fino a centinaia di millisecondi e, in alcuni casi limite, diversi secondi.

Di conseguenza, limitiamo ogni processo a un numero ridotto di richieste simultanee e aumentiamo invece enormemente il numero di processi worker Python.

Ridurre una latenza di coda nelle configurazioni dei flag di funzionalità

Durante il lancio iniziale, il profiling della CPU del servizio in produzione ha rivelato una causa principale dell'elevato ritardo di asyncio, e quindi delle alte latenze di coda: l'analisi periodica in JSON delle configurazioni dei flag di funzionalità tramite Statsig, uno strumento per gestire tali flag, eseguire test A/B e altro.

Per impostazione predefinita, Statsig verificava ogni minuto, senza variazioni temporali, la presenza di configurazioni aggiornate; inoltre, la configurazione includeva tutte le regole di produzione di ogni servizio. Altrove, si era deciso a livello architetturale di eseguire fino a 8 processi Python per pod, così da aumentare l'utilizzo della CPU e ridurre le latenze. Nel complesso, ciò significava che ogni minuto ciascun pod attraversava un momento in cui tutti i worker interrompevano l'elaborazione delle richieste in corso e dedicavano invece i cicli di CPU all'analisi di un enorme file di configurazione.

Una volta individuata la causa grazie al profiling della CPU, la soluzione è stata semplice: distribuire una configurazione più piccola e mirata, allungare l'intervallo di aggiornamento e introdurre una variazione temporale nelle attività in background di questo tipo.

Bilanciare i carichi e gestire i pool di connessioni

Per mantenere basso il ritardo di asyncio è inoltre essenziale bilanciare bene le richieste tra i processi server; senza un'adeguata ottimizzazione, anche il pooling delle connessioni può ostacolare questo obiettivo.

Con il pooling delle connessioni lato client, un singolo processo client che effettua molte richieste simultanee può stabilire solo poche connessioni ai server e, di conseguenza, inviare tutto il proprio carico soltanto a pochi processi. Prima di modificare il bilanciamento del carico, l'utilizzo del nostro servizio variava ampiamente: alcuni processi nella coda della distribuzione gestivano da 5 a 10 volte il numero medio di richieste simultanee.

Lo abbiamo scoperto per caso durante un incidente: nonostante avessimo arrestato il client che sovraccaricava parte del servizio, alcuni processi sono rimasti in stato degradato ben oltre il picco di traffico. Abbiamo anzi notato un degrado incontrollato di quei processi, che ricevevano sempre più richieste finché non li riavviavamo. Quando un pod si sovraccaricava, un determinato comportamento continuava a convogliare altro traffico su di esso. Era una classe di problemi che alcuni colleghi conoscevano bene da esperienze precedenti: errore metastabile(si apre in una nuova finestra).

Sospettavamo che la causa fosse il pool di connessioni e abbiamo verificato l'ipotesi limitando la durata massima del riuso delle connessioni: ciò ha effettivamente contenuto il degrado e confermato la direzione dell'indagine. Ulteriori indagini hanno rivelato che, per impostazione predefinita, il TCPConnector di aiohttp in Python riutilizza le connessioni secondo il criterio LIFO: per la richiesta successiva viene selezionata la connessione rientrata più di recente. Di norma è un'impostazione predefinita ragionevole: il riuso delle connessioni recenti consente alle connessioni aggiuntive create per gestire i picchi di traffico di scadere quando inattive, riducendo i costi del loro mantenimento. Nel nostro caso, però, ha provocato un errore metastabile. Durante un picco, le richieste inviate ai server più lenti e sovraccarichi restituivano le connessioni al pool più tardi; venivano quindi selezionate più spesso dalle richieste successive, concentrando gradualmente altro traffico sui pod già in difficoltà. Modificare il pool di connessioni affinché usasse il riuso FIFO ha interrotto questo ciclo di feedback e ridotto anche la variabilità delle richieste in stato stazionario.

Figura 04A · Pooling delle connessioni lato client

LIFO rimanda il nuovo lavoro al processo lento

Dopo un picco di richieste, i server più lenti restituiscono per ultime le connessioni al pool. LIFO favorisce la concentrazione di altro lavoro sugli stessi server più lenti.

Un picco iniziale raggiunge A, B e il processo C, più lento.

Figura 04B · Pooling delle connessioni lato client

FIFO interrompe il ciclo di feedback del riuso delle connessioni

FIFO mantiene più connessioni attive dopo un picco, ma bilancia equamente i carichi di lavoro tra tutti i server.

Un picco iniziale raggiunge A, B e il processo C, più lento.

Oggi ci affidiamo principalmente a Istio ed Envoy per fornire il pooling delle connessioni e strategie di bilanciamento più consapevoli del carico dei server nell'intera infrastruttura OpenAI, evitando del tutto il problema.

Evitare di sommergere le risorse downstream

Un effetto collaterale dell'ottimizzazione volta a ridurre il ritardo di asyncio e dell'elevato numero di processi Python è l'estrema facilità con cui si possono sovraccaricare le dipendenze downstream con un'enorme quantità di connessioni, fenomeno noto come “thundering herd”.

Una normale distribuzione giornaliera, se non ottimizzata per procedere lentamente, può causare un notevole carico variabile sulla CPU dovuto al ricambio delle connessioni. Oppure una perdita di connessioni può mandare fuori uso la rete saturando il gateway NAT. Non sono problemi insoliti neppure per altri servizi, ma avere un ordine di grandezza in più di processi abbassa notevolmente la soglia di attivazione, spesso saturando risorse di rete che, basandosi sulla sola capacità di elaborazione, i client non prevedono di dover gestire in condizioni stazionarie.

Ci affidiamo inoltre a Envoy per massimizzare l'aggregazione delle connessioni. Lo usiamo per aggiornare le connessioni HTTP/1 di Python a HTTP/2, sfruttare il multiplexing, raggruppare tali connessioni in pool e prolungarne la durata. Envoy ci offre inoltre un punto centrale in cui implementare limiti di frequenza e circuit breaker, che sarebbero meno efficaci in ogni singolo processo Python autonomo.

Figura 05 · Aggregazione delle connessioni

Le stesse richieste, meno connessioni

Il pooling e il multiplexing HTTP/2 delle connessioni contribuiscono a ridurre il carico di connessioni sui servizi downstream.

RichiestaRispostaKeep-alive inattivo

Perché Habitat fa meno

Uno dei motivi per cui abbiamo potuto portare Python a questa scala è l'API limitata di Habitat, che mantiene prevedibile il costo delle richieste. Anziché consentire ai client di costruire query SQL arbitrarie, potenzialmente responsabili di scansioni di grandi tabelle o join tra molte tabelle, Habitat espone una semplice API NoSQL. La scelta di non offrire un'API potente è un compromesso esplicito nella progettazione di Habitat.

Puntiamo a ottimizzare richieste semplici, prevedibili e con un carico di lavoro costante. Nella nostra esperienza, questi sistemi sono molto più facili da scalare e difficili da configurare male o usare impropriamente. Le richieste con fan-out imprevedibile sono rischiose sul piano operativo: complicano l'isolamento e il bilanciamento del carico e introducono bruschi aumenti di latenza difficili da gestire in scala sia per il servizio sia per i client.

Prima del passaggio a Habitat e Azure Cosmos DB, la maggior parte dei dati online di OpenAI era archiviata in Postgres. All'epoca era facile esaminare tutte le modifiche alle query e allo schema per assicurarsi che si comportassero correttamente e operassero su dati indicizzati prima della distribuzione in produzione. Con la crescita del team e dei prodotti, la situazione è diventata rapidamente ingestibile e ha causato frequenti interruzioni: bastava una nuova query costosa in un percorso critico per mandare fuori uso il database.

Il problema è lo squilibrio dei costi: scrivere query SQL costose e difficili da eseguire è semplice ed economico. In Habitat evitiamo questo problema e rendiamo le query costose estremamente evidenti sul lato client. Non esistono query illimitate in grado di sovraccaricare Habitat; inoltre, join complessi e attraversamenti di grafi richiedono ai team di prodotto di svolgere parte del lavoro più impegnativo, favorendo nel complesso progettazioni più efficienti.

Habitat espone un'API NoSQL basata su tipi di oggetti e archi definiti dal client, ispirata a TAO(si apre in una nuova finestra). I client predefiniscono oggetti e archi e le rispettive relazioni, ma non il contenuto di ciascun tipo. Le relazioni risultanti assomigliano a un grafo, ma Habitat non supporta le tipiche query di attraversamento, salvo quelle sugli archi diretti di un determinato oggetto.

Partizioniamo il grafo in modo che ogni oggetto e i relativi archi si trovino nella stessa partizione a livello di storage, ma non compiamo alcuno sforzo specifico a livello di database per collocare insieme gli oggetti e quelli remoti a cui puntano i loro archi. Di conseguenza, il modello si partiziona facilmente per la scalabilità orizzontale, ma gli attraversamenti del grafo sono inefficienti: ogni singolo passaggio tra oggetti può richiedere il recupero da due account Azure Cosmos DB completamente diversi, archiviati in regioni differenti.

Per i client con esigenze di query più complesse, offriamo una vista secondaria offline di Habitat accessibile tramite Rockset. Usiamo Change Data Capture (CDC) per trasmettere quasi in tempo reale le modifiche dallo storage online a istanze Rockset isolate. Ogni team client è responsabile della scalabilità della propria istanza Rockset per le query complesse.

Il provisioning di Rockset crea ulteriori difficoltà per i client, ma riteniamo che al momento sia il giusto compromesso: rendere predefinite le query semplici, offrendo al contempo un'alternativa a chi necessita di query complesse. Questa architettura isola lo storage online dai carichi analitici e di ricerca con numerose operazioni di lettura.

Migrare da Python a Rust

Rinviare di un anno la riscrittura di Python ci ha permesso di concentrarci sulle sfide più urgenti e importanti durante la nostra fase di ipercrescita. Con la maturazione della piattaforma e la continua accelerazione della crescita, Habitat era diventato il secondo servizio OpenAI per numero di core e il quarto per presenza di Envoy: era finalmente giunto il momento di superare Python. Al suo apice, Python ci ha permesso di gestire oltre 20 milioni di richieste al secondo.

Nel secondo trimestre del 2026, con soli 2 ingegneri, Codex e GPT‑5.5, siamo riusciti a riscrivere l'intero servizio in Rust. Il nuovo servizio Rust gestisce ora il 95% delle richieste di produzione; nelle prossime settimane dismetteremo completamente Python. I dati mostrano che il servizio Rust è 6 volte più efficiente nell'uso della CPU e 15 volte più efficiente nell'uso della memoria rispetto alla versione Python, con latenze medie e di coda nettamente inferiori. Condivideremo altri insegnamenti in un futuro articolo del blog.

Ottimizzare il livello database: Azure Cosmos DB

Il servizio Python, e ora Rust, è solo uno degli aspetti di Habitat. Nella seconda parte di questa serie, che spiega come abbiamo scalato rapidamente lo storage online per servire oltre 1 miliardo di utenti ChatGPT, parleremo del livello di storage e di come Habitat gestisce più di 500 petabyte e oltre 70 milioni di richieste al secondo.

Se vuoi lavorare su sistemi OLTP su scala di frontiera e ti interessa questo tipo di ingegneria, scopri questa posizione aperta nel nostro team.

Autori

Jon Lee, Chaomin Yu e Ben Ries