Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Korkea suorituskyky on yksi tärkeimmistä vaatimuksista työskenneltäessä big datan kanssa. Sberbankin tiedonlatausosastolla pumppaamme lähes kaikki tapahtumat Hadoop-pohjaiseen datapilveemme ja käsittelemme siksi todella suuria tietovirtoja. Luonnollisesti etsimme jatkuvasti tapoja parantaa suorituskykyä, ja nyt haluamme kertoa, kuinka onnistuimme korjaamaan RegionServer HBase ja HDFS-asiakkaan, minkä ansiosta pystyimme merkittävästi lisäämään lukutoimintojen nopeutta.
Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Ennen kuin siirrytään parannusten olemukseen, kannattaa kuitenkin puhua rajoituksista, joita ei periaatteessa voi kiertää, jos istut kiintolevyllä.

Miksi HDD ja nopea Random Access -luku ovat yhteensopimattomia
Kuten tiedät, HBase ja monet muut tietokannat tallentavat tietoja useiden kymmenien kilotavujen kokoisiin lohkoihin. Oletuksena se on noin 64 kt. Kuvitellaan nyt, että tarvitsemme vain 100 tavua ja pyydämme HBasea antamaan meille nämä tiedot tietyllä avaimella. Koska HFilesin lohkokoko on 64 kt, pyyntö on 640 kertaa suurempi (vain minuutti!) kuin on tarpeen.

Seuraavaksi, koska pyyntö menee HDFS:n ja sen metatietojen välimuistimekanismin kautta ShortCircuitCache (joka mahdollistaa suoran pääsyn tiedostoihin), tämä johtaa jo 1 Mt:n lukemiseen levyltä. Tätä voidaan kuitenkin säätää parametrilla dfs.client.read.shortcircuit.buffer.size ja monissa tapauksissa on järkevää pienentää tätä arvoa, esimerkiksi 126 kt.

Oletetaan, että teemme tämän, mutta lisäksi, kun alamme lukea tietoja Java-sovellusliittymän kautta, kuten funktioita, kuten FileChannel.read, ja pyydämme käyttöjärjestelmää lukemaan määritetyn määrän dataa, se lukee "varmuuden vuoksi" 2 kertaa enemmän. , eli 256 kt meidän tapauksessamme. Tämä johtuu siitä, että javalla ei ole helppoa tapaa asettaa FADV_RANDOM-lippua tämän toiminnan estämiseksi.

Tämän seurauksena 100 tavumme saamiseksi konepellin alta luetaan 2600 kertaa enemmän. Ratkaisu näyttäisi olevan ilmeinen, pienennetään lohkon koko kilotavuun, laitetaan mainittu lippu ja saadaan suuri valaistuskiihtyvyys. Mutta ongelma on se, että pienentämällä lohkon kokoa 2 kertaa vähennämme myös aikayksikköä kohti luettujen tavujen määrää 2 kertaa.

FADV_RANDOM-lipun asettamisesta voidaan saada jonkin verran hyötyä, mutta vain korkealla monisäikeisyydellä ja 128 kt:n lohkokoolla, mutta tämä on enintään parikymmentä prosenttia:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Testit suoritettiin 100 tiedostolle, kukin 1 Gt kooltaan ja sijaitsi 10 kiintolevyllä.

Lasketaan, mihin voimme periaatteessa luottaa tällä nopeudella:
Oletetaan, että luemme 10 levyltä nopeudella 280 MB/s, ts. 3 miljoonaa kertaa 100 tavua. Mutta kuten muistamme, tarvitsemamme data on 2600 kertaa vähemmän kuin mitä luetaan. Siten jaamme 3 miljoonaa 2600:lla ja saamme 1100 ennätystä sekunnissa.

Masentavaa, eikö? Se on luontoa Satunnainen käyttö pääsy kiintolevyllä oleviin tietoihin - lohkon koosta riippumatta. Tämä on fyysinen satunnaiskäytön raja, eikä mikään tietokanta voi puristaa enemmän tällaisissa olosuhteissa.

Miten tietokannat sitten saavuttavat paljon suurempia nopeuksia? Vastataksesi tähän kysymykseen, katsotaan mitä tapahtuu seuraavassa kuvassa:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Tässä näemme, että ensimmäisten minuuttien aikana nopeus on todella noin tuhat ennätystä sekunnissa. Kuitenkin edelleen, koska luetaan paljon enemmän kuin pyydettiin, tiedot päätyvät käyttöjärjestelmän (linux) buffiin/välimuistiin ja nopeus kasvaa kohtuullisemmalle 60 tuhannelle sekunnissa.

Siten käsittelemme edelleen pääsyn nopeuttamista vain niihin tietoihin, jotka ovat käyttöjärjestelmän välimuistissa tai sijaitsevat SSD/NVMe-tallennuslaitteissa, joilla on vastaava pääsynopeus.

Meidän tapauksessamme suoritamme testit 4 palvelimen kentällä, joista jokainen veloitetaan seuraavasti:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 säiettä.
Muisti: 730 GB.
java-versio: 1.8.0_111

Ja tässä keskeinen kohta on taulukoissa olevien tietojen määrä, joka on luettava. Tosiasia on, että jos luet tietoja taulukosta, joka on kokonaan sijoitettu HBase-välimuistiin, se ei edes ala lukea käyttöjärjestelmän buffista/välimuistista. Koska HBase varaa oletusarvoisesti 40% muistista rakenteelle nimeltä BlockCache. Pohjimmiltaan tämä on ConcurrentHashMap, jossa avain on tiedoston nimi + lohkon offset, ja arvo on tämän siirtymän todellinen data.

Näin ollen, kun luemme vain tästä rakenteesta, me näemme erinomaisen nopeuden, kuten miljoona pyyntöä sekunnissa. Mutta kuvitellaan, että emme voi varata satoja gigatavuja muistia vain tietokantatarpeisiin, koska näillä palvelimilla on käynnissä paljon muuta hyödyllistä.

Esimerkiksi meidän tapauksessamme yhden RS:n BlockCachen määrä on noin 12 Gt. Laskeuduimme kaksi RS:ää yhdelle solmulle, ts. BlockCachelle on varattu 96 Gt kaikissa solmuissa. Ja dataa on monta kertaa enemmän, esimerkiksi olkoon 4 taulukkoa, kussakin 130 aluetta, joissa tiedostot ovat kooltaan 800 MB, FAST_DIFF-pakattuna, ts. yhteensä 410 Gt (tämä on puhdasta dataa, eli ilman replikointikerrointa).

Näin ollen BlockCache on vain noin 23 % datan kokonaismäärästä ja tämä on paljon lähempänä BigDatan todellisia olosuhteita. Ja tästä hauskuus alkaa - koska ilmeisesti mitä vähemmän välimuistin osumia, sitä huonompi suorituskyky. Loppujen lopuksi, jos kaipaat, joudut tekemään paljon työtä - ts. Siirry alas kutsumaan järjestelmätoimintoja. Tätä ei kuitenkaan voida välttää, joten katsotaanpa täysin eri näkökulmasta - mitä tapahtuu välimuistin sisällä oleville tiedoille?

Yksinkertaistetaan tilannetta ja oletetaan, että meillä on välimuisti, joka sopii vain yhdelle objektille. Tässä on esimerkki siitä, mitä tapahtuu, kun yritämme työskennellä välimuistia 1 kertaa suuremmalla tietomäärällä, meidän on:

1. Aseta lohko 1 välimuistiin
2. Poista lohko 1 välimuistista
3. Aseta lohko 2 välimuistiin
4. Poista lohko 2 välimuistista
5. Aseta lohko 3 välimuistiin

5 toimenpidettä suoritettu! Tätä tilannetta ei kuitenkaan voida kutsua normaaliksi, itse asiassa pakotamme HBasen tekemään joukon täysin turhaa työtä. Se lukee jatkuvasti tietoja käyttöjärjestelmän välimuistista, sijoittaa ne BlockCacheen, mutta heittää ne pois lähes välittömästi, koska uusi osa tiedoista on saapunut. Postauksen alun animaatio paljastaa ongelman ytimen - Roskakeräilijä menee skaalalta, ilmapiiri lämpenee, pieni Greta kaukaisessa ja kuumassa Ruotsissa järkyttyy. Ja me IT-ihmiset emme todellakaan pidä siitä, että lapset ovat surullisia, joten alamme miettiä, mitä voisimme tehdä asialle.

Entä jos et laita kaikkia lohkoja välimuistiin, vaan vain tietyn prosenttiosuuden niistä, jotta välimuisti ei vuoda yli? Aloitetaan lisäämällä vain muutama koodirivi toiminnon alkuun tietojen sijoittamiseksi BlockCacheen:

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

Asia on tässä seuraava: offset on lohkon sijainti tiedostossa ja sen viimeiset numerot ovat satunnaisesti ja tasaisesti jakautuneita 00:sta 99:ään. Siksi ohitamme vain ne, jotka kuuluvat tarvitsemamme alueelle.

Aseta esimerkiksi cacheDataBlockPercent = 20 ja katso mitä tapahtuu:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Tulos on ilmeinen. Alla olevista kaavioista käy selväksi, miksi tällainen kiihtyvyys tapahtui - säästämme paljon GC-resursseja tekemättä Sisypholaista työtä sijoittamalla tietoja välimuistiin vain heittääksemme sen välittömästi marsin koirien viemäriin:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Samaan aikaan suorittimen käyttöaste kasvaa, mutta on paljon vähemmän kuin tuottavuus:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

On myös syytä huomata, että BlockCacheen tallennetut lohkot ovat erilaisia. Suurin osa, noin 95 %, on itse dataa. Ja loput ovat metatietoja, kuten Bloom-suodattimet tai LEAF_INDEX ja т.д.. Nämä tiedot eivät riitä, mutta ovat erittäin hyödyllisiä, koska HBase kääntyy ennen tietojen suoraa pääsyä metaan ymmärtääkseen, onko tästä tarpeen etsiä lisää ja jos on, missä tarkalleen kiinnostava lohko sijaitsee.

Siksi koodissa näemme tarkistusehdon buf.getBlockType().isData() ja tämän metan ansiosta jätämme sen joka tapauksessa välimuistiin.

Lisätään nyt kuormitusta ja kiristetään ominaisuutta hieman yhdellä kertaa. Ensimmäisessä testissä teimme rajaprosentin = 20 ja BlockCache oli hieman vajaakäytössä. Asetetaan nyt arvoksi 23 % ja lisätään 100 säiettä 5 minuutin välein nähdäksesi missä vaiheessa kyllästyminen tapahtuu:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Tässä näemme, että alkuperäinen versio osuu lähes välittömästi kattoon noin 100 300 pyynnöllä sekunnissa. Laastari antaa kiihtyvyyden jopa XNUMX tuhatta. Samalla on selvää, että lisäkiihdytys ei ole enää niin "ilmaista", vaan myös prosessorin käyttöaste kasvaa.

Tämä ei kuitenkaan ole kovin tyylikäs ratkaisu, koska emme tiedä etukäteen, kuinka monta prosenttia lohkoista on tallennettava välimuistiin, se riippuu kuormitusprofiilista. Siksi otettiin käyttöön mekanismi tämän parametrin automaattiseksi säätämiseksi lukutoimintojen aktiivisuuden mukaan.

Kolme vaihtoehtoa on lisätty hallitsemaan tätä:

hbase.lru.cache.heavy.eviction.count.limit — määrittää, kuinka monta kertaa tietojen poistamisprosessin välimuistista tulee suorittaa ennen kuin aloitamme optimoinnin (eli lohkojen ohituksen). Oletuksena se on MAX_INT = 2147483647 ja tarkoittaa itse asiassa, että ominaisuus ei koskaan ala toimimaan tällä arvolla. Koska häätöprosessi alkaa 5 - 10 sekunnin välein (se riippuu kuormasta) ja 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 vuotta. Voimme kuitenkin asettaa tämän parametrin arvoon 0 ja saada ominaisuuden toimimaan heti käynnistyksen jälkeen.

Tässä parametrissa on kuitenkin myös hyötykuorma. Jos kuormituksemme on sellainen, että lyhytkestoiset (esim. päivällä) ja pitkäkestoiset (yöllä) lukemat ovat jatkuvasti välissä, voimme varmistaa, että ominaisuus on päällä vain pitkien lukutoimintojen ollessa käynnissä.

Tiedämme esimerkiksi, että lyhytkestoiset lukemat kestävät yleensä noin minuutin. Lohkoja ei tarvitse alkaa heittämään pois, välimuisti ei ehdi vanhentua ja sitten voimme asettaa tämän parametrin arvoksi esimerkiksi 1. Tämä johtaa siihen, että optimointi alkaa toimia vasta, kun pitkä- aikakausi aktiivinen lukeminen on alkanut, ts. 10 sekunnissa. Näin ollen, jos meillä on lyhytaikainen luku, kaikki lohkot menevät välimuistiin ja ovat käytettävissä (paitsi ne, jotka poistetaan vakioalgoritmilla). Ja kun teemme pitkäaikaisia ​​lukuja, ominaisuus kytketään päälle ja meillä olisi paljon parempi suorituskyky.

hbase.lru.cache.heavy.eviction.mb.size.limit — määrittää kuinka monta megatavua haluamme sijoittaa välimuistiin (ja tietysti häätää) 10 sekunnissa. Ominaisuus yrittää saavuttaa tämän arvon ja ylläpitää sitä. Asia on tämä: jos työnnämme gigatavuja välimuistiin, meidän on häätettävä gigatavuja, ja tämä, kuten yllä näimme, on erittäin kallista. Älä kuitenkaan yritä asettaa sitä liian pieneksi, koska tämä saa lohkon ohitustilan poistumaan ennenaikaisesti. Tehokkaille palvelimille (noin 20-40 fyysistä ydintä) on optimaalinen asettaa noin 300-400 Mt. Keskiluokalle (~10 ydintä) 200-300 MB. Heikoissa järjestelmissä (2-5 ydintä) 50-100 MB voi olla normaali (ei testattu näillä).

Katsotaan kuinka tämä toimii: oletetaan, että asetamme hbase.lru.cache.heavy.eviction.mb.size.limit = 500, on jonkinlainen kuorma (luku) ja sitten lasketaan noin 10 sekunnin välein kuinka monta tavua oli häädetään kaavalla:

Overhead = vapautettujen tavujen summa (MB) * 100 / raja (MB) - 100;

Jos itse asiassa 2000 MB häädettiin, Overhead on yhtä suuri kuin:

2000 * 100 / 500 - 100 = 300 %

Algoritmit yrittävät säilyttää enintään muutaman kymmenen prosentin, joten ominaisuus vähentää välimuistissa olevien lohkojen prosenttiosuutta ja toteuttaa siten automaattisen viritysmekanismin.

Jos kuorma kuitenkin putoaa, oletetaan, että vain 200 Mt häädetään ja Overhead muuttuu negatiiviseksi (ns. ylitys):

200 * 100 / 500 - 100 = -60 %

Päinvastoin, ominaisuus lisää välimuistissa olevien lohkojen prosenttiosuutta, kunnes Overhead muuttuu positiiviseksi.

Alla on esimerkki siitä, miltä tämä näyttää todellisessa datassa. Ei tarvitse yrittää saavuttaa 0%, se on mahdotonta. Se on erittäin hyvä, kun se on noin 30 - 100%, tämä auttaa välttämään ennenaikaista poistumista optimointitilasta lyhytaikaisten jännitteiden aikana.

hbase.lru.cache.heavy.eviction.overhead.coefficient — määrittää, kuinka nopeasti haluamme saada tuloksen. Jos tiedämme varmasti, että lukemamme ovat enimmäkseen pitkiä emmekä halua odottaa, voimme nostaa tätä suhdetta ja saada korkean suorituskyvyn nopeammin.

Esimerkiksi asetamme tämän kertoimen = 0.01. Tämä tarkoittaa, että Overhead (katso yllä) kerrotaan tällä luvulla saadulla tuloksella ja välimuistissa olevien lohkojen prosenttiosuutta pienennetään. Oletetaan, että Overhead = 300% ja kerroin = 0.01, niin välimuistissa olevien lohkojen prosenttiosuutta pienennetään 3%.

Samanlainen "vastapaine"-logiikka on toteutettu myös negatiivisille ylimääräisille arvoille. Koska lyhytaikaiset vaihtelut lukumäärissä ja häätöissä ovat aina mahdollisia, tämän mekanismin avulla voit välttää ennenaikaisen poistumisen optimointitilasta. Vastapaineella on käänteinen logiikka: mitä voimakkaampi ylitys, sitä enemmän lohkoja tallennetaan välimuistiin.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Käyttöönottokoodi

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

Katsotaanpa nyt kaikkea tätä todellisen esimerkin avulla. Meillä on seuraava testiskripti:

  1. Aloitetaan skannaus (25 säiettä, erä = 100)
  2. Lisää 5 minuutin kuluttua multi-gets (25 säiettä, erä = 100)
  3. 5 minuutin kuluttua sammuta multi-gets (vain skannaus jää uudelleen)

Suoritamme kaksi ajoa, ensin hbase.lru.cache.heavy.eviction.count.limit = 10000 (mikä itse asiassa poistaa ominaisuuden käytöstä), ja asetamme sitten rajan = 0 (ottaa sen käyttöön).

Alla olevista lokeista näemme, kuinka ominaisuus on otettu käyttöön ja nollaa Ylityksen arvoon 14-71%. Ajoittain kuormitus laskee, mikä ottaa Backpressure-toiminnon käyttöön ja HBase tallentaa taas lisää lohkoja välimuistiin.

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

Skannaukset tarvittiin näyttämään sama prosessi kaaviona kahden välimuistiosion välisestä suhteesta - yksittäinen (jossa lohkot, joita ei ole koskaan pyydetty ennen) ja useat (vähintään kerran "pyydetty" data tallennetaan tähän):

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Ja lopuksi, miltä parametrien toiminta näyttää kaavion muodossa. Vertailun vuoksi välimuisti sammutettiin alussa kokonaan, sitten HBase käynnistettiin välimuistilla ja viivästytti optimointityön alkamista 5 minuuttia (30 häätöjaksoa).

Täysi koodi löytyy Pull Requestista HBASE 23887 githubissa.

300 tuhatta lukua sekunnissa ei kuitenkaan ole kaikki, mitä tällä laitteistolla voidaan saavuttaa näissä olosuhteissa. Tosiasia on, että kun sinun on käytettävä tietoja HDFS:n kautta, käytetään ShortCircuitCache (jäljempänä SSC) -mekanismia, jonka avulla voit käyttää tietoja suoraan välttäen verkkovuorovaikutuksia.

Profilointi osoitti, että vaikka tämä mekanismi antaa suuren voiton, siitä tulee jossain vaiheessa myös pullonkaula, koska lähes kaikki raskaat toiminnot tapahtuvat lukon sisällä, mikä johtaa suurimman osan ajasta tukkeutumiseen.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Tämän tajuttuamme ymmärsimme, että ongelma voidaan kiertää luomalla joukko itsenäisiä SSC:itä:

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

Ja sitten työskentele niiden kanssa, pois lukien risteykset myös viimeisen siirtymänumeron kohdalla:

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

Nyt voit aloittaa testaamisen. Tätä varten luemme tiedostoja HDFS:stä yksinkertaisella monisäikeisellä sovelluksella. Aseta parametrit:

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 lue vain tiedostot:

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

Tämä koodi suoritetaan erillisissä säikeissä ja lisäämme samanaikaisesti luettavien tiedostojen määrää (10:stä 200:een - vaaka-akseli) ja välimuistien määrää (1:stä 10:een - grafiikkaa). Pystyakselilla näkyy kiihtyvyys, joka johtuu SSC:n kasvusta verrattuna tapaukseen, jossa on vain yksi välimuisti.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Kaavion lukeminen: Suoritusaika 100 tuhannelle lukukerralle 64 kt:n lohkoissa yhdellä välimuistilla vaatii 78 sekuntia. Viidellä välimuistilla se kestää 5 sekuntia. Nuo. on ~16-kertainen kiihtyvyys. Kuten kaaviosta näkyy, vaikutus ei ole kovin havaittavissa pienellä määrällä rinnakkaisia ​​lukuja, sillä se alkaa olla havaittavissa, kun säikeen lukuja on yli 5. On myös havaittavissa, että SSC:iden lukumäärä kasvaa 50:sta. ja edellä antavat huomattavasti pienemmän suorituskyvyn lisäyksen.

Huomautus 1: koska testitulokset ovat melko haihtuvia (katso alla), suoritettiin 3 ajoa ja saadut arvot laskettiin keskiarvoiksi.

Huomautus 2: Suorituskykyhyöty satunnaiskäytön määrittämisestä on sama, vaikka itse pääsy on hieman hitaampaa.

On kuitenkin tarpeen selventää, että toisin kuin HBasen tapauksessa, tämä kiihtyvyys ei ole aina ilmaista. Täällä "vapautamme" CPU:n kyvyn tehdä enemmän työtä sen sijaan, että roikkuisimme lukoissa.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Tässä voit havaita, että yleensä välimuistien määrän kasvu lisää prosessorin käyttöastetta suunnilleen verrannollisesti. Voittoyhdistelmiä on kuitenkin hieman enemmän.

Tarkastellaanpa esimerkiksi tarkemmin asetusta SSC = 3. Suorituskyvyn kasvu alueella on noin 3.3-kertainen. Alla on tulokset kaikista kolmesta erillisestä ajosta.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Vaikka suorittimen kulutus kasvaa noin 2.8 kertaa. Ero ei ole kovin suuri, mutta pieni Greta on jo onnellinen ja hänellä saattaa olla aikaa käydä koulua ja ottaa oppitunteja.

Näin ollen tällä on positiivinen vaikutus kaikkiin työkaluihin, jotka käyttävät joukkokäyttöä HDFS:ään (esimerkiksi Spark jne.), edellyttäen että sovelluskoodi on kevyt (eli pistoke on HDFS-asiakaspuolella) ja prosessorivirtaa on vapaata. . Tarkistaaksemme, mitä vaikutusta BlockCache-optimoinnin ja SSC-virityksen yhdistetty käytöllä HBase-lukua varten on.

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Voidaan nähdä, että näissä olosuhteissa vaikutus ei ole niin suuri kuin hienostuneissa testeissä (lukeminen ilman käsittelyä), mutta tästä on täysin mahdollista puristaa ylimääräinen 80K. Yhdessä molemmat optimoinnit tarjoavat jopa 4x nopeuden.

Tätä optimointia varten tehtiin myös PR [HDFS-15202], joka on yhdistetty ja tämä toiminto on saatavilla tulevissa julkaisuissa.

Ja lopuksi oli mielenkiintoista verrata samanlaisen laajasaraisen tietokannan, Cassandra ja HBase, lukusuorituskykyä.

Tätä varten käynnistimme tavallisen YCSB-kuormitustestausapuohjelman esiintymiä kahdesta isännästä (yhteensä 800 säiettä). Palvelinpuolella - 4 RegionServer- ja Cassandra-instanssia neljällä isännällä (ei niitä, joissa asiakkaat ovat käynnissä, jotta vältetään niiden vaikutus). Lukemat tulivat kokoisista taulukoista:

HBase – 300 Gt HDFS:llä (100 Gt puhdasta dataa)

Cassandra - 250 Gt (replikointikerroin = 3)

Nuo. äänenvoimakkuus oli suunnilleen sama (HBasessa hieman enemmän).

HBase-parametrit:

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

hbase.lru.cache.heavy.eviction.count.limit = 30 - tämä tarkoittaa, että laastari alkaa toimia 30 häädön jälkeen (noin 5 minuuttia)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — välimuistin ja häätöjen tavoitemäärä

YCSB-lokit jäsennettiin ja käännettiin Excel-kaavioiksi:

Kuinka lisätä lukunopeutta HBase-tiedostosta jopa 3-kertaiseksi ja HDFS:stä jopa 5-kertaiseksi

Kuten näet, nämä optimoinnit antavat mahdollisuuden verrata näiden tietokantojen suorituskykyä näissä olosuhteissa ja saavuttaa 450 tuhatta lukua sekunnissa.

Toivomme, että nämä tiedot voivat olla hyödyllisiä jollekulle jännittävän tuottavuustaistelun aikana.

Lähde: will.com

Lisää kommentti