Compressione dei dati in Apache Ignite. L'esperienza di Sber

Compressione dei dati in Apache Ignite. L'esperienza di SberQuando si lavora con grandi volumi di dati, a volte può sorgere il problema della mancanza di spazio su disco. Un modo per risolvere questo problema è la compressione, grazie alla quale, a parità di attrezzatura, è possibile permettersi di aumentare i volumi di stoccaggio. In questo articolo esamineremo come funziona la compressione dei dati in Apache Ignite. Questo articolo descriverà solo i metodi di compressione del disco implementati nel prodotto. Altri metodi di compressione dei dati (in rete, in memoria), implementati o meno, rimarranno fuori dal campo di applicazione.

Quindi, con la modalità persistenza abilitata, a seguito delle modifiche ai dati nelle cache, Ignite inizia a scrivere sul disco:

  1. Contenuto delle cache
  2. Write Ahead Log (di seguito semplicemente WAL)

Da qualche tempo esiste un meccanismo per la compressione WAL, chiamato compattazione WAL. Apache Ignite 2.8 rilasciato di recente ha introdotto altri due meccanismi che consentono di comprimere i dati su disco: compressione della pagina del disco per comprimere il contenuto delle cache e compressione dell'istantanea della pagina WAL per comprimere alcune voci WAL. Maggiori dettagli su tutti e tre questi meccanismi di seguito.

Compressione delle pagine del disco

Come funziona

Innanzitutto, diamo uno sguardo molto breve al modo in cui Ignite archivia i dati. La memoria della pagina viene utilizzata per l'archiviazione. La dimensione della pagina viene impostata all'inizio del nodo e non può essere modificata in fasi successive; inoltre, la dimensione della pagina deve essere una potenza di due e un multiplo della dimensione del blocco del file system. Le pagine vengono caricate nella RAM dal disco secondo necessità; la dimensione dei dati sul disco potrebbe superare la quantità di RAM allocata. Se non c'è abbastanza spazio nella RAM per caricare una pagina dal disco, le pagine vecchie e non più utilizzate verranno eliminate dalla RAM.

I dati vengono archiviati su disco nella seguente forma: per ciascuna partizione di ciascun gruppo di cache viene creato un file separato; in questo file le pagine vengono visualizzate una dopo l'altra in ordine di indice crescente. L'identificatore della pagina intera contiene l'identificatore del gruppo di cache, il numero della partizione e l'indice della pagina nel file. Pertanto, utilizzando l'identificatore della pagina intera, possiamo determinare in modo univoco il file e l'offset nel file per ciascuna pagina. Puoi leggere ulteriori informazioni sulla memoria di paging nell'articolo Wiki di Apache Ignite: Accendi il negozio persistente - sotto il cofano.

Il meccanismo di compressione della pagina del disco, come puoi intuire dal nome, funziona a livello di pagina. Quando questo meccanismo è abilitato, i dati nella RAM vengono elaborati così come sono, senza alcuna compressione, ma quando le pagine vengono salvate dalla RAM su disco, vengono compresse.

Ma comprimere ogni pagina individualmente non è una soluzione al problema; è necessario in qualche modo ridurre la dimensione dei file di dati risultanti. Se la dimensione della pagina non è più fissa, non possiamo più scrivere le pagine nel file una dopo l'altra, poiché ciò può creare una serie di problemi:

  • Utilizzando l'indice della pagina, non saremo in grado di calcolare l'offset con il quale si trova nel file.
  • Non è chiaro cosa fare con le pagine che non si trovano alla fine del file e cambiano dimensione. Se la dimensione della pagina diminuisce, lo spazio liberato scompare. Se la dimensione della pagina aumenta, è necessario cercarla in una nuova posizione nel file.
  • Se una pagina si sposta di un numero di byte che non è multiplo della dimensione del blocco del file system, per leggerla o scriverla sarà necessario toccare un ulteriore blocco del file system, il che può portare a un degrado delle prestazioni.

Per evitare di risolvere questi problemi al proprio livello, la compressione delle pagine del disco in Apache Ignite utilizza un meccanismo di file system chiamato file sparsi. Un file sparso è quello in cui alcune regioni riempite con zero possono essere contrassegnate come "buchi". In questo caso, nessun blocco del file system verrà allocato per archiviare questi buchi, con conseguente risparmio di spazio su disco.

È logico che per liberare un blocco del file system, la dimensione del buco debba essere maggiore o uguale al blocco del file system, il che impone un'ulteriore limitazione alla dimensione della pagina e ad Apache Ignite: affinché la compressione abbia qualche effetto, la dimensione della pagina deve essere strettamente maggiore della dimensione del blocco del file system. Se la dimensione della pagina è uguale alla dimensione del blocco, allora non potremo mai liberare un singolo blocco, poiché per liberare un singolo blocco, la pagina compressa deve occupare 0 byte. Se la dimensione della pagina è pari a quella di 2 o 4 blocchi, potremo già liberare almeno un blocco se la nostra pagina sarà compressa almeno al 50% o al 75%, rispettivamente.

Quindi, la descrizione finale di come funziona il meccanismo: Quando si scrive una pagina su disco, viene effettuato un tentativo di comprimere la pagina. Se la dimensione della pagina compressa consente di liberare uno o più blocchi del file system, allora la pagina viene scritta in forma compressa e al posto dei blocchi liberati viene creato un “buco” (viene eseguita una chiamata di sistema fallocate() con la bandiera del foro). Se la dimensione della pagina compressa non consente la liberazione dei blocchi, la pagina viene salvata così com'è, non compressa. Tutti gli offset pagina vengono calcolati allo stesso modo di senza compressione, moltiplicando l'indice della pagina per la dimensione della pagina. Non è necessario spostare le pagine da solo. Gli offset delle pagine, proprio come senza la compressione, rientrano nei limiti dei blocchi del file system.

Compressione dei dati in Apache Ignite. L'esperienza di Sber

Nell'attuale implementazione, Ignite può funzionare solo con file sparsi nel sistema operativo Linux; di conseguenza, la compressione della pagina del disco può essere abilitata solo quando si utilizza Ignite su questo sistema operativo.

Algoritmi di compressione che possono essere utilizzati per la compressione delle pagine del disco: ZSTD, LZ4, Snappy. Inoltre esiste una modalità operativa (SKIP_GARBAGE), in cui viene espulso solo lo spazio inutilizzato nella pagina senza applicare la compressione sui dati rimanenti, il che riduce il carico sulla CPU rispetto agli algoritmi precedentemente elencati.

Impatto sulle prestazioni

Sfortunatamente, non ho effettuato misurazioni effettive delle prestazioni su stand reali, poiché non prevediamo di utilizzare questo meccanismo nella produzione, ma teoricamente possiamo speculare su dove perderemo e dove vinceremo.

Per fare ciò, dobbiamo ricordare come vengono lette e scritte le pagine quando si accede:

  • Quando si esegue un'operazione di lettura, viene prima cercata nella RAM; se la ricerca non ha successo, la pagina viene caricata in RAM dal disco dallo stesso thread che esegue la lettura.
  • Quando viene eseguita un'operazione di scrittura, la pagina nella RAM viene contrassegnata come sporca, ma la pagina non viene salvata fisicamente su disco immediatamente dal thread che esegue la scrittura. Tutte le pagine dirty vengono salvate su disco successivamente nel processo di checkpoint in thread separati.

Quindi l'impatto sulle operazioni di lettura è:

  • Positivo (IO del disco), a causa di una diminuzione del numero di blocchi del file system letti.
  • Negativo (CPU), a causa del carico aggiuntivo richiesto dal sistema operativo per lavorare con file sparsi. È anche possibile che qui vengano implicitamente visualizzate ulteriori operazioni di I/O per salvare una struttura di file sparsi più complessa (sfortunatamente, non ho familiarità con tutti i dettagli su come funzionano i file sparsi).
  • Negativo (CPU), a causa della necessità di decomprimere le pagine.
  • Non vi è alcun impatto sulle operazioni di scrittura.
  • Impatto sul processo del checkpoint (qui tutto è simile alle operazioni di lettura):
  • Positivo (IO del disco), a causa di una diminuzione del numero di blocchi del file system scritti.
  • Negativo (CPU, possibilmente I/O del disco), dovuto al lavoro con file sparsi.
  • Negativo (CPU), a causa della necessità di compressione della pagina.

Quale lato della bilancia farà pendere la bilancia? Tutto dipende molto dall'ambiente, ma sono propenso a credere che la compressione della pagina del disco molto probabilmente porterà a un degrado delle prestazioni sulla maggior parte dei sistemi. Inoltre, i test su altri DBMS che utilizzano un approccio simile con file sparsi mostrano un calo delle prestazioni quando la compressione è abilitata.

Come abilitare e configurare

Come accennato in precedenza, la versione minima di Apache Ignite che supporta la compressione delle pagine del disco è la 2.8 ed è supportato solo il sistema operativo Linux. Abilitare e configurare come segue:

  • Deve essere presente un modulo di compressione ignite nel percorso di classe. Per impostazione predefinita, si trova nella distribuzione Apache Ignite nella directory libs/opzionale e non è incluso nel percorso di classe. Puoi semplicemente spostare la directory su di un livello in libs e poi quando la esegui tramite ignite.sh verrà automaticamente abilitata.
  • La persistenza deve essere abilitata (Abilitata tramite DataRegionConfiguration.setPersistenceEnabled(true)).
  • La dimensione della pagina deve essere maggiore della dimensione del blocco del file system (è possibile impostarla utilizzando DataStorageConfiguration.setPageSize() ).
  • Per ciascuna cache i cui dati devono essere compressi, è necessario configurare il metodo di compressione e (facoltativamente) il livello di compressione (metodi CacheConfiguration.setDiskPageCompression() , CacheConfiguration.setDiskPageCompressionLevel()).

Compattazione WAL

Come funziona

Cos'è WAL e perché è necessario? Molto brevemente: questo è un registro che contiene tutti gli eventi che alla fine modificano la memorizzazione della pagina. Serve soprattutto per poter recuperare in caso di caduta. Qualsiasi operazione, prima di dare il controllo all'utente, deve prima registrare un evento in WAL, in modo che in caso di fallimento, sia possibile riprodurre nel registro e ripristinare tutte le operazioni per le quali l'utente ha ricevuto una risposta positiva, anche se tali operazioni non ha avuto il tempo di riflettersi nell'archiviazione della pagina su disco (già sopra è stato descritto che la scrittura effettiva nell'archivio della pagina viene eseguita in un processo chiamato "checkpoint" con un certo ritardo da thread separati).

Le voci in WAL sono divise in logiche e fisiche. Quelli booleani sono le chiavi e i valori stessi. Fisico: riflette le modifiche alle pagine nell'archivio pagine. Mentre i record logici possono essere utili per altri casi, i record fisici sono necessari solo per il ripristino in caso di arresto anomalo e i record sono necessari solo dopo l'ultimo checkpoint riuscito. Qui non entreremo nel dettaglio e spiegheremo perché funziona in questo modo, ma chi fosse interessato può fare riferimento al già citato articolo sulla Wiki di Apache Ignite: Accendi il negozio persistente - sotto il cofano.

Spesso sono presenti più record fisici per record logico. Cioè, ad esempio, un'operazione di inserimento nella cache influisce su diverse pagine nella memoria della pagina (una pagina con i dati stessi, pagine con indici, pagine con elenchi liberi). In alcuni test sintetici ho riscontrato che i record fisici occupavano fino al 90% del file WAL. Tuttavia, sono necessari per un tempo molto breve (per impostazione predefinita, l'intervallo tra i checkpoint è di 3 minuti). Sarebbe logico sbarazzarsi di questi dati dopo aver perso la loro rilevanza. Questo è esattamente ciò che fa il meccanismo di compattazione WAL: elimina i record fisici e comprime i record logici rimanenti utilizzando zip, mentre la dimensione del file viene ridotta in modo molto significativo (a volte di decine di volte).

Fisicamente, WAL è costituito da diversi segmenti (10 per impostazione predefinita) di dimensione fissa (64 MB per impostazione predefinita), che vengono sovrascritti in modo circolare. Non appena il segmento corrente viene riempito, il segmento successivo viene assegnato come corrente e il segmento riempito viene copiato nell'archivio tramite un thread separato. La compattazione WAL funziona già con i segmenti di archivio. Inoltre, come thread separato, monitora l'esecuzione del checkpoint e inizia la compressione nei segmenti di archivio per i quali i record fisici non sono più necessari.

Compressione dei dati in Apache Ignite. L'esperienza di Sber

Impatto sulle prestazioni

Poiché la compattazione WAL viene eseguita come thread separato, non dovrebbe esserci alcun impatto diretto sulle operazioni eseguite. Ma sottopone comunque un ulteriore carico in background alla CPU (compressione) e al disco (leggendo ogni segmento WAL dall'archivio e scrivendo i segmenti compressi), quindi se il sistema funziona alla sua capacità massima, ciò porterà anche a un degrado delle prestazioni.

Come abilitare e configurare

È possibile abilitare la compattazione WAL utilizzando la proprietà WalCompactionEnabled в DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Inoltre, utilizzando il metodo DataStorageConfiguration.setWalCompactionLevel(), è possibile impostare il livello di compressione se non si è soddisfatti del valore predefinito (BEST_SPEED).

Compressione delle istantanee della pagina WAL

Come funziona

Abbiamo già scoperto che in WAL i record sono divisi in logici e fisici. Per ogni modifica a ciascuna pagina, viene generato un record WAL fisico nella memoria della pagina. I record fisici, a loro volta, sono anch'essi divisi in 2 sottotipi: record di snapshot di pagina e record delta. Ogni volta che modifichiamo qualcosa su una pagina e la trasferiamo da uno stato pulito a uno stato sporco, una copia completa di questa pagina viene archiviata in WAL (page snapshot record). Anche se cambiassimo solo un byte in WAL, il record sarà leggermente più grande della dimensione della pagina. Se modifichiamo qualcosa su una pagina già sporca, si forma un delta record in WAL, che riflette solo le modifiche rispetto allo stato precedente della pagina, ma non l'intera pagina. Poiché il ripristino dello stato delle pagine da sporco a pulito viene eseguito durante il processo del checkpoint, immediatamente dopo l'inizio del checkpoint, quasi tutti i record fisici saranno costituiti solo da istantanee delle pagine (poiché tutte le pagine immediatamente dopo l'inizio del checkpoint sono pulite). , quindi mentre ci avviciniamo al checkpoint successivo, la frazione del record delta inizia a crescere e si reimposta nuovamente all'inizio del checkpoint successivo. Le misurazioni effettuate su alcuni test sintetici hanno dimostrato che la quota di istantanee di pagina nel volume totale dei record fisici raggiunge il 90%.

L'idea della compressione delle istantanee delle pagine WAL è quella di comprimere le istantanee delle pagine utilizzando uno strumento di compressione delle pagine già pronto (vedere Compressione delle pagine del disco). Allo stesso tempo, in WAL, i record vengono salvati in sequenza in modalità di sola aggiunta e non è necessario associare i record ai limiti dei blocchi del file system, quindi qui, a differenza del meccanismo di compressione delle pagine del disco, non abbiamo bisogno di file sparsi su tutto; di conseguenza, questo meccanismo funzionerà non solo sul sistema operativo Linux. Inoltre, per noi non è più importante quanto siamo riusciti a comprimere la pagina. Anche se liberassimo 1 byte, questo è già un risultato positivo e possiamo salvare i dati compressi in WAL, a differenza della compressione delle pagine su disco, dove salviamo la pagina compressa solo se abbiamo liberato più di 1 blocco del file system.

Le pagine sono dati altamente comprimibili, la loro quota nel volume totale WAL è molto elevata, quindi senza modificare il formato del file WAL possiamo ottenere una riduzione significativa delle sue dimensioni. La compressione, inclusi i record logici, richiederebbe una modifica del formato e una perdita di compatibilità, ad esempio, per i consumatori esterni che potrebbero essere interessati ai record logici, ma non porterebbe a una riduzione significativa delle dimensioni del file.

Come con la compressione delle pagine del disco, la compressione delle istantanee delle pagine WAL può utilizzare gli algoritmi di compressione ZSTD, LZ4, Snappy e la modalità SKIP_GARBAGE.

Impatto sulle prestazioni

Non è difficile notare che l'abilitazione diretta della compressione degli snapshot della pagina WAL influisce solo sui thread che scrivono dati nella memoria della pagina, ovvero quei thread che modificano i dati nelle cache. La lettura dei record fisici dal WAL avviene una sola volta, nel momento in cui il nodo viene rialzato dopo una caduta (e solo se cade durante un checkpoint).

Ciò influisce sui thread che modificano i dati nel modo seguente: otteniamo un effetto negativo (CPU) dovuto alla necessità di comprimere la pagina ogni volta prima di scrivere su disco, e un effetto positivo (disco IO) dovuto ad una riduzione della quantità di dati scritti. Di conseguenza, qui tutto è semplice: se le prestazioni del sistema sono limitate dalla CPU, otteniamo un leggero degrado, se sono limitate dall'I/O del disco, otteniamo un aumento.

Indirettamente, la riduzione della dimensione WAL influisce anche (positivamente) sui flussi che scaricano i segmenti WAL nell'archivio e sui flussi di compattazione WAL.

I test delle prestazioni reali nel nostro ambiente utilizzando dati sintetici hanno mostrato un leggero aumento (il throughput è aumentato del 10%-15%, la latenza è diminuita del 10%-15%).

Come abilitare e configurare

Versione minima di Apache Ignite: 2.8. Abilitare e configurare come segue:

  • Deve essere presente un modulo di compressione ignite nel percorso di classe. Per impostazione predefinita, si trova nella distribuzione Apache Ignite nella directory libs/opzionale e non è incluso nel percorso di classe. Puoi semplicemente spostare la directory su di un livello in libs e poi quando la esegui tramite ignite.sh verrà automaticamente abilitata.
  • La persistenza deve essere abilitata (Abilitata tramite DataRegionConfiguration.setPersistenceEnabled(true)).
  • La modalità di compressione deve essere impostata utilizzando il metodo DataStorageConfiguration.setWalPageCompression(), la compressione è disabilitata per impostazione predefinita (modalità DISABLED).
  • Facoltativamente, è possibile impostare il livello di compressione utilizzando il metodo DataStorageConfiguration.setWalPageCompression(), vedere il javadoc per il metodo per i valori validi per ciascuna modalità.

conclusione

I meccanismi di compressione dei dati considerati in Apache Ignite possono essere utilizzati indipendentemente l'uno dall'altro, ma è accettabile anche qualsiasi combinazione di essi. Capire come funzionano ti consentirà di determinare quanto sono adatti ai tuoi compiti nel tuo ambiente e cosa dovrai sacrificare quando li usi. La compressione della pagina del disco è progettata per comprimere la memoria principale e può fornire un rapporto di compressione medio. La compressione delle istantanee della pagina WAL fornirà un grado medio di compressione per i file WAL e molto probabilmente migliorerà anche le prestazioni. La compattazione WAL non avrà un effetto positivo sulle prestazioni, ma ridurrà il più possibile la dimensione dei file WAL rimuovendo i record fisici.

Fonte: habr.com

Aggiungi un commento