Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Errendimendu handia da big datarekin lan egiteko baldintza nagusietako bat. Sberbank-eko datuak kargatzeko departamentuan, ia transakzio guztiak gure Hadoop-en oinarritutako Datu Hodeian ponpatzen ditugu eta, beraz, informazio-fluxu oso handiak lantzen ditugu. Jakina, errendimendua hobetzeko moduen bila gabiltza beti, eta orain esan nahi dizugu nola lortu genuen RegionServer HBase eta HDFS bezeroa adabakitzea, eta horri esker irakurketa eragiketen abiadura nabarmen handitu ahal izan genuen.
Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Hala ere, hobekuntzen funtsera joan aurretik, merezi du, printzipioz, HDD batean esertzen bazara saihestu ezin diren murrizketez hitz egitea.

Zergatik ez dira bateragarriak HDD eta Random Access irakurketa azkarrak
Dakizuenez, HBase eta beste datu-base askok hainbat hamarnaka kilobyte-ko blokeetan gordetzen dituzte datuak. Berez 64 KB ingurukoa da. Orain imajina dezagun 100 byte bakarrik lortu behar ditugula eta HBase-ri eskatzen diogula datu hauek gako jakin bat erabiliz emateko. HFiles-en blokearen tamaina 64 KB denez, eskaera behar baino 640 aldiz handiagoa izango da (minutu bat besterik ez!).

Ondoren, eskaera HDFS eta bere metadatuen cache-mekanismotik pasatuko baita ShortCircuitCache (fitxategietara zuzeneko sarbidea ahalbidetzen duena), honek dagoeneko 1 MB diskotik irakurtzea dakar. Hala ere, parametroarekin doi daiteke dfs.client.read.shortcircuit.buffer.tamaina eta kasu askotan balio hori murriztea zentzuzkoa da, adibidez 126 KB-ra.

Demagun hau egiten dugula, baina gainera, java apiaren bidez datuak irakurtzen hasten garenean, hala nola, FileChannel.read bezalako funtzioak eta sistema eragileari zehaztutako datu kopurua irakurtzeko eskatzen diogunean, "badaezpada" 2 aldiz gehiago irakurtzen du. , hau da. 256 KB gure kasuan. Hau da java-k ez duelako FADV_RANDOM bandera ezartzeko modu errazik portaera hori saihesteko.

Ondorioz, gure 100 byte lortzeko, 2600 aldiz gehiago irakurtzen da kaputxa azpian. Konponbidea begi-bistakoa dela dirudi, murrizten dezagun blokearen tamaina kilobyte batera, jarri dezagun aipatutako bandera eta lor dezagun argitasunaren azelerazio handia. Baina arazoa da blokearen tamaina 2 aldiz murriztuz gero, denbora-unitate bakoitzeko irakurtzen diren byte-kopurua ere 2 aldiz murrizten dugula.

FADV_RANDOM bandera ezartzean irabazi apur bat lor daiteke, baina hari anitzeko hari handiarekin eta 128 KB-ko bloke-tamainarekin soilik, baina ehuneko hamarren pare bat da gehienez:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Probak 100 fitxategitan egin ziren, bakoitza 1 GBko tamainakoa eta 10 HDDtan kokatuta.

Kalkula dezagun zertan zenbatu gaitezkeen, printzipioz, abiadura honetan:
Demagun 10 diskotatik irakurtzen dugula 280 MB/seg abiaduran, hau da. 3 milioi aldiz 100 byte. Baina gogoratzen dugunez, behar ditugun datuak irakurritakoak baino 2600 aldiz txikiagoak dira. Horrela, 3 milioi 2600z zatitzen ditugu eta lortzen dugu 1100 erregistro segundoko.

Etsigarria, ezta? Hori da natura Ausazko sarbidea HDDko datuetarako sarbidea - blokearen tamaina edozein dela ere. Hau da ausazko sarbidearen muga fisikoa eta datu-base batek ezin du gehiago estutu horrelako baldintzetan.

Orduan, nola lortzen dute datu-baseek abiadura askoz handiagoa? Galdera honi erantzuteko, ikus dezagun zer gertatzen den hurrengo irudian:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Hemen ikusten dugu lehenengo minutuetan abiadura benetan mila disko ingurukoa dela segundoko. Hala ere, gainera, eskatutakoa baino askoz gehiago irakurtzen denez, datuak sistema eragilearen buff/cachean amaitzen dira (linux) eta abiadura segundoko 60 mila duinagora igotzen da.

Horrela, aurrerago, sarbide bizkortzeaz lan egingo dugu sistema eragilearen cachean dauden edo sarbide-abiadura pareko SSD/NVMe biltegiratze gailuetan dauden datuetarako soilik.

Gure kasuan, probak egingo ditugu 4 zerbitzariko banku batean, eta horietako bakoitza honela kobratzen da:

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

Eta hemen gakoa irakurri beharreko tauletako datu kopurua da. Kontua da guztiz HBase cachean kokatutako taula bateko datuak irakurtzen badituzu, sistema eragilearen buff/cachetik irakurtzera ere ez dela iritsiko. HBase-k berez esleitzen baitu memoriaren % 40 BlockCache izeneko egitura bati. Funtsean, hau ConcurrentHashMap bat da, non gakoa fitxategiaren izena + blokearen desplazamendua den, eta balioa desplazamendu honetako benetako datuak diren.

Horrela, egitura honetatik soilik irakurtzean, guk abiadura bikaina ikusten dugu, segundoko milioi bat eskaera bezala. Baina pentsa dezagun ezin ditugula ehunka gigabyte memoria esleitu datu-baseen beharretarako soilik, zerbitzari hauetan exekutatzen diren beste gauza erabilgarriak daudelako.

Adibidez, gure kasuan, RS batean BlockCache-ren bolumena 12 GB ingurukoa da. Nodo batean bi RS lurreratu ditugu, hau da. 96 GB esleitzen dira BlockCacherako nodo guztietan. Eta hainbat aldiz datu gehiago dago, adibidez, izan bedi 4 taula, 130 eskualde bakoitza, zeinetan fitxategiak 800 MB-ko tamaina dutenak, FAST_DIFF bidez konprimituta, alegia. guztira 410 GB (datu hutsa da, hau da, erreplikazio faktorea kontuan hartu gabe).

Horrela, BlockCache datu-bolumen osoaren % 23 baino ez da eta hau BigData deritzonaren benetako baldintzetatik askoz hurbilago dago. Eta hor hasten da dibertsioa; jakina, zenbat eta cache-ren arrakasta gutxiago izan, orduan eta okerragoa da errendimendua. Azken finean, galduz gero, lan asko egin beharko duzu -hau da. joan sistemaren funtzioak deitzera. Hala ere, hori ezin da saihestu, beraz, ikus dezagun guztiz bestelako alderdi bat: zer gertatzen da cacheko datuekin?

Sinplifikatu dezagun egoera eta demagun objektu bakarrera egokitzen den cache bat dugula. Hona hemen cachea baino 1 aldiz handiagoa den datu-bolumen batekin lan egiten saiatzen garenean gertatuko denaren adibide bat, honako hau egin beharko dugu:

1. Jarri 1. blokea cachean
2. Kendu 1. blokea cachetik
3. Jarri 2. blokea cachean
4. Kendu 2. blokea cachetik
5. Jarri 3. blokea cachean

5 ekintza burutu dira! Hala ere, egoera honi ezin zaio normaltzat jo; izan ere, HBase behartzen ari gara guztiz alferrikako lan mordoa egitera. Sistema eragilearen cacheko datuak etengabe irakurtzen ditu, BlockCache-n jartzen ditu, ia berehala botatzeko, datuen zati berri bat iritsi delako. Argitalpenaren hasierako animazioak arazoaren funtsa erakusten du: Garbage Collector eskala handitzen ari da, giroa berotzen ari da, Greta txikia Suedia urrun eta beroan haserretzen ari da. Eta IT-ko jendeari ez zaigu benetan gustatzen umeak triste daudenean, beraz, zer egin dezakegun pentsatzen hasten gara.

Zer gertatzen da cachean bloke guztiak ez badituzu jartzen, horien ehuneko jakin bat baizik, cachea gainezka ez dadin? Hasteko, BlockCache-n datuak sartzeko funtzioaren hasieran kode lerro batzuk gehituz:

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

Hona hemen puntua honako hau: offset blokearen posizioa da fitxategian eta bere azken zifrak ausaz eta berdin banatzen dira 00tik 99ra. Horregatik, behar dugun barrutian sartzen direnak bakarrik saltatuko ditugu.

Adibidez, ezarri cacheDataBlockPercent = 20 eta ikusi zer gertatzen den:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Emaitza agerikoa da. Beheko grafikoetan, argi geratzen da zergatik gertatu den halako azelerazioa - GC baliabide asko aurrezten ditugu datuak cachean sartzeko Sisyphean lana egin gabe, martziano txakurren hustubidera berehala botatzeko:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Aldi berean, PUZaren erabilera handitzen da, baina produktibitatea baino askoz txikiagoa da:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Kontuan izan behar da BlockCache-n gordetako blokeak desberdinak direla. Gehienak, %95 inguru, datuak berak dira. Eta gainerakoa metadatuak dira, hala nola Bloom iragazkiak edo LEAF_INDEX eta etab.. Datu hauek ez dira nahikoak, baina oso baliagarriak dira, izan ere, datuetara zuzenean sartu baino lehen, metara jotzen du HBase, hemen gehiago bilatu behar ote den ulertzeko eta, hala bada, interes-blokea zehazki non dagoen.

Hori dela eta, kodean egiaztapen-baldintza bat ikusten dugu buf.getBlockType().isData() eta meta honi esker, edozein kasutan, cachean utziko dugu.

Orain handitu dezagun karga eta apur bat estutu funtzioa bat-batean. Lehenengo proban ebaki-ehunekoa = 20 egin genuen eta BlockCache apur bat gutxietsi zen. Orain ezar dezagun %23an eta gehitu 100 hari 5 minuturo, saturazioa zein puntutan gertatzen den ikusteko:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Hemen ikusten dugu jatorrizko bertsioak ia berehala jotzen duela sabaia segundoko 100 mila eskaera ingururekin. Adabakiak 300 milarainoko azelerazioa ematen du. Aldi berean, argi dago azelerazio gehiago jada ez dela hain "doakoa"; PUZaren erabilera ere handitzen ari da.

Hala ere, hau ez da oso irtenbide dotorea, ez baitakigu aldez aurretik zer bloke-portzentaia gorde behar den cachean, karga-profilaren araberakoa da. Horregatik, parametro hori automatikoki doitzeko mekanismo bat ezarri zen irakurketa-eragiketen jardueraren arabera.

Hau kontrolatzeko hiru aukera gehitu dira:

hbase.lru.cache.heavy.desalojo.zenbaketa.muga β€” Optimizazioa erabiltzen hasi baino lehen ezartzen du zenbat aldiz exekutatu behar den cacheko datuak kanporatzeko prozesua (hau da, blokeak saltatzea). Lehenespenez MAX_INT = 2147483647 berdina da eta, hain zuzen ere, esan nahi du funtzioa ez dela inoiz balio honekin lanean hasiko. Desalojo prozesua 5 - 10 segundoro hasten delako (kargaren araberakoa da) eta 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 urte. Hala ere, parametro hau 0-n ezar dezakegu eta funtzioak abiarazi eta berehala funtziona dezan.

Hala ere, parametro honetan karga bat ere badago. Gure karga epe laburreko irakurketak (egunean zehar esate baterako) eta epe luzeko irakurketak (gauez) etengabe tartekatzen badira, ziurta dezakegu funtzioa aktibatuta dagoela irakurketa luzeko eragiketak egiten ari direnean soilik.

Adibidez, badakigu epe laburreko irakurketak minutu 1 inguru irauten duela. Ez dago blokeak botatzen hasi beharrik, cacheak ez du zaharkitzeko astirik izango eta, ondoren, parametro hau berdina ezar dezakegu, adibidez, 10. Horrek optimizazioa denbora luzean bakarrik hasiko dela funtzionatuko du. epeko irakurketa aktiboa hasi da, hau da. 100 segundotan. Horrela, epe laburreko irakurketa badugu, bloke guztiak cachean sartuko dira eta erabilgarri egongo dira (algoritmo estandarrak kanporatuko dituenak izan ezik). Eta epe luzerako irakurketak egiten ditugunean, funtzioa aktibatuta dago eta askoz errendimendu handiagoa izango genuke.

hbase.lru.cache.heavy.eviction.mb.size.limit β€” 10 segundotan zenbat megabyte jarri nahi ditugun cachean (eta, noski, desalojatu) ezartzen du. Ezaugarri hori balio horretara iristen eta mantentzen saiatuko da. Kontua hau da: cachean gigabyte sartzen baditugu, gigabyteak desalojatu beharko ditugu, eta hau, goian ikusi dugunez, oso garestia da. Hala ere, ez duzu txikiegia ezartzen saiatu behar, honek blokea saltatzeko modua lehenago irtengo baita. Zerbitzari indartsuetarako (20-40 nukleo fisiko inguru), 300-400 MB inguru ezartzea da egokiena. Erdi mailako klaserako (~10 nukleo) 200-300 MB. Sistema ahuletarako (2-5 nukleo) 50-100 MB normala izan daiteke (ez da probatu hauetan).

Ikus dezagun nola funtzionatzen duen: demagun hbase.lru.cache.heavy.eviction.mb.size.limit = 500 ezartzen dugula, nolabaiteko karga (irakurketa) dago eta gero ~10 segundoro zenbat byte ziren kalkulatzen dugula. desalojatu formula hau erabiliz:

Gastu orokorra = Byte askatuen batura (MB) * 100 / Muga (MB) - 100;

Izan ere, 2000 MB kanporatu baziren, gainkostua honako hau da:

2000 * 100 / 500 - 100 = % 300

Algoritmoak ehuneko hamarren batzuk baino gehiago mantentzen saiatzen dira, beraz, funtzioak cachean gordetako blokeen ehunekoa murriztuko du, eta, horrela, sintonizazio automatikoko mekanismo bat ezarriko du.

Hala ere, karga jaisten bada, demagun 200 MB bakarrik kanporatzen direla eta Overhead negatiboa bihurtzen dela (overshooting delakoa):

200 * 100 / 500 - 100 = -% 60

Aitzitik, funtzioak cachean gordetako blokeen ehunekoa handituko du Overhead positiboa izan arte.

Behean datu errealetan nola ikusten den adierazten da. Ez dago %0ra iristen saiatu beharrik, ezinezkoa da. Oso ona da % 30 - 100 ingurukoa denean, honek optimizazio modutik irtete goiztiarra saihesten laguntzen du epe laburreko igoeretan.

hbase.lru.cache.heavy.eviction.overhead.koefizientea β€” Emaitza zein azkar lortu nahiko genukeen ezartzen du. Gure irakurketak gehienetan luzeak direla eta ez dugula itxaron nahi ziur badakigu, ratio hori handitu eta errendimendu handia azkarrago lor dezakegu.

Adibidez, koefiziente hau = 0.01 ezarri dugu. Horrek esan nahi du Overhead (ikus goian) zenbaki horrekin biderkatuko dela emaitzarekin eta cachean gordetako blokeen ehunekoa murriztuko da. Demagun Overhead = % 300 eta koefizientea = 0.01 direla, orduan cacheko blokeen ehunekoa % 3 murriztuko dela.

"Atzera-presioa" antzeko logika bat ere ezartzen da Overhead (gainditze) balio negatiboetarako. Irakurketen eta desalojoen bolumenaren epe laburreko gorabeherak beti posible direnez, mekanismo honek optimizazio modutik goiz irten saihesteko aukera ematen du. Atzerapresioak alderantzizko logika du: zenbat eta indar handiagoa izan, orduan eta bloke gehiago gordetzen dira cachean.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Ezarpen-kodea

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

Ikus dezagun orain hau guztia benetako adibide bat erabiliz. Test script hau dugu:

  1. Has gaitezen Scan egiten (25 hari, lote = 100)
  2. 5 minutu igaro ondoren, gehitu multi-gets (25 hari, lote = 100)
  3. 5 minutu igaro ondoren, desaktibatu lorpen anitzak (eskaneatzea bakarrik geratzen da berriro)

Bi exekuzio egiten ditugu, lehenik hbase.lru.cache.heavy.eviction.count.limit = 10000 (horrek funtzioa desgaitzen du benetan), eta gero muga = 0 ezarri (gaitzen du).

Beheko erregistroetan funtzioa nola aktibatzen den eta Overshooting % 14-71ean berrezartzen den ikusten dugu. Noizean behin karga gutxitzen da, eta horrek Atzerapresioa pizten du eta HBase-k bloke gehiago gordetzen ditu berriro.

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

Azterketak prozesu bera erakusteko behar ziren bi cache atalen arteko erlazioaren grafiko baten moduan: bakarrekoak (non orain arte eskatu ez diren blokeak) eta anitzekoak (gutxienez behin β€œeskatzen diren” datuak hemen gordetzen diren):

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Eta azkenik, parametroen funtzionamendua nolakoa da grafiko moduan. Konparazio baterako, hasieran cachea erabat desaktibatu zen, gero HBase abiarazi zen cachearekin eta optimizazio-lanaren hasiera 5 minutuz (30 desalojo-ziklo) atzeratuz.

Kode osoa Pull Request-en aurki dezakezu HBASE 23887 github-en.

Hala ere, 300 mila irakurketa segundoko ez da hardware honetan lor daitekeen guztia baldintza hauetan. Kontua da datuetara HDFS bidez sartu behar denean, ShortCircuitCache (aurrerantzean SSC) mekanismoa erabiltzen dela, eta horrek datuetara zuzenean sartzeko aukera ematen du, sareko elkarrekintzak saihestuz.

Profilak erakutsi zuen mekanismo honek irabazi handia ematen badu ere, noizbait botila-lepo bihurtzen dela, ia eragiketa astun guztiak sarraila baten barruan gertatzen direlako, eta horrek blokeatzea dakar gehienetan.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Horretaz konturatuta, konturatu ginen arazoa saihestu daitekeela SSC independente sorta bat sortuz:

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

Eta gero lan egin haiekin, elkarguneak kenduta azken desplazamendu-digitan ere:

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

Orain probak egiten has zaitezke. Horretarako, HDFSko fitxategiak irakurriko ditugu hari anitzeko aplikazio sinple batekin. Ezarri parametroak:

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

Eta irakurri fitxategiak:

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

Kode hau hari bereizietan exekutatzen da eta aldi berean irakurritako fitxategien kopurua (10etik 200era - ardatz horizontala) eta cache kopurua (1etik 10era - grafikoak) handituko dugu. Ardatz bertikalak SSC-ren gehikuntzaren ondoriozko azelerazioa erakusten du cache bakarra dagoen kasuarekiko.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Grafikoa nola irakurri: 100 mila irakurketen exekuzio-denborak 64 KB-ko blokeetan cache batekin 78 segundo behar ditu. 5 cacherekin, berriz, 16 segundo behar dira. Horiek. ~5 aldiz azelerazioa dago. Grafikoan ikusten den bezala, efektua ez da oso nabaria irakurketa paralelo kopuru txiki batean;50 hari irakurketa baino gehiago daudenean paper nabarmena izaten hasten da.Era berean, nabaria da SSC kopurua handitzea 6tik aurrera. eta gainetik errendimendu-igoera nabarmen txikiagoa ematen du.

1. oharra: probaren emaitzak nahiko lurrunkorrak direnez (ikus behean), 3 lasterketa egin ziren eta ondoriozko balioak batez bestekoak egin ziren.

2. oharra: ausazko sarbidea konfiguratzearen errendimendu irabazia berdina da, nahiz eta sarbidea bera apur bat motelagoa den.

Dena den, argitu beharra dago, HBaserekin ez bezala, azelerazio hori ez dela beti librea. Hemen PUZaren lan gehiago egiteko gaitasuna "desblokeatzen" dugu, sarrailetan zintzilikatu beharrean.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Hemen ikus dezakezu, oro har, cache-kopurua handitzeak CPUaren erabileraren gehikuntza gutxi gorabehera proportzionala ematen duela. Hala ere, konbinazio irabazle apur bat gehiago daude.

Adibidez, ikus ditzagun SSC = 3 ezarpena hurbilagotik. Barrutiaren errendimenduaren igoera 3.3 aldiz ingurukoa da. Jarraian, hiru lasterketa ezberdinetako emaitzak daude.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

CPUaren kontsumoa 2.8 aldiz handitzen den bitartean. Aldea ez da oso handia, baina Greta txikia dagoeneko pozik dago eta baliteke eskolara joateko eta eskolak hartzeko denbora edukitzea.

Horrela, horrek eragin positiboa izango du HDFSrako sarbide masiboa erabiltzen duen edozein tresnarentzat (adibidez Spark, etab.), baldin eta aplikazioaren kodea arina bada (hau da, entxufea HDFS bezeroaren aldean badago) eta PUZaren potentzia doan badago. . Egiaztatzeko, proba dezagun HBase-tik irakurtzeko BlockCache optimizazioaren eta SSC sintonizazioaren erabilera konbinatuak zer eragin izango duen.

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Ikusten da baldintza horietan efektua ez dela proba finduetan bezain handia (prozesatu gabe irakurtzea), baina oso posible da hemen 80K gehigarri ateratzea. Bi optimizazioek batera, 4 aldiz bizkortzea eskaintzen dute.

Optimizazio honetarako PR bat ere egin zen [HDFS-15202], batu egin dena eta funtzionalitate hau eskuragarri egongo da hurrengo bertsioetan.

Eta, azkenik, interesgarria izan zen antzeko zutabe zabaleko datu-base baten irakurketa-errendimendua konparatzea, Cassandra eta HBase.

Horretarako, YCSB karga probatzeko utilitate estandarraren instantziak abiarazi ditugu bi ostalaritatik (800 hari guztira). Zerbitzariaren aldean - RegionServer eta Cassandra-ren 4 instantzia 4 ostalaritan (ez bezeroak exekutatzen ari diren horietan, haien eragina saihesteko). Irakurketak tamainako tauletatik atera dira:

HBase - 300 GB HDFS-n (100 GB datu hutsak)

Cassandra - 250 GB (erreplikatze-faktorea = 3)

Horiek. bolumena gutxi gorabehera berdina zen (HBase-n apur bat gehiago).

HBase parametroak:

dfs.client.short.circuit.num = 5 (HDFS bezeroaren optimizazioa)

hbase.lru.cache.heavy.eviction.count.limit = 30 - horrek esan nahi du adabakia 30 etxe kaleratzeen ondoren (~ 5 minutu) funtzionatzen hasiko dela

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 β€” Cachearen eta desalojoaren xede-bolumena

YCSB erregistroak Excel grafikoetan analizatu eta bildu ziren:

Nola handitu irakurtzeko abiadura HBasetik 3 aldiz arte eta HDFStik 5 aldiz arte

Ikus dezakezunez, optimizazio hauek datu-base hauen errendimendua alderatu eta segundoko 450 mila irakurketa lortzen dituzte baldintza hauetan.

Espero dugu informazio hau norbaitentzat erabilgarria izatea produktibitatearen aldeko borroka zirraragarrian.

Iturria: www.habr.com

Gehitu iruzkin berria