OpenAI

23 ottobre 2026

Ingegneria

Scalare rapidamente lo storage online per oltre 1 miliardo di utenti ChatGPT (Parte II)

Come abbiamo ottimizzato Azure Cosmos DB in Habitat, la nostra piattaforma di archiviazione per le applicazioni, per gestire una crescita senza precedenti.

Di Jon Lee, Hrachya Hakobyan e Chaomin Yu, membri dello staff tecnico

Caricamento in corso...

Tutti i prodotti OpenAI richiedono un accesso rapido e affidabile ai dati, fornito dalla nostra piattaforma di archiviazione, Habitat. Deve rispondere in millisecondi a oltre 1 miliardo di utenti in 40 regioni geografiche. La nostra infrastruttura ha affrontato una sfida straordinaria: sostenere una crescita esplosiva e nuovi casi d'uso, continuando a migliorare affidabilità, prestazioni e velocità di sviluppo dei prodotti. Per vincere questa sfida abbiamo dovuto ripensare la progettazione e la gestione della nostra piattaforma di archiviazione online. Abbiamo valutato costantemente i compromessi, seguendo questa sequenza: diventare prima abbastanza affidabili per il traffico dei prodotti mission-critical, poi abbastanza veloci per gli utenti di tutto il mondo e, infine, operare con agilità su vastissima scala.

Questa serie in due parti spiega come abbiamo trasformato Habitat da una semplice libreria client e un database in una piattaforma di archiviazione distribuita a livello globale, che serve oltre 500 petabyte di dati. La Parte I era dedicata all'evoluzione di Habitat da client Python a servizio. Descriveva anche le tecniche che ci hanno permesso di spingere un servizio basato su Python ben oltre ciò che molti riterrebbero praticabile.

In questo articolo, la Parte II, illustriamo come abbiamo scalato il componente di archiviazione sottostante: Azure Cosmos DB. Grazie a una stretta collaborazione con il team Azure, siamo arrivati a un approccio multi-tenant sostenibile basato su contenitori gestiti congiuntamente, con eccellenti prestazioni di lettura su vastissima scala globale.

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

Rendere affidabile l'architettura multi-tenant su larga scala

Man mano che le aziende crescono, i team infrastrutturali spesso passano dalla gestione di sistemi single-tenant a quella di sistemi multi-tenant. Nell'architettura single-tenant, ogni team responsabile di un'area di prodotto gestisce il proprio sistema. Questo riduce l'impatto dei guasti e facilita la separazione delle responsabilità e l'isolamento dei carichi di lavoro. Con la crescita dei team e delle aree di prodotto, questa gestione diventa costosa. L'architettura single-tenant rende ogni team di prodotto responsabile della pianificazione della capacità e spesso porta a un notevole sovradimensionamento delle risorse.

Al contrario, l'architettura multi-tenant affida il controllo a un team centralizzato dedicato ai database, con maggiori conoscenze ed esperienza nel provisioning delle risorse. Ma l'architettura multi-tenant crea problemi propri, come quello dei vicini rumorosi: un tenant consuma una quota insolitamente elevata di risorse a scapito degli altri.

In Habitat abbiamo dovuto valutare tutti questi compromessi per progettare un sistema di archiviazione affidabile ed efficiente su larga scala. Oggi Habitat è un livello database multi-tenant per molti prodotti OpenAI, ma per arrivarci abbiamo compiuto un percorso circolare: da un unico contenitore Azure Cosmos DB multi-tenant siamo passati al provisioning di contenitori sottostanti single-tenant, per poi tornare a un contenitore multi-tenant come opzione predefinita. Il nostro percorso multi-tenant con Habitat è ancora agli inizi: supportiamo molti casi d'uso, ma abbiamo potuto rimandare gli investimenti in quest'area sfruttando, dove possibile, le primitive integrate di Azure Cosmos DB.

Una breve introduzione ai contenitori Azure Cosmos DB

Prima di ripercorrere la nostra esperienza con Azure Cosmos DB, ecco alcune nozioni di base:

  • Un account Azure Cosmos DB definisce un insieme di contenitori e un criterio di replica

  • A ogni contenitore viene allocata una determinata capacità di elaborazione, espressa in unità richiesta al secondo (RU/s)

  • I dati di ogni contenitore sono archiviati secondo il consueto schema di sharding basato su una chiave di partizione logica specificata, con limiti documentati di archiviazione e capacità di elaborazione per chiave

  • Azure Cosmos DB distribuisce le chiavi di partizione logica su un numero inferiore di partizioni fisiche sottostanti. Le suddivide in più partizioni fisiche quando i dati archiviati superano una soglia o quando il contenitore richiede più partizioni fisiche per sostenere la capacità di elaborazione allocata

Fase I: affidarsi alle primitive di Azure Cosmos DB

Inizialmente lasciavamo passare qualsiasi carico dei client, affidandoci al limite dichiarato di 10K RU/s per chiave di partizione logica per esercitare contropressione. All'inizio usavamo anche un solo contenitore Azure Cosmos DB predefinito per account. Funzionava abbastanza bene, ma si è rivelato problematico per diversi motivi:

Limiti delle chiavi di partizione logica

Per via delle scelte iniziali relative alle API, avevamo esposto direttamente ai client la selezione della chiave di partizione logica. Di conseguenza, riscontriamo frequenti collisioni su chiavi di partizione comuni, come user_id o workspace_id, tra diversi casi d'uso dei prodotti. Poiché questi casi d'uso condividevano i limiti di Azure Cosmos DB sulle stesse chiavi di partizione logica, questa progettazione limitava la capacità di elaborazione complessiva per un dato utente.

Prima di passare a Habitat e Azure Cosmos DB, un problema ricorrente nella scalabilità di Postgres era che molte richieste da un'organizzazione o una chiave molto attiva, cioè un tenant che generava un carico sproporzionatamente elevato, potevano mettere fuori uso l'intero database. Abbiamo dedicato un notevole impegno collettivo a mettere in cache quelle chiavi e a introdurre lease nella gestione dei cache miss, affinché il carico generato dai dati non trovati in cache non sovraccaricasse il database sottostante.

Figure 02a · Logical partition key limits

Different clients, the same partition key

Cosmos DB containerPhysical partitionPK: user-id-28104CapacityMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedConversationsSuccess rateResponses returned in this animation loop—AuthSuccess rateResponses returned in this animation loop—Memory / dreamingSuccess rateResponses returned in this animation loop—

Vicini rumorosi

Sebbene i limiti documentati sulle dimensioni dei dati e sulla capacità di elaborazione si riferiscano alle chiavi di partizione logica, è sulla partizione fisica che vengono effettivamente applicati. In pratica, questo significa che le partizioni logiche non possono raggiungere 10.000 RU/s. Condividono invece i limiti della partizione fisica con tutte le altre chiavi di partizione logica collocate sulla stessa partizione fisica.

Questo permetteva ai vicini rumorosi di influire su molte chiavi di partizione logica non correlate: carichi di lavoro sui dati di funzionalità di nicchia compromettevano i flussi fondamentali di accesso e registrazione.

Figure 02b · Noisy neighbors

Different users, the same physical partition

Cosmos DB containerPhysical partitionPKs: user-id-28104, user-id-10384Conversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversationsuser-id-28104Success rateResponses returned in this animation loop—Conversationsuser-id-10384Success rateResponses returned in this animation loop—

Partizioni deboli

Anche il limite di capacità di elaborazione di una partizione fisica è variabile e dipende dalla capacità di elaborazione totale assegnata al contenitore Azure Cosmos DB. La capacità di elaborazione di cui è stato effettuato il provisioning per il contenitore è distribuita in modo approssimativamente uniforme tra le sue partizioni fisiche.

Al crescere del volume dei dati, le partizioni fisiche si dividono, aumentando il numero totale di partizioni, mentre la capacità di elaborazione totale allocata resta invariata. Di conseguenza, il limite effettivo di capacità di elaborazione per partizione diminuisce all'aumentare delle dimensioni dei dati.

Nei nostri casi d'uso con dati simili a blob e maggiore consumo di spazio, questo crea una criticità: la sola crescita dei dati archiviati riduce la capacità di elaborazione dei carichi di lavoro attivi, anche se la frequenza delle richieste e l'utilizzo restano stabili.

Figure 02C · Weak partitions

The limit falls below demand

A partition splits when it reaches its storage limit. Each child receives half the throughput capacity.

Before split: 10K RU/s capacity

After split: 5K RU/s capacity × 2 partitions

RU/s05K6K10K200200429

Fase II: sperimentare l'isolamento dei carichi di lavoro e l'architettura single-tenant

Usare contenitori separati aiuta a risolvere in parte questi limiti delle chiavi di partizione e i problemi dei vicini rumorosi. A ogni contenitore separato si possono assegnare partizioni fisiche indipendenti: solo i carichi di lavoro al suo interno possono quindi comportarsi da vicini rumorosi o sovrapporsi nello spazio delle chiavi di partizione. D'altro canto, il provisioning di nuovi contenitori comporta un costo operativo: ciascuno richiede una pianificazione specifica della capacità. Vogliamo quindi creare contenitori solo quando ha senso separarli per servizio o caso d'uso.

Abbiamo iniziato a usare contenitori Azure Cosmos DB separati per i nuovi casi d'uso più critici che richiedevano un forte isolamento. Insieme a uno script per gestire le partizioni deboli, che adeguava la capacità di elaborazione totale allocata a ogni contenitore al suo numero di partizioni fisiche, questo ci ha permesso di evitare quasi tutti i problemi che stavamo affrontando.

Tuttavia, essendo partiti da un contenitore predefinito, molti casi d'uso critici preesistenti vi erano purtroppo rimasti vincolati, in assenza di una migrazione dei dati. Per proteggere i servizi critici che dipendevano ancora dai dati nel contenitore predefinito condiviso, consigliavamo spesso ai team di creare contenitori separati per nuovi servizi o funzionalità di prodotto importanti. Man mano che crescevamo, aumentavano sia la pressione sul contenitore predefinito sia gli incidenti in produzione. Abbiamo iniziato a predisporre contenitori per ogni nuovo servizio o funzionalità importante, al solo scopo di proteggere il contenitore predefinito da ulteriore carico.

Questo ci ha evitato molte interruzioni dell'intero sito, limitando l'impatto al degrado o all'interruzione di singole funzionalità o servizi. Alla fine, però, il costo operativo del provisioning di nuovi contenitori si è fatto sentire:

Assenza di una crescita aggregata stabile

Nel vecchio contenitore predefinito condiviso, era improbabile che un nuovo caso d'uso passato da 0 a 1 milione di QPS modificasse in modo significativo l'utilizzo totale aggregato. Su un contenitore isolato, invece, un nuovo caso d'uso passato da 0 a 1 milione di QPS poteva rappresentare un carico 1.000 volte superiore al livello di base, dovuto al solo utilizzo interno di prova.

Con i contenitori dedicati, i team dovevano ora ricordarsi di predisporre in anticipo la capacità di elaborazione prevista per i lanci. Spesso i team di prodotto rilasciavano le funzionalità in produzione dimenticandosi di aumentare prima la capacità. Così, il giorno del lancio ci ritrovavamo nelle sessioni di gestione degli incidenti gravi a tentare di potenziare i contenitori all'ultimo minuto.

È un vantaggio fondamentale della combinazione di carichi di lavoro nei sistemi multi-tenant, a cui rinunciamo in favore di un migliore isolamento con questa strategia dei contenitori.

Figure 03 · Lack of stable aggregate growth

The same workload, different headroom

1K RU/s during load testing → 1M RU/s at launch

Shared container

100M RU/s capacity · 80% utilized

Load test

1K20M=

0.005%

of headroom

Launch

1M20M=

5%

of headroom

New dedicated container

10K RU/s capacity · no other workloads

Load test

1K10K=

10%

of headroom

Launch

1M10K=

10,000%

of headroom

Pianificazione della capacità

Nel tempo, anche monitorare l'utilizzo dei contenitori dedicati ha richiesto una conoscenza approfondita dei prodotti per capire quando il traffico sarebbe cambiato o aumentato.

Possiamo fornire un semplice script del piano di controllo per aumentare la capacità di elaborazione allocata a ogni contenitore in base alle tendenze di crescita. Senza una crescita aggregata stabile, però, dimensionarli correttamente richiedeva di coinvolgere praticamente ogni team di prodotto di OpenAI nella pianificazione della capacità.

Questo approccio non ha retto alla crescita organizzativa: l'attenzione dedicata al provisioning di Azure Cosmos DB variava molto tra i team responsabili dei diversi servizi.

Fase III: adottare il multi-tenant come opzione predefinita, con la possibilità di spostare i dati

Con la crescita dell'organizzazione, quei problemi operativi sono diventati difficili da gestire su scala sempre maggiore. Senza un unico team centrale incaricato della pianificazione, molti team finivano per pianificare male. Siamo quindi tornati verso una soluzione multi-tenant e abbiamo collaborato strettamente con il team Azure per risolvere i problemi iniziali con un'architettura a tenancy condivisa:

Limitazione delle chiavi più attive

Limitare per prime le chiavi più attive impedisce ai vicini rumorosi di consumare tutta la capacità di elaborazione disponibile per una partizione fisica. Questo riduce l'effetto dei vicini rumorosi, per cui una sola chiave molto attiva può avere ripercussioni sull'intero sito.

Qualità del servizio (QoS)

Analogamente, la QoS basata sulla classificazione delle richieste impedisce ai carichi offline, come gli inserimenti massivi o le estrazioni di dati, di utilizzare tutta la capacità di elaborazione di una partizione fisica. Assegnando una priorità inferiore alle richieste di questi carichi di lavoro, le si limita per prime, evitando di limitare le richieste interattive a priorità più alta che servono gli utenti finali.

Modalità serverless

Azure Cosmos DB disponeva già di una modalità serverless che avrebbe risolto il problema delle partizioni deboli riscontrato nella modalità con provisioning. Aveva però un grosso svantaggio: scalava solo in base allo spazio di archiviazione utilizzato e limitava l'uso delle RU a 5K RU/s per partizione fisica. Avrebbe risolto il problema delle partizioni deboli, ma ci avrebbe impedito di scalare i casi d'uso con un elevato consumo di RU.

Il team Azure Cosmos DB ha creato per noi una modalità serverless migliorata, che permette a ogni partizione fisica di arrivare fino a 10K RU/s e introduce divisioni basate sull'utilizzo delle RU, oltre a quelle basate sullo spazio occupato. Questo ci ha consentito di smettere di monitorare e potenziare manualmente i contenitori.

Grazie a queste modifiche, siamo tornati a usare un unico contenitore condiviso. Questo ha semplificato notevolmente la pianificazione iniziale e continua della capacità per i team di prodotto, preservando l'isolamento tra i carichi di lavoro. Nei mesi successivi non abbiamo avuto incidenti legati al provisioning o ai vicini rumorosi, nonostante l'accelerazione dello sviluppo dei prodotti.

Maggiore affidabilità grazie alla gestione congiunta dei servizi

Quando le aziende raggiungono le dimensioni di OpenAI, i servizi gestiti diventano meno “gestiti”. Molti carichi di lavoro diventano casi anomali nell'infrastruttura del provider cloud. Man mano che OpenAI diventava uno dei maggiori utilizzatori di Azure Cosmos DB, gestirlo con successo ha richiesto un approccio diverso: test e dashboard condivisi, canali diretti tra ingegneri e responsabilità congiunta per i guasti che emergono solo alla nostra scala. Oggi il nostro account Azure Cosmos DB più grande archivia decine di petabyte di dati e gestisce oltre 20 milioni di QPS. Per quanto ne sappiamo, siamo l'unica azienda ad aver implementato Azure Cosmos DB su questa scala.

Alla fine del 2024, un'interruzione regionale ha reso evidente perché questa collaborazione fosse necessaria. In quell'occasione, un blackout ha colpito la regione Azure primaria di uno dei nostri account Azure Cosmos DB più grandi. Durante il conseguente failover, ci siamo resi conto che i nostri account più grandi avrebbero impiegato diverse ore per passare a una regione secondaria: un'operazione che né il nostro team né quello di Azure Cosmos DB avevano testato alla nostra scala.

Circa un mese dopo, riportando quegli account nella regione originaria, abbiamo innescato un'altra grave interruzione. Un'ondata di connessioni simultanee verso la regione originaria ha sovraccaricato il gateway Azure Cosmos DB di quella regione. Una particolarità del modo in cui il client Azure Cosmos DB in modalità gateway gestiva i failover induceva tutti i client a stabilire immediatamente connessioni e inviare richieste alla regione originaria, anche se solo una parte delle partizioni fisiche vi era tornata.

A ogni interruzione, sia OpenAI sia Azure hanno applicato i propri processi di gestione degli incidenti, identificando diverse cause profonde e distribuendo le relative correzioni. Ma queste interruzioni erano sintomi di un problema sistemico più ampio, legato al modo in cui il nostro team e quello di Azure Cosmos DB operavano e svolgevano i test. All'inizio, ricorrere a servizi gestiti ci aveva permesso di muoverci rapidamente e concentrarci sugli obiettivi aziendali. Su larga scala, i servizi gestiti diventano meno gestiti e richiedono una stretta integrazione con il fornitore a tutti i livelli, dalla progettazione alle operazioni.

Oggi i team di ingegneria di Habitat e Azure monitorano congiuntamente ogni livello di Habitat, dai nostri livelli di servizio fino a quelli di archiviazione fisica di Azure Cosmos DB. Eseguiamo e testiamo le modifiche su un account Azure Cosmos DB non di produzione, ma delle stesse dimensioni di quello di produzione. Testiamo e perfezioniamo insieme gli SDK e definiamo le best practice per scalare e dimensionare i nostri account Azure Cosmos DB. I nostri ingegneri possono contattarsi direttamente tramite Teams e abbiamo persino predisposto modi per allertarci a vicenda senza passare dai vari livelli di assistenza.

Nell'ultimo anno abbiamo svolto un intenso lavoro congiunto per migliorare la scalabilità del piano di controllo e del gateway di Azure Cosmos DB. Abbiamo aggiunto al gateway protezioni dal carico e una cache di vari metadati di Azure Cosmos DB per proteggerne la partizione dei metadati; ridotto la pressione esercitata dall'insieme dei nostri processi Python habitat-service tramite il pooling e un maggiore riutilizzo delle connessioni; collocato i carichi di lavoro nella stessa sede dove possibile; e isolato i carichi critici da quelli offline usando VPC diverse.

Questo livello di collaborazione ingegneristica è stato fondamentale per la stabilità di Azure Cosmos DB nell'ultimo anno. Qualsiasi sistema sufficientemente grande o critico richiede questo tipo di lavoro di squadra: superare le divisioni di responsabilità e collaborare oltre i confini organizzativi.

Migliorare le prestazioni su scala globale

Come accennato nella Parte I, la nostra filosofia di fondo è: farlo funzionare, renderlo veloce, renderlo economico. Prima l'affidabilità, poi le prestazioni, infine l'efficienza. Habitat era diventato un'infrastruttura critica per OpenAI, quindi la prima priorità era renderlo affidabile. Nel 2025, mentre il sistema si stabilizzava e OpenAI si espandeva a livello globale, la latenza è diventata il vincolo successivo per utenti e team di prodotto.

Le risorse di calcolo e archiviazione si trovavano spesso in regioni diverse, rendendo inevitabili le richieste tra regioni.

Figure 04 · Improving performance at a global scale

Global traffic

Compute and storage capacity were often located in different regions, making cross-region requests unavoidable.

ComputeStorageIllustrative routes · Longer journeys take more time

Le posizioni geografiche delle risorse di calcolo e archiviazione sono puramente illustrative.

La latenza variava notevolmente in base alla posizione dei dati e al luogo in cui veniva gestita la richiesta. Per rendere veloci le richieste a Habitat, gli sviluppatori dei prodotti ricorrevano a cache aggiuntive lato client, raggruppavano le richieste in batch, riducevano al minimo letture e scritture nel database e spostavano le operazioni fuori dal percorso critico ovunque possibile. Questo ha portato ad alcune soluzioni di progettazione interessanti, più resilienti ai problemi del database, ma ha anche reso i prodotti più complessi e rallentato le iterazioni. Veniva così meno l'obiettivo di Habitat: favorire uno sviluppo rapido dei prodotti senza dover conoscere il funzionamento del database.

Nel 2026 ci siamo concentrati molto su questo aspetto e abbiamo sviluppato una strategia a più livelli per ottimizzare le prestazioni delle letture, che rappresentano oltre il 90% delle richieste a Habitat:

Archiviare i dati vicino agli utenti quando la capacità lo consente

Disponiamo di diversi account Azure Cosmos DB in varie regioni Azure nel mondo per gli utenti con requisiti di residenza dei dati. Anche per gli utenti senza requisiti vincolanti di residenza dei dati, collocare i dati nelle vicinanze migliora l'esperienza grazie a una minore latenza.

In pratica, non riusciamo ad adottare questa soluzione su larga scala al di fuori dei casi d'uso legati alla residenza dei dati, perché la collocazione è vincolata dalla capacità fisica del cloud. Date le dimensioni dei nostri dataset, molte regioni non possono offrire l'enorme spazio di archiviazione necessario per la quota di traffico utenti che spetterebbe loro.

Nelle regioni con capacità limitata dei data center, di alimentazione elettrica o di raffreddamento, a volte dobbiamo scegliere: riservare capacità allo storage in loco e ridurre la latenza di Habitat, oppure usarla per ulteriori risorse di calcolo dedicate alla gestione delle richieste e instradare quelle di archiviazione più lontano.

Di conseguenza, le decisioni sulla collocazione richiedono spesso un compromesso di prodotto tra: A) ottimizzazione della latenza per diversi prodotti e gruppi di utenti e B) capacità di archiviazione e calcolo disponibile.

Replicare su larga scala solo i dataset più piccoli e più sensibili alla latenza

Per i carichi di lavoro più sensibili alla latenza, come i metadati di autenticazione necessari a ogni richiesta, riduciamo il tempo di andata e ritorno (RTT) tra le risorse di calcolo e Habitat usando account Azure Cosmos DB ampiamente replicati, con molte regioni secondarie. Alcuni account replicano i dati in tutte le 40 regioni Azure in cui gestiamo il traffico; altri in almeno una regione per continente.

Abbiamo dovuto integrare in Habitat, prima di quanto avremmo voluto, un livello trasparente di spostamento dei dati e instradamento, per poter trasferire i dataset tra account con topologie di replica diverse.

Anche questo approccio, però, è in ultima analisi vincolato dalla capacità e quindi limitato a pochi dataset selezionati con cura.

Affidarsi alla cache integrata per tutto il resto

I carichi di lavoro di Habitat sono prevalentemente di lettura: le applicazioni recuperano continuamente impostazioni, stato e altre proprietà. Una quota sproporzionata del traffico è dovuta a un insieme di lavoro relativamente piccolo di tipi di dati consultati spesso. Questo rende il sistema particolarmente adatto al caching distribuito. Il caching migliora le prestazioni nelle infrastrutture a regione singola, ma i benefici si amplificano quando si opera su più regioni, dove può “nascondere” la latenza tra regioni.

Abbiamo integrato in Habitat un livello di cache distribuita di tipo lookaside. Memorizza una copia degli oggetti e degli archi consultati di recente in server Valkey collocati nella nostra infrastruttura, vicino a dove vengono elaborate le richieste degli utenti. In genere, queste cache usano un TTL che bilancia il tasso di hit e l'obsolescenza dei dati. TTL brevi riducono l'obsolescenza, ma attenuano i benefici del caching: gli elementi scadono rapidamente e devono essere recuperati più spesso. TTL più lunghi migliorano il tasso di hit, ma forniscono dati obsoleti per periodi prolungati, peggiorando l'esperienza utente e rendendo imprevedibile il comportamento del prodotto.

Un altro approccio consiste nell'eseguire invalidazioni esplicite quando l'applicazione effettua scritture sugli stessi dati sottostanti. Questo migliora notevolmente l'aggiornamento dei dati in cache, ma può essere difficile da implementare correttamente. Resta inoltre esposto a vari casi limite, come i guasti parziali, che lasciano la cache obsoleta. Le implementazioni multiregione amplificano questi problemi: le reti tra regioni sono meno affidabili e la cache è soggetta a condizioni di concorrenza con la replica dello storage sottostante. Integrare questo approccio con i TTL pone un limite massimo all'obsolescenza, ma comporta comunque lo stesso compromesso dei TTL descritto sopra.

Abbiamo adottato un approccio più avanzato all'invalidazione, seguendo l'esempio di sistemi come TAO di Meta. La cache integrata di Habitat si basa sull'acquisizione delle modifiche ai dati (CDC) tramite il feed di modifiche di Azure Cosmos DB per propagare le invalidazioni a livello globale in pochi secondi, ottenendo alti tassi di hit con una bassa obsolescenza. Un servizio nella nostra infrastruttura legge questo feed ed esegue invalidazioni nelle cache locali quando i dati sottostanti vengono aggiornati. La lettura successiva genera quindi un cache miss e la cache viene popolata con la versione più recente dei dati.

Ridurre l'overhead del proxy con Rust

Abbiamo anche ridotto sensibilmente la latenza eliminando l'overhead del servizio Habitat stesso, riscrivendo il codice in Rust, più performante rispetto al Python originale.

Nel complesso, queste modifiche hanno ridotto la latenza p50 globale delle letture da circa 120 ms a circa 10 ms: un miglioramento di 12 volte da quando, all'inizio di quest'anno, abbiamo iniziato a concentrarci sulle prestazioni.

Figure 05 · Improving performance at a global scale

p50 latency for reads

Three major design changes improved read latency

roughly 120 ms → roughly 10 ms

12x improvement

Milliseconds020406080100120140160Tempo123
  1. critical data replicated into nearby geos
  2. in-region caching launched
  3. rust rewrite launched

Il caching ha ridotto drasticamente la latenza mediana, ma le richieste più lente richiedono ancora di recuperare dati non in cache dallo storage sottostante, spesso in regioni lontane. Man mano che si rende disponibile più capacità e miglioriamo l'efficienza dello storage, intendiamo affrontare una sfida più fondamentale: collocare fin dall'inizio i dati vicino a dove verranno consultati.

Scalare l'acquisizione delle modifiche ai dati

Una CDC affidabile e scalabile, che propaga le modifiche del database ai sistemi a valle, è fondamentale non solo per la strategia di caching di Habitat. Milioni di modifiche acquisite al secondo, su migliaia di contenitori e milioni di partizioni, transitano attraverso oltre cento topic Kafka per alimentare anche indici secondari a consistenza eventuale, pipeline di spostamento dei dati, analisi offline e altre funzionalità di prodotto in tempo reale. Con l'espansione globale dell'infrastruttura sottostante, mettersi in ascolto delle modifiche alle righe è diventato sempre più complesso. Se un client vuole registrare una callback da eseguire quando cambiano righe del proprio tipo, deve prima, tra le altre cose, individuare gli account e i contenitori Cosmos che ospitano i suoi dati, identificare l'istanza del servizio CDC in ascolto su quei contenitori nelle rispettive regioni e dimensionare correttamente l'insieme dei worker CDC, Kafka e il proprio consumer. La nostra piattaforma CDC mira a nascondere tutta questa complessità agli sviluppatori dei prodotti, offrendo un'interfaccia semplice: i client registrano una funzione da chiamare sulle righe modificate e noi gestiamo tutti i collegamenti e la scalabilità.

Un piano di controllo interno verifica continuamente che vengano acquisite le modifiche di tutti i contenitori necessari, seleziona le regioni Cosmos da cui leggere e distribuisce il lavoro in modo efficiente tra migliaia di worker in più regioni geografiche. L'uso di regioni replica e di letture del feed a priorità inferiore protegge il traffico Habitat sensibile alla latenza dai carichi di acquisizione in background. In un insieme di istanze così vasto, crash dei worker, problemi dei contenitori, errori nell'archivio dei lease, configurazioni non valide e guasti delle destinazioni si verificano di continuo. Abbiamo reso più robusti il ciclo di vita dei worker CDC e la gestione dei lease, isolato i guasti tra i flussi e introdotto protezioni per le modifiche di configurazione e i rilasci. Usiamo code dead-letter per evitare che errori permanenti di consegna blocchino l'avanzamento della coda. Tolleriamo le interruzioni regionali spostando i lettori su regioni Cosmos alternative. Alla nostra scala, abbiamo persino dovuto modificare l'SDK CDC di Azure per rendere più efficiente il checkpointing, raggruppando i checkpoint in memoria e scrivendoli su storage solo periodicamente.

Sebbene l'uso interno di Azure Cosmos DB in Habitat sia complesso, il suo valore è sempre stato nascondere i dettagli infrastrutturali agli sviluppatori per accelerare lo sviluppo dei prodotti. La nostra piattaforma CDC non fa eccezione.

Uno sguardo al futuro

Un piccolo team è riuscito a sostenere una piattaforma di archiviazione in rapida crescita mantenendo volutamente circoscritta l'API di archiviazione, continuando a scalare il servizio Python e dando priorità alle sfide ingegneristiche più urgenti. Ci siamo concentrati prima sull'affidabilità per il traffico dei prodotti mission-critical, poi sulle prestazioni di lettura per gli utenti di tutto il mondo e infine sull'efficienza operativa su larga scala. Questo approccio ha consentito una crescita rapida e nuovi casi d'uso, migliorando al contempo affidabilità, prestazioni e velocità di sviluppo dei prodotti.

Per scalare Habitat e l'archiviazione delle conversazioni in vista del prossimo miliardo di utenti, dobbiamo affrontare la difficoltà di replicare i dati nella nostra infrastruttura su una scala ancora maggiore. Per anticipare questa esigenza, stiamo sviluppando internamente un sistema di archiviazione online su misura, su scala exabyte, ottimizzato per i tipi di dati voluminosi di Habitat. Questo sistema in fase di sviluppo combina il nucleo RocksDB Cloud di Rockset con la replica basata su Raft. Implementa lo stesso modello documentale di Azure Cosmos DB, ma usa storage disaggregato e replica fisica per ridurre la capacità flash necessaria, accettando in cambio un picco di QPS inferiore.

Se ti interessa lavorare a progetti di infrastruttura dati di questo tipo su scala di frontiera, scopri le nostre posizioni aperte⁠.

Autore

OpenAI