Compressione dei dati in Apache Ignite. Esperienza di Sber.

Compressione dei dati in Apache Ignite. Esperienza di Sber.Quando si lavora con grandi volumi di dati, può sorgere acutamente il problema della mancanza di spazio sui dischi. Uno dei modi per risolvere questo problema è la compressione, grazie alla quale, con la stessa attrezzatura, è possibile aumentare i volumi di archiviazione. In questo articolo esamineremo come funziona la compressione dei dati in Apache Ignite. L'articolo descriverà solo i metodi di compressione implementati all'interno del prodotto. Altri metodi di compressione dei dati (su rete, in memoria), sia implementati che non, rimarranno fuori dal campo d'azione.

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

  1. Il contenuto delle cache
  2. Il registro di scrittura anticipata (Write Ahead Log, di seguito semplicemente WAL)

Da tempo esiste un meccanismo per la compressione del WAL, chiamato WAL compaction. Nella recente versione di Apache Ignite 2.8 sono stati introdotti altri due meccanismi che consentono di comprimere i dati su disco, ovvero la compressione delle pagine disco per comprimere il contenuto delle cache e la compressione delle snapshot delle pagine WAL per comprimere alcune voci del WAL. Maggiori dettagli su questi tre meccanismi di seguito.

Compressione delle pagine disco

Come funziona

Iniziamo brevemente a esaminare come Ignite memorizza i dati. Viene utilizzata la memoria a pagine. La dimensione della pagina è impostata all'avvio del nodo e non può essere modificata in fasi successive; inoltre, la dimensione della pagina deve essere una potenza di due e multipla della dimensione del blocco del file system. Le pagine vengono caricate in RAM dal disco secondo le necessità; la dimensione dei dati su disco può superare l'ammontare di RAM dedicata. In caso di mancanza di spazio in RAM per caricare una pagina dal disco, le vecchie pagine già non utilizzate verranno espulse dalla RAM.

Su disco, i dati sono memorizzati nel seguente modo: per ogni partizione di ciascun gruppo di cache viene creato un file separato, in questo file, in ordine crescente di indice, si trovano una dopo l'altra le pagine. L'identificatore completo della pagina contiene l'identificatore del gruppo di cache, il numero di partizione e l'indice della pagina nel file. In questo modo, attraverso l'identificatore completo della pagina, possiamo determinare in modo univoco il file e l'offset nel file per ogni pagina. Maggiori dettagli sulla struttura della memoria a pagine possono essere trovati nell'articolo sulla Wiki di Apache Ignite: Ignite Persistent Store - sotto il cofano.

Il meccanismo di compressione delle pagine disk, come suggerisce il nome, funziona a livello di pagina. Quando questo meccanismo è attivato, il lavoro con i dati nella RAM avviene senza alcuna compressione, ma al momento del salvataggio delle pagine dalla RAM su disco, viene eseguita la loro compressione.

Tuttavia, comprimere ogni pagina singolarmente non risolve il problema, è necessario in qualche modo ridurre le dimensioni dei file finali con i dati. Se la dimensione della pagina smette di essere fissa, non possiamo più scrivere pagine nel file una dopo l'altra, poiché questo potrebbe generare una serie di problemi:

  • Non saremo in grado di calcolare l'offset della pagina utilizzando l'indice della pagina stessa.
  • Non è chiaro come gestire le pagine che non si trovano alla fine del file e che cambiano le loro dimensioni. Se la dimensione della pagina diminuisce, lo spazio liberato si perde. Se la dimensione della pagina aumenta, è necessario cercare un nuovo spazio nel file per essa.
  • Se la pagina si sposta di un numero di byte che non è un multiplo della dimensione del blocco del file system, la lettura o la scrittura richiederà di toccare un blocco del file system in più, il che potrebbe portare a una degradazione delle prestazioni.

Per evitare di affrontare questi problemi a livello locale, la compressione delle pagine disk in Apache Ignite utilizza un meccanismo del file system chiamato file sparse. Un file sparse è un file in cui alcune aree riempite di zeri possono essere contrassegnate come "buchi". A tal fine, non verranno allocati blocchi del file system per memorizzare questi buchi, il che comporta un risparmio di spazio su disco.

È logico che, per liberare un blocco del file system, la dimensione del buco deve essere maggiore o uguale a quella del blocco del file system, il che impone un ulteriore vincolo sulla dimensione della pagina in Apache Ignite: affinché la compressione abbia un effetto, la dimensione della pagina deve essere strettamente maggiore della dimensione del blocco del file system. Se la dimensione della pagina è uguale a quella del blocco, non saremo mai in grado di liberare un blocco, poiché per liberare un singolo blocco è necessario che la pagina compressa occupi 0 byte. Se la dimensione della pagina è uguale a quella di 2 o 4 blocchi, potremo liberare almeno un blocco se la nostra pagina si comprime di almeno il 50% o il 75% rispettivamente.

Pertanto, la descrizione finale del funzionamento del meccanismo è la seguente: quando una pagina viene scritta su disco, si tenta di comprimere la pagina. Se la dimensione della pagina compressa consente di liberare uno o più blocchi del file system, la pagina viene scritta in formato compresso, lasciando un "buco" (viene effettuata una chiamata di sistema con flag "punch hole"). Se la dimensione della pagina compressa non consente di liberare blocchi, la pagina viene salvata così com'è, in formato non compresso. Tutti gli offset delle pagine vengono calcolati come senza compressione, moltiplicando l'indice della pagina per la dimensione della pagina. Non è necessaria alcuna rilocazione manuale delle pagine. Gli offset delle pagine, come senza compressione, si trovano sui confini dei blocchi del file system. fallocate() con il flag "punch hole"). Se la dimensione della pagina compressa non consente di liberare blocchi, la pagina viene salvata così com'è, in formato non compresso. Tutti gli offset delle pagine vengono calcolati come senza compressione, moltiplicando l'indice della pagina per la dimensione della pagina. Non è necessaria alcuna rilocazione manuale delle pagine. Gli offset delle pagine, come senza compressione, si trovano sui confini dei blocchi del file system.

Compressione dei dati in Apache Ignite. Esperienza di Sber.

Nell'attuale implementazione, Ignite è in grado di lavorare con file sparsi solo su sistemi operativi Linux; di conseguenza, la compressione delle pagine su disco può essere attivata solo utilizzando Ignite su questo sistema operativo.

Gli algoritmi di compressione che possono essere utilizzati per la compressione delle pagine su disco sono: ZSTD, LZ4, Snappy. Inoltre, esiste una modalità operativa (SKIP_GARBAGE), in cui viene semplicemente scartato lo spazio non utilizzato nella pagina senza applicare compressione ai dati rimanenti, il che consente di ridurre il carico sulla CPU rispetto agli algoritmi elencati in precedenza.

Impatto sulle prestazioni

Sfortunatamente, non ho effettuato una misurazione reale delle prestazioni su stand reali, poiché non abbiamo intenzione di utilizzare questo meccanismo in produzione, ma possiamo riflettere teoricamente su dove perderemo e dove guadagneremo.

Per questo, dobbiamo ricordare come avviene la lettura e la scrittura delle pagine quando vi accediamo:

  • Durante l'operazione di lettura, prima viene cercata nella RAM; se la ricerca ha esito negativo, la pagina viene caricata in RAM dal disco dallo stesso thread che esegue la lettura.
  • Durante l'operazione di scrittura, la pagina nella RAM viene contrassegnata come sporca, ma il salvataggio fisico della pagina su disco non avviene immediatamente nel thread che esegue la scrittura. Tutte le pagine sporche vengono salvate su disco successivamente nel processo di checkpoint da thread separati.

Pertanto, l'impatto sulle operazioni di lettura è:

  • Positivo (disk IO), grazie alla riduzione del numero di blocchi letti dal file system.
  • Negativo (CPU), a causa del carico aggiuntivo necessario al sistema operativo per lavorare con i file sparsi. È anche possibile che qui appaiano implicitamente ulteriori operazioni di IO per salvare una struttura di file sparso più complessa (purtroppo non sono esperto di tutti i dettagli relativi ai file sparsi).
  • Negativo (CPU), a causa della necessità di decomprimere le pagine.
  • Non ci sono influenze sulle operazioni di scrittura.
  • Influenza sul processo di checkpoint (è tutto analogo alle operazioni di lettura):
  • Positivo (disk IO), grazie alla riduzione del numero di blocchi scritti nel file system.
  • Negativo (CPU, possibile disk IO), a causa del lavoro con file sparsi.
  • Negativo (CPU), a causa della necessità di comprimere le pagine.

Quale piatto della bilancia prevarrà? Questo dipende molto dall'ambiente, ma sono propenso a pensare che la compressione delle pagine su disco porterà piuttosto a una degradazione delle prestazioni nella maggior parte dei sistemi. Inoltre, i test su altri DBMS che utilizzano un approccio simile con i file sparsi mostrano un calo delle prestazioni quando la compressione è attivata.

Come abilitare e configurare

Come già detto sopra, la versione minima di Apache Ignite che supporta la compressione delle pagine su disco è 2.8 e supporta solo il sistema operativo Linux. L'abilitazione e la configurazione vengono effettuate nel seguente modo:

  • Nel class-path deve esserci il modulo ignite-compression. Di default si trova nella distribuzione di Apache Ignite nella directory libs/optional e non è incluso nel class-path. Puoi semplicemente spostare la directory di un livello verso l'alto in libs e allora, all'avvio tramite ignite.sh, sarà automaticamente incluso.
  • La persistenza deve essere abilitata (si abilita tramite DataRegionConfiguration.setPersistenceEnabled(true)).
  • La dimensione della pagina deve essere maggiore della dimensione del blocco del file system (può essere impostata tramite DataStorageConfiguration.setPageSize() ).
  • Per ogni cache i cui dati devono essere compressi, è necessario configurare il metodo di compressione nella configurazione e (opzionalmente) il livello di compressione (metodi CacheConfiguration.setDiskPageCompression(), CacheConfiguration.setDiskPageCompressionLevel()).

Compattazione WAL

Come funziona

Che cos'è il WAL e a cosa serve? In breve: è un registro in cui vengono registrati tutti gli eventi che alla fine modificano lo storage delle pagine. Serve principalmente per il ripristino in caso di crash. Qualsiasi operazione, prima di restituire il controllo all'utente, deve prima registrare un evento nel WAL, affinché in caso di crash possa ripristinare tutti le operazioni che hanno ricevuto una risposta positiva, anche se queste operazioni non sono state ancora riflettute nello storage delle pagine su disco (come già descritto, la registrazione effettiva nello storage delle pagine avviene attraverso un processo chiamato 'checkpoint', con un certo ritardo da parte di thread separati).

Le registrazioni nel WAL si dividono in logiche e fisiche. Le logiche sono le chiavi e i valori stessi. Le fisiche riflettono le modifiche delle pagine nello storage delle pagine. Se le registrazioni logiche possono essere utili in altre circostanze, le registrazioni fisiche sono necessarie solo per il ripristino in caso di crash e sono necessarie solo le registrazioni dall'ultimo checkpoint riuscito. Qui non entreremo nei dettagli per spiegare perché funziona in questo modo, ma chi è interessato può fare riferimento all'articolo già menzionato su Apache Ignite Wiki: Ignite Persistent Store - sotto il cofano.

A volte, una registrazione logica corrisponde a più registrazioni fisiche. Ad esempio, un'operazione di put nella cache riguarda diverse pagine nella memoria paginata (la pagina con i dati stessi, le pagine con gli indici, le pagine con le free-list). In alcuni test sintetici, ho osservato che le registrazioni fisiche occupavano fino al 90% del volume del file WAL. Tuttavia, esse sono necessarie solo per un tempo molto limitato (per impostazione predefinita, l'intervallo tra i checkpoint è di 3 minuti). Sarebbe logico liberarsi di questi dati dopo che hanno perso la loro attualità. Questo è precisamente ciò che fa il meccanismo di WAL compaction: elimina le registrazioni fisiche e comprime le registrazioni logiche rimanenti con zip, riducendo significativamente la dimensione del file (a volte anche di dieci volte).

FISICAMENTE, WAL è composto da diversi segmenti (di default 10) di dimensioni fisse (di default 64MB), che vengono sovrascritti in modo circolare. Una volta che il segmento corrente è pieno, il segmento successivo viene assegnato come corrente e il segmento pieno viene copiato in archivio tramite un flusso separato. La compattazione WAL lavora già con i segmenti archiviati. Inoltre, tramite un flusso separato, monitora l'esecuzione del checkpoint e inizia la compressione dei segmenti archiviati, per cui le registrazioni fisiche non sono più necessarie.

Compressione dei dati in Apache Ignite. Esperienza di Sber.

Impatto sulle prestazioni

Poiché la compattazione WAL funziona tramite un flusso separato, non dovrebbe avere un impatto diretto sulle operazioni in corso. Tuttavia, genera un carico di lavoro in background aggiuntivo sulla CPU (compressione) e sul disco (lettura di ogni segmento WAL dall'archivio e scrittura dei segmenti compressi), quindi se il sistema è già al limite delle sue capacità, ciò porterà anche a una degradazione delle prestazioni.

Come abilitare e configurare

È possibile attivare la compattazione WAL tramite la proprietà WalCompactionEnabled in DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Inoltre, con il metodo DataStorageConfiguration.setWalCompactionLevel() è possibile impostare il livello di compressione, se il valore di default (BEST_SPEED) non soddisfa le esigenze.

Compressione dello snapshot della pagina WAL

Come funziona

Abbiamo già chiarito in precedenza che le registrazioni WAL si dividono in logiche e fisiche. Ogni modifica di ogni pagina nella memoria pagina genera una registrazione fisica WAL. Le registrazioni fisiche si suddividono a loro volta in due sottocategorie: registrazione dello snapshot della pagina e registrazione delta. Ogni volta che apportiamo una modifica a una pagina e la convertiamo da uno stato pulito a uno sporco, viene salvata una copia completa di quella pagina nel WAL (registrazione dello snapshot della pagina). Anche se abbiamo cambiato solo un byte, nel WAL verrà salvata una registrazione di dimensioni leggermente superiori a quelle della pagina. Se invece apportiamo modifiche a una pagina già sporca, nel WAL si genera una registrazione delta, che riflette solo le modifiche rispetto allo stato precedente della pagina, ma non tutta la pagina nella sua interezza. Poiché il ripristino dello stato delle pagine da sporco a pulito avviene durante il checkpoint, subito dopo l'inizio del checkpoint, praticamente tutte le registrazioni fisiche consisteranno solo in snapshot delle pagine (poiché tutte le pagine subito dopo l'inizio del checkpoint sono pulite), poi, man mano che ci si avvicina al successivo checkpoint, la quota delle registrazioni delta inizia a crescere e di nuovo viene azzerata all'inizio del prossimo checkpoint. Misurazioni in alcuni test sintetici hanno mostrato che la quota degli snapshot delle pagine nel volume totale delle registrazioni fisiche raggiunge il 90%.

L'idea della compressione degli snapshot delle pagine WAL consiste nel comprimere gli snapshot delle pagine utilizzando uno strumento già pronto per la compressione delle pagine (vedi compressione delle pagine su disco). In questo caso, le registrazioni WAL vengono salvate in modo sequenziale in modalità append-only e non è necessaria l'associazione delle registrazioni ai limiti dei blocchi del file system; pertanto, qui, a differenza del meccanismo di compressione delle pagine su disco, non abbiamo affatto bisogno di file sparsi, quindi questo meccanismo funzionerà non solo su sistemi operativi Linux. Inoltre, non ci importa più quanto siamo riusciti a comprimere la pagina. Anche se abbiamo liberato solo 1 byte, questo è già un risultato positivo e possiamo salvare dati compressi nel 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 ben comprimibili, la loro percentuale rispetto al volume totale del WAL è molto alta, quindi, mantenendo il formato del file WAL, possiamo ottenere una significativa riduzione delle sue dimensioni. La compressione, inclusa quella delle registrazioni logiche, richiederebbe una modifica del formato e la perdita di compatibilità, ad esempio, per i consumatori esterni che potrebbero essere interessati alle registrazioni logiche, senza portare a una riduzione significativa del volume del file.

Come per la compressione delle pagine su disco, anche per la compressione degli snapshot delle pagine WAL possono essere utilizzati algoritmi di compressione come ZSTD, LZ4, Snappy, oltre alla modalità SKIP_GARBAGE.

Impatto sulle prestazioni

Come non è difficile notare, l'attivazione diretta della compressione degli snapshot delle pagine WAL influisce solo sui thread che scrivono dati nella memoria delle pagine, ovvero su quelli che modificano i dati nella cache. La lettura delle registrazioni fisiche WAL avviene solo una volta, al momento dell'avvio del nodo dopo un crash (e solo in caso di crash durante il checkpoint).

L'impatto sui thread che modificano i dati è il seguente: otteniamo un effetto negativo (CPU) dovuto alla necessità di comprimere ogni volta la pagina prima di scriverla su disco e un effetto positivo (disk IO) grazie alla riduzione della quantità di dati scritti. Pertanto, è semplice: se le prestazioni del sistema sono limitate dalla CPU, otteniamo una leggera degradazione; se dal disco in input/output, otteniamo un guadagno.

Indirettamente, la riduzione delle dimensioni del WAL influisce anche (positivamente) sui thread che archiviano i segmenti WAL e sui thread di compattazione del WAL.

Test di performance reali nel nostro ambiente con dati sintetici hanno mostrato un leggero guadagno (il throughput è aumentato del 10%-15%, la latenza è diminuita del 10%-15%).

Come abilitare e configurare

Versione minima di Apache Ignite: 2.8. L'abilitazione e la configurazione avvengono nel seguente modo:

  • Nel class-path deve esserci il modulo ignite-compression. Di default si trova nella distribuzione di Apache Ignite nella directory libs/optional e non è incluso nel class-path. Puoi semplicemente spostare la directory di un livello verso l'alto in libs e allora, all'avvio tramite ignite.sh, sarà automaticamente incluso.
  • La persistenza deve essere abilitata (si abilita tramite DataRegionConfiguration.setPersistenceEnabled(true)).
  • Deve essere impostata la modalità di compressione tramite il metodo DataStorageConfiguration.setWalPageCompression(), per impostazione predefinita la compressione è disabilitata (modalità DISABLED).
  • Facoltativamente, è possibile impostare il grado di compressione tramite il metodo DataStorageConfiguration.setWalPageCompression(), valori ammissibili per ciascuna delle modalità sono disponibili nella javadoc del metodo.

Conclusione

I meccanismi di compressione dei dati esaminati in Apache Ignite possono essere utilizzati indipendentemente l'uno dall'altro, ma è anche possibile combinare le loro funzionalità. Comprendere i principi del loro funzionamento permetterà di definire quanto siano adatti alle vostre esigenze nel vostro ambiente e quali compromessi saranno necessari nel loro utilizzo. La compressione delle pagine del disco è destinata a comprimere lo storage principale e può fornire un livello medio di compressione. La compressione degli snapshot delle pagine WAL offrirà un livello medio di compressione dei file WAL, e molto probabilmente aumenterà anche le prestazioni. La compattazione WAL non avrà un impatto positivo sulle prestazioni, ma ridurrà al minimo le dimensioni dei file WAL grazie all'eliminazione delle registrazioni fisiche.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster