Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Suur jõudlus on suurandmetega töötamisel üks põhinõudeid. Sberbanki andmete laadimise osakonnas pumpame peaaegu kõik tehingud oma Hadoopi-põhisesse andmepilve ja tegeleme seetõttu tõeliselt suurte teabevoogudega. Loomulikult otsime alati võimalusi jõudluse parandamiseks ja nüüd tahame teile rääkida, kuidas meil õnnestus RegionServer HBase'i ja HDFS-kliendi paikamine, tänu millele suutsime lugemistoimingute kiirust märkimisväärselt suurendada.
Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Enne täiustuste olemuse juurde liikumist tasub aga rääkida piirangutest, millest HDD-l istudes põhimõtteliselt mööda ei saa.

Miks HDD ja kiire suvapöörduslugemine ei ühildu?
Nagu teate, salvestavad HBase ja paljud teised andmebaasid andmeid mitmekümne kilobaidi suurustes plokkides. Vaikimisi on see umbes 64 KB. Nüüd kujutame ette, et peame saama ainult 100 baiti ja palume HBase'il need andmed meile teatud võtme abil anda. Kuna HFilesi ploki suurus on 64 KB, on taotlus 640 korda suurem (ainult minut!) kui vaja.

Järgmiseks, kuna taotlus läbib HDFS-i ja selle metaandmete vahemällu salvestamise mehhanismi ShortCircuitCache (mis võimaldab otsest juurdepääsu failidele), viib see juba 1 MB kettalt lugemiseni. Seda saab aga parameetriga reguleerida dfs.client.read.shortcircuit.buffer.size ja paljudel juhtudel on mõttekas seda väärtust vähendada, näiteks 126 KB-ni.

Oletame, et me teeme seda, kuid lisaks sellele, kui hakkame java api kaudu andmeid lugema, näiteks funktsioone, nagu FileChannel.read, ja palume operatsioonisüsteemil määratud andmemahu lugeda, loeb see "igaks juhuks" 2 korda rohkem. , st. 256 KB meie puhul. Selle põhjuseks on asjaolu, et javal pole lihtsat viisi lipu FADV_RANDOM määramiseks, et seda käitumist vältida.

Selle tulemusel loetakse meie 100 baiti saamiseks kapoti all 2600 korda rohkem. Tundub, et lahendus on ilmne, vähendame ploki suurust kilobaidini, paneme mainitud lipu ja saavutame suure valgustuskiirenduse. Aga häda on selles, et vähendades ploki suurust 2 korda, vähendame ka ajaühikus loetavate baitide arvu 2 korda.

FADV_RANDOM lipu seadmisest saab teatud kasu, kuid ainult suure mitmelõimega ja ploki suurusega 128 KB, kuid see on maksimaalselt paarkümmend protsenti:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Testid viidi läbi 100 failiga, millest igaüks oli 1 GB suurune ja asus 10 HDD-l.

Arvutame välja, millele saame sellisel kiirusel põhimõtteliselt loota:
Oletame, et loeme 10 kettalt kiirusega 280 MB/sek, st. 3 miljonit korda 100 baiti. Kuid nagu mäletame, on meil vaja andmeid 2600 korda vähem kui loetud. Seega jagame 3 miljonit 2600-ga ja saame 1100 rekordit sekundis.

Masendav, kas pole? See on loodus Juhuslik juurdepääs juurdepääs kõvakettal olevatele andmetele – olenemata ploki suurusest. See on juhusliku juurdepääsu füüsiline piirang ja ükski andmebaas ei suuda sellistel tingimustel rohkem välja pigistada.

Kuidas siis andmebaasid palju suuremat kiirust saavutavad? Sellele küsimusele vastamiseks vaatame järgmisel pildil toimuvat:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Siin näeme, et esimestel minutitel on kiirus tõesti umbes tuhat rekordit sekundis. Kuid veelgi enam, kuna loetakse palju rohkem, kui nõuti, satuvad andmed operatsioonisüsteemi (linux) puhvrisse/vahemällu ja kiirus kasvab korralikuma 60 tuhandeni sekundis.

Seega käsitleme edaspidi juurdepääsu kiirendamist ainult neile andmetele, mis asuvad OS-i vahemälus või võrreldava juurdepääsukiirusega SSD/NVMe salvestusseadmetes.

Meie puhul viime testid läbi neljast serverist koosneval pingil, millest igaühe eest võetakse tasu järgmiselt:

Protsessor: Xeon E5-2680 v4 @ 2.40 GHz 64 keermega.
Mälu: 730 GB.
java versioon: 1.8.0_111

Ja siin on võtmepunkt tabelites olevate andmete hulk, mida tuleb lugeda. Fakt on see, et kui loete andmeid tabelist, mis on täielikult paigutatud HBase'i vahemällu, siis ei jõua need isegi operatsioonisüsteemi puhv-/vahemälu lugemiseni. Kuna HBase eraldab vaikimisi 40% mälust struktuurile nimega BlockCache. Põhimõtteliselt on see ConcurrentHashMap, kus võti on failinimi + ploki nihe ja väärtus on selle nihke tegelikud andmed.

Seega ainult sellest struktuurist lugedes me näeme suurepärast kiirust, nagu miljon taotlust sekundis. Kuid kujutame ette, et me ei saa eraldada sadu gigabaite mälu ainult andmebaasi vajaduste jaoks, sest neis serverites töötab palju muud kasulikku.

Näiteks meie puhul on ühe RS-i BlockCache'i maht umbes 12 GB. Ühele sõlmele maandusime kaks RS-i, st. Kõigis sõlmedes on BlockCache jaoks eraldatud 96 GB. Ja andmeid on kordades rohkem, olgu selleks näiteks 4 tabelit, igaühes 130 piirkonda, milles failid on 800 MB suurused, tihendatud FAST_DIFF-iga, st. kokku 410 GB (see on puhas andmed, st ilma replikatsioonitegurit arvestamata).

Seega moodustab BlockCache ainult umbes 23% kogu andmemahust ja see on palju lähemal nn BigData tegelikele tingimustele. Ja siit algab lõbu – sest ilmselgelt, mida vähem vahemälu tabab, seda halvem on jõudlus. Ju kui vahele jääd, pead palju tööd tegema – s.t. minge süsteemi funktsioonide helistamise juurde. Seda aga vältida ei saa, seega vaatame hoopis teist aspekti – mis juhtub vahemälu sees olevate andmetega?

Lihtsustame olukorda ja eeldame, et meil on vahemälu, mis mahub ainult 1 objektile. Siin on näide sellest, mis juhtub, kui proovime töötada vahemälust 3 korda suurema andmemahuga, peame tegema järgmist:

1. Asetage plokk 1 vahemällu
2. Eemaldage vahemälust plokk 1
3. Asetage plokk 2 vahemällu
4. Eemaldage vahemälust plokk 2
5. Asetage plokk 3 vahemällu

5 toimingut tehtud! Normaalseks seda olukorda aga nimetada ei saa, tegelikult sundime HBase’i tegema hunnikut täiesti kasutut tööd. See loeb pidevalt andmeid OS-i vahemälust, asetab need BlockCache'i, et visata need peaaegu kohe välja, kuna saabunud on uus osa andmeid. Postituse alguses olev animatsioon näitab probleemi olemust - Prügikoguja läheb mastaapselt maha, atmosfäär kuumeneb, väike Greta kauges ja kuumas Rootsis on ärritumas. Ja meile IT-inimestele väga ei meeldi, kui lapsed on kurvad, nii et hakkame mõtlema, mida saaksime sellega teha.

Mis siis, kui te ei pane vahemällu mitte kõiki plokke, vaid ainult teatud protsendi neist, et vahemälu üle ei voolaks? Alustame andmete BlockCache'i sisestamise funktsiooni algusesse lihtsalt mõne koodirea lisamisega:

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

Mõte on siin järgmine: nihe on ploki asukoht failis ja selle viimased numbrid on juhuslikult ja ühtlaselt jaotatud vahemikus 00 kuni 99. Seetõttu jätame vahele ainult need, mis jäävad meile vajalikku vahemikku.

Näiteks määrake cacheDataBlockPercent = 20 ja vaadake, mis juhtub:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Tulemus on ilmne. Allolevatel graafikutel saab selgeks, miks selline kiirendus aset leidis – säästame palju GC ressursse, tegemata Sisyphoslikku tööd andmete vahemällu paigutamiseks, et need kohe Marsi koerte kanalisatsiooni visata:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Samal ajal suureneb protsessori kasutamine, kuid see on palju väiksem kui tootlikkus:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Samuti väärib märkimist, et BlockCache'is salvestatud plokid on erinevad. Enamik, umbes 95%, on andmed ise. Ja ülejäänud on metaandmed, näiteks Bloomi filtrid või LEAF_INDEX ja т.д.. Nendest andmetest ei piisa, kuid need on väga kasulikud, sest enne andmetele otse juurde pääsemist pöördub HBase meta poole, et mõista, kas siin on vaja edasi otsida ja kui on, siis kus täpselt huvipakkuv plokk asub.

Seetõttu näeme koodis kontrolli tingimust buf.getBlockType().isData() ja tänu sellele metale jätame selle igal juhul vahemällu.

Nüüd suurendame koormust ja pingutame funktsiooni veidi ühe korraga. Esimeses testis tegime piirmääraks 20 ja BlockCache oli veidi alakasutatud. Nüüd määrame selle väärtuseks 23% ja lisame iga 100 minuti järel 5 lõime, et näha, millisel hetkel tekib küllastus:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Siin näeme, et algversioon jõuab peaaegu kohe lakke umbes 100 tuhande päringu sekundis. Kusjuures plaaster annab kiirenduse kuni 300 tuhat. Samas on selge, et edasine kiirendus pole enam nii “tasuta”, samuti kasvab protsessori kasutus.

See pole aga kuigi elegantne lahendus, kuna me ei tea ette, mitu protsenti plokkidest tuleb vahemällu salvestada, oleneb koormusprofiilist. Seetõttu rakendati mehhanism selle parameetri automaatseks reguleerimiseks sõltuvalt lugemistoimingute aktiivsusest.

Selle juhtimiseks on lisatud kolm võimalust:

hbase.lru.cache.heavy.eviction.count.limit — määrab, mitu korda peaks andmete vahemälust väljatõstmise protsess käima, enne kui hakkame optimeerima (st plokkide vahele jätma). Vaikimisi võrdub see väärtusega MAX_INT = 2147483647 ja tähendab tegelikult, et funktsioon ei hakka kunagi selle väärtusega töötama. Kuna väljatõstmisprotsess algab iga 5 - 10 sekundi järel (oleneb koormusest) ja 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 aastat. Siiski saame määrata selle parameetri väärtuseks 0 ja panna funktsiooni tööle kohe pärast käivitamist.

Kuid selles parameetris on ka kasulik koormus. Kui meie koormus on selline, et lühiajalised lugemised (ütleme päevasel ajal) ja pikaajalised lugemised (öised) jäävad pidevalt vahele, siis saame veenduda, et funktsioon on sisse lülitatud ainult siis, kui käimas on pikad lugemistoimingud.

Näiteks teame, et lühiajalised näidud kestavad tavaliselt umbes 1 minuti. Pole vaja hakata plokke välja viskama, vahemälu ei jõua aeguda ja siis saame selle parameetri määrata näiteks 10-ga võrdseks. See viib selleni, et optimeerimine hakkab tööle alles siis, kui termin aktiivne lugemine on alanud, st. 100 sekundi jooksul. Seega, kui meil on lühiajaline lugemine, lähevad kõik plokid vahemällu ja on saadaval (välja arvatud need, mis standardalgoritmiga välja tõstetakse). Ja kui teeme pikaajalisi lugemisi, lülitatakse see funktsioon sisse ja meil oleks palju suurem jõudlus.

hbase.lru.cache.heavy.eviction.mb.size.limit — määrab, mitu megabaiti soovime vahemällu paigutada (ja loomulikult välja tõsta) 10 sekundi jooksul. Funktsioon püüab seda väärtust saavutada ja seda säilitada. Asi on selles: kui me toome gigabaite vahemällu, siis peame gigabaite välja tõstma ja see, nagu eespool nägime, on väga kallis. Kuid te ei tohiks proovida seda liiga väikeseks seada, kuna see põhjustab blokeeringu vahelejätmise režiimi enneaegse väljumise. Võimsate serverite jaoks (umbes 20-40 füüsilist tuuma) on optimaalne määrata umbes 300-400 MB. Keskklassile (~10 tuuma) 200-300 MB. Nõrkade süsteemide (2–5 tuuma) puhul võib 50–100 MB olla normaalne (nende peal pole testitud).

Vaatame, kuidas see toimib: oletame, et määrame hbase.lru.cache.heavy.eviction.mb.size.limit = 500, toimub mingi koormus (lugemine) ja siis iga ~10 sekundi järel arvutame, mitu baiti oli välja tõstetud valemiga:

Üldkulud = vabastatud baitide summa (MB) * 100 / limiit (MB) - 100;

Kui tegelikult tõsteti välja 2000 MB, siis üldkulud on võrdne:

2000 * 100 / 500 - 100 = 300%

Algoritmid püüavad säilitada mitte rohkem kui mõnikümmend protsenti, nii et funktsioon vähendab vahemällu salvestatud plokkide protsenti, rakendades seeläbi automaatse häälestamise mehhanismi.

Kui aga koormus langeb, oletame, et tõstetakse välja ainult 200 MB ja Overhead muutub negatiivseks (nn ületamine):

200 * 100 / 500 - 100 = -60%

Vastupidi, funktsioon suurendab vahemällu salvestatud plokkide protsenti, kuni Overhead muutub positiivseks.

Allpool on näide sellest, kuidas see tegelike andmete põhjal välja näeb. Pole vaja püüda 0%ni jõuda, see on võimatu. See on väga hea, kui see on umbes 30–100%, see aitab vältida enneaegset optimeerimisrežiimist väljumist lühiajaliste tõusute ajal.

hbase.lru.cache.heavy.eviction.overhead.coefficient — määrab, kui kiiresti soovime tulemust saada. Kui teame kindlalt, et meie lugemised on enamasti pikad ja ei taha oodata, saame seda suhet suurendada ja jõuda kiiremini.

Näiteks määrame selle koefitsiendi = 0.01. See tähendab, et üldkulud (vt eespool) korrutatakse saadud tulemusega selle arvuga ja vahemällu salvestatud plokkide protsenti vähendatakse. Oletame, et Overhead = 300% ja koefitsient = 0.01, siis väheneb vahemällu salvestatud plokkide protsent 3%.

Sarnast "vasturõhu" loogikat rakendatakse ka negatiivsete üldkulude (ülelöögi) väärtuste jaoks. Kuna lugemiste ja väljatõstmiste mahu lühiajalised kõikumised on alati võimalikud, võimaldab see mehhanism vältida enneaegset optimeerimisrežiimist väljumist. Vasturõhul on ümberpööratud loogika: mida tugevam on ületamine, seda rohkem plokke vahemällu salvestatakse.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Rakenduskood

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

Vaatame nüüd seda kõike reaalse näite abil. Meil on järgmine testskript:

  1. Alustame skannimist (25 lõime, partii = 100)
  2. 5 minuti pärast lisage multi-gets (25 lõime, partii = 100)
  3. 5 minuti pärast lülitage multi-gets välja (taas jääb ainult skannimine)

Teeme kaks käitamist, esmalt hbase.lru.cache.heavy.eviction.count.limit = 10000 (mis tegelikult keelab funktsiooni) ja seejärel määrame limiit = 0 (lubab selle).

Allpool olevates logides näeme, kuidas funktsioon on sisse lülitatud ja lähtestab ülelöögi väärtusele 14–71%. Aeg-ajalt koormus väheneb, mis lülitab Backpressure'i sisse ja HBase salvestab taas rohkem plokke.

Logi RegionServer
välja tõstetud (MB): 0, suhe 0.0, üldkulud (%): -100, raske väljatõstmise loendur: 0, praegune vahemälu DataBlock (%): 100
välja tõstetud (MB): 0, suhe 0.0, üldkulud (%): -100, raske väljatõstmise loendur: 0, praegune vahemälu DataBlock (%): 100
välja tõstetud (MB): 2170, suhe 1.09, üldkulud (%): 985, raske väljatõstmise loendur: 1, praegune vahemälu DataBlock (%): 91 < algus
välja tõstetud (MB): 3763, suhe 1.08, üldkulud (%): 1781, raske väljatõstmise loendur: 2, praegune vahemälu DataBlock (%): 76
välja tõstetud (MB): 3306, suhe 1.07, üldkulud (%): 1553, raske väljatõstmise loendur: 3, praegune vahemälu DataBlock (%): 61
välja tõstetud (MB): 2508, suhe 1.06, üldkulud (%): 1154, raske väljatõstmise loendur: 4, praegune vahemälu DataBlock (%): 50
välja tõstetud (MB): 1824, suhe 1.04, üldkulud (%): 812, raske väljatõstmise loendur: 5, praegune vahemälu DataBlock (%): 42
välja tõstetud (MB): 1482, suhe 1.03, üldkulud (%): 641, raske väljatõstmise loendur: 6, praegune vahemälu DataBlock (%): 36
välja tõstetud (MB): 1140, suhe 1.01, üldkulud (%): 470, raske väljatõstmise loendur: 7, praegune vahemälu DataBlock (%): 32
välja tõstetud (MB): 913, suhe 1.0, üldkulud (%): 356, raske väljatõstmise loendur: 8, praegune vahemälu DataBlock (%): 29
välja tõstetud (MB): 912, suhe 0.89, üldkulud (%): 356, raske väljatõstmise loendur: 9, praegune vahemälu DataBlock (%): 26
välja tõstetud (MB): 684, suhe 0.76, üldkulud (%): 242, raske väljatõstmise loendur: 10, praegune vahemälu DataBlock (%): 24
välja tõstetud (MB): 684, suhe 0.61, üldkulud (%): 242, raske väljatõstmise loendur: 11, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 456, suhe 0.51, üldkulud (%): 128, raske väljatõstmise loendur: 12, praegune vahemälu DataBlock (%): 21
välja tõstetud (MB): 456, suhe 0.42, üldkulud (%): 128, raske väljatõstmise loendur: 13, praegune vahemälu DataBlock (%): 20
välja tõstetud (MB): 456, suhe 0.33, üldkulud (%): 128, raske väljatõstmise loendur: 14, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 15, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 342, suhe 0.32, üldkulud (%): 71, raske väljatõstmise loendur: 16, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 342, suhe 0.31, üldkulud (%): 71, raske väljatõstmise loendur: 17, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.3, üldkulud (%): 14, raske väljatõstmise loendur: 18, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.29, üldkulud (%): 14, raske väljatõstmise loendur: 19, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.27, üldkulud (%): 14, raske väljatõstmise loendur: 20, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.25, üldkulud (%): 14, raske väljatõstmise loendur: 21, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.24, üldkulud (%): 14, raske väljatõstmise loendur: 22, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.22, üldkulud (%): 14, raske väljatõstmise loendur: 23, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.21, üldkulud (%): 14, raske väljatõstmise loendur: 24, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.2, üldkulud (%): 14, raske väljatõstmise loendur: 25, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 228, suhe 0.17, üldkulud (%): 14, raske väljatõstmise loendur: 26, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 456, suhe 0.17, üldkulud (%): 128, raske väljatõstmise loendur: 27, praegune vahemälu DataBlock (%): 18 < lisatud saab (aga tabel sama)
välja tõstetud (MB): 456, suhe 0.15, üldkulud (%): 128, raske väljatõstmise loendur: 28, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 342, suhe 0.13, üldkulud (%): 71, raske väljatõstmise loendur: 29, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 342, suhe 0.11, üldkulud (%): 71, raske väljatõstmise loendur: 30, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 342, suhe 0.09, üldkulud (%): 71, raske väljatõstmise loendur: 31, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.08, üldkulud (%): 14, raske väljatõstmise loendur: 32, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.07, üldkulud (%): 14, raske väljatõstmise loendur: 33, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.06, üldkulud (%): 14, raske väljatõstmise loendur: 34, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.05, üldkulud (%): 14, raske väljatõstmise loendur: 35, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.05, üldkulud (%): 14, raske väljatõstmise loendur: 36, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 228, suhe 0.04, üldkulud (%): 14, raske väljatõstmise loendur: 37, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 109, suhe 0.04, üldkulud (%): -46, raske väljatõstmise loendur: 37, praegune vahemälu DataBlock (%): 22 < vasturõhk
välja tõstetud (MB): 798, suhe 0.24, üldkulud (%): 299, raske väljatõstmise loendur: 38, praegune vahemälu DataBlock (%): 20
välja tõstetud (MB): 798, suhe 0.29, üldkulud (%): 299, raske väljatõstmise loendur: 39, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 570, suhe 0.27, üldkulud (%): 185, raske väljatõstmise loendur: 40, praegune vahemälu DataBlock (%): 17
välja tõstetud (MB): 456, suhe 0.22, üldkulud (%): 128, raske väljatõstmise loendur: 41, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 342, suhe 0.16, üldkulud (%): 71, raske väljatõstmise loendur: 42, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 342, suhe 0.11, üldkulud (%): 71, raske väljatõstmise loendur: 43, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 228, suhe 0.09, üldkulud (%): 14, raske väljatõstmise loendur: 44, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 228, suhe 0.07, üldkulud (%): 14, raske väljatõstmise loendur: 45, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 228, suhe 0.05, üldkulud (%): 14, raske väljatõstmise loendur: 46, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 222, suhe 0.04, üldkulud (%): 11, raske väljatõstmise loendur: 47, praegune vahemälu DataBlock (%): 16
välja tõstetud (MB): 104, suhe 0.03, üldkulud (%): -48, raske väljatõstmise loendur: 47, praegune vahemälu DataBlock (%): 21 < katkestus saab
välja tõstetud (MB): 684, suhe 0.2, üldkulud (%): 242, raske väljatõstmise loendur: 48, praegune vahemälu DataBlock (%): 19
välja tõstetud (MB): 570, suhe 0.23, üldkulud (%): 185, raske väljatõstmise loendur: 49, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 342, suhe 0.22, üldkulud (%): 71, raske väljatõstmise loendur: 50, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 228, suhe 0.21, üldkulud (%): 14, raske väljatõstmise loendur: 51, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 228, suhe 0.2, üldkulud (%): 14, raske väljatõstmise loendur: 52, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 228, suhe 0.18, üldkulud (%): 14, raske väljatõstmise loendur: 53, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 228, suhe 0.16, üldkulud (%): 14, raske väljatõstmise loendur: 54, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 228, suhe 0.14, üldkulud (%): 14, raske väljatõstmise loendur: 55, praegune vahemälu DataBlock (%): 18
välja tõstetud (MB): 112, suhe 0.14, üldkulud (%): -44, raske väljatõstmise loendur: 55, praegune vahemälu DataBlock (%): 23 < vasturõhk
välja tõstetud (MB): 456, suhe 0.26, üldkulud (%): 128, raske väljatõstmise loendur: 56, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.31, üldkulud (%): 71, raske väljatõstmise loendur: 57, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 58, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 59, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 60, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 61, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 62, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 63, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.32, üldkulud (%): 71, raske väljatõstmise loendur: 64, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 65, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 66, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.32, üldkulud (%): 71, raske väljatõstmise loendur: 67, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 68, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.32, üldkulud (%): 71, raske väljatõstmise loendur: 69, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.32, üldkulud (%): 71, raske väljatõstmise loendur: 70, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 71, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 72, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 73, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 74, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 75, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 342, suhe 0.33, üldkulud (%): 71, raske väljatõstmise loendur: 76, praegune vahemälu DataBlock (%): 22
välja tõstetud (MB): 21, suhe 0.33, üldkulud (%): -90, raske väljatõstmise loendur: 76, praegune vahemälu DataBlock (%): 32
välja tõstetud (MB): 0, suhe 0.0, üldkulud (%): -100, raske väljatõstmise loendur: 0, praegune vahemälu DataBlock (%): 100
välja tõstetud (MB): 0, suhe 0.0, üldkulud (%): -100, raske väljatõstmise loendur: 0, praegune vahemälu DataBlock (%): 100

Skaneeringuid oli vaja sama protsessi kuvamiseks graafikuna kahe vahemälu sektsiooni - ühe (kus plokid, mida pole kunagi varem taotletud) ja mitme (siin salvestatakse vähemalt korra küsitud) vahelise seose graafiku kujul:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Ja lõpuks, kuidas näeb välja parameetrite toimimine graafiku kujul. Võrdluseks, vahemälu oli alguses täielikult välja lülitatud, seejärel käivitati HBase koos vahemällu salvestamisega ja optimeerimistööde algust 5 minuti võrra edasi lükates (30 väljatõstmistsüklit).

Täieliku koodi leiate tõmbetaotlusest HBASE 23887 githubis.

Kuid 300 tuhat lugemist sekundis pole veel kõik, mida selle riistvaraga sellistes tingimustes saavutada on võimalik. Fakt on see, et kui teil on vaja HDFS-i kaudu andmetele juurde pääseda, kasutatakse ShortCircuitCache (edaspidi SSC) mehhanismi, mis võimaldab teil andmetele otse juurde pääseda, vältides võrgu suhtlust.

Profileerimine näitas, et kuigi see mehhanism annab suure kasu, muutub see ühel hetkel ka kitsaskohaks, sest peaaegu kõik rasked toimingud toimuvad luku sees, mis viib enamuse ajast blokeerimiseni.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Olles sellest aru saanud, mõistsime, et probleemist saab mööda hiilida sõltumatute SSC-de massiivi loomisega:

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

Ja seejärel töötage nendega, jättes välja ristmikud ka viimase nihkenumbri juures:

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

Nüüd saate alustada testimist. Selleks loeme HDFS-ist faile lihtsa mitme lõimega rakendusega. Määrake parameetrid:

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

Ja lihtsalt lugege faile:

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

See kood käivitatakse eraldi lõimedes ja me suurendame samaaegselt loetavate failide arvu (10-lt 200-le - horisontaaltelg) ja vahemälude arvu (1-lt 10-le - graafika). Vertikaaltelg näitab kiirendust, mis tuleneb SSC suurenemisest juhul, kui vahemälu on ainult üks.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Kuidas graafikut lugeda: 100 tuhande lugemise täitmisaeg 64 KB plokkides ühe vahemäluga nõuab 78 sekundit. 5 vahemälu puhul kulub selleks 16 sekundit. Need. on ~5-kordne kiirendus. Nagu graafikult näha, ei ole efekt väikese arvu paralleellugemiste puhul eriti märgatav, see hakkab mängima märgatavat rolli siis, kui lõime lugemisi on üle 50. Samuti on märgata, et SSC-de arvu suurendamine 6-lt ja üle selle annab oluliselt väiksema jõudluse kasvu.

Märkus 1: kuna testi tulemused on üsna muutlikud (vt allpool), viidi läbi 3 jooksu ja saadud väärtused keskmistati.

Märkus 2. Juhusliku juurdepääsu konfigureerimisest tulenev jõudluse kasv on sama, kuigi juurdepääs ise on veidi aeglasem.

Siiski on vaja selgitada, et erinevalt HBase'i juhtumist ei ole see kiirendus alati tasuta. Siin "vabastame" protsessori võime rohkem tööd teha, selle asemel, et lukkudel rippuda.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Siin näete, et üldiselt suurendab vahemälude arvu suurenemine protsessori kasutust ligikaudu proportsionaalselt. Võidukombinatsioone on aga veidi rohkem.

Näiteks vaatame lähemalt sätet SSC = 3. Vahemiku jõudluse kasv on umbes 3.3 korda. Allpool on tulemused kõigist kolmest eraldi jooksust.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Samal ajal suureneb protsessori tarbimine umbes 2.8 korda. Vahe pole väga suur, aga väike Greta on juba rahul ja tal võib olla aega koolis käia ja tundi võtta.

Seega on sellel positiivne mõju kõikidele tööriistadele, mis kasutavad hulgijuurdepääsu HDFS-ile (nt Spark jne), eeldusel, et rakenduse kood on kerge (st pistik on HDFS-kliendi poolel) ja protsessori toide on vaba. . Kontrollimiseks testime, millist mõju avaldab BlockCache optimeerimise ja SSC häälestamise kombineeritud kasutamine HBase'ist lugemiseks.

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

On näha, et sellistes tingimustes pole efekt nii suur kui rafineeritud testides (lugedes ilma igasuguse töötluseta), kuid siin on täiesti võimalik 80K lisaks välja pigistada. Mõlemad optimeerimised koos tagavad kuni 4x kiirenduse.

Selle optimeerimise jaoks tehti ka PR [HDFS-15202], mis on ühendatud ja see funktsioon on saadaval tulevastes versioonides.

Ja lõpuks oli huvitav võrrelda sarnase laia veeruga andmebaasi Cassandra ja HBase lugemisjõudlust.

Selleks käivitasime kahe hosti (kokku 800 lõime) standardse YCSB koormustestimise utiliidi eksemplarid. Serveri poolel - 4 RegionServeri ja Cassandra eksemplari 4 hostil (mitte need, kus kliendid töötavad, et vältida nende mõju). Näidud tulid suuruste tabelitest:

HBase – 300 GB HDFS-is (100 GB puhast andmemahtu)

Cassandra – 250 GB (replikatsioonitegur = 3)

Need. helitugevus oli ligikaudu sama (HBase'is veidi rohkem).

HBase parameetrid:

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

hbase.lru.cache.heavy.eviction.count.limit = 30 - see tähendab, et plaaster hakkab tööle pärast 30 väljatõstmist (~5 minutit)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — vahemällu salvestamise ja väljatõstmise sihtmaht

YCSB logid sõeluti ja kompileeriti Exceli graafikuteks:

Kuidas suurendada lugemiskiirust HBase'ist kuni 3 korda ja HDFS-ist kuni 5 korda

Nagu näete, võimaldavad need optimeerimised võrrelda nende andmebaaside jõudlust nendes tingimustes ja saavutada 450 tuhat lugemist sekundis.

Loodame, et see teave on kellelegi kasulik tootlikkuse eest peetava põneva võitluse ajal.

Allikas: www.habr.com

Lisa kommentaar