Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Hög prestanda Àr ett av de viktigaste kraven nÀr man arbetar med stora datamÀngder. Vi pÄ dataladdningshantering i Sber arbetar med att pumpa nÀstan alla transaktioner till vÄr Data Cloud baserat pÄ Hadoop, och dÀrmed hanterar vi verkligen stora informationsflöden. Naturligtvis letar vi hela tiden efter sÀtt att öka prestandan, och nu vill vi berÀtta hur vi lyckades patcha RegionServer HBase och HDFS-klienten, vilket har ökat hastigheten för lÀsoperationer betydligt.
Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Men innan vi gÄr vidare till detaljerna i Àndringarna, Àr det vÀrt att nÀmna begrÀnsningarna som i princip inte gÄr att kringgÄ om man anvÀnder HDD.

Varför HDD och snabba Random Access-lÀsningar Àr oförenliga
Som bekant lagrar HBase och mÄnga andra databaser data i block, vanligtvis med en storlek pÄ flera tiotals kilobyte. Som standard Àr detta cirka 64 KB. LÄt oss nu tÀnka oss att vi behöver hÀmta endast 100 byte och ber HBase att ge oss dessa data via en viss nyckel. Eftersom storleken pÄ blocket i HFiles Àr 64 KB kommer det begÀrda att vara 640 gÄnger mer (tÀnk pÄ det!) Àn vad vi behöver.

Dessutom, eftersom förfrÄgan kommer att gÄ genom HDFS och dess mekanism för cache av metadatan ShortCircuitCache (som möjliggör direkt Ätkomst till filer), leder det till att man lÀser 1 MB frÄn disken. Det Àr dock möjligt att reglera detta med parametern dfs.client.read.shortcircuit.buffer.size och i mÄnga fall Àr det meningsfullt att minska detta vÀrde, till exempel till 126 KB.

Antag att vi gör detta, men dessutom, nÀr vi börjar lÀsa data genom java API:er, med funktioner som FileChannel.read och ber operativsystemet att lÀsa den angivna datamÀngden, lÀser det "för sÀkerhets skull" dubbelt sÄ mycket, det vill sÀga 256 KB i vÄrt fall. Detta sker eftersom det i java inte finns en enkel möjlighet att stÀlla in flaggan FADV_RANDOM som förhindrar ett sÄdant beteende.

Som ett resultat, för att fĂ„ vĂ„ra 100 byte pĂ„ insidan lĂ€ses 2600 gĂ„nger mer. Tycktes uppenbart — lĂ„t oss minska blockstorleken till en kilobyte, stĂ€lla in den nĂ€mnda flaggan och uppnĂ„ stort accelererat resultat. Men problemet Ă€r att genom att halvera blockstorleken minskar vi ocksĂ„ antalet lĂ€sta byte per tidsenhet med samma faktor.

Det Àr möjligt att fÄ viss vinst av att sÀtta flaggan FADV_RANDOM, men endast vid hög grad av flertrÄdadhet och med en blockstorlek pÄ minst 128 kB, vilket ger högst ett par tiotal procent:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Tester utfördes pÄ 100 filer, var och en med en storlek pÄ 1 GB, som lagrades pÄ 10 HDD-enheter.

LÄt oss berÀkna vad vi i princip kan förvÀnta oss med sÄdan hastighet:
Anta att vi lÀser frÄn 10 enheter med en hastighet av 280 MB/s, d.v.s. 3 miljoner gÄnger 100 byte. Men som vi minns förekommer de data vi behöver 2600 gÄnger mindre Àn vad som lÀses. SÄledes delar vi 3 miljoner med 2600 och fÄr 1100 poster per sekund.

TrĂ„kigt, eller hur? SĂ„dan Ă€r verkligheten Random Access av data pĂ„ HDD – oavsett blockstorlek. Detta Ă€r den fysiska grĂ€nsen för slumpmĂ€ssig Ă„tkomst och ingen databas kan fĂ„ ut mer Ă€n det under dessa förhĂ„llanden.

Hur kan databaserna dÄ uppnÄ betydligt högre hastigheter? För att svara pÄ denna frÄga, lÄt oss titta pÄ vad som hÀnder i nÀsta bild:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

HÀr ser vi att hastigheten de första minuterna faktiskt Àr omkring tusen poster per sekund. Men dÀrefter, eftersom det lÀses betydligt mer Àn vad som begÀrts, lagras data i buff/cache i operativsystemet (linux) och hastigheten ökar till mer respektabla 60 000 per sekund.

SÄledes kommer vi framöver att fokusera pÄ att öka Ätkomsten endast till de data som finns i OS-cache eller tillgÀngliga i jÀmförbara snabba lagringssystem som SSD/NVMe.

I vÄrt fall kommer vi att utföra tester pÄ en rigg bestÄende av 4 servrar, dÀr varje enhet Àr konfigurerad pÄ följande sÀtt:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 trÄdar.
Minne: 730 GB.
java version: 1.8.0_111

Och hĂ€r Ă€r den avgörande punkten – mĂ€ngden data i tabellerna som behöver lĂ€sas. Det Ă€r sĂ„ att om man lĂ€ser data frĂ„n en tabell som helt fĂ„r plats i HBase-cache, kommer det inte ens att bli aktuellt att lĂ€sa frĂ„n buff/cache i operativsystemet. Eftersom HBase som standard avsĂ€tter 40% av minnet för en struktur som kallas BlockCache. I praktiken Ă€r detta en ConcurrentHashMap, dĂ€r nyckeln Ă€r filnamn + blockoffset, och vĂ€rdet Ă€r de faktiska data vid denna förskjutning.

SÄledes, nÀr lÀsning endast sker frÄn denna struktur, ser vi en fantastisk hastighet., ungefÀr en miljon förfrÄgningar per sekund. Men lÄt oss förestÀlla oss att vi inte kan allokera hundratals gigabyte minne enbart för databasens behov, eftersom pÄ dessa servrar körs mÄnga andra anvÀndbara applikationer.

Till exempel, i vÄrt fall Àr storleken pÄ BlockCache pÄ en RS cirka 12 GB. Vi har lanserat tvÄ RS pÄ en nod, vilket innebÀr att 96 GB har avsatts för BlockCache pÄ alla noder. Och mÀngden data Àr Ätskilligt större, lÄt oss sÀga att det handlar om 4 tabeller, med 130 regioner, dÀr filerna har en storlek pÄ 800 MB, komprimerade med FAST_DIFF, vilket i summan ger 410 GB (detta Àr ren data, utan hÀnsyn till replikationsfaktorn).

SĂ„ledes utgör BlockCache endast cirka 23% av den totala datamĂ€ngden, och detta ligger mycket nĂ€rmare de verkliga förhĂ„llandena för vad som kallas BigData. Och hĂ€r börjar det intressanta – det Ă€r uppenbart att ju fĂ€rre trĂ€ffar i cachen, desto sĂ€mre prestanda. För i fallet med missar kommer vi att behöva utföra en hel del arbete – det vill sĂ€ga, vi mĂ„ste gĂ„ ner till anropen av systemfunktioner. Men det gĂ„r inte att undvika, sĂ„ lĂ„t oss övervĂ€ga en helt annan aspekt – vad hĂ€nder med datan inne i cachen?

LÄt oss förenkla situationen och anta att vi har en cache som bara rymmer 1 objekt. HÀr Àr ett exempel pÄ vad som hÀnder nÀr vi försöker arbeta med en datamÀngd som Àr 3 gÄnger sÄ stor som cachen:

1. Placera block 1 i cachen
2. Ta bort block 1 frÄn cachen
3. Placera block 2 i cachen
4. Ta bort block 2 frÄn cachen
5. Placera block 3 i cachen

Fem Ă„tgĂ€rder har genomförts! Men det gĂ„r inte att kalla denna situation normal, vi tvingar i praktiken HBase att utföra en hel del helt meningslös arbetsinsats. Det lĂ€ser stĂ€ndigt ut data frĂ„n operativsystemets cache, lĂ€gger det i sin BlockCache, för att nĂ€stan omedelbart kasta bort det, eftersom en ny mĂ€ngd data anlĂ€nde. Animationen i början av inlĂ€gget visar problemets kĂ€rna – Garbage Collector överbelastas, atmosfĂ€ren vĂ€rms upp, lilla Greta i fjĂ€rran och varma Sverige blir nedslagen. Och vi IT-proffs gillar verkligen inte nĂ€r barn Ă€r ledsna, sĂ„ vi börjar tĂ€nka pĂ„ vad vi kan göra Ă„t detta.

Men vad om vi bara lÀgger in en viss procent av blocken i cachen, sÄ att cachen inte överfylls? LÄt oss för börja med att bara lÀgga till nÄgra rader kod i början av funktionen för att placera data i BlockCache:

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

PoÀngen hÀr Àr att offset Àr positionen för blocket i filen och de sista siffrorna Àr slumpmÀssigt och jÀmnt fördelade frÄn 00 till 99. DÀrför kommer vi enbart att hoppa över de som faller inom det intervall vi behöver.

Till exempel, lÄt oss sÀtta cacheDataBlockPercent = 20 och se vad som hÀnder:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Resultatet talar för sig sjÀlv. I graferna nedan blir det tydligt vad som orsakar denna acceleration - vi sparar en massa resurser frÄn GC genom att inte Àgna oss Ät Sisypheus arbete med att placera data i cachen, endast för att genast kasta bort dem till marsianerna under svansen:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

CPU-anvÀndningen ökar, men betydligt mindre Àn prestandan:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Det Àr ocksÄ vÀrt att nÀmna att block som lagras i BlockCache kan vara av olika slag. Merparten, omkring 95%, Àr faktiskt data. Resten Àr metadata, sÄsom Bloom-filter eller LEAF_INDEX och etc.. Denna metadata Àr liten men mycket anvÀndbar, eftersom HBase, innan den direkt vÀnder sig till data, först konsulterar metadata för att förstÄ om det behövs en vidare sökning hÀr, och om sÄ Àr fallet, var det intressanta blocket ligger.

DÀrför ser vi en villkorskontroll i koden buf.getBlockType().isData() och tack vare denna metadata kommer vi alltid att behÄlla den i cachen.

Nu lÄt oss öka belastningen och ocksÄ justera funktionen nÄgot. I det första testet satte vi grÀnsen för urvalet till 20 och BlockCache var nÄgot överbelastad. Nu lÄter vi det vara 23% och lÀgger till 100 trÄdar var 5:e minut för att se vid vilken punkt mÀttnaden intrÀffar:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

HÀr ser vi att den ursprungliga versionen nÀstan omedelbart nÄr taket pÄ omkring 100 tusen förfrÄgningar per sekund. Medan patchen ger en acceleration upp till 300 tusen. Det Àr tydligt att vidare acceleration inte Àr lika "gratis", och CPU-anvÀndningen ökar ocksÄ.

Men det Àr inte en sÀrskilt elegant lösning eftersom vi i förvÀg inte vet vilken procentandel av blocken som behöver cachas, det beror pÄ belastningsprofilen. DÀrför implementerades en mekanism för automatisk justering av denna parameter beroende pÄ aktivitetsnivÄn av lÀsningsoperationer.

För att hantera detta har tre parametrar lagts till:

hbase.lru.cache.heavy.eviction.count.limit — stĂ€ller in hur mĂ„nga gĂ„nger datavalvets utskiljandeprocess bör köras innan vi börjar anvĂ€nda optimering (dvs. hoppa över block). Som standard Ă€r det MAX_INT = 2147483647, vilket i praktiken innebĂ€r att funktionen aldrig kommer att börja fungera vid detta vĂ€rde. Utskiljningsprocessen körs var 5:e till 10:e sekund (det beror pĂ„ belastningen) och 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 Ă„r. Vi kan dock stĂ€lla in detta parameter till 0 och tvinga funktionen att fungera omedelbart efter start.

Det finns dock en nyttolast i denna parameter. Om vÄr belastningskaraktÀr innebÀr att kortvariga lÀsningar (antagligen under dagen) stÀndigt vÀxlar med lÄngvariga lÀsningar (pÄ natten), kan vi göra sÄ att funktionen aktiveras endast under pÄgÄende lÄngvariga lÀsningsoperationer.

Till exempel vet vi att kortvariga lÀsningar vanligtvis varar i cirka 1 minut. Under denna tid behöver vi inte börja ta bort block, cachen hinner inte bli förÄldrad och vi kan stÀlla in denna parameter till exempelvis 10. Det innebÀr att optimering börjar fungera först nÀr en lÄngvarig lÀsning har inletts, det vill sÀga efter 100 sekunder. SÄledes, om vi har en kortvarig lÀsning, kommer alla block att hamna i cachen och vara tillgÀngliga (förutom de som tas bort med den standardalgoritmen). Men nÀr vi genomför lÄngvariga lÀsningar, aktiveras funktionen och vi fÄr betydligt högre prestanda.

hbase.lru.cache.heavy.eviction.mb.size.limit — stĂ€ller in hur mĂ„nga megabyte vi skulle vilja placera i cachen (och naturligtvis ta bort) var 10:e sekund. Funktionen kommer att försöka nĂ„ detta vĂ€rde och upprĂ€tthĂ„lla det. Syftet Ă€r följande: om vi trycker in gigabyte i cachen, kommer vi ocksĂ„ att behöva ta bort gigabyte, vilket, som vi sett ovan, Ă€r ganska kostsamt. Men man bör inte försöka göra det för litet, eftersom det kan leda till att blockhoppslĂ€get avslutas för tidigt. För kraftfulla servrar (omkring 20-40 fysiska kĂ€rnor) Ă€r det optimalt att stĂ€lla in detta pĂ„ cirka 300-400 MB. För medelklass system (ungefĂ€r 10 kĂ€rnor) 200-300 MB. För svagare system (2-5 kĂ€rnor) kan 50-100 MB vara rimligt (detta har inte testats pĂ„ sĂ„dana system).

LÄt oss titta pÄ hur detta fungerar: anta att vi har stÀllt in hbase.lru.cache.heavy.eviction.mb.size.limit = 500, det finns en viss belastning (lÀsningar) och var 10:e sekund berÀknar vi hur mÄnga byte som har fristÀllts med formeln:

Overhead = Frigjorda byte-summan (MB) * 100 / GrĂ€ns (MB) — 100;

Om det faktiskt har fristÀllts 2000 MB, blir Overhead:

2000 * 100 / 500 — 100 = 300%

Algoritmerna strÀvar efter att hÄlla overhead under nÄgra tiotal procent, sÄ funktionen kommer att minska procenten av cachelagrade block och dÀrmed implementera en automatisk justeringsmekanism.

Men om belastningen minskar, lÄt oss sÀga att det har fristÀllts totalt 200 MB och Overhead blivit negativt (sÄ kallad overshooting):

200 * 100 / 500 — 100 = -60%

SÄ kommer funktionen istÀllet att öka procenten av cachelagrade block tills Overhead blir positiv.

Nedan följer ett exempel pĂ„ hur detta ser ut med verkliga data. Försök inte strĂ€va efter 0%, det Ă€r omöjligt. Det Ă€r ganska bra nĂ€r det ligger runt 30–100%, det hjĂ€lper till att undvika ett för tidigt avbrott i optimeringslĂ€get vid kortsiktiga toppar.

hbase.lru.cache.heavy.eviction.overhead.coefficient — stĂ€ller in hur snabbt vi skulle vilja fĂ„ resultat. Om vi vet att vĂ„ra lĂ€sningar mestadels Ă€r lĂ„ngvariga och inte vill vĂ€nta, kan vi öka denna koefficient och fĂ„ hög prestanda snabbare.

Till exempel, om vi har stÀllt in denna koefficient till 0.01. Detta betyder att Overhead (se ovan) kommer att multipliceras med detta tal och procenten av cachelagrade block kommer att minskas. LÄt oss sÀga att Overhead = 300%, och koefficienten = 0.01, dÄ kommer procenten av cachelagrade block att minskas med 3%.

En liknande logik för «Backpressure» Àr implementerad Àven för negativa vÀrden av Overhead (overshooting). Eftersom kortsiktiga svÀngningar i lÀsnings- och fristÀllningsvolymer alltid Àr möjliga, sÄ tillÄter denna mekanism att undvika ett för tidigt avbrott i optimeringslÀget. Backpressure har en omvÀnd logik: ju mer overshooting, desto fler block cachelagras.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Implementeringskod

        LruBlockCache cache = this.cache.get();
        if (cache == null) {
          break;
        }
        freedSumMb += cache.evict() / 1024 / 1024;
        /*
        * Ibland lÀser vi mer data Àn vad som fÄr plats i BlockCache
        * och detta orsakar en hög frekvens av utslagning.
        * Detta leder i sin tur till mycket arbete för skrÀpföraren.
        * SÄ mÄnga block lÀggs in i BlockCache men lÀses aldrig,
        * men anvÀnder mycket CPU-resurser.
        * HÀr kommer vi att analysere hur mÄnga byte som frigjordes och besluta
        * huruvida tiden har kommit att minska mÀngden cachade block.
        * Det hjÀlper till att undvika att lÀgga för mÄnga block i BlockCache
        * nÀr evict() arbetar vÀldigt aktivt och sparar CPU för andra uppgifter.
        * Mer detaljer: https://issues.apache.org/jira/browse/HBASE-23887
        */

        // Först och frÀmst mÄste vi kontrollera hur mycket tid
        // som har passerat sedan föregÄende evict() kördes
        // Detta bör vara nÀstan samma tid (+/- 10s)
        // eftersom vi fÄr jÀmförbara volymer av frigjorda byte varje gÄng.
        // 10s eftersom detta Àr den standardperiod för att köra evict() (se ovan this.wait)
        long stopTime = System.currentTimeMillis();
        if ((stopTime - startTime) > 1000 * 10 - 1) {
          // HÀr mÄste vi berÀkna vilken situation vi har fÄtt.
          // Vi har begrÀnsningen "hbase.lru.cache.heavy.eviction.bytes.size.limit"
          // och kan berÀkna överbelastningen pÄ den.
          // Vi kommer anvÀnda denna information för att besluta,
          // hur man Àndrar procenten av cachade block.
          freedDataOverheadPercent =
            (int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
          if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
            // Nu Àr vi i en situation dÀr vi Àr över grÀnsen
            // Men kanske vi kommer att ignorera det eftersom det kommer att ta slut ganska snart
            heavyEvictionCount++;
            if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
              // Det har pÄgÄtt lÀnge och vi mÄste minska cachningen
              // av block nu. SÄ vi berÀknar hÀr hur mÄnga block vi vill hoppa över.
              // Det beror pÄ:
              // 1. Överbelastning - om överbelastningen Ă€r stor kan vi vara mer aggressiva
              // reducera mÀngden cachade block.
              // 2. Hur snabbt vi vill fÄ resultatet. Om vi vet att vÄr
              // tunga lÀsning pÄgÄr lÀnge, vill vi inte vÀnta och kan
              // öka koefficienten och fÄ bra prestanda ganska snart.
              // Men om vi inte Àr sÀkra kan vi göra det lÄngsamt och det kan förhindra
              // för tidig ÄtgÀrd frÄn detta lÀge. SÄ nÀr koefficienten Àr
              // högre kan vi fÄ bÀttre prestanda nÀr tunga lÀsningen Àr stabil.
              // Men nÀr lÀsningen Àndras kan vi justera det och sÀtta
              // koefficienten till ett lÀgre vÀrde.
              int change =
                (int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
              // Men praktiken visar att 15% minskning Àr ganska tillrÀckligt.
              // Vi Àr inte giriga (det kan leda till för tidig ÄtgÀrd).
              change = Math.min(15, change);
              change = Math.max(0, change); // Jag tror aldrig att det kommer att hÀnda men kontrollera för sÀkerhets skull
              // SÄ detta Àr nyckelpunkten, hÀr minskar vi % av cachade block
              cache.cacheDataBlockPercent -= change;
              // Om vi gÄr ner för djupt mÄste vi stoppa hÀr, 1% mÄste vara kvar.
              cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
            }
          } else {
            // Tja, vi har fÄtt överskott.
            // Kanske Àr det bara en kortsiktig fluktuation och vi kan stanna kvar i detta lÀge.
            // Det hjÀlper att undvika för tidig utgÄng under kortsiktiga fluktuationer.
            // Om överskottet Àr mindre Àn 90% kommer vi att försöka öka procenten av
            // cachade block och hoppas att det Àr tillrÀckligt.
            if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
              // Enkel logik: mer överskott - fler cachade block (mottryck)
              int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
              cache.cacheDataBlockPercent += change;
              // Men det kan inte vara mer Àn 100%, sÄ kontrollera det.
              cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
            } else {
              // Det verkar som om den tunga lÀsningen Àr över.
              // Bara avsluta detta lÀge.
              heavyEvictionCount = 0;
              cache.cacheDataBlockPercent = 100;
            }
          }
          LOG.info("BlockCache utslagen (MB): {}, överbelastning (%): {}, " +
            "tung utslagningsrÀknare: {}, " +
            "nuvarande cachad DataBlock (%): {}",
            freedSumMb, freedDataOverheadPercent,
            heavyEvictionCount, cache.cacheDataBlockPercent);

          freedSumMb = 0;
          startTime = stopTime;
       }

LÄt oss nu titta pÄ detta med ett verkligt exempel. Vi har följande testscenarie:

  1. Vi börjar med att göra en Scan (25 trÄdar, batch = 100)
  2. Efter 5 minuter lÀgger vi till multi-gets (25 trÄdar, batch = 100)
  3. Efter 5 minuter stÀnger vi av multi-gets (enbart scan kvar)

Vi gör tvÄ körningar, först hbase.lru.cache.heavy.eviction.count.limit = 10000 (vilket i praktiken stÀnger av funktionen), och sedan stÀller vi in limit = 0 (aktiverar).

I loggarna nedan ser vi hur funktionen aktiveras och ÄterstÀller Overshooting till 14-71%. Ibland minskar belastningen, vilket aktiverar Backpressure och HBase cachelagrar fler block igen.

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

Skanningarna behövdes för att visa samma process i form av ett diagram som visar förhĂ„llandet mellan tvĂ„ cache-sektioner – single (dĂ€r block som Ă€nnu ingen har begĂ€rt hamnar) och multi (hĂ€r lagras 'efterfrĂ„gade' data Ă€ven om de har begĂ€rts minst en gĂ„ng):

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Och slutligen hur parameternas arbete ser ut i form av ett diagram. För jÀmförelse var cachen helt avstÀngd i början, sedan startades HBase med caching och en fördröjning pÄ 5 minuter innan optimeringen pÄbörjades (30 cykler av utkastning).

Den fullstÀndiga koden kan hittas i Pull Request HBASE 23887 pÄ github.

Men 300 000 lÀsningar per sekund Àr inte allt som kan pressas ur denna utrustning under dessa förhÄllanden. Anledningen Àr att nÀr man behöver komma Ät data via HDFS anvÀnds mekanismen ShortCircuitCache (hÀdanefter SSC), som gör det möjligt att fÄ direkt Ätkomst till data utan att involvera nÀtverksinteraktion.

Profileringsresultatet visade att denna mekanism, trots att den ger stor vinst, ocksÄ blir en flaskehals vid en tidpunkt, eftersom praktiskt taget alla tunga operationer sker inom lÄset, vilket leder till blockeringar en stor del av tiden.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

NÀr vi insÄg detta förstod vi att vi kunde kringgÄ problemet genom att skapa en uppsÀttning oberoende SSC:

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

Och sedan arbeta med dem, utesluta överlappningar Àven baserat pÄ den sista siffran i offset:

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

Nu kan vi börja med testerna. För detta kommer vi att lÀsa filer frÄn HDFS med en enkel flerkÀrnig applikation. Vi stÀller in parametrarna:

conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // som standard = 1 MB och detta saktar ner lÀsningen, sÄ det Àr bÀttre att anpassa det till de verkliga behoven
conf.set("dfs.client.short.circuit.num", num); // frÄn 1 till 10

Och vi lÀser helt enkelt filerna:

FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i  900000000)
        position = 0L;
    int res = in.read(position, byteBuffer, 0, 65536);
}

Denna kod körs i separata trĂ„dar och vi kommer att öka antalet filer som lĂ€ses samtidigt (frĂ„n 10 till 200 — horisontell axel) och antalet cacher (frĂ„n 1 till 10 — grafer). Den vertikala axeln visar den acceleration som uppnĂ„s genom att öka SSC i förhĂ„llande till fallet nĂ€r cache bara Ă€r en.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

SÄ hÀr lÀser man grafen: tiden för 100 000 lÀsningar i block om 64 KB med en enda cache tar 78 sekunder. Med 5 cacher utförs det pÄ 16 sekunder. Det vill sÀga, det finns en acceleration pÄ ~5 gÄnger. Som man kan se frÄn grafen Àr effekten inte sÀrskilt mÀrkbar vid ett litet antal parallella lÀsningar; det börjar spela en mÀrkbar roll nÀr lÀsningarna Àr fler Àn 50. Det Àr ocksÄ tydligt att ökningen av antalet SSC frÄn 6 och uppÄt ger betydligt mindre prestandaförbÀttring.

Not 1: Eftersom testresultaten Àr ganska volatila (se nedan) genomfördes 3 körningar och de erhÄllna vÀrdena genomsnittsbildades.

Not 2: PrestandaförbÀttringen frÄn instÀllningen för slumpmÀssig Ätkomst Àr densamma, Àven om sjÀlva Ätkomsten Àr nÄgot lÄngsammare.

Det Àr dock viktigt att notera att, till skillnad frÄn fallet med HBase, denna ökning inte alltid Àr gratis. HÀr "lÄser vi upp" CPU:ns möjligheter att utföra arbete istÀllet för att fastna pÄ lÄs.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

HÀr kan man se att en allmÀn ökning av antalet cachar ger en ungefÀr proportionerlig ökning av CPU-anvÀndningen. Det finns dock nÄgra mer fördelaktiga kombinationer.

Till exempel, lÄt oss titta nÀrmare pÄ instÀllningen SSC = 3. Prestandaökningen i intervallet ligger pÄ cirka 3,3 gÄnger. Nedan visas resultaten frÄn alla tre enskilda körningar.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

DÀremot ökar CPU-förbrukningen med ungefÀr 2,8 gÄnger. Skillnaden Àr inte sÄ stor, men lilla Greta Àr redan glad och kanske fÄr hon tid att besöka skolan och lektionerna.

SÄledes kommer detta att ha en positiv effekt för vilket verktyg som helst som anvÀnder massivt tillgÄng till HDFS (t.ex. Spark med mera), förutsatt att applikationskoden Àr lÀtt (det vill sÀga flaskhalsen ligger pÄ HDFS-klientsidan) och att det finns ledig CPU-kapacitet. För att kontrollera detta, lÄt oss testa vilken effekt det ger att kombinera optimeringar av BlockCache och tunning av SSC för lÀsning frÄn HBase.

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

HÀr syns det att under sÄdana förhÄllanden Àr effekten inte lika stor som i raffinerade tester (lÀsning utan nÄgon bearbetning), men att fÄ ut ytterligare 80K Àr fullt möjligt. Tillsammans ger bÄda optimeringarna en hastighetsökning pÄ upp till 4 gÄnger.

En PR har ocksÄ gjorts för denna optimering. [HDFS-15202], som har sammanfogats och denna funktionalitet kommer att vara tillgÀnglig i kommande utgÄvor.

Och slutligen var det intressant att jÀmföra lÀsprestandan hos en liknande wide-column databas Cassandra och HBase.

För detta kördes instanser av det standardiserade belastningstestverktyget YCSB frÄn tvÄ vÀrdar (totalt 800 trÄdar). PÄ server-sidan - 4 instanser av RegionServer och Cassandra pÄ 4 vÀrdar (inte de dÀr klienterna körs, för att undvika pÄverkan). LÀsningarna gjordes frÄn tabeller av storleken:

HBase - 300 GB pÄ HDFS (100 GB rena data)

Cassandra - 250 GB (replication factor = 3)

Det vill sÀga, volymen var ungefÀr densamma (HBase nÄgot mer).

HBase-parametrar:

dfs.client.short.circuit.num = 5 (HDFS-klientoptimering)

hbase.lru.cache.heavy.eviction.count.limit = 30 — detta innebĂ€r att patchen kommer att börja fungera efter 30 vrĂ€kningar (cirka 5 minuter)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — det mĂ„l som ska cachas och vrĂ€kas

YCSB-loggarna har parsats och sammanstÀllts i Excel-diagram:

Hur man ökar lÀshastigheten frÄn HBase tre gÄnger och frÄn HDFS fem gÄnger

Som det framgÄr gör dessa optimeringsÄtgÀrder att vi kan jÀmföra prestandan hos dessa databaser under dessa förhÄllanden och uppnÄ 450 000 lÀsningar per sekund.

Vi hoppas att denna information kan vara till nytta för nÄgon i den spÀnnande kampen om prestanda.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster