Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Un altu rendimentu hè unu di i requisiti chjave quandu u travagliu cù big data. In u dipartimentu di carica di dati in Sberbank, pompemu quasi tutte e transazzione in u nostru Data Cloud basatu in Hadoop è per quessa trattà cù flussi d'infurmazioni veramente grandi. Naturalmente, simu sempre à circà modi per migliurà u rendiment, è avà vulemu cuntà cumu avemu riesciutu à patch RegionServer HBase è u cliente HDFS, grazia à quale pudemu aumentà significativamente a vitezza di l'operazioni di lettura.
Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

In ogni casu, prima di passà à l'essenza di e migliure, vale a pena parlà di e restrizioni chì, in principiu, ùn ponu micca esse aggirate se site nantu à un HDD.

Perchè HDD è letture veloci d'Accessu aleatoriu sò incompatibili
Cum'è sapete, HBase, è parechje altre basa di dati, almacenanu dati in blocchi di parechje decine di kilobytes in grandezza. Per automaticamente, hè di circa 64 KB. Avà imaginate chì avemu bisognu di ottene solu 100 bytes è dumandemu à HBase per dà noi sti dati cù una certa chjave. Siccomu a dimensione di u bloccu in HFiles hè 64 KB, a dumanda serà 640 volte più grande (solu un minutu!) chè necessariu.

Dopu, postu chì a dumanda passerà per HDFS è u so mecanismu di cache di metadati ShortCircuitCache (chì permette un accessu direttu à i schedari), questu porta à leghje digià 1 MB da u discu. Tuttavia, questu pò esse aghjustatu cù u paràmetru dfs.client.read.shortcircuit.buffer.size è in parechji casi hè sensu per riduce stu valore, per esempiu à 126 KB.

Diciamu chì facemu questu, ma in più, quandu avemu principiatu à leghje e dati attraversu l'api java, cum'è funzioni cum'è FileChannel.read è dumandate à u sistema operatore per leghje a quantità specifica di dati, leghje "in casu" 2 volte più. , i.e. 256 KB in u nostru casu. Questu hè chì Java ùn hà micca un modu faciule per stabilisce a bandiera FADV_RANDOM per impedisce stu cumpurtamentu.

In u risultatu, per ottene i nostri 100 bytes, 2600 volte più hè lettu sottu u cappucciu. Sembra chì a suluzione hè ovvia, riducemu a dimensione di u bloccu à un kilobyte, stabilisce a bandiera citata è uttene una grande accelerazione di l'illuminazione. Ma u prublema hè chì riducendu a dimensione di u bloccu per 2 volte, avemu ancu reduciu u numeru di bytes letti per unità di tempu da 2 volte.

Qualchidunu guadagnu da a creazione di a bandiera FADV_RANDOM pò esse ottenuta, ma solu cù un altu multi-threading è cù una dimensione di bloccu di 128 KB, ma questu hè un massimu di un paru di decine di percentu:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

I testi sò stati realizati nantu à i schedari 100, ogni 1 GB in grandezza è situati in 10 HDD.

Calculemu ciò chì pudemu, in principiu, cuntà à questa velocità:
Dicemu chì leghje da 10 dischi à una vitezza di 280 MB/sec, i.e. 3 milioni di volte 100 bytes. Ma cum'è ricurdamu, i dati chì avemu bisognu hè 2600 volte menu di ciò chì hè lettu. Cusì, dividimu 3 milioni per 2600 è uttene 1100 records per seconda.

Deprimente, ùn hè micca? Hè a natura Accessu aleatoriu accessu à e dati nantu à u HDD - indipendentemente da a dimensione di u bloccu. Questu hè u limitu fisicu di l'accessu aleatoriu è nisuna basa di dati pò sprimà più in tali cundizioni.

Cumu allora e basa di dati ottene velocità assai più altu? Per risponde à sta quistione, fighjemu ciò chì succede in a seguente stampa:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Quì vedemu chì per i primi minuti a vitezza hè veramente circa un milla records per seconda. Tuttavia, in più, per via di u fattu chì si leghje assai più di ciò chì era dumandatu, i dati finiscinu in u buff / cache di u sistema operatore (linux) è a velocità aumenta à un più decentu 60 mila per seconda.

Cusì, più avemu da trattà cù l'accelerazione di l'accessu solu à e dati chì si trovanu in u cache di u SO o situati in i dispositi di almacenamiento SSD / NVMe di una velocità d'accessu paragunabili.

In u nostru casu, faremu teste nantu à un bancu di servitori 4, ognuna di e quali hè caricata cum'è seguente:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 threads.
Memoria: 730 GB.
versione java: 1.8.0_111

E quì u puntu chjave hè a quantità di dati in e tavule chì deve esse lettu. U fattu hè chì si leghjite dati da una tavula chì hè interamente situata in a cache HBase, allora ùn vene ancu à leghje da u buff / cache di u sistema operatore. Perchè HBase per difettu allocate 40% di memoria à una struttura chjamata BlockCache. Essenzialmente questu hè un ConcurrentHashMap, induve a chjave hè u nome di u schedariu + offset di u bloccu, è u valore hè a data attuale à questu offset.

Cusì, quandu leghje solu da sta struttura, avemu vedemu una velocità eccellente, cum'è un milione di richieste per seconda. Ma imaginemu chì ùn pudemu micca attribuisce centinaie di gigabyte di memoria solu per i bisogni di a basa di dati, perchè ci sò parechje altre cose utili in esecuzione in questi servitori.

Per esempiu, in u nostru casu, u voluminu di BlockCache nantu à una RS hè di circa 12 GB. Avemu sbarcatu dui RS nantu à un node, i.e. 96 GB sò attribuiti per BlockCache in tutti i nodi. E ci hè parechje volte più dati, per esempiu, lasciate esse 4 tavule, 130 regioni ognunu, in quale i schedari sò 800 MB in size, compressed by FAST_DIFF, i.e. un totale di 410 GB (questu hè dati puri, vale à dì senza piglià in contu u fattore di replicazione).

Cusì, BlockCache hè solu circa 23% di u voluminu di dati tutali è questu hè assai più vicinu à e cundizioni reali di ciò chì hè chjamatu BigData. È quì hè quì chì u divertimentu principia - perchè ovviamente, u menu cache hits, u peghju u rendiment. Dopu tuttu, s'ellu vi manca, vi tuccherà à fà assai di travagliu - i.e. falà à chjamà funzioni di u sistema. Tuttavia, questu ùn pò micca esse evitata, allora fighjemu un aspettu completamente diversu - chì succede à e dati in u cache?

Simplificà a situazione è assume chì avemu un cache chì si adatta solu à 1 ughjettu. Eccu un esempiu di ciò chì succede quandu avemu da pruvà à travaglià cù un voluminu di dati 3 volte più grande di u cache, avemu da:

1. Pone u bloccu 1 in cache
2. Eliminate u bloccu 1 da a cache
3. Pone u bloccu 2 in cache
4. Eliminate u bloccu 2 da a cache
5. Pone u bloccu 3 in cache

5 azzioni cumpletate! Tuttavia, sta situazione ùn pò esse chjamata normale; in fattu, furzemu HBase à fà un munzeddu di travagliu completamente inutile. Leghje constantemente e dati da u cache di u SO, u mette in BlockCache, solu per scaccià quasi subitu perchè una nova parte di dati hè ghjunta. L'animazione à l'iniziu di u post mostra l'essenza di u prublema - Garbage Collector hè andatu fora di scala, l'atmosfera si riscalda, a piccula Greta in a Svezia distante è calda si arrabbia. È noi IT persone veramente ùn piace micca quandu i zitelli sò tristi, cusì cuminciamu à pensà à ciò chì pudemu fà.

Chì s'ellu ùn mette micca tutti i blocchi in u cache, ma solu un certu percentinu di elli, per chì u cache ùn sia micca overflow? Cuminciamu per aghjunghje solu uni pochi di linee di codice à u principiu di a funzione per mette dati in BlockCache:

  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
    if (cacheDataBlockPercent != 100 && buf.getBlockType().isData()) {
      if (cacheKey.getOffset() % 100 >= cacheDataBlockPercent) {
        return;
      }
    }
...

U puntu quì hè u seguente: l'offset hè a pusizione di u bloccu in u schedariu è i so ultimi numeri sò distribuiti in modu aleatoriu è uniforme da 00 à 99. Per quessa, saltaremu solu quelli chì cascanu in a gamma chì avemu bisognu.

Per esempiu, stabilisce cacheDataBlockPercent = 20 è vede ciò chì succede:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

U risultatu hè evidenti. In i grafici sottu, diventa chjaru perchè una tale accelerazione hè accaduta - salvemu assai risorse GC senza fà u travagliu Sisyphean di mette dati in u cache solu per scaccià subitu in u drenu di i cani marziani:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

À u listessu tempu, l'utilizazione di CPU aumenta, ma hè assai menu di a produtividade:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Hè nutate ancu chì i blocchi guardati in BlockCache sò diffirenti. A maiò parte, circa 95%, sò dati stessu. È u restu hè metadata, cum'è i filtri Bloom o LEAF_INDEX è ecc.. Questa dati ùn hè micca abbastanza, ma hè assai utile, perchè prima di accede à i dati direttamente, HBase si gira à a meta per capisce s'ellu hè necessariu di circà quì più in più è, se cusì, induve esattamente u bloccu d'interessu hè situatu.

Dunque, in u codice vedemu una cundizione di cuntrollu buf.getBlockType().isData() è grazia à sta meta, lasceremu in u cache in ogni casu.

Avà aumentemu a carica è stringe ligeramente a funzione in una volta. In a prima prova avemu fattu u percentualità di cutoff = 20 è BlockCache hè stata ligeramente sottoutilizata. Ora mettemu à u 23% è aghjunghje 100 fili ogni 5 minuti per vede à quale puntu si trova a saturazione:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Quì vedemu chì a versione originale hè quasi subitu à u tettu à circa 100 mila richieste per seconda. Mentre chì u patch dà una accelerazione di finu à 300 mila. À u listessu tempu, hè chjaru chì l'accelerazione ulteriore ùn hè più cusì "gratuita"; L'utilizazione di CPU hè ancu crescente.

Tuttavia, questu ùn hè micca una suluzione assai eleganti, postu chì ùn sapemu micca in anticipu chì percentuale di blocchi deve esse cache, dipende da u prufilu di carica. Dunque, un mecanismu hè statu implementatu per aghjustà automaticamente stu paràmetru secondu l'attività di l'operazioni di lettura.

Trè opzioni sò state aghjunte per cuntrullà questu:

hbase.lru.cache.heavy.eviction.count.limit - stabilisce quante volte u prucessu di evicting data da u cache deve esse svoltu prima di cumincià à aduprà l'ottimisazione (vale à dì saltà i blocchi). Per automaticamente hè uguali à MAX_INT = 2147483647 è in fattu significa chì a funzione ùn hà mai principiatu à travaglià cù stu valore. Perchè u prucessu di eviction principia ogni 5 - 10 seconde (dipende di a carica) è 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 anni. Tuttavia, pudemu stabilisce stu paràmetru à 0 è fà u funziunamentu di a funzione immediatamente dopu à u lanciu.

Tuttavia, ci hè ancu una carica utile in stu paràmetru. Se a nostra carica hè tale chì e letture à cortu termine (per dì durante u ghjornu) è leghjite à longu andà (di notte) sò constantemente intercalate, allora pudemu assicurà chì a funzione hè attivata solu quandu l'operazioni di lettura longu sò in corso.

Per esempiu, sapemu chì e letture à cortu termine duranu generalmente circa 1 minutu. Ùn ci hè bisognu di cumincià à scaccià i blocchi, u cache ùn avarà micca u tempu di diventà obsoletu è poi pudemu stabilisce stu paràmetru uguale à, per esempiu, 10. Questu hà da purtà à u fattu chì l'ottimisazione cumincià à travaglià solu quandu longu- terminu lettura attiva hè cuminciatu, i.e. in 100 seconde. Cusì, s'ellu avemu una lettura di cortu-termine, allora tutti i blocchi andaranu in u cache è seranu dispunibili (eccettu quelli chì saranu scacciati da l'algoritmu standard). È quandu facemu leghje à longu andà, a funzione hè attivata è averemu un rendimentu assai più altu.

hbase.lru.cache.heavy.eviction.mb.size.limit - stabilisce quanti megabyte vulemu mette in a cache (è, sicuru, evict) in 10 seconde. A funzione hà da pruvà à ghjunghje à stu valore è mantene. U puntu hè questu: se spingemu gigabytes in a cache, allora avemu da evittu gigabytes, è questu, cum'è avemu vistu sopra, hè assai caru. Tuttavia, ùn deve micca pruvà à mette troppu chjucu, perchè questu pruvucarà u modu di salta di bloccu per esce prematuremente. Per i servitori putenti (circa 20-40 nuclei fisichi), hè ottimali per stabilisce circa 300-400 MB. Per a classe media (~ 10 core) 200-300 MB. Per i sistemi debuli (2-5 core) 50-100 MB pò esse normale (micca testatu nantu à questi).

Fighjemu cumu funziona questu: dicemu chì avemu stabilitu hbase.lru.cache.heavy.eviction.mb.size.limit = 500, ci hè un tipu di carica (lettura) è dopu ogni ~ 10 seconde calculemu quanti byte eranu. scacciatu cù a formula:

Overhead = Freed Bytes Sum (MB) * 100 / Limit (MB) - 100;

Sì in fattu 2000 MB sò stati scacciati, allora Overhead hè uguale à:

2000 * 100 / 500 - 100 = 300%

L'algoritmi pruvate à mantene micca più di uni pochi decine di percentuali, cusì a funzione riducerà u percentualità di blocchi in cache, implementendu cusì un mecanismu d'autotuning.

Tuttavia, se a carica scende, dicemu chì solu 200 MB sò scacciati è Overhead diventa negativu (u cusì chjamatu overshooting):

200 * 100 / 500 - 100 = -60%

À u cuntrariu, a funzione aumenterà u percentuale di blocchi in cache finu à chì Overhead diventa pusitivu.

Quì sottu hè un esempiu di cumu si vede nantu à e dati reali. Ùn ci hè bisognu di pruvà à ghjunghje à 0%, hè impussibile. Hè assai bonu quandu si tratta di circa 30 - 100%, questu aiuta per evità l'uscita prematura da u modu di ottimisazione durante i surghjenti di corta durazione.

hbase.lru.cache.heavy.eviction.overhead.coefficient - stabilisce a rapidità chì vulemu ottene u risultatu. Se sapemu di sicuru chì e nostre letture sò soprattuttu longu è ùn volenu micca aspittà, pudemu aumentà sta ratio è uttene un altu rendimentu più veloce.

Per esempiu, avemu stabilitu stu coefficient = 0.01. Questu significa chì Overhead (vede sopra) serà multiplicatu da stu numeru da u risultatu resultanti è u percentualità di blocchi in cache serà ridutta. Assumimu chì Overhead = 300% è coefficient = 0.01, allura u percentuale di blocchi in cache serà ridutta da 3%.

Una logica simile "Backpressure" hè ancu implementata per i valori negativi Overhead (overshooting). Siccomu i fluttuazioni di cortu termine in u voluminu di leghje è eviczioni sò sempre pussibuli, stu mecanismu permette di evità a surtita prematura da u modu di ottimisazione. A backpressure hà una logica invertita: u più forte u overshooting, i più blocchi sò in cache.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Codice di implementazione

        LruBlockCache cache = this.cache.get();
        if (cache == null) {
          break;
        }
        freedSumMb += cache.evict()/1024/1024;
        /*
        * Sometimes we are reading more data than can fit into BlockCache
        * and it is the cause a high rate of evictions.
        * This in turn leads to heavy Garbage Collector works.
        * So a lot of blocks put into BlockCache but never read,
        * but spending a lot of CPU resources.
        * Here we will analyze how many bytes were freed and decide
        * decide whether the time has come to reduce amount of caching blocks.
        * It help avoid put too many blocks into BlockCache
        * when evict() works very active and save CPU for other jobs.
        * More delails: https://issues.apache.org/jira/browse/HBASE-23887
        */

        // First of all we have to control how much time
        // has passed since previuos evict() was launched
        // This is should be almost the same time (+/- 10s)
        // because we get comparable volumes of freed bytes each time.
        // 10s because this is default period to run evict() (see above this.wait)
        long stopTime = System.currentTimeMillis();
        if ((stopTime - startTime) > 1000 * 10 - 1) {
          // Here we have to calc what situation we have got.
          // We have the limit "hbase.lru.cache.heavy.eviction.bytes.size.limit"
          // and can calculte overhead on it.
          // We will use this information to decide,
          // how to change percent of caching blocks.
          freedDataOverheadPercent =
            (int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
          if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
            // Now we are in the situation when we are above the limit
            // But maybe we are going to ignore it because it will end quite soon
            heavyEvictionCount++;
            if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
              // It is going for a long time and we have to reduce of caching
              // blocks now. So we calculate here how many blocks we want to skip.
              // It depends on:
             // 1. Overhead - if overhead is big we could more aggressive
              // reducing amount of caching blocks.
              // 2. How fast we want to get the result. If we know that our
              // heavy reading for a long time, we don't want to wait and can
              // increase the coefficient and get good performance quite soon.
              // But if we don't sure we can do it slowly and it could prevent
              // premature exit from this mode. So, when the coefficient is
              // higher we can get better performance when heavy reading is stable.
              // But when reading is changing we can adjust to it and set
              // the coefficient to lower value.
              int change =
                (int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
              // But practice shows that 15% of reducing is quite enough.
              // We are not greedy (it could lead to premature exit).
              change = Math.min(15, change);
              change = Math.max(0, change); // I think it will never happen but check for sure
              // So this is the key point, here we are reducing % of caching blocks
              cache.cacheDataBlockPercent -= change;
              // If we go down too deep we have to stop here, 1% any way should be.
              cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
            }
          } else {
            // Well, we have got overshooting.
            // Mayby it is just short-term fluctuation and we can stay in this mode.
            // It help avoid permature exit during short-term fluctuation.
            // If overshooting less than 90%, we will try to increase the percent of
            // caching blocks and hope it is enough.
            if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
              // Simple logic: more overshooting - more caching blocks (backpressure)
              int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
              cache.cacheDataBlockPercent += change;
              // But it can't be more then 100%, so check it.
              cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
            } else {
              // Looks like heavy reading is over.
              // Just exit form this mode.
              heavyEvictionCount = 0;
              cache.cacheDataBlockPercent = 100;
            }
          }
          LOG.info("BlockCache evicted (MB): {}, overhead (%): {}, " +
            "heavy eviction counter: {}, " +
            "current caching DataBlock (%): {}",
            freedSumMb, freedDataOverheadPercent,
            heavyEvictionCount, cache.cacheDataBlockPercent);

          freedSumMb = 0;
          startTime = stopTime;
       }

Fighjemu avà tuttu questu cù un esempiu veru. Avemu u seguente script di prova:

  1. Cuminciamu à fà Scan (25 fili, batch = 100)
  2. Dopu à 5 minuti, aghjunghje multi-gets (25 fili, batch = 100)
  3. Dopu à 5 minuti, disattivate i multi-gets (solu a scansione resta di novu)

Facemu duie corse, prima hbase.lru.cache.heavy.eviction.count.limit = 10000 (chì in realtà disattiva a funzione), è dopu stabilisce limite = 0 (permette).

In i logs sottu vedemu cumu a funzione hè attivata è resetta Overshooting à 14-71%. Da u tempu à u tempu a carica diminuisce, chì attiva Backpressure è HBase cache più blocchi di novu.

Log RegionServer
eviccted (MB): 0, ratio 0.0, overhead (%): -100, pesanti eviction counter: 0, current caching DataBlock (%): 100
eviccted (MB): 0, ratio 0.0, overhead (%): -100, pesanti eviction counter: 0, current caching DataBlock (%): 100
eviccted (MB): 2170, ratio 1.09, overhead (%): 985, pesanti eviction counter: 1, current caching DataBlock (%): 91 < start
eviccted (MB): 3763, ratio 1.08, overhead (%): 1781, heavy eviction counter: 2, current caching DataBlock (%): 76
eviccted (MB): 3306, ratio 1.07, overhead (%): 1553, heavy eviction counter: 3, current caching DataBlock (%): 61
eviccted (MB): 2508, ratio 1.06, overhead (%): 1154, heavy eviction counter: 4, current caching DataBlock (%): 50
eviccted (MB): 1824, ratio 1.04, overhead (%): 812, heavy eviction counter: 5, current caching DataBlock (%): 42
eviccted (MB): 1482, ratio 1.03, overhead (%): 641, heavy eviction counter: 6, current caching DataBlock (%): 36
eviccted (MB): 1140, ratio 1.01, overhead (%): 470, heavy eviction counter: 7, current caching DataBlock (%): 32
eviccted (MB): 913, ratio 1.0, overhead (%): 356, heavy eviction counter: 8, current caching DataBlock (%): 29
eviccted (MB): 912, ratio 0.89, overhead (%): 356, heavy eviction counter: 9, current caching DataBlock (%): 26
eviccted (MB): 684, ratio 0.76, overhead (%): 242, heavy eviction counter: 10, current caching DataBlock (%): 24
eviccted (MB): 684, ratio 0.61, overhead (%): 242, heavy eviction counter: 11, current caching DataBlock (%): 22
eviccted (MB): 456, ratio 0.51, overhead (%): 128, heavy eviction counter: 12, current caching DataBlock (%): 21
eviccted (MB): 456, ratio 0.42, overhead (%): 128, heavy eviction counter: 13, current caching DataBlock (%): 20
eviccted (MB): 456, ratio 0.33, overhead (%): 128, heavy eviction counter: 14, current caching DataBlock (%): 19
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 15, current caching DataBlock (%): 19
eviccted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 16, current caching DataBlock (%): 19
eviccted (MB): 342, ratio 0.31, overhead (%): 71, heavy eviction counter: 17, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.3, overhead (%): 14, heavy eviction counter: 18, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.29, overhead (%): 14, heavy eviction counter: 19, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.27, overhead (%): 14, heavy eviction counter: 20, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.25, overhead (%): 14, heavy eviction counter: 21, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.24, overhead (%): 14, heavy eviction counter: 22, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.22, overhead (%): 14, heavy eviction counter: 23, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.21, overhead (%): 14, heavy eviction counter: 24, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.2, overhead (%): 14, heavy eviction counter: 25, current caching DataBlock (%): 19
eviccted (MB): 228, ratio 0.17, overhead (%): 14, heavy eviction counter: 26, current caching DataBlock (%): 19
eviccted (MB): 456, ratio 0.17, overhead (%): 128, heavy eviction counter: 27, current caching DataBlock (%): 18 < aghjuntu gets (ma table u listessu)
eviccted (MB): 456, ratio 0.15, overhead (%): 128, heavy eviction counter: 28, current caching DataBlock (%): 17
eviccted (MB): 342, ratio 0.13, overhead (%): 71, heavy eviction counter: 29, current caching DataBlock (%): 17
eviccted (MB): 342, ratio 0.11, overhead (%): 71, heavy eviction counter: 30, current caching DataBlock (%): 17
eviccted (MB): 342, ratio 0.09, overhead (%): 71, heavy eviction counter: 31, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.08, overhead (%): 14, heavy eviction counter: 32, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.07, overhead (%): 14, heavy eviction counter: 33, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.06, overhead (%): 14, heavy eviction counter: 34, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 35, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 36, current caching DataBlock (%): 17
eviccted (MB): 228, ratio 0.04, overhead (%): 14, heavy eviction counter: 37, current caching DataBlock (%): 17
eviccted (MB): 109, ratio 0.04, overhead (%): -46, heavy eviction counter: 37, current caching DataBlock (%): 22 <back pressure
eviccted (MB): 798, ratio 0.24, overhead (%): 299, heavy eviction counter: 38, current caching DataBlock (%): 20
eviccted (MB): 798, ratio 0.29, overhead (%): 299, heavy eviction counter: 39, current caching DataBlock (%): 18
eviccted (MB): 570, ratio 0.27, overhead (%): 185, heavy eviction counter: 40, current caching DataBlock (%): 17
eviccted (MB): 456, ratio 0.22, overhead (%): 128, heavy eviction counter: 41, current caching DataBlock (%): 16
eviccted (MB): 342, ratio 0.16, overhead (%): 71, heavy eviction counter: 42, current caching DataBlock (%): 16
eviccted (MB): 342, ratio 0.11, overhead (%): 71, heavy eviction counter: 43, current caching DataBlock (%): 16
eviccted (MB): 228, ratio 0.09, overhead (%): 14, heavy eviction counter: 44, current caching DataBlock (%): 16
eviccted (MB): 228, ratio 0.07, overhead (%): 14, heavy eviction counter: 45, current caching DataBlock (%): 16
eviccted (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 46, current caching DataBlock (%): 16
eviccted (MB): 222, ratio 0.04, overhead (%): 11, heavy eviction counter: 47, current caching DataBlock (%): 16
eviccted (MB): 104, ratio 0.03, overhead (%): -48, heavy eviction counter: 47, current caching DataBlock (%): 21 < interrupt gets
eviccted (MB): 684, ratio 0.2, overhead (%): 242, heavy eviction counter: 48, current caching DataBlock (%): 19
eviccted (MB): 570, ratio 0.23, overhead (%): 185, heavy eviction counter: 49, current caching DataBlock (%): 18
eviccted (MB): 342, ratio 0.22, overhead (%): 71, heavy eviction counter: 50, current caching DataBlock (%): 18
eviccted (MB): 228, ratio 0.21, overhead (%): 14, heavy eviction counter: 51, current caching DataBlock (%): 18
eviccted (MB): 228, ratio 0.2, overhead (%): 14, heavy eviction counter: 52, current caching DataBlock (%): 18
eviccted (MB): 228, ratio 0.18, overhead (%): 14, heavy eviction counter: 53, current caching DataBlock (%): 18
eviccted (MB): 228, ratio 0.16, overhead (%): 14, heavy eviction counter: 54, current caching DataBlock (%): 18
eviccted (MB): 228, ratio 0.14, overhead (%): 14, heavy eviction counter: 55, current caching DataBlock (%): 18
eviccted (MB): 112, ratio 0.14, overhead (%): -44, heavy eviction counter: 55, current caching DataBlock (%): 23 <back pressure
eviccted (MB): 456, ratio 0.26, overhead (%): 128, heavy eviction counter: 56, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.31, overhead (%): 71, heavy eviction counter: 57, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 58, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 59, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 60, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 61, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 62, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 63, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 64, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 65, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 66, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 67, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 68, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 69, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 70, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 71, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 72, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 73, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 74, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 75, current caching DataBlock (%): 22
eviccted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 76, current caching DataBlock (%): 22
eviccted (MB): 21, ratio 0.33, overhead (%): -90, pesanti eviction counter: 76, current caching DataBlock (%): 32
eviccted (MB): 0, ratio 0.0, overhead (%): -100, pesanti eviction counter: 0, current caching DataBlock (%): 100
eviccted (MB): 0, ratio 0.0, overhead (%): -100, pesanti eviction counter: 0, current caching DataBlock (%): 100

I scans eranu necessarii per mostrà u stessu prucessu in forma di un graficu di a relazione trà duie sezioni di cache - unicu (induve blocchi chì ùn sò mai stati dumandati prima) è multi (dati "richiesti" almenu una volta sò almacenati quì):

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

E finarmenti, ciò chì u funziunamentu di i paràmetri s'assumiglia in a forma di un graficu. Per paragunà, a cache hè stata completamente disattivata à u principiu, dopu HBase hè stata lanciata cù caching è ritardà l'iniziu di u travagliu di ottimisazione da 5 minuti (cicli di eviction 30).

U codice cumpletu pò esse truvatu in Pull Request HBASE 23887 nantu à github.

Tuttavia, 300 mila letture per seconda ùn hè micca tuttu ciò chì pò esse ottinutu nantu à questu hardware in queste cundizioni. U fattu hè chì quandu avete bisognu di accede à e dati via HDFS, u mecanismu ShortCircuitCache (in seguitu chjamatu SSC) hè utilizatu, chì permette di accede à e dati direttamente, evitendu l'interazzione di a rete.

U prufilu hà dimustratu chì, ancu s'è stu mecanismu dà un grande guadagnu, ancu in un certu puntu diventa un collu di buttiglia, perchè quasi tutte l'operazioni pisanti si trovanu in una serratura, chì porta à bluccà a maiò parte di u tempu.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Dopu avè realizatu questu, avemu capitu chì u prublema pò esse aggiratu creendu una serie di SSC indipendenti:

private final ShortCircuitCache[] shortCircuitCache;
...
shortCircuitCache = new ShortCircuitCache[this.clientShortCircuitNum];
for (int i = 0; i < this.clientShortCircuitNum; i++)
  this.shortCircuitCache[i] = new ShortCircuitCache(…);

E poi travaglià cun elli, escludendu intersezioni ancu à l'ultimu cifru offset:

public ShortCircuitCache getShortCircuitCache(long idx) {
    return shortCircuitCache[(int) (idx % clientShortCircuitNum)];
}

Avà pudete inizià a prova. Per fà questu, avemu da leghje i schedari da HDFS cù una applicazione simplice multi-threaded. Definite i paràmetri:

conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // по дефолту = 1 МБ и это сильно замедляет чтение, поэтому лучше привести в соответствие к реальным нуждам
conf.set("dfs.client.short.circuit.num", num); // от 1 до 10

È basta à leghje i schedari:

FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i < count; i++) {
    position += 65536;
    if (position > 900000000)
        position = 0L;
    int res = in.read(position, byteBuffer, 0, 65536);
}

Stu codice hè eseguitu in fili separati è aumenteremu u numeru di schedarii di lettura simultaneamente (da 10 à 200 - assi horizontale) è u numeru di cache (da 1 à 10 - grafica). L'assi verticale mostra l'accelerazione chì risulta da un aumentu di SSC relative à u casu quandu ci hè solu un cache.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Cumu leghje u graficu: U tempu d'esekzione per 100 mila leghje in blocchi 64 KB cù una cache richiede 78 seconde. Mentre chì cù 5 cache ci vole 16 seconde. Quelli. ci hè una accelerazione di ~ 5 volte. Comu pò esse vistu da u gràficu, l'effettu ùn hè micca assai notevuli per un picculu numeru di letture parallele, cumencia à ghjucà un rolu notevuli quandu ci sò più di leghje fili 50. Hè ancu notu chì l'aumentu di u numeru di SSC da 6. è sopra dà un aumentu di rendiment significativamente più chjucu.

Nota 1: postu chì i risultati di a prova sò abbastanza volatili (vede sottu), 3 corse sò state realizate è i valori resultanti sò stati mediati.

Nota 2: U guadagnu di rendiment da a cunfigurazione di l'accessu aleatoriu hè u stessu, ancu s'ellu l'accessu stessu hè un pocu più lento.

Tuttavia, hè necessariu di clarificà chì, à u cuntrariu di u casu cù HBase, sta accelerazione ùn hè micca sempre libera. Quì avemu "sbloccare" a capacità di u CPU per fà u travagliu più, invece di appiccà nantu à i chjusi.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Quì pudete osservà chì, in generale, un aumentu di u numeru di cache dà un incrementu apprussimatamente proporzionale in l'utilizazione di CPU. Tuttavia, ci sò un pocu più cumminazzioni vincitori.

Per esempiu, fighjemu un ochju più vicinu à u paràmetru SSC = 3. L'aumentu di u rendiment nantu à a gamma hè di circa 3.3 volte. Quì sottu sò i risultati di tutti i trè runs separati.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Mentre u cunsumu di CPU aumenta da circa 2.8 volte. A diffarenza ùn hè micca assai grande, ma a piccula Greta hè digià cuntenta è pò avè u tempu di assistisce à a scola è di piglià lezioni.

Cusì, questu avarà un effettu pusitivu per qualsiasi strumentu chì usa l'accessu in massa à HDFS (per esempiu Spark, etc.), basta chì u codice di l'applicazione hè ligeru (vale à dì chì u plug hè in u latu di u cliente HDFS) è ci hè una putenza CPU libera. . Per verificà, testemu quale effettu averà l'usu cumminatu di l'ottimisazione BlockCache è a sintonizazione SSC per a lettura da HBase.

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Pò esse vistu chì in tali cundizioni l'effettu ùn hè micca cusì grande cum'è in testi raffinati (lettura senza alcuna trasfurmazioni), ma hè abbastanza pussibule di strincà un 80K supplementu quì. Inseme, e duie ottimisazioni furniscenu una accelerazione finu à 4x.

Un PR hè statu ancu fattu per questa ottimisazione [HDFS-15202], chì hè stata fusionata è sta funziunalità serà dispunibule in versioni future.

È infine, era interessante paragunà a prestazione di lettura di una basa di dati simili di colonna larga, Cassandra è HBase.

Per fà questu, avemu lanciatu istanze di l'utilità standard di prova di carica YCSB da dui ospiti (800 fili in totale). Da u latu di u servitore - 4 istanze di RegionServer è Cassandra nantu à 4 hosts (micca quelli induve i clienti sò in esecuzione, per evità a so influenza). E letture venenu da tavule di dimensione:

HBase - 300 GB nantu à HDFS (100 GB di dati puri)

Cassandra - 250 GB (fattore di replicazione = 3)

Quelli. u voluminu era apprussimatamente u listessu (in HBase un pocu più).

Parametri HBase:

dfs.client.short.circuit.num = 5 (Ottimisazione di u cliente HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - questu significa chì u patch cumminciarà à travaglià dopu à 30 evacuazioni (~ 5 minuti)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 - u voluminu di destinazione di caching è eviction

I logs YCSB sò stati analizati è compilati in grafici Excel:

Cumu aumentà a velocità di lettura da HBase finu à 3 volte è da HDFS finu à 5 volte

Comu pudete vede, sti ottimisazioni facenu pussibule paragunà u rendiment di sti basa di dati in queste cundizioni è ottene 450 mila letture per seconda.

Speremu chì sta infurmazione pò esse utile à qualchissia durante a lotta eccitante per a produtividade.

Source: www.habr.com

Add a comment