Parte 1: Come i nodi compute Postgres di grandi dimensioni sono fino a 2 volte più veloci con una latenza inferiore
Il modello di storage disaggregato di Lakebase Postgres offre una piattaforma ricca di funzionalità, flessibile ed economica. Una memorizzazione nella cache efficiente dei dati è fondamentale per garantire un throughput elevato e una bassa latenza quando i dati sono archiviati in un object store come S3.
Questa memorizzazione nella cache avviene a due livelli: nello storage distribuito, dove le pagine Postgres vengono materializzate per un elevato throughput di scrittura e per la gestione delle letture; e sul compute Postgres stesso per servire le pagine ad accesso frequente dalla DRAM per un accesso ultra-rapido.
Abbiamo lavorato sodo per apportare miglioramenti alla memorizzazione nella cache lato compute e in questo blog illustreremo i nostri piani a breve termine ed esploreremo ciò che è già stato distribuito ai clienti.
Innanzitutto, un po' di contesto su come siamo arrivati qui.
I database sono notoriamente affamati di DRAM (memoria). Utilizzano principalmente questa memoria come cache di dati e si aspettano che l'accesso alle righe nella cache sia misurato in nanosecondi, ordini di grandezza più veloce anche delle unità NVMe più rapide.
Postgres organizza i dati in righe su pagine, e le pagine a cui si accede attivamente devono essere caricate in un'area di memoria nota come "shared buffers". Poiché Postgres tradizionalmente memorizza le pagine utilizzando il filesystem del sistema operativo, anche il kernel del OS utilizzerà la sua page cache flessibile per fornire la memorizzazione nella cache tra gli shared buffers di Postgres e il disco.
Questo schema shared buffers + page cache funziona abbastanza bene, ma presenta alcuni svantaggi e alcune sfide.

Ora che abbiamo fornito un po' di contesto, parliamo di come li stiamo risolvendo in Databricks.
Il nostro obiettivo finale è fare l'uso più efficiente possibile della DRAM sul tuo compute tramite shared buffers dinamici di Postgres che eseguono l'autoscaling con il tuo carico di lavoro e utilizzano fino al 75% della memoria disponibile.
Dobbiamo infine adattare la nostra piattaforma compute per sfruttare gli shared buffers con autoscaling, ma vogliamo anche fornire miglioramenti incrementali significativi ai nostri clienti man mano che diventano disponibili. Ogni rilascio incrementale ci consente di distribuire con sicurezza uno o più elementi della roadmap, offrendo al contempo un vantaggio reale ai clienti. Quindi, anche se l'obiettivo sono i compute con autoscaling, abbiamo iniziato con compute fissi, come descritto nella sezione successiva.
Ecco cosa abbiamo implementato.

Se ricordi le sfide tecniche sopra menzionate, un sistema disaggregato come Lakebase non instrada le sue letture attraverso il file system standard del OS e la sua page cache. Ricorda anche che gli shared buffers di Postgres sono statici e non possono eseguire l'autoscaling.
Per risolvere questo problema abbiamo creato un livello che abbiamo chiamato local file cache (LFC). L'LFC ha agito come sostituto, creando una cache con autoscaling che funzionava in tandem con gli shared buffers e manteneva quanti più dati possibile memorizzati nella cache sul compute. Questa è stata una soluzione intelligente e pragmatica che ha permesso a Lakebase Postgres di lanciare l'autoscaling ed è stata utilizzata su tutti i compute sin dal lancio.
Sebbene esposta agli utenti come un'unica cache di compute ad alta velocità, l'architettura sottostante supporta fino a due livelli:
Gli shared buffers sono stati sintonizzati in modo conservativo in modo da non consumare troppa memoria durante l'esecuzione alla CU minima configurata, con la dimensione massima mai configurata a 1 GB di shared buffers e l'LFC che consuma il resto della capacità totale della cache del compute (fino al 75% della DRAM). Qualsiasi richiesta che si traduce in un miss in entrambi i livelli viene instradata dal nodo compute al livello di storage distribuito.
Su working set più grandi, limitare gli shared buffers a 1 GB ha costretto la maggior parte dei cache hit a passare attraverso il livello LFC più lento. L'LFC ci ha servito bene, ma la nostra intenzione è di ritirare la sua forma attuale man mano che procediamo verso shared buffers completamente dinamici.
Nota: i compute fissi sono arrivati per primi La nostra prima fornitura di shared buffers più grandi si rivolge a compute di dimensioni fisse, poiché gli shared buffers non sono ancora dinamici. Su questi, ora disabilitiamo l'LFC e impostiamo gli shared buffers al 75% della DRAM. Questo è attivo oggi per i compute di dimensioni fisse con CU >= 80. L'eliminazione del limite di buffer di circa 1 GB mantiene le pagine calde nel livello di memoria più veloce invece di scendere a cascata nello storage di file locale. Per verificare se gli shared buffers di grandi dimensioni sono abilitati per il tuo compute, esegui |
Mantenere i dati caldi negli shared buffers anziché nella page cache del OS risolve anche gli svantaggi descritti in precedenza. Non c'è un doppio buffering, quindi 1 GB di dati memorizzati nella cache consuma 1 GB di RAM invece di 2 GB. E poiché la cache risiede all'interno di Postgres anziché nel kernel, le decisioni di rimozione possono essere prese conoscendo lo stato del database; questo ci consente di perseguire criteri di sostituzione più intelligenti rispetto a quelli offerti dal OS.
Il dimensionamento degli shared buffers al 75% della DRAM su compute di dimensioni fisse non è stato semplice come apportare una modifica alla configurazione. Questo a causa della terza sfida tecnica, l'architettura process-per-backend.
La sezione successiva descrive la nostra soluzione.
Postgres utilizza una struttura basata su processi in cui ogni backend mappa gli shared buffers nel proprio spazio di indirizzamento, richiedendo le proprie voci della tabella delle pagine (le strutture gestite dal kernel che l'hardware percorre per tradurre gli indirizzi virtuali in memoria fisica). Per impostazione predefinita, Linux esegue questa mappatura su pagine da 4 KB.
Alcuni semplici numeri: ogni 1 GB di shared buffers corrisponde a 262.144 voci della tabella delle pagine per processo. Con 32 GB di shared buffers e 512 backend, si tratta di circa 4,3 miliardi di voci, ovvero circa 32 GB di tabelle delle pagine per mappare 32 GB di cache.
Questo working set supera di gran lunga anche la capacità del Translation Lookaside Buffer (TLB), una cache nell'unità di gestione della memoria della CPU che accelera la traduzione da virtuale a fisico. Anche un hit dello shared buffer comporta quindi una penalità dovuta a TLB miss e percorsi della tabella delle pagine.
Per mitigare questo problema, la community di Postgres consiglia di utilizzare un meccanismo del OS chiamato huge pages (da 2 MB ciascuna) con shared buffers di grandi dimensioni. Il passaggio alle huge pages riduce le dimensioni della tabella delle pagine di un fattore pari a 512 e riduce significativamente i tassi di TLB miss.
Lakebase Postgres viene eseguito all'interno di macchine virtuali guest leggere su host bare-metal. La traduzione degli indirizzi di memoria coinvolge due livelli virtualizzati. Sfruttare le huge pages richiede un'implementazione coerente in tutto lo stack: dalla prenotazione a livello di host, attraverso l'hypervisor che supporta la memoria della VM, fino al kernel guest. Un'interruzione in qualsiasi livello degrada i vantaggi prestazionali risultanti.
Di recente abbiamo introdotto il supporto dedicato per le huge pages nella nostra infrastruttura VM. Abbiamo scelto di utilizzare pagine HugeTLB esplicite da 2 MB anziché affidarci a transparent huge pages best-effort. Ora, le VM allocate per compute di grandi dimensioni fisse si inizializzano con un volume predeterminato di huge pages sufficiente per l'avvio di Postgres. Per ottimizzare le risorse di sistema, l'avvio del compute rilascia automaticamente le huge pages in eccedenza rispetto a quelle richieste da Postgres.
Suggerimento: per verificare se le huge pages esplicite di grandi dimensioni sono abilitate per il tuo compute, esegui show huge_pages all'interno di una connessione Postgres. Un endpoint Lakebase da 80 CU dovrebbe mostrare un valore pari a "on" |
Il rollout è iniziato regione per regione qualche settimana fa. Gli esempi seguenti sono stati misurati su endpoint di produzione di grandi dimensioni dopo il riavvio che ha abilitato la nuova configurazione.
Su un endpoint di grandi dimensioni, la modifica è diventata attiva intorno alle 06:10 UTC dell'11 agosto. I blocchi Postgres letti al secondo sono raddoppiati, dato che qui usiamo come proxy per il throughput. Il cliente ha riscontrato una latenza p50 e p99 inferiore rispetto al giorno, alla settimana e al mese precedenti.

Questo endpoint configurava una cache di file locali di grandi dimensioni. Con buffer condivisi più grandi, le GetPage/s dello storage sono scese da circa 8K al secondo a circa 1,5K.

Su un altro endpoint di grandi dimensioni, la modifica è diventata attiva intorno alle 01:30 UTC del 14 agosto. Il throughput è aumentato di circa il 43%.

La cache hit rate del compute ha raggiunto quasi il 100%, con richieste servite quasi interamente dai buffer condivisi.

Su questo workload, l'uso della CPU è sceso da 20 core a 4 dopo il rollout del 15 agosto. La cache hit rate del compute è salita a quasi il 100% e il throughput misurato è raddoppiato.


Attualmente stiamo lavorando per portare buffer condivisi più grandi sui compute Postgres con autoscaling. L'autoscaling introduce un'ulteriore complessità: dobbiamo espandere dinamicamente i buffer condivisi durante lo scale-up e ridurli durante lo scale-down, il tutto allocando l'esatto volume richiesto di huge page.
Per andare oltre i compute a dimensione fissa, abbiamo sviluppato un protocollo per l'autoscaling delle huge page fornite al guest. Le huge page vengono scalate in concerto con i buffer condivisi dinamici, garantendo il mantenimento di una traduzione degli indirizzi efficiente anche in presenza di elevata concorrenza e dimensioni di memoria elevate. Il nostro prossimo post (parte 2) entrerà nei dettagli tecnici di questa implementazione dei buffer condivisi dinamici, incluso lo stato attuale di Postgres open source e le aree in cui abbiamo scelto di far progredire ulteriormente la funzionalità e contribuire upstream.
Tutti questi miglioramenti delle prestazioni derivano dall'architettura Lakebase Postgres. Il livello di storage funge da system of record autorevole, un nodo di compute è stateless e la sua memoria funge da livello di caching.
Distribuisci Lakebase Postgres e metti alla prova le prestazioni. Inizia da qui.
Lakebase Postgres può essere utilizzato come database autonomo e puoi anche integrarlo con il resto della piattaforma Databricks Data + AI: governance di Unity Catalog, analisi lakehouse, notebook e workflow di AI.
(Questo post sul blog è stato tradotto utilizzando strumenti basati sull'intelligenza artificiale) Post originale
Iscriviti al nostro blog e ricevi gli ultimi articoli direttamente nella tua casella di posta.