Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Performanca e lartë është një nga kërkesat kryesore kur punoni me të dhëna të mëdha. Në departamentin e ngarkimit të të dhënave në Sberbank, ne pompojmë pothuajse të gjitha transaksionet në Data Cloud-in tonë të bazuar në Hadoop dhe për këtë arsye përballemi me flukse vërtet të mëdha informacioni. Natyrisht, ne jemi gjithmonë në kërkim të mënyrave për të përmirësuar performancën dhe tani duam t'ju tregojmë se si arritëm të korrigjojmë RegionServer HBase dhe klientin HDFS, falë të cilit ne mundëm të rrisim ndjeshëm shpejtësinë e operacioneve të leximit.
Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Sidoqoftë, para se të kaloni në thelbin e përmirësimeve, ia vlen të flasim për kufizime që, në parim, nuk mund të anashkalohen nëse uleni në një HDD.

Pse HDD dhe leximet e shpejta me qasje të rastësishme janë të papajtueshme
Siç e dini, HBase dhe shumë baza të tjera të të dhënave, ruajnë të dhënat në blloqe me madhësi disa dhjetëra kilobajtë. Si parazgjedhje është rreth 64 KB. Tani le të imagjinojmë se duhet të marrim vetëm 100 bajt dhe i kërkojmë HBase të na japë këto të dhëna duke përdorur një çelës të caktuar. Meqenëse madhësia e bllokut në HFiles është 64 KB, kërkesa do të jetë 640 herë më e madhe (vetëm një minutë!) sesa duhet.

Më pas, meqenëse kërkesa do të kalojë përmes HDFS dhe mekanizmit të tij të ruajtjes së meta të dhënave ShortCircuitCache (që lejon qasje të drejtpërdrejtë në skedarë), kjo çon në leximin e tashmë 1 MB nga disku. Megjithatë, kjo mund të rregullohet me parametrin dfs.client.read.shortcircuit.buffer.size dhe në shumë raste ka kuptim të reduktohet kjo vlerë, për shembull në 126 KB.

Le të themi se e bëjmë këtë, por përveç kësaj, kur fillojmë të lexojmë të dhëna përmes api java, siç janë funksionet si FileChannel.read dhe i kërkojmë sistemit operativ të lexojë sasinë e specifikuar të të dhënave, ai lexon "vetëm në rast" 2 herë më shumë , d.m.th. 256 KB në rastin tonë. Kjo është për shkak se Java nuk ka një mënyrë të lehtë për të vendosur flamurin FADV_RANDOM për të parandaluar këtë sjellje.

Si rezultat, për të marrë 100 bajtë tanë, 2600 herë më shumë lexohen nën kapuç. Duket se zgjidhja është e qartë, le të zvogëlojmë madhësinë e bllokut në një kilobajt, të vendosim flamurin e përmendur dhe të fitojmë përshpejtim të madh ndriçimi. Por problemi është se duke reduktuar madhësinë e bllokut me 2 herë, ne gjithashtu zvogëlojmë numrin e bajteve të lexuara për njësi të kohës me 2 herë.

Mund të arrihet njëfarë përfitimi nga vendosja e flamurit FADV_RANDOM, por vetëm me shumë fije të larta dhe me një madhësi blloku prej 128 KB, por kjo është një maksimum prej disa dhjetëra përqind:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Testet u kryen në 100 skedarë, secili me madhësi 1 GB dhe të vendosur në 10 HDD.

Le të llogarisim se çfarë mund të llogarisim, në parim, me këtë shpejtësi:
Le të themi se lexojmë nga 10 disqe me një shpejtësi prej 280 MB/sek, d.m.th. 3 milion herë 100 bajt. Por siç kujtojmë, të dhënat që na duhen janë 2600 herë më pak se ato që lexohen. Kështu, ne ndajmë 3 milion me 2600 dhe marrim 1100 rekorde në sekondë.

Dëshpëruese, apo jo? Kjo është natyra Qasje e rastësishme qasja në të dhëna në HDD - pavarësisht nga madhësia e bllokut. Ky është kufiri fizik i aksesit të rastësishëm dhe asnjë bazë të dhënash nuk mund të shtrydh më shumë në kushte të tilla.

Si atëherë bazat e të dhënave arrijnë shpejtësi shumë më të larta? Për t'iu përgjigjur kësaj pyetjeje, le të shohim se çfarë po ndodh në foton e mëposhtme:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Këtu shohim se për minutat e para shpejtësia është me të vërtetë rreth një mijë rekorde në sekondë. Megjithatë, më tej, për faktin se lexohet shumë më tepër sesa kërkohet, të dhënat përfundojnë në buff/cache të sistemit operativ (linux) dhe shpejtësia rritet në 60 mijë në sekondë.

Kështu, më tej do të merremi me përshpejtimin e aksesit vetëm në të dhënat që janë në cache-in e OS ose të vendosura në pajisjet ruajtëse SSD/NVMe me shpejtësi të krahasueshme aksesi.

Në rastin tonë, ne do të kryejmë teste në një stol prej 4 serverësh, secili prej të cilëve tarifohet si më poshtë:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 fije.
Kujtesa: 730 GB.
versioni java: 1.8.0_111

Dhe këtu pika kryesore është sasia e të dhënave në tabela që duhet të lexohen. Fakti është se nëse lexoni të dhëna nga një tabelë që është plotësisht e vendosur në cache HBase, atëherë nuk do të lexohet as nga buff/cache e sistemit operativ. Sepse HBase si parazgjedhje shpërndan 40% të memories në një strukturë të quajtur BlockCache. Në thelb ky është një ConcurrentHashMap, ku çelësi është emri i skedarit + kompensimi i bllokut, dhe vlera është të dhënat aktuale në këtë kompensim.

Kështu, kur lexojmë vetëm nga kjo strukturë, ne shohim shpejtësi të shkëlqyer, si një milion kërkesa në sekondë. Por le të imagjinojmë se nuk mund të ndajmë qindra gigabajt memorie vetëm për nevojat e bazës së të dhënave, sepse ka shumë gjëra të tjera të dobishme që funksionojnë në këta serverë.

Për shembull, në rastin tonë, vëllimi i BlockCache në një RS është rreth 12 GB. Ne zbritëm dy RS në një nyje, d.m.th. 96 GB janë ndarë për BlockCache në të gjitha nyjet. Dhe ka shumë herë më shumë të dhëna, për shembull, le të jenë 4 tabela, 130 rajone secila, në të cilat skedarët janë me madhësi 800 MB, të ngjeshur nga FAST_DIFF, d.m.th. gjithsej 410 GB (këto janë të dhëna të pastra, d.m.th. pa marrë parasysh faktorin e përsëritjes).

Kështu, BlockCache është vetëm rreth 23% e vëllimit total të të dhënave dhe kjo është shumë më afër kushteve reale të asaj që quhet BigData. Dhe këtu fillon argëtimi - sepse padyshim, sa më pak goditje në cache, aq më e keqe është performanca. Në fund të fundit, nëse ju mungon, do t'ju duhet të bëni shumë punë - d.m.th. shkoni te thirrja e funksioneve të sistemit. Sidoqoftë, kjo nuk mund të shmanget, kështu që le të shohim një aspekt krejtësisht të ndryshëm - çfarë ndodh me të dhënat brenda cache?

Le të thjeshtojmë situatën dhe të supozojmë se kemi një cache që përshtatet vetëm me 1 objekt. Këtu është një shembull i asaj që do të ndodhë kur përpiqemi të punojmë me një vëllim të dhënash 3 herë më të madh se cache, do të duhet:

1. Vendosni bllokun 1 në cache
2. Hiqni bllokun 1 nga cache
3. Vendosni bllokun 2 në cache
4. Hiqni bllokun 2 nga cache
5. Vendosni bllokun 3 në cache

5 veprime të përfunduara! Sidoqoftë, kjo situatë nuk mund të quhet normale; në fakt, ne po e detyrojmë HBase të bëjë një mori punësh krejtësisht të padobishme. Ai vazhdimisht lexon të dhëna nga cache i OS, i vendos në BlockCache, vetëm për t'i hedhur jashtë pothuajse menjëherë sepse një pjesë e re e të dhënave ka mbërritur. Animacioni në fillim të postimit tregon thelbin e problemit - Mbledhësi i mbeturinave po largohet, atmosfera po nxehet, Greta e vogël në Suedinë e largët dhe të nxehtë po mërzitet. Dhe ne njerëzit e IT-së nuk na pëlqen vërtet kur fëmijët janë të trishtuar, kështu që ne fillojmë të mendojmë se çfarë mund të bëjmë për këtë.

Po sikur të vendosni jo të gjitha blloqet në cache, por vetëm një përqindje të caktuar të tyre, në mënyrë që cache të mos tejmbushet? Le të fillojmë thjesht duke shtuar vetëm disa rreshta kodi në fillim të funksionit për vendosjen e të dhënave në BlockCache:

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

Çështja këtu është si vijon: kompensimi është pozicioni i bllokut në skedar dhe shifrat e tij të fundit shpërndahen rastësisht dhe në mënyrë të barabartë nga 00 në 99. Prandaj, ne do të kapërcejmë vetëm ato që hyjnë në diapazonin që na duhen.

Për shembull, vendosni cacheDataBlockPercent = 20 dhe shikoni se çfarë ndodh:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Rezultati është i dukshëm. Në grafikët e mëposhtëm, bëhet e qartë pse ndodhi një përshpejtim i tillë - ne kursejmë shumë burime GC pa bërë punën Siziphean të vendosjes së të dhënave në cache vetëm për t'i hedhur menjëherë në kullimin e qenve marsianë:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Në të njëjtën kohë, përdorimi i CPU-së rritet, por është shumë më pak se produktiviteti:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Vlen gjithashtu të përmendet se blloqet e ruajtura në BlockCache janë të ndryshme. Shumica, rreth 95%, janë vetë të dhënat. Dhe pjesa tjetër janë meta të dhëna, të tilla si filtrat e Bloom ose LEAF_INDEX dhe т.д.. Këto të dhëna nuk janë të mjaftueshme, por janë shumë të dobishme, sepse përpara se t'i qasen drejtpërdrejt të dhënat, HBase i drejtohet meta-s për të kuptuar nëse është e nevojshme të kërkohet më tej këtu dhe, nëse po, ku ndodhet saktësisht blloku i interesit.

Prandaj, në kod shohim një kusht kontrolli buf.getBlockType().isData() dhe falë kësaj meta, ne do ta lëmë atë në cache në çdo rast.

Tani le të rrisim ngarkesën dhe ta shtrëngojmë pak funksionin me një lëvizje. Në provën e parë ne bëmë përqindjen e ndërprerjes = 20 dhe BlockCache ishte pak i nënshfrytëzuar. Tani le ta vendosim atë në 23% dhe të shtojmë 100 fije çdo 5 minuta për të parë se në cilën pikë ndodh ngopja:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Këtu shohim se versioni origjinal pothuajse menjëherë arrin tavanin me rreth 100 mijë kërkesa në sekondë. Ndërsa patch-i jep një përshpejtim deri në 300 mijë. Në të njëjtën kohë, është e qartë se përshpejtimi i mëtejshëm nuk është më aq "falas"; përdorimi i CPU-së po rritet gjithashtu.

Sidoqoftë, kjo nuk është një zgjidhje shumë elegante, pasi nuk e dimë paraprakisht se sa përqind e blloqeve duhet të ruhen në memorie, kjo varet nga profili i ngarkesës. Prandaj, u zbatua një mekanizëm për të rregulluar automatikisht këtë parametër në varësi të aktivitetit të operacioneve të leximit.

Janë shtuar tre opsione për ta kontrolluar këtë:

hbase.lru.cache.heavy.eviction.count.limit — përcakton se sa herë duhet të ekzekutohet procesi i nxjerrjes së të dhënave nga cache para se të fillojmë të përdorim optimizimin (d.m.th., kapërcimi i blloqeve). Si parazgjedhje është e barabartë me MAX_INT = 2147483647 dhe në fakt do të thotë që funksioni nuk do të fillojë kurrë të punojë me këtë vlerë. Sepse procesi i dëbimit fillon çdo 5 - 10 sekonda (kjo varet nga ngarkesa) dhe 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 vjet. Megjithatë, ne mund ta vendosim këtë parametër në 0 dhe ta bëjmë funksionin të funksionojë menjëherë pas lëshimit.

Sidoqoftë, ka gjithashtu një ngarkesë në këtë parametër. Nëse ngarkesa jonë është e tillë që leximet afatshkurtra (të themi gjatë ditës) dhe leximet afatgjata (natën) ndërthuren vazhdimisht, atëherë mund të sigurohemi që funksioni të aktivizohet vetëm kur operacionet e leximit të gjatë janë në proces.

Për shembull, ne e dimë se leximet afatshkurtra zakonisht zgjasin rreth 1 minutë. Nuk ka nevojë të filloni të hidhni blloqe, cache nuk do të ketë kohë të vjetërohet dhe më pas mund ta vendosim këtë parametër të barabartë me, për shembull, 10. Kjo do të çojë në faktin se optimizimi do të fillojë të funksionojë vetëm kur ka filluar leximi aktiv i termit, d.m.th. në 100 sekonda. Kështu, nëse kemi një lexim afatshkurtër, atëherë të gjitha blloqet do të hyjnë në cache dhe do të jenë të disponueshme (përveç atyre që do të dëbohen nga algoritmi standard). Dhe kur bëjmë lexime afatgjatë, funksioni aktivizohet dhe do të kishim performancë shumë më të lartë.

hbase.lru.cache.heavy.eviction.mb.size.limit — përcakton se sa megabajt do të dëshironim të vendosnim në cache (dhe, natyrisht, të nxirrnim) në 10 sekonda. Veçoria do të përpiqet ta arrijë këtë vlerë dhe ta ruajë atë. Çështja është kjo: nëse futim gigabajt në cache, atëherë do të duhet të nxjerrim gigabajt, dhe kjo, siç e pamë më lart, është shumë e shtrenjtë. Megjithatë, nuk duhet të përpiqeni ta vendosni atë shumë të vogël, pasi kjo do të bëjë që modaliteti i kapërcimit të bllokut të dalë para kohe. Për serverë të fuqishëm (rreth 20-40 bërthama fizike), është optimale të vendosni rreth 300-400 MB. Për klasën e mesme (~ 10 bërthama) 200-300 MB. Për sistemet e dobëta (2-5 bërthama) 50-100 MB mund të jetë normale (nuk është testuar në këto).

Le të shohim se si funksionon kjo: le të themi se kemi vendosur hbase.lru.cache.heavy.eviction.mb.size.limit = 500, ka një lloj ngarkese (leximi) dhe më pas çdo ~ 10 sekonda ne llogarisim sa bajt ishin dëbuar duke përdorur formulën:

Mbështetja = Shuma e Bajteve të Liruara (MB) * 100 / Limiti (MB) - 100;

Nëse në fakt 2000 MB janë dëbuar, atëherë shpenzimet e përgjithshme janë të barabarta me:

2000 * 100 / 500 - 100 = 300%

Algoritmet përpiqen të ruajnë jo më shumë se disa dhjetëra për qind, kështu që funksioni do të zvogëlojë përqindjen e blloqeve të memorizuara, duke zbatuar kështu një mekanizëm akordimi automatik.

Sidoqoftë, nëse ngarkesa bie, le të themi se vetëm 200 MB janë dëbuar dhe sipërfaqja bëhet negative (e ashtuquajtura tejkalim):

200 * 100 / 500 - 100 = -60%

Përkundrazi, funksioni do të rrisë përqindjen e blloqeve të memories së ruajtur derisa "Overhead" të bëhet pozitive.

Më poshtë është një shembull se si duket kjo në të dhënat reale. Nuk ka nevojë të përpiqeni të arrini 0%, është e pamundur. Është shumë mirë kur është rreth 30 - 100%, kjo ndihmon për të shmangur daljen e parakohshme nga mënyra e optimizimit gjatë rritjeve afatshkurtra.

hbase.lru.cache.heavy.eviction.overhead.koeficienti — përcakton se sa shpejt do të dëshironim të merrnim rezultatin. Nëse e dimë me siguri se leximet tona janë kryesisht të gjata dhe nuk duam të presim, mund ta rrisim këtë raport dhe të marrim performancë të lartë më shpejt.

Për shembull, ne vendosëm këtë koeficient = 0.01. Kjo do të thotë se "Overhead" (shih më lart) do të shumëzohet me këtë numër me rezultatin që rezulton dhe përqindja e blloqeve të memorizuara do të reduktohet. Le të supozojmë se Overhead = 300% dhe koeficienti = 0.01, atëherë përqindja e blloqeve të memorizuara do të reduktohet me 3%.

Një logjikë e ngjashme "Backpressure" zbatohet gjithashtu për vlerat negative të sipërme (tejkalimit). Meqenëse luhatjet afatshkurtra në vëllimin e leximeve dhe dëbimeve janë gjithmonë të mundshme, ky mekanizëm ju lejon të shmangni daljen e parakohshme nga mënyra e optimizimit. Presioni prapa ka një logjikë të përmbysur: sa më i fortë të jetë tejkalimi, aq më shumë blloqe ruhen në memorie.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Kodi i zbatimit

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

Tani le t'i shohim të gjitha këto duke përdorur një shembull real. Ne kemi skriptin e mëposhtëm të testit:

  1. Le të fillojmë të bëjmë Skanimin (25 fije, grup = 100)
  2. Pas 5 minutash, shtoni multi-get (25 fije, grumbull = 100)
  3. Pas 5 minutash, çaktivizoni multi-gets (mbetet vetëm skanimi përsëri)

Ne bëjmë dy ekzekutime, së pari hbase.lru.cache.heavy.eviction.count.limit = 10000 (që në fakt çaktivizon veçorinë), dhe më pas vendosim limit = 0 (e mundëson atë).

Në regjistrat e mëposhtëm shohim se si funksioni është aktivizuar dhe rivendos Overshooting në 14-71%. Herë pas here ngarkesa zvogëlohet, gjë që aktivizon Backpressure dhe HBase ruan përsëri më shumë blloqe.

Regjistrohu RegionServer
i dëbuar (MB): 0, raporti 0.0, shpenzimet e sipërme (%): -100, numëruesi i largimit të rëndë: 0, bllokimi i të dhënave me memorie aktuale (%): 100
i dëbuar (MB): 0, raporti 0.0, shpenzimet e sipërme (%): -100, numëruesi i largimit të rëndë: 0, bllokimi i të dhënave me memorie aktuale (%): 100
i dëbuar (MB): 2170, raporti 1.09, shpenzimet e sipërme (%): 985, numëruesi i rëndë i dëbimit: 1, memoria aktuale e bllokut të të dhënave (%): 91 < fillimi
i dëbuar (MB): 3763, raporti 1.08, shpenzimet e sipërme (%): 1781, numëruesi i rëndë i dëbimit: 2, arkivimi aktual i DataBlock (%): 76
i dëbuar (MB): 3306, raporti 1.07, shpenzimet e sipërme (%): 1553, numëruesi i rëndë i dëbimit: 3, arkivimi aktual i DataBlock (%): 61
i dëbuar (MB): 2508, raporti 1.06, shpenzimet e sipërme (%): 1154, numëruesi i rëndë i dëbimit: 4, arkivimi aktual i DataBlock (%): 50
i dëbuar (MB): 1824, raporti 1.04, shpenzimet e sipërme (%): 812, numëruesi i rëndë i dëbimit: 5, arkivimi aktual i DataBlock (%): 42
i dëbuar (MB): 1482, raporti 1.03, shpenzimet e sipërme (%): 641, numëruesi i rëndë i dëbimit: 6, arkivimi aktual i DataBlock (%): 36
i dëbuar (MB): 1140, raporti 1.01, shpenzimet e sipërme (%): 470, numëruesi i rëndë i dëbimit: 7, arkivimi aktual i DataBlock (%): 32
i dëbuar (MB): 913, raporti 1.0, shpenzimet e sipërme (%): 356, numëruesi i rëndë i dëbimit: 8, arkivimi aktual i DataBlock (%): 29
i dëbuar (MB): 912, raporti 0.89, shpenzimet e sipërme (%): 356, numëruesi i rëndë i dëbimit: 9, arkivimi aktual i DataBlock (%): 26
i dëbuar (MB): 684, raporti 0.76, shpenzimet e sipërme (%): 242, numëruesi i rëndë i dëbimit: 10, arkivimi aktual i DataBlock (%): 24
i dëbuar (MB): 684, raporti 0.61, shpenzimet e sipërme (%): 242, numëruesi i rëndë i dëbimit: 11, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 456, raporti 0.51, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 12, arkivimi aktual i DataBlock (%): 21
i dëbuar (MB): 456, raporti 0.42, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 13, arkivimi aktual i DataBlock (%): 20
i dëbuar (MB): 456, raporti 0.33, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 14, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 15, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 342, raporti 0.32, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 16, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 342, raporti 0.31, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 17, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.3, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 18, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.29, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 19, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.27, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 20, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.25, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 21, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.24, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 22, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.22, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 23, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.21, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 24, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.2, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 25, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 228, raporti 0.17, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 26, arkivimi aktual i DataBlock (%): 19
dëbuar (MB): 456, raporti 0.17, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 27, bllokimi i të dhënave me memorie aktuale (%): 18 < shtohet merr (por tabela e njëjtë)
i dëbuar (MB): 456, raporti 0.15, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 28, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 342, raporti 0.13, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 29, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 342, raporti 0.11, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 30, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 342, raporti 0.09, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 31, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.08, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 32, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.07, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 33, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.06, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 34, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.05, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 35, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.05, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 36, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 228, raporti 0.04, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 37, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 109, raporti 0.04, sipërfaqja (%): -46, numëruesi i rëndë i dëbimit: 37, memoria aktuale DataBlock (%): 22 < presioni prapa
i dëbuar (MB): 798, raporti 0.24, shpenzimet e sipërme (%): 299, numëruesi i rëndë i dëbimit: 38, arkivimi aktual i DataBlock (%): 20
i dëbuar (MB): 798, raporti 0.29, shpenzimet e sipërme (%): 299, numëruesi i rëndë i dëbimit: 39, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 570, raporti 0.27, shpenzimet e sipërme (%): 185, numëruesi i rëndë i dëbimit: 40, arkivimi aktual i DataBlock (%): 17
i dëbuar (MB): 456, raporti 0.22, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 41, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 342, raporti 0.16, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 42, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 342, raporti 0.11, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 43, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 228, raporti 0.09, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 44, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 228, raporti 0.07, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 45, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 228, raporti 0.05, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 46, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 222, raporti 0.04, shpenzimet e sipërme (%): 11, numëruesi i rëndë i dëbimit: 47, arkivimi aktual i DataBlock (%): 16
i dëbuar (MB): 104, raporti 0.03, sipërfaqja (%): -48, numëruesi i rëndë i dëbimit: 47, memoria aktuale e të dhënaveBlloku (%): 21 <ndërprerja merr
i dëbuar (MB): 684, raporti 0.2, shpenzimet e sipërme (%): 242, numëruesi i rëndë i dëbimit: 48, arkivimi aktual i DataBlock (%): 19
i dëbuar (MB): 570, raporti 0.23, shpenzimet e sipërme (%): 185, numëruesi i rëndë i dëbimit: 49, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 342, raporti 0.22, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 50, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 228, raporti 0.21, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 51, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 228, raporti 0.2, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 52, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 228, raporti 0.18, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 53, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 228, raporti 0.16, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 54, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 228, raporti 0.14, shpenzimet e sipërme (%): 14, numëruesi i rëndë i dëbimit: 55, arkivimi aktual i DataBlock (%): 18
i dëbuar (MB): 112, raporti 0.14, sipërfaqja (%): -44, numëruesi i rëndë i dëbimit: 55, memoria aktuale DataBlock (%): 23 < presioni prapa
i dëbuar (MB): 456, raporti 0.26, shpenzimet e sipërme (%): 128, numëruesi i rëndë i dëbimit: 56, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.31, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 57, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 58, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 59, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 60, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 61, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 62, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 63, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.32, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 64, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 65, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 66, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.32, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 67, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 68, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.32, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 69, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.32, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 70, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 71, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 72, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 73, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 74, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 75, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 342, raporti 0.33, shpenzimet e sipërme (%): 71, numëruesi i rëndë i dëbimit: 76, arkivimi aktual i DataBlock (%): 22
i dëbuar (MB): 21, raporti 0.33, shpenzimet e sipërme (%): -90, numëruesi i largimit të rëndë: 76, bllokimi i të dhënave me memorie aktuale (%): 32
i dëbuar (MB): 0, raporti 0.0, shpenzimet e sipërme (%): -100, numëruesi i largimit të rëndë: 0, bllokimi i të dhënave me memorie aktuale (%): 100
i dëbuar (MB): 0, raporti 0.0, shpenzimet e sipërme (%): -100, numëruesi i largimit të rëndë: 0, bllokimi i të dhënave me memorie aktuale (%): 100

Skanimet ishin të nevojshme për të treguar të njëjtin proces në formën e një grafiku të marrëdhënies midis dy seksioneve të cache - të vetme (ku blloqet që nuk janë kërkuar kurrë më parë) dhe shumë (të dhënat "kërkohen" të paktën një herë ruhen këtu):

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Dhe së fundi, si duket funksionimi i parametrave në formën e një grafiku. Për krahasim, cache ishte fikur plotësisht në fillim, më pas HBase u lançua me caching dhe vonuar fillimin e punës së optimizimit për 5 minuta (30 cikle dëbimi).

Kodi i plotë mund të gjendet në Kërkesën për tërheqje HBASE 23887 në github.

Megjithatë, 300 mijë lexime në sekondë nuk janë gjithçka që mund të arrihet në këtë pajisje në këto kushte. Fakti është se kur ju duhet të përdorni të dhëna përmes HDFS, përdoret mekanizmi ShortCircuitCache (në tekstin e mëtejmë si SSC), i cili ju lejon të aksesoni drejtpërdrejt të dhënat, duke shmangur ndërveprimet në rrjet.

Profilizimi tregoi se megjithëse ky mekanizëm jep një fitim të madh, ai në një moment bëhet edhe një pengesë, sepse pothuajse të gjitha operacionet e rënda ndodhin brenda një blloku, gjë që çon në bllokim në shumicën e kohës.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Pasi e kuptuam këtë, ne kuptuam se problemi mund të anashkalohet duke krijuar një grup SSC-sh të pavarura:

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

Dhe pastaj punoni me ta, duke përjashtuar kryqëzimet edhe në shifrën e fundit të kompensimit:

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

Tani mund të filloni testimin. Për ta bërë këtë, ne do të lexojmë skedarë nga HDFS me një aplikacion të thjeshtë me shumë fije. Vendosni parametrat:

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

Dhe thjesht lexoni skedarët:

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

Ky kod ekzekutohet në tema të veçanta dhe ne do të rrisim numrin e skedarëve të lexuar njëkohësisht (nga 10 në 200 - boshti horizontal) dhe numrin e cache-ve (nga 1 në 10 - grafikë). Boshti vertikal tregon përshpejtimin që rezulton nga një rritje në SSC në raport me rastin kur ka vetëm një cache.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Si të lexoni grafikun: Koha e ekzekutimit për 100 mijë lexime në blloqe 64 KB me një cache kërkon 78 sekonda. Ndërsa me 5 cache duhen 16 sekonda. Ato. ka një nxitim prej ~ 5 herë. Siç mund të shihet nga grafiku, efekti nuk është shumë i dukshëm për një numër të vogël leximesh paralele, ai fillon të luajë një rol të dukshëm kur ka më shumë se 50 lexime të temave. Gjithashtu vërehet se rritja e numrit të SSC-ve nga 6 dhe më lart jep një rritje dukshëm më të vogël të performancës.

Shënim 1: meqenëse rezultatet e testit janë mjaft të paqëndrueshme (shih më poshtë), u kryen 3 ekzekutime dhe u vlerësuan vlerat që rezultuan.

Shënim 2: Përfitimi i performancës nga konfigurimi i aksesit të rastësishëm është i njëjtë, megjithëse vetë qasja është pak më e ngadaltë.

Megjithatë, është e nevojshme të sqarohet se, ndryshe nga rasti me HBase, ky përshpejtim nuk është gjithmonë falas. Këtu ne "zhbllokojmë" aftësinë e CPU-së për të bërë më shumë punë, në vend që të varemi në bravë.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Këtu mund të vëreni se, në përgjithësi, një rritje në numrin e cache-ve jep një rritje afërsisht proporcionale në përdorimin e CPU. Megjithatë, ka pak më shumë kombinime fituese.

Për shembull, le të hedhim një vështrim më të afërt në cilësimin SSC = 3. Rritja e performancës në interval është rreth 3.3 herë. Më poshtë janë rezultatet nga të tre garat e veçanta.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Ndërsa konsumi i CPU-së rritet me rreth 2.8 herë. Diferenca nuk është shumë e madhe, por Greta e vogël tashmë është e lumtur dhe mund të ketë kohë të ndjekë shkollën dhe të marrë mësime.

Kështu, kjo do të ketë një efekt pozitiv për çdo mjet që përdor akses të madh në HDFS (për shembull Spark, etj.), me kusht që kodi i aplikacionit të jetë i lehtë (d.m.th. spina të jetë në anën e klientit HDFS) dhe të ketë fuqi të lirë të CPU-së . Për të kontrolluar, le të testojmë se çfarë efekti do të ketë përdorimi i kombinuar i optimizimit të BlockCache dhe akordimit SSC për lexim nga HBase.

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Mund të shihet se në kushte të tilla efekti nuk është aq i madh sa në testet e rafinuara (leximi pa asnjë përpunim), por është mjaft e mundur të shtrydhni një shtesë prej 80K këtu. Së bashku, të dy optimizimet ofrojnë shpejtësi deri në 4 herë.

Për këtë optimizim është bërë edhe një PR [HDFS-15202], i cili është bashkuar dhe ky funksionalitet do të jetë i disponueshëm në publikimet e ardhshme.

Dhe së fundi, ishte interesante të krahasohej performanca e leximit të një baze të dhënash të ngjashme me kolona të gjera, Cassandra dhe HBase.

Për ta bërë këtë, ne lançuam shembuj të mjetit standard të testimit të ngarkesës YCSB nga dy hoste (800 threads në total). Në anën e serverit - 4 instanca të RegionServer dhe Cassandra në 4 hoste (jo ato ku po ekzekutohen klientët, për të shmangur ndikimin e tyre). Leximet erdhën nga tabelat e madhësisë:

HBase – 300 GB në HDFS (100 GB të dhëna të pastra)

Cassandra - 250 GB (faktori i përsëritjes = 3)

Ato. vëllimi ishte afërsisht i njëjtë (në HBase pak më shumë).

Parametrat e HBase:

dfs.client.short.circuit.num = 5 (Optimizimi i klientit HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - kjo do të thotë që patch-i do të fillojë të funksionojë pas 30 dëbimeve (~5 minuta)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — vëllimi i synuar i ruajtjes dhe dëbimit

Regjistrat e YCSB u analizuan dhe u përpiluan në grafikët e Excel:

Si të rritet shpejtësia e leximit nga HBase deri në 3 herë dhe nga HDFS deri në 5 herë

Siç mund ta shihni, këto optimizime bëjnë të mundur krahasimin e performancës së këtyre bazave të të dhënave në këto kushte dhe arritjen e 450 mijë leximeve në sekondë.

Shpresojmë që ky informacion të jetë i dobishëm për dikë gjatë luftës emocionuese për produktivitet.

Burimi: www.habr.com

Shto një koment