Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Vysoký výkon je jednou z kľúčových požiadaviek pri práci s veľkými dátami. Na oddelení načítania dát v Sberbank pumpujeme takmer všetky transakcie do nášho dátového cloudu založeného na Hadoop, a preto sa zaoberáme naozaj veľkými tokmi informácií. Prirodzene, stále hľadáme spôsoby, ako zlepšiť výkon, a teraz vám chceme povedať, ako sa nám podarilo opraviť RegionServer HBase a HDFS klienta, vďaka čomu sme dokázali výrazne zvýšiť rýchlosť operácií čítania.
Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Predtým, ako prejdeme k podstate vylepšení, stojí za to hovoriť o obmedzeniach, ktoré v zásade nemožno obísť, ak sedíte na pevnom disku.

Prečo sú pevný disk a rýchle čítanie s náhodným prístupom nekompatibilné
Ako viete, HBase a mnohé ďalšie databázy ukladajú údaje v blokoch s veľkosťou niekoľkých desiatok kilobajtov. Štandardne je to asi 64 KB. Teraz si predstavme, že potrebujeme získať len 100 bajtov a požiadame HBase, aby nám tieto údaje poskytla pomocou určitého kľúča. Keďže veľkosť bloku v HFiles je 64 KB, požiadavka bude 640-krát väčšia (iba minúta!), ako je potrebné.

Ďalej, keďže požiadavka prejde cez HDFS a jeho mechanizmus ukladania metadát do vyrovnávacej pamäte ShortCircuitCache (čo umožňuje priamy prístup k súborom), to vedie k prečítaniu už 1 MB z disku. To sa však dá upraviť pomocou parametra dfs.client.read.shortcircuit.buffer.size a v mnohých prípadoch má zmysel túto hodnotu znížiť, napríklad na 126 KB.

Povedzme, že to urobíme, ale navyše, keď začneme čítať údaje cez rozhranie java api, ako sú funkcie ako FileChannel.read, a požiadame operačný systém, aby prečítal zadané množstvo údajov, prečíta „pre každý prípad“ 2-krát viac , t.j. 256 KB v našom prípade. Je to preto, že java nemá jednoduchý spôsob, ako nastaviť príznak FADV_RANDOM, aby sa tomuto správaniu zabránilo.

Výsledkom je, že na získanie našich 100 bajtov sa pod kapotou prečíta 2600-krát viac. Zdalo by sa, že riešenie je zrejmé, zmenšíme veľkosť bloku na kilobajt, nastavíme spomínaný príznak a získame veľké zrýchlenie osvietenia. Problém je však v tom, že znížením veľkosti bloku 2-krát znížime aj počet bajtov prečítaných za jednotku času dvakrát.

Určitý zisk z nastavenia príznaku FADV_RANDOM sa dá získať, ale len pri vysokom multivlákne a s veľkosťou bloku 128 KB, ale ide maximálne o pár desiatok percent:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Testy boli vykonané na 100 súboroch, každý s veľkosťou 1 GB a umiestnených na 10 HDD.

Vypočítajme si, s čím môžeme v zásade pri tejto rýchlosti počítať:
Povedzme, že čítame z 10 diskov rýchlosťou 280 MB/sec, t.j. 3 milióny krát 100 bajtov. Ale ako si pamätáme, údaje, ktoré potrebujeme, sú 2600-krát menšie ako tie, ktoré sa čítajú. Takto vydelíme 3 milióny 2600 a dostaneme 1100 záznamov za sekundu.

Depresívne, však? Taká je príroda Náhodný prístup prístup k dátam na HDD – bez ohľadu na veľkosť bloku. Toto je fyzický limit náhodného prístupu a žiadna databáza nemôže za takýchto podmienok vytlačiť viac.

Ako potom databázy dosahujú oveľa vyššie rýchlosti? Aby sme odpovedali na túto otázku, pozrime sa, čo sa deje na nasledujúcom obrázku:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Tu vidíme, že prvých pár minút je rýchlosť naozaj okolo tisíc záznamov za sekundu. Ďalej však vďaka tomu, že sa číta oveľa viac, ako bolo žiadané, dáta končia v buffe/cache operačného systému (linux) a rýchlosť stúpa na slušnejších 60 tis.

Ďalej sa teda budeme zaoberať zrýchlením prístupu len k údajom, ktoré sú vo vyrovnávacej pamäti OS alebo sa nachádzajú v úložných zariadeniach SSD/NVMe s porovnateľnou rýchlosťou prístupu.

V našom prípade vykonáme testy na 4 serveroch, z ktorých každý je spoplatnený nasledovne:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 vlákien.
Pamäť: 730 GB.
verzia java: 1.8.0_111

A tu je kľúčovým bodom množstvo údajov v tabuľkách, ktoré je potrebné prečítať. Faktom je, že ak čítate údaje z tabuľky, ktorá je celá umiestnená vo vyrovnávacej pamäti HBase, nedôjde ani k čítaniu z vyrovnávacej pamäte / vyrovnávacej pamäte operačného systému. Pretože HBase štandardne prideľuje 40 % pamäte štruktúre s názvom BlockCache. V podstate ide o ConcurrentHashMap, kde kľúčom je názov súboru + offset bloku a hodnota sú skutočné údaje v tomto offsetu.

Teda pri čítaní len z tejto štruktúry sme vidíme skvelú rýchlosť, ako milión žiadostí za sekundu. Predstavme si však, že nemôžeme alokovať stovky gigabajtov pamäte len pre potreby databázy, pretože na týchto serveroch beží množstvo iných užitočných vecí.

Napríklad v našom prípade je objem BlockCache na jednom RS cca 12 GB. Na jednom uzle sme pristáli dve RS, t.j. 96 GB je alokovaných pre BlockCache na všetkých uzloch. A dát je mnohonásobne viac, napríklad nech sú to 4 tabuľky po 130 regiónoch, v ktorých majú súbory veľkosť 800 MB, komprimované FAST_DIFF, t.j. celkovo 410 GB (ide o čisté dáta, t.j. bez zohľadnenia replikačného faktora).

BlockCache je teda len asi 23 % z celkového objemu dát a to je oveľa bližšie k reálnym podmienkam toho, čo sa nazýva BigData. A tu začína zábava – pretože je zrejmé, že čím menej nájdených súborov cache, tým horší výkon. Ak totiž vynecháte, budete musieť urobiť veľa práce – t.j. prejdite nadol na volanie systémových funkcií. Tomu sa však nedá vyhnúť, a tak sa pozrime na úplne iný aspekt – čo sa stane s dátami vo vnútri vyrovnávacej pamäte?

Zjednodušme situáciu a predpokladajme, že máme cache, do ktorej sa zmestí len 1 objekt. Tu je príklad toho, čo sa stane, keď sa pokúsime pracovať s objemom údajov 3-krát väčším ako vyrovnávacia pamäť, budeme musieť:

1. Umiestnite blok 1 do vyrovnávacej pamäte
2. Odstráňte blok 1 z vyrovnávacej pamäte
3. Umiestnite blok 2 do vyrovnávacej pamäte
4. Odstráňte blok 2 z vyrovnávacej pamäte
5. Umiestnite blok 3 do vyrovnávacej pamäte

5 akcií dokončených! Túto situáciu však nemožno nazvať normálnou, v skutočnosti nútime HBase robiť kopu úplne zbytočnej práce. Neustále číta údaje z vyrovnávacej pamäte OS, umiestňuje ich do BlockCache, aby ich takmer okamžite vyhodila, pretože prišla nová časť údajov. Animácia na začiatku príspevku ukazuje podstatu problému - Garbage Collector ide mimo váhu, atmosféra sa zahrieva, malá Greta v ďalekom a horúcom Švédsku sa rozčuľuje. A my, IT ľudia, naozaj nemáme radi, keď sú deti smutné, a tak začneme premýšľať, čo s tým môžeme urobiť.

Čo keby ste do vyrovnávacej pamäte vložili nie všetky bloky, ale len určité percento z nich, aby sa vyrovnávacia pamäť nepreplnila? Začnime jednoduchým pridaním niekoľkých riadkov kódu na začiatok funkcie na vkladanie údajov do BlockCache:

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

Ide o nasledovné: offset je pozícia bloku v súbore a jeho posledné číslice sú náhodne a rovnomerne rozdelené od 00 do 99. Preto preskočíme len tie, ktoré spadajú do rozsahu, ktorý potrebujeme.

Nastavte napríklad cacheDataBlockPercent = 20 a uvidíte, čo sa stane:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Výsledok je zrejmý. V nižšie uvedených grafoch je zrejmé, prečo k takémuto zrýchleniu došlo - ušetríme veľa zdrojov GC bez toho, aby sme vykonali sizyfovskú prácu s umiestnením údajov do vyrovnávacej pamäte, len aby sme ich okamžite vyhodili do odtoku marťanských psov:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Súčasne sa zvyšuje využitie CPU, ale je oveľa menšie ako produktivita:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Za zmienku tiež stojí, že bloky uložené v BlockCache sú odlišné. Väčšinu, asi 95 %, tvoria samotné dáta. A zvyšok sú metadáta, ako sú Bloom filtre alebo LEAF_INDEX a т.д.. Tieto údaje nestačia, ale sú veľmi užitočné, pretože pred priamym prístupom k údajom sa HBase obráti na meta, aby pochopila, či je potrebné hľadať tu ďalej a ak áno, kde presne sa blok záujmu nachádza.

Preto v kóde vidíme kontrolnú podmienku buf.getBlockType().isData() a vdaka tejto meta ho v kazdom pripade nechame v cache.

Teraz poďme zvýšiť zaťaženie a mierne sprísniť funkciu jedným ťahom. V prvom teste sme urobili medzné percento = 20 a BlockCache bola mierne nedostatočne využívaná. Teraz to nastavíme na 23 % a každých 100 minút pridáme 5 vlákien, aby sme videli, v akom bode nastane saturácia:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Tu vidíme, že pôvodná verzia takmer okamžite narazí na strop pri približne 100 300 požiadavkách za sekundu. Zatiaľ čo náplasť dáva zrýchlenie až XNUMX tis. Zároveň je jasné, že ďalšie zrýchľovanie už nie je také „zadarmo“, zvyšuje sa aj vyťaženie CPU.

Nie je to však veľmi elegantné riešenie, keďže dopredu nevieme, koľko percent blokov je potrebné cachovať, závisí to od profilu zaťaženia. Preto bol implementovaný mechanizmus na automatickú úpravu tohto parametra v závislosti od aktivity operácií čítania.

Na ovládanie boli pridané tri možnosti:

hbase.lru.cache.heavy.eviction.count.limit — nastavuje, koľkokrát má prebehnúť proces vysťahovania údajov z vyrovnávacej pamäte, kým začneme používať optimalizáciu (t. j. preskakovanie blokov). Predvolene sa rovná MAX_INT = 2147483647 a v skutočnosti znamená, že funkcia nikdy nezačne pracovať s touto hodnotou. Pretože proces vysťahovania začína každých 5 - 10 sekúnd (závisí to od zaťaženia) a 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 rokov. Tento parameter však môžeme nastaviť na 0 a sfunkčniť funkciu ihneď po spustení.

V tomto parametri je však aj užitočné zaťaženie. Ak je naša záťaž taká, že krátkodobé čítanie (povedzme cez deň) a dlhodobé čítanie (v noci) sa neustále prelínajú, potom sa môžeme uistiť, že funkcia je zapnutá len vtedy, keď prebiehajú operácie dlhého čítania.

Napríklad vieme, že krátkodobé čítania zvyčajne trvajú približne 1 minútu. Netreba začať vyhadzovať bloky, cache nestihne zastarať a potom môžeme tento parameter nastaviť napríklad na 10. To povedie k tomu, že optimalizácia začne fungovať až po dlho- začal termín aktívne čítanie, t.j. za 100 sekúnd. Ak teda vykonáme krátkodobé čítanie, všetky bloky sa dostanú do vyrovnávacej pamäte a budú dostupné (okrem tých, ktoré budú vyradené štandardným algoritmom). A keď robíme dlhodobé čítania, funkcia je zapnutá a mali by sme oveľa vyšší výkon.

hbase.lru.cache.heavy.eviction.mb.size.limit — nastaví, koľko megabajtov by sme chceli umiestniť do vyrovnávacej pamäte (a, samozrejme, odstrániť) za 10 sekúnd. Funkcia sa pokúsi dosiahnuť túto hodnotu a udržať ju. Ide o toto: ak vložíme gigabajty do vyrovnávacej pamäte, potom budeme musieť gigabajty vysťahovať, a to, ako sme videli vyššie, je veľmi drahé. Nemali by ste sa však snažiť nastaviť ho príliš malý, pretože to spôsobí predčasné ukončenie režimu preskočenia bloku. Pre výkonné servery (cca 20-40 fyzických jadier) je optimálne nastaviť cca 300-400 MB. Pre strednú triedu (~10 jadier) 200-300 MB. Pre slabé systémy (2-5 jadier) môže byť 50-100 MB normálnych (netestované na týchto).

Pozrime sa, ako to funguje: povedzme, že nastavíme hbase.lru.cache.heavy.eviction.mb.size.limit = 500, dôjde k nejakému zaťaženiu (čítanie) a potom každých ~10 sekúnd vypočítame, koľko bajtov bolo vysťahovaný podľa vzorca:

Režijné náklady = súčet voľných bajtov (MB) * 100 / limit (MB) - 100;

Ak bolo v skutočnosti vysťahovaných 2000 XNUMX MB, potom sa réžia rovná:

2000 100 * 500 / 100 - 300 = XNUMX %

Algoritmy sa snažia zachovať nie viac ako niekoľko desiatok percent, takže funkcia zníži percento blokov uložených vo vyrovnávacej pamäti, čím sa implementuje mechanizmus automatického ladenia.

Ak však zaťaženie klesne, povedzme, že sa odstráni iba 200 MB a réžia bude záporná (takzvané prekročenie):

200 * 100 / 500 - 100 = -60 %

Naopak, táto funkcia zvýši percento blokov uložených vo vyrovnávacej pamäti, kým sa Overhead nestane kladným.

Nižšie je uvedený príklad toho, ako to vyzerá na skutočných údajoch. Nie je potrebné snažiť sa dosiahnuť 0 %, je to nemožné. Je to veľmi dobré, keď je to asi 30 - 100%, čo pomáha vyhnúť sa predčasnému opusteniu režimu optimalizácie počas krátkodobých prepätí.

hbase.lru.cache.heavy.eviction.overhead.koeficient — nastavuje, ako rýchlo chceme získať výsledok. Ak s istotou vieme, že naše čítania sú väčšinou dlhé a nechce sa nám čakať, môžeme tento pomer zvýšiť a rýchlejšie dosiahnuť vysoký výkon.

Napríklad tento koeficient nastavíme = 0.01. To znamená, že réžia (pozri vyššie) sa týmto číslom vynásobí výsledným výsledkom a zníži sa percento uložených blokov. Predpokladajme, že réžia = 300 % a koeficient = 0.01, potom sa percento blokov vo vyrovnávacej pamäti zníži o 3 %.

Podobná logika „Backpressure“ je implementovaná aj pre záporné hodnoty Overhead (prekročenie). Keďže krátkodobé výkyvy v objeme čítaní a vysťahovaní sú vždy možné, tento mechanizmus vám umožňuje vyhnúť sa predčasnému opusteniu režimu optimalizácie. Protitlak má prevrátenú logiku: čím silnejšie je prestrelenie, tým viac blokov je uložených do vyrovnávacej pamäte.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Implementačný kód

        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;
       }

Pozrime sa teraz na to všetko na skutočnom príklade. Máme nasledujúci testovací skript:

  1. Začnime skenovať (25 vlákien, dávka = 100)
  2. Po 5 minútach pridajte multi-gety (25 vlákien, dávka = 100)
  3. Po 5 minútach vypnite multi-get (ostane znova iba skenovanie)

Urobíme dve spustenia, najprv hbase.lru.cache.heavy.eviction.count.limit = 10000 0 (čo v skutočnosti deaktivuje funkciu) a potom nastavíme limit = XNUMX (povolí ju).

V nižšie uvedených protokoloch vidíme, ako je funkcia zapnutá a resetuje Prekročenie na 14-71%. Z času na čas sa záťaž zníži, čím sa zapne Backpressure a HBase opäť ukladá do vyrovnávacej pamäte viac blokov.

Log RegionServer
vysťahovaní (MB): 0, pomer 0.0, réžia (%): -100, počítadlo ťažkého vysťahovania: 0, aktuálna vyrovnávacia pamäť DataBlock (%): 100
vysťahovaní (MB): 0, pomer 0.0, réžia (%): -100, počítadlo ťažkého vysťahovania: 0, aktuálna vyrovnávacia pamäť DataBlock (%): 100
vyradených (MB): 2170, pomer 1.09, réžia (%): 985, počítadlo ťažkých vysťahovaní: 1, aktuálna vyrovnávacia pamäť DataBlock (%): 91 < štart
vyradených (MB): 3763, pomer 1.08, réžia (%): 1781, počítadlo ťažkých vysťahovaní: 2, aktuálna vyrovnávacia pamäť DataBlock (%): 76
vyradených (MB): 3306, pomer 1.07, réžia (%): 1553, počítadlo ťažkých vysťahovaní: 3, aktuálna vyrovnávacia pamäť DataBlock (%): 61
vyradených (MB): 2508, pomer 1.06, réžia (%): 1154, počítadlo ťažkých vysťahovaní: 4, aktuálna vyrovnávacia pamäť DataBlock (%): 50
vyradených (MB): 1824, pomer 1.04, réžia (%): 812, počítadlo ťažkých vysťahovaní: 5, aktuálna vyrovnávacia pamäť DataBlock (%): 42
vyradených (MB): 1482, pomer 1.03, réžia (%): 641, počítadlo ťažkých vysťahovaní: 6, aktuálna vyrovnávacia pamäť DataBlock (%): 36
vyradených (MB): 1140, pomer 1.01, réžia (%): 470, počítadlo ťažkých vysťahovaní: 7, aktuálna vyrovnávacia pamäť DataBlock (%): 32
vyradených (MB): 913, pomer 1.0, réžia (%): 356, počítadlo ťažkých vysťahovaní: 8, aktuálna vyrovnávacia pamäť DataBlock (%): 29
vyradených (MB): 912, pomer 0.89, réžia (%): 356, počítadlo ťažkých vysťahovaní: 9, aktuálna vyrovnávacia pamäť DataBlock (%): 26
vyradených (MB): 684, pomer 0.76, réžia (%): 242, počítadlo ťažkých vysťahovaní: 10, aktuálna vyrovnávacia pamäť DataBlock (%): 24
vyradených (MB): 684, pomer 0.61, réžia (%): 242, počítadlo ťažkých vysťahovaní: 11, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 456, pomer 0.51, réžia (%): 128, počítadlo ťažkých vysťahovaní: 12, aktuálna vyrovnávacia pamäť DataBlock (%): 21
vyradených (MB): 456, pomer 0.42, réžia (%): 128, počítadlo ťažkých vysťahovaní: 13, aktuálna vyrovnávacia pamäť DataBlock (%): 20
vyradených (MB): 456, pomer 0.33, réžia (%): 128, počítadlo ťažkých vysťahovaní: 14, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 15, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 342, pomer 0.32, réžia (%): 71, počítadlo ťažkých vysťahovaní: 16, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 342, pomer 0.31, réžia (%): 71, počítadlo ťažkých vysťahovaní: 17, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.3, réžia (%): 14, počítadlo ťažkých vysťahovaní: 18, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.29, réžia (%): 14, počítadlo ťažkých vysťahovaní: 19, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.27, réžia (%): 14, počítadlo ťažkých vysťahovaní: 20, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.25, réžia (%): 14, počítadlo ťažkých vysťahovaní: 21, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.24, réžia (%): 14, počítadlo ťažkých vysťahovaní: 22, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.22, réžia (%): 14, počítadlo ťažkých vysťahovaní: 23, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.21, réžia (%): 14, počítadlo ťažkých vysťahovaní: 24, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.2, réžia (%): 14, počítadlo ťažkých vysťahovaní: 25, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 228, pomer 0.17, réžia (%): 14, počítadlo ťažkých vysťahovaní: 26, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 456, pomer 0.17, réžia (%): 128, počítadlo ťažkých vysťahovaní: 27, aktuálna vyrovnávacia pamäť DataBlock (%): 18 < pridané dostane (ale tabuľka rovnaká)
vyradených (MB): 456, pomer 0.15, réžia (%): 128, počítadlo ťažkých vysťahovaní: 28, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 342, pomer 0.13, réžia (%): 71, počítadlo ťažkých vysťahovaní: 29, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 342, pomer 0.11, réžia (%): 71, počítadlo ťažkých vysťahovaní: 30, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 342, pomer 0.09, réžia (%): 71, počítadlo ťažkých vysťahovaní: 31, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.08, réžia (%): 14, počítadlo ťažkých vysťahovaní: 32, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.07, réžia (%): 14, počítadlo ťažkých vysťahovaní: 33, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.06, réžia (%): 14, počítadlo ťažkých vysťahovaní: 34, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.05, réžia (%): 14, počítadlo ťažkých vysťahovaní: 35, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.05, réžia (%): 14, počítadlo ťažkých vysťahovaní: 36, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 228, pomer 0.04, réžia (%): 14, počítadlo ťažkých vysťahovaní: 37, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vysťahovaní (MB): 109, pomer 0.04, réžia (%): -46, počítadlo ťažkého vysťahovania: 37, aktuálne ukladanie do vyrovnávacej pamäte DataBlock (%): 22 < spätný tlak
vyradených (MB): 798, pomer 0.24, réžia (%): 299, počítadlo ťažkých vysťahovaní: 38, aktuálna vyrovnávacia pamäť DataBlock (%): 20
vyradených (MB): 798, pomer 0.29, réžia (%): 299, počítadlo ťažkých vysťahovaní: 39, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 570, pomer 0.27, réžia (%): 185, počítadlo ťažkých vysťahovaní: 40, aktuálna vyrovnávacia pamäť DataBlock (%): 17
vyradených (MB): 456, pomer 0.22, réžia (%): 128, počítadlo ťažkých vysťahovaní: 41, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 342, pomer 0.16, réžia (%): 71, počítadlo ťažkých vysťahovaní: 42, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 342, pomer 0.11, réžia (%): 71, počítadlo ťažkých vysťahovaní: 43, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 228, pomer 0.09, réžia (%): 14, počítadlo ťažkých vysťahovaní: 44, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 228, pomer 0.07, réžia (%): 14, počítadlo ťažkých vysťahovaní: 45, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 228, pomer 0.05, réžia (%): 14, počítadlo ťažkých vysťahovaní: 46, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 222, pomer 0.04, réžia (%): 11, počítadlo ťažkých vysťahovaní: 47, aktuálna vyrovnávacia pamäť DataBlock (%): 16
vyradených (MB): 104, pomer 0.03, réžia (%): -48, počítadlo ťažkého vysťahovania: 47, aktuálne ukladanie do vyrovnávacej pamäte DataBlock (%): 21 < prerušenie získa
vyradených (MB): 684, pomer 0.2, réžia (%): 242, počítadlo ťažkých vysťahovaní: 48, aktuálna vyrovnávacia pamäť DataBlock (%): 19
vyradených (MB): 570, pomer 0.23, réžia (%): 185, počítadlo ťažkých vysťahovaní: 49, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 342, pomer 0.22, réžia (%): 71, počítadlo ťažkých vysťahovaní: 50, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 228, pomer 0.21, réžia (%): 14, počítadlo ťažkých vysťahovaní: 51, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 228, pomer 0.2, réžia (%): 14, počítadlo ťažkých vysťahovaní: 52, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 228, pomer 0.18, réžia (%): 14, počítadlo ťažkých vysťahovaní: 53, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 228, pomer 0.16, réžia (%): 14, počítadlo ťažkých vysťahovaní: 54, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vyradených (MB): 228, pomer 0.14, réžia (%): 14, počítadlo ťažkých vysťahovaní: 55, aktuálna vyrovnávacia pamäť DataBlock (%): 18
vysťahovaní (MB): 112, pomer 0.14, réžia (%): -44, počítadlo ťažkého vysťahovania: 55, aktuálne ukladanie do vyrovnávacej pamäte DataBlock (%): 23 < spätný tlak
vyradených (MB): 456, pomer 0.26, réžia (%): 128, počítadlo ťažkých vysťahovaní: 56, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.31, réžia (%): 71, počítadlo ťažkých vysťahovaní: 57, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 58, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 59, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 60, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 61, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 62, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 63, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.32, réžia (%): 71, počítadlo ťažkých vysťahovaní: 64, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 65, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 66, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.32, réžia (%): 71, počítadlo ťažkých vysťahovaní: 67, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 68, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.32, réžia (%): 71, počítadlo ťažkých vysťahovaní: 69, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.32, réžia (%): 71, počítadlo ťažkých vysťahovaní: 70, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 71, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 72, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 73, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 74, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 75, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vyradených (MB): 342, pomer 0.33, réžia (%): 71, počítadlo ťažkých vysťahovaní: 76, aktuálna vyrovnávacia pamäť DataBlock (%): 22
vysťahovaní (MB): 21, pomer 0.33, réžia (%): -90, počítadlo ťažkého vysťahovania: 76, aktuálna vyrovnávacia pamäť DataBlock (%): 32
vysťahovaní (MB): 0, pomer 0.0, réžia (%): -100, počítadlo ťažkého vysťahovania: 0, aktuálna vyrovnávacia pamäť DataBlock (%): 100
vysťahovaní (MB): 0, pomer 0.0, réžia (%): -100, počítadlo ťažkého vysťahovania: 0, aktuálna vyrovnávacia pamäť DataBlock (%): 100

Skenovanie bolo potrebné na zobrazenie toho istého procesu vo forme grafu vzťahu medzi dvoma časťami vyrovnávacej pamäte – jednoduchou (kde sa nikdy predtým nepožadovali bloky) a viacerými (tu sú uložené aspoň raz „vyžiadané“ údaje):

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

A nakoniec, ako vyzerá fungovanie parametrov vo forme grafu. Pre porovnanie, cache bola na začiatku úplne vypnutá, následne bola spustená HBase s cachovaním a oneskorením začiatku optimalizačných prác o 5 minút (30 cyklov vysťahovania).

Úplný kód nájdete v žiadosti o stiahnutie HBASE 23887 na githube.

300 tisíc čítaní za sekundu však nie je všetko, čo sa dá na tomto hardvéri za týchto podmienok dosiahnuť. Faktom je, že keď potrebujete pristupovať k údajom cez HDFS, používa sa mechanizmus ShortCircuitCache (ďalej len SSC), ktorý vám umožňuje pristupovať k údajom priamo a vyhnúť sa sieťovým interakciám.

Profilovanie ukázalo, že hoci tento mechanizmus prináša veľký zisk, v určitom bode sa tiež stáva prekážkou, pretože takmer všetky ťažké operácie prebiehajú vo vnútri zámku, čo väčšinou vedie k zablokovaniu.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Keď sme si to uvedomili, uvedomili sme si, že problém možno obísť vytvorením radu nezávislých SSC:

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

A potom s nimi pracujte, okrem križovatiek aj na poslednej posunutej číslici:

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

Teraz môžete začať testovať. Aby sme to dosiahli, budeme čítať súbory z HDFS pomocou jednoduchej viacvláknovej aplikácie. Nastavte parametre:

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

A stačí si prečítať súbory:

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);
}

Tento kód sa vykonáva v samostatných vláknach a zvýšime počet súčasne čítaných súborov (z 10 na 200 - horizontálna os) a počet cache (z 1 na 10 - grafika). Vertikálna os zobrazuje zrýchlenie, ktoré je výsledkom zvýšenia SSC v porovnaní s prípadom, keď existuje iba jedna vyrovnávacia pamäť.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Ako čítať graf: Čas vykonania pre 100 tisíc čítaní v 64 KB blokoch s jednou vyrovnávacou pamäťou vyžaduje 78 sekúnd. Zatiaľ čo s 5 vyrovnávacími pamäťami to trvá 16 sekúnd. Tie. tam je zrýchlenie ~ 5 krát. Ako vidno z grafu, efekt nie je príliš badateľný pri malom počte paralelných čítaní, citeľnú rolu začína hrať pri viac ako 50 čítaniach vlákien. Je tiež badateľné, že zvýšenie počtu SSC zo 6 a vyššie poskytuje výrazne menší nárast výkonu.

Poznámka 1: Keďže výsledky testov sú dosť nestále (pozri nižšie), vykonali sa 3 cykly a výsledné hodnoty sa spriemerovali.

Poznámka 2: Zvýšenie výkonu z konfigurácie náhodného prístupu je rovnaké, aj keď samotný prístup je o niečo pomalší.

Je však potrebné objasniť, že na rozdiel od HBase nie je toto zrýchlenie vždy zadarmo. Tu „odomkneme“ schopnosť CPU pracovať viac, namiesto toho, aby sme viseli na zámkoch.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Tu môžete pozorovať, že vo všeobecnosti zvýšenie počtu vyrovnávacích pamätí vedie k približne úmernému zvýšeniu využitia CPU. Výherných kombinácií je však o niečo viac.

Pozrime sa napríklad bližšie na nastavenie SSC = 3. Nárast výkonu na dosahu je približne 3.3-násobný. Nižšie sú uvedené výsledky zo všetkých troch samostatných jázd.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Zatiaľ čo spotreba CPU sa zvýši asi 2.8 krát. Rozdiel nie je príliš veľký, ale malá Greta je už šťastná a môže mať čas chodiť do školy a chodiť na hodiny.

To bude mať pozitívny účinok pre každý nástroj, ktorý využíva hromadný prístup k HDFS (napríklad Spark atď.), za predpokladu, že kód aplikácie je ľahký (t. j. zástrčka je na strane klienta HDFS) a je k dispozícii voľný výkon CPU. . Pre kontrolu si otestujme, aký efekt bude mať kombinované využitie optimalizácie BlockCache a ladenia SSC na čítanie z HBase.

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Je vidieť, že za takýchto podmienok nie je efekt taký veľký ako pri spresnených testoch (čítanie bez akéhokoľvek spracovania), ale celkom dobre sa tu dá vyžmýkať 80K navyše. Spoločne obe optimalizácie poskytujú až 4-násobné zrýchlenie.

Na túto optimalizáciu bolo urobené aj PR [HDFS-15202], ktorý bol zlúčený a táto funkcia bude dostupná v budúcich vydaniach.

A nakoniec bolo zaujímavé porovnať výkon čítania podobnej širokostĺpcovej databázy Cassandra a HBase.

Za týmto účelom sme spustili inštancie štandardnej pomôcky na testovanie záťaže YCSB z dvoch hostiteľov (celkovo 800 vlákien). Na strane servera - 4 inštancie RegionServer a Cassandra na 4 hostiteľoch (nie na tých, kde bežia klienti, aby sa predišlo ich vplyvu). Údaje pochádzajú z tabuliek veľkosti:

HBase – 300 GB na HDFS (100 GB čistých dát)

Cassandra – 250 GB (replikačný faktor = 3)

Tie. objem bol približne rovnaký (v HBase trochu viac).

Parametre HBase:

dfs.client.short.circuit.num = 5 (optimalizácia klienta HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - to znamená, že náplasť začne fungovať po 30 vytlačeniach (~5 minút)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — cieľový objem ukladania do vyrovnávacej pamäte a vysťahovania

Protokoly YCSB boli analyzované a zostavené do grafov programu Excel:

Ako zvýšiť rýchlosť čítania z HBase až 3-krát a z HDFS až 5-krát

Ako vidíte, tieto optimalizácie umožňujú porovnať výkon týchto databáz za týchto podmienok a dosiahnuť 450 tisíc čítaní za sekundu.

Dúfame, že tieto informácie môžu byť pre niekoho užitočné počas vzrušujúceho boja o produktivitu.

Zdroj: hab.com

Pridať komentár