HBase
 sql >> Database >  >> NoSQL >> HBase

HBase BlockCache 101

Questo post sul blog è stato pubblicato su Hortonworks.com prima della fusione con Cloudera. Alcuni collegamenti, risorse o riferimenti potrebbero non essere più accurati.

Questo post del blog è apparso originariamente qui ed è riprodotto nella sua interezza qui.

HBase è un database distribuito costruito attorno ai concetti fondamentali di un registro di scrittura ordinato e un albero di unione strutturato in registri. Come con qualsiasi database, l'I/O ottimizzato è una preoccupazione fondamentale per HBase. Quando possibile, la priorità è non eseguire alcun I/O. Ciò significa che l'utilizzo della memoria e le strutture di memorizzazione nella cache sono della massima importanza. A tal fine, HBase mantiene due strutture di cache:il "memory store" e il "block cache". Memoria, implementata come MemStore , accumula le modifiche ai dati man mano che vengono ricevuti, memorizzandoli nel buffer (1). La cache dei blocchi, un'implementazione di BlockCache interfaccia, mantiene i blocchi di dati residenti in memoria dopo che sono stati letti.

Il MemStore è importante per accedere alle modifiche recenti. Senza il MemStore , l'accesso a quei dati così come sono stati scritti nel registro di scrittura richiederebbe la lettura e la deserializzazione delle voci di quel file, almeno un O(n) operazione. Invece, MemStore mantiene una struttura di skiplist, che gode di un O(log n) costo di accesso e non richiede I/O su disco. Il MemStore contiene solo una piccola parte dei dati archiviati in HBase, tuttavia.

La manutenzione esegue letture da BlockCache è il meccanismo principale attraverso il quale HBase è in grado di fornire letture casuali con latenza di millisecondi. Quando un blocco di dati viene letto da HDFS, viene memorizzato nella cache in BlockCache . Le letture successive di dati vicini – dati dello stesso blocco – non subiscono la penalità di I/O di recuperare nuovamente quei dati dal disco (2). È il BlockCache quello sarà il focus rimanente di questo post.

Blocchi da memorizzare nella cache

Prima di comprendere il BlockCache , aiuta a capire cos'è esattamente un "blocco" HBase. Nel contesto HBase, un blocco è una singola unità di I/O. Quando si scrivono dati in un HFile, il blocco è l'unità più piccola di dati scritti. Allo stesso modo, un singolo blocco è la più piccola quantità di dati che HBase può leggere da un HFile. Fai attenzione a non confondere un blocco HBase con un blocco HDFS o con i blocchi del file system sottostante:sono tutti diversi (3).

I blocchi HBase sono disponibili in 4 varietà: DATAMETAINDEXBLOOM .

DATA i blocchi memorizzano i dati dell'utente. Quando il BLOCKSIZE è specificato per una famiglia di colonne, è un suggerimento per questo tipo di blocco. Intendiamoci, è solo un suggerimento. Durante lo svuotamento del MemStore , HBase farà del suo meglio per rispettare questa linea guida. Dopo ogni Cell viene scritto, lo scrivente controlla se l'importo scritto è>=il target BLOCKSIZE . In tal caso, chiuderà il blocco corrente e avvierà il successivo (4).

INDEXBLOOM i blocchi servono allo stesso obiettivo; entrambi sono usati per velocizzare il percorso di lettura. INDEX i blocchi forniscono un indice sulla Cell s contenuti in DATA blocchi. BLOOM i blocchi contengono un filtro di fioritura sugli stessi dati. L'indice consente al lettore di sapere rapidamente dove si trova una Cell dovrebbe essere conservato. Il filtro informa il lettore quando è presente un Cell è decisamente assente dai dati.

Infine, META i blocchi memorizzano le informazioni sull'HFile stesso e altre informazioni varie:metadati, come ci si potrebbe aspettare. Una panoramica più completa dei formati HFile e dei ruoli dei vari tipi di blocco è disponibile in Apache HBase I/O – HFile.

HBase BlockCache e le sue implementazioni

Esiste un unico BlockCache istanza in un server di regione, il che significa che tutti i dati di tutte le regioni ospitate da quel server condividono lo stesso pool di cache (5). Il BlockCache viene istanziata all'avvio del server della regione e viene conservata per l'intera durata del processo. Tradizionalmente, HBase forniva un solo BlockCache implementazione:il LruBlockCache . La versione 0.92 ha introdotto la prima alternativa in HBASE-4027:la SlabCache . HBase 0.96 ha introdotto un'altra opzione tramite HBASE-7404, denominata BucketCache .

La differenza fondamentale tra il collaudato LruBlockCache e queste alternative è il modo in cui gestiscono la memoria. In particolare, LruBlockCache è una struttura dati che risiede interamente nell'heap della JVM, mentre le altre due sono in grado di sfruttare la memoria dall'esterno dell'heap della JVM. Questa è una distinzione importante perché la memoria heap della JVM è gestita da JVM Garbage Collector, mentre le altre non lo sono. Nei casi di SlabCacheBucketCache , l'idea è di ridurre la pressione GC subita dal processo del server della regione riducendo il numero di oggetti conservati nell'heap.

LruBlockCache

Questa è l'implementazione predefinita. I blocchi di dati vengono memorizzati nella cache nell'heap JVM utilizzando questa implementazione. È suddiviso in tre aree:accesso singolo, multiaccesso e in memoria. Le aree sono dimensionate al 25%, 50%, 25% del totale BlockCache dimensione, rispettivamente (6). Un blocco inizialmente letto da HDFS viene popolato nell'area ad accesso singolo. Gli accessi consecutivi promuovono quel blocco nell'area multiaccesso. L'area in-memory è riservata ai blocchi caricati da famiglie di colonne contrassegnate come IN_MEMORY . Indipendentemente dall'area, i vecchi blocchi vengono rimossi per fare spazio ai nuovi blocchi utilizzando un algoritmo utilizzato meno di recente, da cui il "Lru" in "LruBlockCache".

SlabCache

Questa implementazione alloca aree di memoria al di fuori dell'heap JVM utilizzando DirectByteBuffer S. Queste aree forniscono il corpo di questo BlockCache . L'area precisa in cui verrà posizionato un particolare blocco dipende dalle dimensioni del blocco. Per impostazione predefinita, vengono allocate due aree, che consumano rispettivamente l'80% e il 20% della dimensione totale della cache off-heap configurata. Il primo viene utilizzato per memorizzare nella cache i blocchi che hanno approssimativamente la dimensione del blocco di destinazione (7). Quest'ultimo contiene blocchi che sono circa 2 volte la dimensione del blocco di destinazione. Un blocco viene posizionato nell'area più piccola in cui può adattarsi. Se la cache incontra un blocco più grande di quello che può stare in una delle due aree, quel blocco non verrà memorizzato nella cache. Come LruBlockCache , l'eliminazione dei blocchi viene gestita utilizzando un algoritmo LRU.

BucketCache

Questa implementazione può essere configurata per funzionare in una delle tre diverse modalità: heapoffheapfile . Indipendentemente dalla modalità operativa, il BucketCache gestisce aree di memoria denominate "bucket" per contenere i blocchi memorizzati nella cache. Ogni bucket viene creato con una dimensione del blocco di destinazione. L'heap l'implementazione crea quei bucket nell'heap JVM; offheap l'implementazione utilizza DirectByteByffers per gestire i bucket al di fuori dell'heap JVM; file mode prevede un percorso per un file nel filesystem in cui vengono creati i bucket. file la modalità è pensata per l'uso con un backup store a bassa latenza:un filesystem in memoria o forse un file che si trova su una memoria SSD (8). Indipendentemente dalla modalità, BucketCache crea 14 secchi di diverse dimensioni. Utilizza la frequenza di accesso ai blocchi per informare l'utilizzo, proprio come LruBlockCache , e ha la stessa suddivisione in accesso singolo, multiaccesso e in memoria del 25%, 50%, 25%. Inoltre, come la cache predefinita, l'eliminazione dei blocchi viene gestita utilizzando un algoritmo LRU.

Memorizzazione nella cache multilivello

Sia il SlabCacheBucketCache sono progettati per essere utilizzati come parte di una strategia di memorizzazione nella cache multilivello. Pertanto, una parte del totale BlockCache la dimensione è assegnata a un LruBlockCache esempio. Questa istanza funge da cache di primo livello, "L1", mentre l'altra istanza di cache viene trattata come cache di secondo livello, "L2". Tuttavia, l'interazione tra LruBlockCacheSlabCache è diverso da come LruBlockCache e il BucketCache interagisci.

Il SlabCache strategia, denominata DoubleBlockCache , consiste nel memorizzare sempre nella cache i blocchi in entrambe le cache L1 e L2. I due livelli di cache funzionano indipendentemente:entrambi vengono controllati quando si recupera un blocco e ciascuno rimuove i blocchi indipendentemente dall'altro. Il BucketCache strategia, denominata CombinedBlockCache , utilizza la cache L1 esclusivamente per i blocchi Bloom e Index. I blocchi di dati vengono inviati direttamente alla cache L2. In caso di rimozione del blocco L1, anziché essere eliminato del tutto, il blocco viene retrocesso nella cache L2.

Quale scegliere?

Ci sono due ragioni per considerare l'abilitazione di una delle alternative BlockCache implementazioni. Il primo è semplicemente la quantità di RAM che puoi dedicare al server della regione. La saggezza della comunità riconosce che il limite superiore dell'heap JVM, per quanto riguarda il server della regione, è compreso tra 14 GB e 31 GB (9). Il limite preciso di solito dipende da una combinazione di profilo hardware, configurazione del cluster, forma delle tabelle di dati e modelli di accesso alle applicazioni. Saprai di essere entrato nella zona di pericolo quando GC si ferma e RegionTooBusyException s inizia a inondare i tuoi log.

L'altra volta per considerare una cache alternativa è quando la latenza della risposta veramente importa. Mantenere l'heap intorno a 8-12 GB consente al raccoglitore CMS di funzionare in modo molto fluido (10), il che ha un impatto misurabile sul 99° percentile dei tempi di risposta. Data questa restrizione, le uniche scelte sono esplorare un Garbage Collector alternativo o prendere una di queste implementazioni off-heap per un giro.

Questa seconda opzione è esattamente quello che ho fatto. Nel prossimo post condividerò alcuni risultati di esperimenti non scientifici ma informativi in ​​cui confronto i tempi di risposta per diversi BlockCache implementazioni.

Come sempre, resta sintonizzato e continua con HBase!

1:il MemStore accumula le modifiche ai dati man mano che vengono ricevuti, memorizzandoli nel buffer. Ciò ha due scopi:aumenta la quantità totale di dati scritti su disco in una singola operazione e conserva le modifiche recenti in memoria per l'accesso successivo sotto forma di letture a bassa latenza. Il primo è importante in quanto mantiene i blocchi di scrittura HBase più o meno sincronizzati con le dimensioni dei blocchi HDFS, allineando i modelli di accesso HBase con l'archiviazione HDFS sottostante. Quest'ultimo è autoesplicativo, facilitando le richieste di lettura ai dati scritti di recente. Vale la pena sottolineare che questa struttura non è coinvolta nella durabilità dei dati. Le modifiche vengono scritte anche nel registro di scrittura ordinato, il HLog , che implica un'operazione di aggiunta HDFS a un intervallo configurabile, generalmente immediato.

2:la rilettura dei dati dal file system locale è lo scenario migliore. HDFS è un file system distribuito, dopotutto, quindi il caso peggiore richiede la lettura di quel blocco sulla rete. HBase fa del suo meglio per mantenere la località dei dati. Questi due articoli forniscono uno sguardo approfondito su cosa significa la località dei dati per HBase e come viene gestita.

3:I blocchi di file system, HDFS e HBase sono tutti diversi ma correlati. Il moderno sottosistema di I/O è costituito da molti livelli di astrazione in cima all'astrazione. Il fulcro di tale astrazione è il concetto di una singola unità di dati, denominata "blocco". Quindi, tutti e tre questi livelli di archiviazione definiscono il proprio blocco, ciascuno con le proprie dimensioni. In generale, una dimensione del blocco maggiore significa un aumento del throughput di accesso sequenziale. Una dimensione del blocco più piccola facilita un accesso casuale più rapido.

4:Posizionamento di BLOCKSIZE il controllo dopo che i dati sono stati scritti ha due ramificazioni. Una singola Cell è l'unità di dati più piccola scritta in un DATA bloccare. Significa anche un Cell non può estendersi su più blocchi.

5:è diverso dal MemStore , per la quale esiste un'istanza separata per ogni regione ospitata dal server della regione.

6:Fino a tempi molto recenti, queste partizioni di memoria erano definite staticamente; non c'era modo di ignorare la divisione 25/50/25. Un determinato segmento, ad esempio l'area multi-accesso, potrebbe crescere più del suo 50% di assegnazione fintanto che le altre aree fossero sottoutilizzate. Un maggiore utilizzo nelle altre aree sfratterà gli ingressi dall'area ad accesso multiplo fino al raggiungimento del saldo 25/50/25. L'operatore non ha potuto modificare queste dimensioni predefinite. HBASE-10263, distribuito in HBase 0.98.0, introduce i parametri di configurazione per queste dimensioni. Il comportamento flessibile viene mantenuto.

7:L'attività "approssimativamente" è quella di consentire un po' di spazio di manovra nelle dimensioni dei blocchi. La dimensione del blocco HBase è un obiettivo o un suggerimento approssimativo, non un vincolo rigorosamente imposto. La dimensione esatta di un particolare blocco di dati dipenderà dalla dimensione del blocco di destinazione e dalla dimensione della Cell valori in esso contenuti. Il suggerimento per la dimensione del blocco è specificato come dimensione del blocco predefinita di 64 kb.

8:Utilizzo di BucketCache in file la modalità con un archivio di supporto persistente ha un altro vantaggio:la persistenza. All'avvio, cercherà i dati esistenti nella cache e ne verificherà la validità.

9:A quanto ho capito, ci sono due componenti che consigliano il limite superiore su questo intervallo. Il primo è un limite all'indirizzabilità degli oggetti JVM. La JVM è in grado di fare riferimento a un oggetto nell'heap con un indirizzo relativo a 32 bit invece dell'indirizzo nativo completo a 64 bit. Questa ottimizzazione è possibile solo se la dimensione totale dell'heap è inferiore a 32 GB. Vedi Compressed Oops per maggiori dettagli. Il secondo è la capacità del Garbage Collector di tenere il passo con la quantità di abbandono degli oggetti nel sistema. Da quello che posso dire, le tre fonti di abbandono degli oggetti sono MemStoreBlockCache e operazioni di rete. Il primo è mitigato da MemSlab funzione, abilitata per impostazione predefinita. Il secondo è influenzato dalla dimensione del tuo set di dati rispetto alla dimensione della cache. Il terzo non può essere evitato fintanto che HBase utilizza uno stack di rete che si basa sulla copia dei dati.

10:Proprio come con 8, questo presuppone "hardware moderno". Le interazioni qui sono piuttosto complesse e ben oltre lo scopo di un singolo post sul blog.