Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Hoë werkverrigting is een van die sleutelvereistes wanneer daar met groot data gewerk word. In die datalaai-afdeling by Sberbank pomp ons byna alle transaksies in ons Hadoop-gebaseerde Datawolk en hanteer dus werklik groot vloeie van inligting. Natuurlik is ons altyd op soek na maniere om werkverrigting te verbeter, en nou wil ons jou vertel hoe ons dit reggekry het om RegionServer HBase en die HDFS-kliënt te pleister, waardeur ons die spoed van leesbewerkings aansienlik kon verhoog.
Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Voordat u egter na die essensie van die verbeterings gaan, is dit die moeite werd om te praat oor beperkings wat in beginsel nie omseil kan word as u op 'n HDD sit nie.

Waarom HDD en vinnige Random Access-lesings onversoenbaar is
Soos u weet, stoor HBase, en baie ander databasisse, data in blokke van etlike tiene kilogrepe groot. By verstek is dit ongeveer 64 KB. Kom ons stel ons nou voor dat ons net 100 grepe moet kry en ons vra HBase om vir ons hierdie data te gee deur 'n sekere sleutel te gebruik. Aangesien die blokgrootte in HFiles 64 KB is, sal die versoek 640 keer groter wees (net 'n minuut!) as wat nodig is.

Vervolgens, aangesien die versoek deur HDFS en sy metadata-kasmeganisme sal gaan ShortCircuitCache (wat direkte toegang tot lêers moontlik maak), dit lei tot die lees van reeds 1 MB vanaf die skyf. Dit kan egter met die parameter aangepas word dfs.kliënt.lees.kortsluiting.buffergrootte en in baie gevalle maak dit sin om hierdie waarde te verminder, byvoorbeeld na 126 KB.

Kom ons sê ons doen dit, maar daarbenewens, wanneer ons data deur die java-api begin lees, soos funksies soos FileChannel.read en die bedryfstelsel vra om die gespesifiseerde hoeveelheid data te lees, lees dit “net ingeval” 2 keer meer , d.w.s. 256 KB in ons geval. Dit is omdat java nie 'n maklike manier het om die FADV_RANDOM-vlag te stel om hierdie gedrag te voorkom nie.

As gevolg hiervan, om ons 100 grepe te kry, word 2600 keer meer onder die enjinkap gelees. Dit wil voorkom asof die oplossing voor die hand liggend is, kom ons verminder die blokgrootte tot 'n kilogreep, stel die genoemde vlag en kry groot verligtingversnelling. Maar die probleem is dat deur die blokgrootte met 2 keer te verminder, ons ook die aantal grepe wat per tydeenheid gelees word met 2 keer verminder.

Sommige wins deur die FADV_RANDOM-vlag te stel kan verkry word, maar slegs met 'n hoë multi-threading en met 'n blokgrootte van 128 KB, maar dit is 'n maksimum van 'n paar tientalle persent:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Toetse is uitgevoer op 100 lêers, elk 1 GB groot en geleë op 10 HDD's.

Kom ons bereken waarop ons in beginsel teen hierdie spoed kan reken:
Kom ons sê ons lees vanaf 10 skywe teen 'n spoed van 280 MB/sek, d.w.s. 3 miljoen keer 100 grepe. Maar soos ons onthou, is die data wat ons benodig 2600 keer minder as wat gelees word. Dus deel ons 3 miljoen deur 2600 en kry 1100 rekords per sekonde.

Depressief, is dit nie? Dis die natuur Willekeurige toegang toegang tot data op die HDD - ongeag die blokgrootte. Dit is die fisiese limiet van ewekansige toegang en geen databasis kan meer onder sulke omstandighede uitdruk nie.

Hoe bereik databasisse dan baie hoër snelhede? Om hierdie vraag te beantwoord, kom ons kyk na wat in die volgende prentjie gebeur:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Hier sien ons dat die spoed vir die eerste paar minute werklik omtrent 'n duisend rekords per sekonde is. Maar verder, as gevolg van die feit dat baie meer gelees word as wat versoek is, beland die data in die buff/cache van die bedryfstelsel (linux) en die spoed verhoog tot 'n meer ordentlike 60 duisend per sekonde

Ons sal dus verder handel oor die versnelling van toegang slegs tot die data wat in die OS-kas is of geleë is in SSD/NVMe-bergingstoestelle met vergelykbare toegangspoed.

In ons geval sal ons toetse op 'n bank van 4 bedieners uitvoer, wat elkeen soos volg gehef word:

SVE: Xeon E5-2680 v4 @ 2.40GHz 64 drade.
Geheue: 730 GB.
java weergawe: 1.8.0_111

En hier is die sleutelpunt die hoeveelheid data in die tabelle wat gelees moet word. Die feit is dat as jy data lees van 'n tabel wat geheel en al in die HBase-kas geplaas is, sal dit nie eers kom by die lees van die bedryfstelsel se buff/cache nie. Omdat HBase by verstek 40% van die geheue toeken aan 'n struktuur genaamd BlockCache. In wese is dit 'n ConcurrentHashMap, waar die sleutel die lêernaam + offset van die blok is, en die waarde is die werklike data by hierdie offset.

Dus, wanneer ons slegs uit hierdie struktuur lees, sal ons ons sien uitstekende spoed, soos 'n miljoen versoeke per sekonde. Maar kom ons stel ons voor dat ons nie honderde gigagrepe geheue net vir databasisbehoeftes kan toewys nie, want daar is baie ander nuttige dinge wat op hierdie bedieners loop.

Byvoorbeeld, in ons geval is die volume BlockCache op een RS ongeveer 12 GB. Ons het twee RS op een nodus geland, m.a.w. 96 GB word vir BlockCache op alle nodusse toegeken. En daar is baie keer meer data, byvoorbeeld, laat dit 4 tabelle wees, 130 streke elk, waarin lêers 800 MB groot is, saamgepers deur FAST_DIFF, d.w.s. 'n totaal van 410 GB (dit is suiwer data, dit wil sê sonder om die replikasiefaktor in ag te neem).

BlockCache is dus slegs sowat 23% van die totale datavolume en dit is baie nader aan die werklike toestande van wat BigData genoem word. En dit is waar die pret begin – want natuurlik, hoe minder kastreffers, hoe slegter die prestasie. As jy mis, sal jy immers baie werk moet doen – m.a.w. gaan af na die roepstelselfunksies. Dit kan egter nie vermy word nie, so kom ons kyk na 'n heeltemal ander aspek - wat gebeur met die data binne die kas?

Kom ons vereenvoudig die situasie en neem aan dat ons 'n kas het wat net by 1 voorwerp pas. Hier is 'n voorbeeld van wat sal gebeur wanneer ons probeer werk met 'n datavolume 3 keer groter as die kas, ons sal moet:

1. Plaas blok 1 in die kas
2. Verwyder blok 1 uit die kas
3. Plaas blok 2 in die kas
4. Verwyder blok 2 uit die kas
5. Plaas blok 3 in die kas

5 aksies voltooi! Hierdie situasie kan egter nie normaal genoem word nie; Trouens, ons dwing HBase om 'n klomp heeltemal nuttelose werk te doen. Dit lees voortdurend data vanaf die OS-kas, plaas dit in BlockCache, net om dit byna onmiddellik uit te gooi omdat 'n nuwe gedeelte data aangekom het. Die animasie aan die begin van die plasing wys die essensie van die probleem - Vullisversamelaar raak van skaal af, die atmosfeer word warm, klein Greta in verre en warm Swede raak ontsteld. En ons IT-mense hou regtig nie daarvan as kinders hartseer is nie, so ons begin dink oor wat ons daaraan kan doen.

Wat as jy nie alle blokke in die kas plaas nie, maar slegs 'n sekere persentasie daarvan, sodat die kas nie oorloop nie? Kom ons begin deur net 'n paar reëls kode by die begin van die funksie by te voeg om data in BlockCache te plaas:

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

Die punt hier is die volgende: offset is die posisie van die blok in die lêer en sy laaste syfers is ewekansig en eweredig van 00 tot 99 versprei. Daarom sal ons net diegene oorslaan wat in die reeks val wat ons benodig.

Stel byvoorbeeld cacheDataBlockPercent = 20 en kyk wat gebeur:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Die resultaat is duidelik. In die grafieke hieronder word dit duidelik waarom so 'n versnelling plaasgevind het - ons spaar baie GC-hulpbronne sonder om die Sisyfiese werk te doen om data in die kas te plaas net om dit onmiddellik in die drein van die Mars-honde te gooi:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Terselfdertyd neem SVE-benutting toe, maar is baie minder as produktiwiteit:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Dit is ook opmerklik dat die blokke wat in BlockCache gestoor is, verskillend is. Die meeste, ongeveer 95%, is data self. En die res is metadata, soos Bloom-filters of LEAF_INDEX en т.д.. Hierdie data is nie genoeg nie, maar dit is baie nuttig, want voordat HBase direk toegang tot die data verkry, wend HBase na die meta om te verstaan ​​of dit nodig is om hier verder te soek en, indien wel, waar presies die blok van belangstelling geleë is.

Daarom, in die kode sien ons 'n tjek toestand buf.getBlockType().isData() en danksy hierdie meta sal ons dit in elk geval in die kas laat.

Laat ons nou die las verhoog en die kenmerk in een slag effens verskerp. In die eerste toets het ons die afsnypersentasie = 20 gemaak en BlockCache is effens onderbenut. Kom ons stel dit nou op 23% en voeg elke 100 minute 5 drade by om te sien wanneer versadiging plaasvind:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Hier sien ons dat die oorspronklike weergawe amper dadelik die plafon tref teen ongeveer 100 duisend versoeke per sekonde. Terwyl die pleister 'n versnelling van tot 300 duisend gee. Terselfdertyd is dit duidelik dat verdere versnelling nie meer so "gratis" is nie; SVE-benutting neem ook toe.

Dit is egter nie 'n baie elegante oplossing nie, aangesien ons nie vooraf weet watter persentasie blokke gekas moet word nie, dit hang af van die lasprofiel. Daarom is 'n meganisme geïmplementeer om hierdie parameter outomaties aan te pas na gelang van die aktiwiteit van leesbewerkings.

Drie opsies is bygevoeg om dit te beheer:

hbase.lru.cache.heavy.eviction.count.limiet - stel in hoeveel keer die proses om data uit die kas te verwyder moet loop voordat ons optimalisering begin gebruik (d.w.s. slaan blokke oor). By verstek is dit gelyk aan MAX_INT = 2147483647 en beteken in werklikheid dat die kenmerk nooit met hierdie waarde sal begin werk nie. Omdat die uitsettingsproses elke 5 - 10 sekondes begin (dit hang af van die vrag) en 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 jaar. Ons kan egter hierdie parameter op 0 stel en die kenmerk onmiddellik na bekendstelling laat werk.

Daar is egter ook 'n loonvrag in hierdie parameter. As ons las sodanig is dat korttermynlesings (sê gedurende die dag) en langtermynlesings (snags) voortdurend afgewissel word, dan kan ons seker maak dat die funksie slegs aangeskakel word wanneer langleesbewerkings aan die gang is.

Ons weet byvoorbeeld dat korttermynlesings gewoonlik ongeveer 1 minuut duur. Dit is nie nodig om blokke te begin uitgooi nie, die kas sal nie tyd hê om verouderd te raak nie en dan kan ons hierdie parameter gelyk stel aan byvoorbeeld 10. Dit sal daartoe lei dat die optimalisering eers sal begin werk wanneer lang- kwartaal aktiewe lees het begin, m.a.w. binne 100 sekondes. Dus, as ons 'n korttermynlees het, sal alle blokke in die kas ingaan en beskikbaar wees (behalwe dié wat deur die standaardalgoritme uitgesit sal word). En wanneer ons langtermynlees doen, is die kenmerk aangeskakel en sal ons baie hoër werkverrigting hê.

hbase.lru.cache.heavy.eviction.mb.grootte.limiet - stel hoeveel megagrepe ons in die kas wil plaas (en natuurlik uitsit) in 10 sekondes. Die kenmerk sal probeer om hierdie waarde te bereik en dit in stand te hou. Die punt is dit: as ons gigagrepe in die kas indruk, sal ons gigagrepe moet uitsit, en dit, soos ons hierbo gesien het, is baie duur. Jy moet egter nie probeer om dit te klein te stel nie, want dit sal veroorsaak dat die blok oorslaan-modus voortydig verlaat. Vir kragtige bedieners (ongeveer 20-40 fisiese kerne), is dit optimaal om ongeveer 300-400 MB in te stel. Vir die middelklas (~ 10 kerns) 200-300 MB. Vir swak stelsels (2-5 kerne) kan 50-100 MB normaal wees (nie op hierdie getoets nie).

Kom ons kyk hoe dit werk: kom ons sê ons stel hbase.lru.cache.heavy.eviction.mb.size.limit = 500, daar is 'n soort las (lees) en dan elke ~10 sekondes bereken ons hoeveel grepe was uitgesit met die formule:

Bokoste = Vrygestelde Bytes Som (MB) * 100 / Limiet (MB) - 100;

As in werklikheid 2000 MB uitgesit is, dan is oorhoofse koste gelyk aan:

2000 * 100 / 500 - 100 = 300%

Die algoritmes probeer om nie meer as 'n paar tientalle persent te handhaaf nie, so die kenmerk sal die persentasie blokke in die kas verminder en sodoende 'n outo-instelmeganisme implementeer.

As die vrag egter daal, kom ons sê net 200 MB word uitgesit en Oorhoofse word negatief (die sogenaamde oorskiet):

200 * 100 / 500 - 100 = -60%

Inteendeel, die kenmerk sal die persentasie kasblokke verhoog totdat oorhoofse pos positief word.

Hieronder is 'n voorbeeld van hoe dit op werklike data lyk. Dit is nie nodig om te probeer om 0% te bereik nie, dit is onmoontlik. Dit is baie goed as dit ongeveer 30 - 100% is, dit help om voortydige uitgang van die optimaliseringsmodus tydens korttermyn-stuwings te vermy.

hbase.lru.cache.swaar.uitsetting.bokoste.koëffisiënt - bepaal hoe vinnig ons die resultaat wil kry. As ons seker weet dat ons leeswerk meestal lank is en nie wil wag nie, kan ons hierdie verhouding verhoog en vinniger hoë werkverrigting kry.

Byvoorbeeld, ons stel hierdie koëffisiënt = 0.01. Dit beteken dat bokoste (sien hierbo) met hierdie getal vermenigvuldig sal word met die gevolglike resultaat en die persentasie blokke in die kas sal verminder word. Kom ons neem aan dat bokoste = 300% en koëffisiënt = 0.01, dan sal die persentasie blokke in die kas met 3% verminder word.

'n Soortgelyke "Terugdruk" logika word ook geïmplementeer vir negatiewe oorhoofse (oorskiet) waardes. Aangesien korttermynskommelings in die volume van lees en uitsettings altyd moontlik is, laat hierdie meganisme u toe om voortydige uitgang uit die optimaliseringsmodus te vermy. Terugdruk het 'n omgekeerde logika: hoe sterker die oorskiet, hoe meer blokke word gekas.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Implementeringskode

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

Kom ons kyk nou na dit alles deur 'n werklike voorbeeld te gebruik. Ons het die volgende toetsskrif:

  1. Kom ons begin skandering doen (25 drade, bondel = 100)
  2. Na 5 minute, voeg multi-gets by (25 drade, bondel = 100)
  3. Na 5 minute, skakel multi-gets af (net skandering bly weer oor)

Ons doen twee lopies, eers hbase.lru.cache.heavy.eviction.count.limit = 10000 (wat eintlik die kenmerk deaktiveer), en stel dan limiet = 0 (aktiveer dit).

In die logs hieronder sien ons hoe die funksie aangeskakel word en oorskiet terugstel na 14-71%. Van tyd tot tyd neem die las af, wat Backpressure aanskakel en HBase kas weer meer blokke.

Meld RegionServer aan
uitgesit (MB): 0, verhouding 0.0, bokoste (%): -100, swaar uitsettingteller: 0, huidige kas DataBlok (%): 100
uitgesit (MB): 0, verhouding 0.0, bokoste (%): -100, swaar uitsettingteller: 0, huidige kas DataBlok (%): 100
uitgesit (MB): 2170, verhouding 1.09, bokoste (%): 985, swaar uitsettings teller: 1, huidige kas DataBlok (%): 91 < begin
uitgesit (MB): 3763, verhouding 1.08, oorhoofse koste (%): 1781, swaar uitsettingteller: 2, huidige kas DataBlok (%): 76
uitgesit (MB): 3306, verhouding 1.07, oorhoofse koste (%): 1553, swaar uitsettingteller: 3, huidige kas DataBlok (%): 61
uitgesit (MB): 2508, verhouding 1.06, oorhoofse koste (%): 1154, swaar uitsettingteller: 4, huidige kas DataBlok (%): 50
uitgesit (MB): 1824, verhouding 1.04, oorhoofse koste (%): 812, swaar uitsettingteller: 5, huidige kas DataBlok (%): 42
uitgesit (MB): 1482, verhouding 1.03, oorhoofse koste (%): 641, swaar uitsettingteller: 6, huidige kas DataBlok (%): 36
uitgesit (MB): 1140, verhouding 1.01, oorhoofse koste (%): 470, swaar uitsettingteller: 7, huidige kas DataBlok (%): 32
uitgesit (MB): 913, verhouding 1.0, oorhoofse koste (%): 356, swaar uitsettingteller: 8, huidige kas DataBlok (%): 29
uitgesit (MB): 912, verhouding 0.89, oorhoofse koste (%): 356, swaar uitsettingteller: 9, huidige kas DataBlok (%): 26
uitgesit (MB): 684, verhouding 0.76, oorhoofse koste (%): 242, swaar uitsettingteller: 10, huidige kas DataBlok (%): 24
uitgesit (MB): 684, verhouding 0.61, oorhoofse koste (%): 242, swaar uitsettingteller: 11, huidige kas DataBlok (%): 22
uitgesit (MB): 456, verhouding 0.51, oorhoofse koste (%): 128, swaar uitsettingteller: 12, huidige kas DataBlok (%): 21
uitgesit (MB): 456, verhouding 0.42, oorhoofse koste (%): 128, swaar uitsettingteller: 13, huidige kas DataBlok (%): 20
uitgesit (MB): 456, verhouding 0.33, oorhoofse koste (%): 128, swaar uitsettingteller: 14, huidige kas DataBlok (%): 19
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 15, huidige kas DataBlok (%): 19
uitgesit (MB): 342, verhouding 0.32, oorhoofse koste (%): 71, swaar uitsettingteller: 16, huidige kas DataBlok (%): 19
uitgesit (MB): 342, verhouding 0.31, oorhoofse koste (%): 71, swaar uitsettingteller: 17, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.3, oorhoofse koste (%): 14, swaar uitsettingteller: 18, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.29, oorhoofse koste (%): 14, swaar uitsettingteller: 19, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.27, oorhoofse koste (%): 14, swaar uitsettingteller: 20, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.25, oorhoofse koste (%): 14, swaar uitsettingteller: 21, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.24, oorhoofse koste (%): 14, swaar uitsettingteller: 22, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.22, oorhoofse koste (%): 14, swaar uitsettingteller: 23, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.21, oorhoofse koste (%): 14, swaar uitsettingteller: 24, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.2, oorhoofse koste (%): 14, swaar uitsettingteller: 25, huidige kas DataBlok (%): 19
uitgesit (MB): 228, verhouding 0.17, oorhoofse koste (%): 14, swaar uitsettingteller: 26, huidige kas DataBlok (%): 19
uitgesit (MB): 456, verhouding 0.17, oorhoofse koste (%): 128, swaar uitsettingteller: 27, huidige kas DataBlok (%): 18 < bygevoeg kry (maar tabel dieselfde)
uitgesit (MB): 456, verhouding 0.15, oorhoofse koste (%): 128, swaar uitsettingteller: 28, huidige kas DataBlok (%): 17
uitgesit (MB): 342, verhouding 0.13, oorhoofse koste (%): 71, swaar uitsettingteller: 29, huidige kas DataBlok (%): 17
uitgesit (MB): 342, verhouding 0.11, oorhoofse koste (%): 71, swaar uitsettingteller: 30, huidige kas DataBlok (%): 17
uitgesit (MB): 342, verhouding 0.09, oorhoofse koste (%): 71, swaar uitsettingteller: 31, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.08, oorhoofse koste (%): 14, swaar uitsettingteller: 32, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.07, oorhoofse koste (%): 14, swaar uitsettingteller: 33, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.06, oorhoofse koste (%): 14, swaar uitsettingteller: 34, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.05, oorhoofse koste (%): 14, swaar uitsettingteller: 35, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.05, oorhoofse koste (%): 14, swaar uitsettingteller: 36, huidige kas DataBlok (%): 17
uitgesit (MB): 228, verhouding 0.04, oorhoofse koste (%): 14, swaar uitsettingteller: 37, huidige kas DataBlok (%): 17
uitgesit (MB): 109, verhouding 0.04, bokoste (%): -46, swaar uitsettingteller: 37, huidige kas DataBlok (%): 22 < terugdruk
uitgesit (MB): 798, verhouding 0.24, oorhoofse koste (%): 299, swaar uitsettingteller: 38, huidige kas DataBlok (%): 20
uitgesit (MB): 798, verhouding 0.29, oorhoofse koste (%): 299, swaar uitsettingteller: 39, huidige kas DataBlok (%): 18
uitgesit (MB): 570, verhouding 0.27, oorhoofse koste (%): 185, swaar uitsettingteller: 40, huidige kas DataBlok (%): 17
uitgesit (MB): 456, verhouding 0.22, oorhoofse koste (%): 128, swaar uitsettingteller: 41, huidige kas DataBlok (%): 16
uitgesit (MB): 342, verhouding 0.16, oorhoofse koste (%): 71, swaar uitsettingteller: 42, huidige kas DataBlok (%): 16
uitgesit (MB): 342, verhouding 0.11, oorhoofse koste (%): 71, swaar uitsettingteller: 43, huidige kas DataBlok (%): 16
uitgesit (MB): 228, verhouding 0.09, oorhoofse koste (%): 14, swaar uitsettingteller: 44, huidige kas DataBlok (%): 16
uitgesit (MB): 228, verhouding 0.07, oorhoofse koste (%): 14, swaar uitsettingteller: 45, huidige kas DataBlok (%): 16
uitgesit (MB): 228, verhouding 0.05, oorhoofse koste (%): 14, swaar uitsettingteller: 46, huidige kas DataBlok (%): 16
uitgesit (MB): 222, verhouding 0.04, oorhoofse koste (%): 11, swaar uitsettingteller: 47, huidige kas DataBlok (%): 16
uitgesit (MB): 104, verhouding 0.03, bokoste (%): -48, swaar uitsettingteller: 47, huidige kas DataBlok (%): 21 < onderbreking kry
uitgesit (MB): 684, verhouding 0.2, oorhoofse koste (%): 242, swaar uitsettingteller: 48, huidige kas DataBlok (%): 19
uitgesit (MB): 570, verhouding 0.23, oorhoofse koste (%): 185, swaar uitsettingteller: 49, huidige kas DataBlok (%): 18
uitgesit (MB): 342, verhouding 0.22, oorhoofse koste (%): 71, swaar uitsettingteller: 50, huidige kas DataBlok (%): 18
uitgesit (MB): 228, verhouding 0.21, oorhoofse koste (%): 14, swaar uitsettingteller: 51, huidige kas DataBlok (%): 18
uitgesit (MB): 228, verhouding 0.2, oorhoofse koste (%): 14, swaar uitsettingteller: 52, huidige kas DataBlok (%): 18
uitgesit (MB): 228, verhouding 0.18, oorhoofse koste (%): 14, swaar uitsettingteller: 53, huidige kas DataBlok (%): 18
uitgesit (MB): 228, verhouding 0.16, oorhoofse koste (%): 14, swaar uitsettingteller: 54, huidige kas DataBlok (%): 18
uitgesit (MB): 228, verhouding 0.14, oorhoofse koste (%): 14, swaar uitsettingteller: 55, huidige kas DataBlok (%): 18
uitgesit (MB): 112, verhouding 0.14, bokoste (%): -44, swaar uitsettingteller: 55, huidige kas DataBlok (%): 23 < terugdruk
uitgesit (MB): 456, verhouding 0.26, oorhoofse koste (%): 128, swaar uitsettingteller: 56, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.31, oorhoofse koste (%): 71, swaar uitsettingteller: 57, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 58, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 59, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 60, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 61, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 62, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 63, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.32, oorhoofse koste (%): 71, swaar uitsettingteller: 64, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 65, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 66, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.32, oorhoofse koste (%): 71, swaar uitsettingteller: 67, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 68, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.32, oorhoofse koste (%): 71, swaar uitsettingteller: 69, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.32, oorhoofse koste (%): 71, swaar uitsettingteller: 70, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 71, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 72, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 73, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 74, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 75, huidige kas DataBlok (%): 22
uitgesit (MB): 342, verhouding 0.33, oorhoofse koste (%): 71, swaar uitsettingteller: 76, huidige kas DataBlok (%): 22
uitgesit (MB): 21, verhouding 0.33, bokoste (%): -90, swaar uitsettingteller: 76, huidige kas DataBlok (%): 32
uitgesit (MB): 0, verhouding 0.0, bokoste (%): -100, swaar uitsettingteller: 0, huidige kas DataBlok (%): 100
uitgesit (MB): 0, verhouding 0.0, bokoste (%): -100, swaar uitsettingteller: 0, huidige kas DataBlok (%): 100

Die skanderings was nodig om dieselfde proses te wys in die vorm van 'n grafiek van die verhouding tussen twee kasafdelings - enkel (waar blokke wat nog nooit vantevore aangevra is nie) en multi (data "versoek" ten minste een keer hier gestoor word):

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

En laastens, hoe lyk die werking van die parameters in die vorm van 'n grafiek. Ter vergelyking, die kas was heeltemal afgeskakel aan die begin, dan is HBase geloods met kas en vertraag die begin van optimaliseringswerk met 5 minute (30 uitsettingsiklusse).

Volledige kode kan gevind word in Pull Request HBASE 23887 op github.

300 duisend lees per sekonde is egter nie al wat onder hierdie omstandighede op hierdie hardeware bereik kan word nie. Die feit is dat wanneer jy toegang tot data via HDFS moet kry, die ShortCircuitCache (hierna verwys as SSC) meganisme gebruik word, wat jou toelaat om direk toegang tot die data te verkry, wat netwerkinteraksies vermy.

Profilering het getoon dat hoewel hierdie meganisme 'n groot wins gee, dit ook op 'n stadium 'n bottelnek word, want byna alle swaar bewerkings vind binne 'n slot plaas, wat die meeste van die tyd tot blokkering lei.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Nadat ons dit besef het, het ons besef dat die probleem omseil kan word deur 'n verskeidenheid onafhanklike SSC's te skep:

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

En werk dan met hulle, met die uitsondering van kruisings ook by die laaste afwykingssyfer:

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

Nou kan jy begin toets. Om dit te doen, sal ons lêers van HDFS lees met 'n eenvoudige multi-threaded toepassing. Stel die parameters in:

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

En lees net die lêers:

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

Hierdie kode word in aparte drade uitgevoer en ons sal die aantal gelyktydig geleesde lêers (van 10 tot 200 - horisontale as) en die aantal kas (van 1 tot 10 - grafika) verhoog. Die vertikale as toon die versnelling wat voortspruit uit 'n toename in SSC relatief tot die geval wanneer daar net een kas is.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Hoe om die grafiek te lees: Die uitvoeringstyd vir 100 duisend leeswerk in 64 KB-blokke met een kas vereis 78 sekondes. Terwyl dit met 5 caches 16 sekondes neem. Dié. daar is 'n versnelling van ~5 keer. Soos uit die grafiek gesien kan word, is die effek nie baie opvallend vir 'n klein aantal parallelle leeswerk nie; dit begin 'n merkbare rol speel wanneer daar meer as 50 draadlesings is. Dit is ook opmerklik dat die verhoging van die aantal SSC's vanaf 6 en bo gee 'n aansienlik kleiner prestasieverhoging.

Nota 1: aangesien die toetsresultate redelik wisselvallig is (sien hieronder), is 3 lopies uitgevoer en die resulterende waardes is gemiddeld.

Nota 2: Die prestasiewins om ewekansige toegang op te stel is dieselfde, hoewel die toegang self effens stadiger is.

Dit is egter nodig om te verduidelik dat, anders as die geval met HBase, hierdie versnelling nie altyd gratis is nie. Hier "ontsluit" ons die SVE se vermoë om meer werk te doen, in plaas daarvan om aan slotte te hang.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Hier kan u waarneem dat 'n toename in die aantal kasgeheue oor die algemeen 'n ongeveer proporsionele toename in SVE-benutting gee. Daar is egter effens meer wenkombinasies.

Kom ons kyk byvoorbeeld van naderby na die instelling SSC = 3. Die toename in werkverrigting op die reeks is ongeveer 3.3 keer. Hieronder is die resultate van al drie afsonderlike lopies.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Terwyl SVE-verbruik met ongeveer 2.8 keer toeneem. Die verskil is nie baie groot nie, maar klein Greta is reeds gelukkig en het dalk tyd om skool by te woon en lesse te neem.

Dit sal dus 'n positiewe uitwerking hê vir enige instrument wat grootmaattoegang tot HDFS gebruik (byvoorbeeld Spark, ens.), mits die toepassingskode liggewig is (m.a.w. die prop is aan die HDFS-kliëntkant) en daar gratis SVE-krag is . Om na te gaan, kom ons toets watter effek die gekombineerde gebruik van BlockCache-optimalisering en SSC-instelling vir lees vanaf HBase sal hê.

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Dit kan gesien word dat die effek onder sulke omstandighede nie so groot is soos in verfynde toetse nie (lees sonder enige verwerking), maar dit is heel moontlik om 'n bykomende 80K hier uit te druk. Saam bied beide optimaliserings tot 4x versnelling.

'n PR is ook gemaak vir hierdie optimalisering [HDFS-15202], wat saamgevoeg is en hierdie funksionaliteit sal in toekomstige vrystellings beskikbaar wees.

En laastens was dit interessant om die leesprestasie van 'n soortgelyke wye-kolom databasis, Cassandra en HBase, te vergelyk.

Om dit te doen, het ons gevalle van die standaard YCSB-ladingstoetsprogram van twee gashere (800 drade in totaal) bekendgestel. Aan die bedienerkant - 4 gevalle van RegionServer en Cassandra op 4 gashere (nie dié waar die kliënte loop nie, om hul invloed te vermy). Lesings kom uit tabelle van grootte:

HBase – 300 GB op HDFS (100 GB suiwer data)

Cassandra - 250 GB (replikasiefaktor = 3)

Dié. die volume was ongeveer dieselfde (in HBase 'n bietjie meer).

HBase parameters:

dfs.client.shortcircuit.num = 5 (HDFS-kliëntoptimering)

hbase.lru.cache.heavy.eviction.count.limit = 30 - dit beteken dat die pleister sal begin werk na 30 uitsettings (~ 5 minute)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 - teikenvolume van caching en uitsetting

YCSB-logboeke is ontleed en saamgestel in Excel-grafieke:

Hoe om leesspoed vanaf HBase tot 3 keer te verhoog en vanaf HDFS tot 5 keer

Soos u kan sien, maak hierdie optimalisering dit moontlik om die werkverrigting van hierdie databasisse onder hierdie toestande te vergelyk en 450 duisend leeswerk per sekonde te behaal.

Ons hoop hierdie inligting kan nuttig wees vir iemand tydens die opwindende stryd om produktiwiteit.

Bron: will.com

Voeg 'n opmerking