Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Alta rendimento estas unu el la ĉefaj postuloj kiam oni laboras kun grandaj datumoj. En la fako pri ŝarĝo de datumoj ĉe Sberbank, ni pumpas preskaŭ ĉiujn transakciojn en nian Datuman Nubon bazitan en Hadoop kaj tial traktas vere grandajn fluojn de informoj. Kompreneble, ni ĉiam serĉas manierojn plibonigi rendimenton, kaj nun ni volas rakonti al vi kiel ni sukcesis fliki RegionServer HBase kaj la klienton HDFS, dank' al kiu ni povis signife pliigi la rapidecon de legado de operacioj.
Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Tamen, antaŭ ol pluiri al la esenco de la plibonigoj, indas paroli pri limigoj, kiuj principe ne povas esti evititaj se vi sidas sur HDD.

Kial HDD kaj rapida Hazarda Aliro legas estas malkongruaj
Kiel vi scias, HBase, kaj multaj aliaj datumbazoj, stokas datumojn en blokoj de pluraj dekoj da kilobajtoj en grandeco. Defaŭlte ĝi estas proksimume 64 KB. Nun ni imagu, ke ni bezonas akiri nur 100 bajtojn kaj ni petas HBase doni al ni ĉi tiujn datumojn uzante certan ŝlosilon. Ĉar la blokgrandeco en HFiles estas 64 KB, la peto estos 640 fojojn pli granda (nur minuto!) ol necesa.

Poste, ĉar la peto iros tra HDFS kaj ĝia metadatuma kaŝmemormekanismo ShortCircuitCache (kiu permesas rektan aliron al dosieroj), tio kondukas al legi jam 1 MB de la disko. Tamen, ĉi tio povas esti ĝustigita per la parametro dfs.client.read.shortcircuit.buffer.size kaj en multaj kazoj estas senco redukti ĉi tiun valoron, ekzemple al 126 KB.

Ni diru, ke ni faras tion, sed krome, kiam ni komencas legi datumojn per la java api, kiel funkcioj kiel FileChannel.read kaj petas la operaciumon legi la specifitan kvanton da datumoj, ĝi legas "ĉiaokaze" 2 fojojn pli. , t.e. 256 KB en nia kazo. Ĉi tio estas ĉar java ne havas facilan manieron agordi la flagon FADV_RANDOM por malhelpi ĉi tiun konduton.

Kiel rezulto, por akiri niajn 100 bajtojn, 2600 fojojn pli estas legita sub la kapuĉo. Ŝajnus, ke la solvo estas evidenta, ni reduktu la blokgrandecon al kilobajto, starigu la menciitan flagon kaj akiru grandan klerig-akcelon. Sed la problemo estas, ke reduktante la blokgrandecon je 2 fojojn, ni ankaŭ reduktas la nombron da bajtoj legitaj po unuo de tempo je 2 fojojn.

Iom da gajno de agordo de la flago FADV_RANDOM povas esti akirita, sed nur kun alta multfadenado kaj kun blokgrandeco de 128 KB, sed ĉi tio estas maksimume kelkaj dekoj da procentoj:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Testoj estis faritaj sur 100 dosieroj, ĉiu 1 GB en grandeco kaj situanta sur 10 HDD-oj.

Ni kalkulu, pri kio ni principe povas kalkuli je ĉi tiu rapido:
Ni diru, ke ni legas el 10 diskoj kun rapideco de 280 MB/sec, t.e. 3 milionoj oble 100 bajtoj. Sed kiel ni memoras, la datumoj, kiujn ni bezonas, estas 2600 fojojn malpli ol tio, kion oni legas. Tiel, ni dividas 3 milionojn per 2600 kaj ricevas 1100 rekordoj sekundo.

Deprima, ĉu ne? Tio estas la naturo Hazarda Aliro aliro al datumoj sur la HDD - sendepende de la blokgrandeco. Ĉi tiu estas la fizika limo de hazarda aliro kaj neniu datumbazo povas elpremi pli sub tiaj kondiĉoj.

Kiel do datumbazoj atingas multe pli altajn rapidojn? Por respondi ĉi tiun demandon, ni rigardu, kio okazas en la sekva bildo:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Ĉi tie ni vidas, ke dum la unuaj minutoj la rapideco estas vere ĉirkaŭ mil rekordoj sekundo. Tamen, plue, pro tio, ke oni legas multe pli ol oni petis, la datumoj finiĝas en la sablo/kaŝmemoro de la operaciumo (linukso) kaj la rapido pliiĝas al pli deca 60 mil sekundo.

Tiel, plue ni traktos akceli aliron nur al la datumoj kiuj estas en la OS-kaŝmemoro aŭ situantaj en SSD/NVMe-stokaj aparatoj kun komparebla alirrapideco.

En nia kazo, ni faros testojn sur benko de 4 serviloj, ĉiu el kiuj estas ŝargita jene:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 fadenoj.
Memoro: 730 GB.
java versio: 1.8.0_111

Kaj ĉi tie la ŝlosila punkto estas la kvanto da datumoj en la tabeloj, kiujn oni devas legi. La fakto estas, ke se vi legas datumojn de tabelo, kiu estas tute metita en la kaŝmemoron HBase, tiam ĝi eĉ ne venos al legado de la sablo/kaŝmemoro de la operaciumo. Ĉar HBase defaŭlte asignas 40% de memoro al strukturo nomita BlockCache. Esence ĉi tio estas ConcurrentHashMap, kie la ŝlosilo estas la dosiernomo + ofseto de la bloko, kaj la valoro estas la reala datumo ĉe ĉi tiu ofseto.

Tiel, legante nur el ĉi tiu strukturo, ni ni vidas bonegan rapidecon, kiel miliono da petoj sekundo. Sed ni imagu, ke ni ne povas asigni centojn da gigabajtoj da memoro nur por datumbazaj bezonoj, ĉar ekzistas multaj aliaj utilaj aferoj kurantaj sur ĉi tiuj serviloj.

Ekzemple, en nia kazo, la volumo de BlockCache sur unu RS estas ĉirkaŭ 12 GB. Ni surterigis du RS sur unu nodo, t.e. 96 GB estas asignitaj por BlockCache sur ĉiuj nodoj. Kaj estas multoble pli da datumoj, ekzemple, estu 4 tabeloj, po 130 regionoj, en kiuj dosieroj havas grandecon de 800 MB, kunpremitaj de FAST_DIFF, t.e. entute 410 GB (ĉi tio estas pura datumo, t.e. sen konsideri la reproduktan faktoron).

Tiel, BlockCache estas nur ĉirkaŭ 23% de la totala datumvolumo kaj ĉi tio estas multe pli proksima al la realaj kondiĉoj de tio, kio nomiĝas BigData. Kaj ĉi tie komenciĝas la amuzo - ĉar evidente, ju malpli da kaŝmemortrafoj, des pli malbona la agado. Finfine, se vi maltrafas, vi devos fari multe da laboro - t.e. malsupreniru al vokado de sistemaj funkcioj. Tamen tio ne povas esti evitita, do ni rigardu tute alian aspekton - kio okazas al la datumoj ene de la kaŝmemoro?

Ni simpligu la situacion kaj supozu, ke ni havas kaŝmemoron, kiu taŭgas nur por 1 objekto. Jen ekzemplo de kio okazos kiam ni provos labori kun datumvolumo 3 fojojn pli granda ol la kaŝmemoro, ni devos:

1. Metu blokon 1 en kaŝmemoron
2. Forigu blokon 1 el kaŝmemoro
3. Metu blokon 2 en kaŝmemoron
4. Forigu blokon 2 el kaŝmemoro
5. Metu blokon 3 en kaŝmemoron

5 agoj plenumitaj! Tamen, ĉi tiu situacio ne povas esti nomata normala; fakte, ni devigas HBase fari amason da tute senutilaj laboroj. Ĝi konstante legas datumojn de la OS-kaŝmemoro, metas ĝin en BlockCache, nur por forĵeti ĝin preskaŭ tuj ĉar nova parto de datumoj alvenis. La animacio komence de la afiŝo montras la esencon de la problemo - Garbage Collector malgrandiĝas, la atmosfero varmiĝas, la malgranda Greta en malproksima kaj varma Svedio ĉagreniĝas. Kaj al ni IT homoj vere ne ŝatas, kiam infanoj estas malĝojaj, do ni komencas pensi pri tio, kion ni povas fari pri ĝi.

Kio se vi metas ne ĉiujn blokojn en la kaŝmemoron, sed nur certan procenton de ili, por ke la kaŝmemoro ne superfluu? Ni komencu simple aldonante nur kelkajn liniojn de kodo al la komenco de la funkcio por meti datumojn en BlockCache:

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

La punkto ĉi tie estas la sekva: ofseto estas la pozicio de la bloko en la dosiero kaj ĝiaj lastaj ciferoj estas hazarde kaj egale distribuitaj de 00 ĝis 99. Tial ni transsaltos nur tiujn, kiuj falas en la gamon, kiun ni bezonas.

Ekzemple, agordu cacheDataBlockPercent = 20 kaj vidu, kio okazas:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

La rezulto estas evidenta. En la malsupraj grafikaĵoj, evidentiĝas kial tia akcelo okazis - ni ŝparas multajn GC-resursojn sen fari la Sisyphean-laboron meti datumojn en la kaŝmemoron nur por tuj ĵeti ĝin al la drenilo de la marshundoj:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Samtempe, CPU-uzado pliiĝas, sed estas multe malpli ol produktiveco:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Ankaŭ indas rimarki, ke la blokoj stokitaj en BlockCache estas malsamaj. Plejparto, ĉirkaŭ 95%, estas datumoj mem. Kaj la resto estas metadatenoj, kiel Bloom-filtriloj aŭ LEAF_INDEX kaj ktp.. Ĉi tiuj datumoj ne sufiĉas, sed ĝi estas tre utila, ĉar antaŭ ol rekte aliri la datumojn, HBase turnas sin al la meta por kompreni ĉu necesas serĉi ĉi tie plu kaj, se jes, kie ĝuste troviĝas la interesa bloko.

Tial, en la kodo ni vidas kontrolon kondiĉon buf.getBlockType().isData() kaj danke al ĉi tiu meta, ni lasos ĝin en la kaŝmemoro ĉiukaze.

Nun ni pliigu la ŝarĝon kaj iomete streĉu la funkcion unufoje. En la unua testo ni faris la tranĉprocenton = 20 kaj BlockCache estis iomete subuzita. Nun ni agordu ĝin al 23% kaj aldonu 100 fadenojn ĉiujn 5 minutojn por vidi, en kiu punkto okazas saturiĝo:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Ĉi tie ni vidas, ke la originala versio preskaŭ tuj trafas la plafonon je ĉirkaŭ 100 mil petoj sekundo. Dum la diakilo donas akcelon de ĝis 300 mil. Samtempe, estas klare, ke plua akcelo ne plu estas tiel "senpaga"; CPU-uzado ankaŭ pliiĝas.

Tamen, ĉi tio ne estas tre eleganta solvo, ĉar ni ne scias antaŭe, kian procenton da blokoj devas esti konservitaj en kaŝmemoro, ĝi dependas de la ŝarĝa profilo. Sekve, mekanismo estis efektivigita por aŭtomate ĝustigi ĉi tiun parametron depende de la aktiveco de legado de operacioj.

Tri opcioj estis aldonitaj por kontroli ĉi tion:

hbase.lru.cache.heavy.eviction.count.limit — fiksas kiom da fojoj la procezo de elpelado de datumoj de la kaŝmemoro devus funkcii antaŭ ol ni ekuzi optimumigon (t.e. preterpasi blokojn). Defaŭlte ĝi estas egala al MAX_INT = 2147483647 kaj fakte signifas, ke la funkcio neniam komencos funkcii kun ĉi tiu valoro. Ĉar la elpela procezo komenciĝas ĉiujn 5 - 10 sekundojn (dependas de la ŝarĝo) kaj 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 jaroj. Tamen, ni povas agordi ĉi tiun parametron al 0 kaj igi la funkcion funkcii tuj post lanĉo.

Tamen, estas ankaŭ utila ŝarĝo en ĉi tiu parametro. Se nia ŝarĝo estas tia, ke mallongdaŭraj legaĵoj (diru tage) kaj longdaŭraj (nokte) estas konstante intermetitaj, tiam ni povas certigi, ke la funkcio estas ŝaltita nur kiam longaj legadoj okazas.

Ekzemple, ni scias, ke mallongdaŭraj legadoj kutime daŭras ĉirkaŭ 1 minuton. Ne necesas komenci elĵeti blokojn, la kaŝmemoro ne havos tempon por malmoderniĝi kaj tiam ni povas agordi ĉi tiun parametron egala al, ekzemple, 10. Ĉi tio kondukos al la fakto, ke la optimumigo komencos funkcii nur kiam longe- termino aktiva legado komenciĝis, t.e. en 100 sekundoj. Tiel, se ni havas mallongdaŭran legadon, tiam ĉiuj blokoj iros en la kaŝmemoron kaj estos disponeblaj (krom tiuj, kiuj estos elmetitaj de la norma algoritmo). Kaj kiam ni faras longdaŭrajn legadojn, la funkcio estas ŝaltita kaj ni havus multe pli altan rendimenton.

hbase.lru.cache.heavy.eviction.mb.size.lim — fiksas kiom da megabajtoj ni ŝatus meti en la kaŝmemoron (kaj, kompreneble, forpeli) en 10 sekundoj. La funkcio provos atingi ĉi tiun valoron kaj konservi ĝin. La afero estas jena: se ni enŝovas gigabajtojn en la kaŝmemoron, tiam ni devos elpeli gigabajtojn, kaj ĉi tio, kiel ni vidis supre, estas tre multekosta. Tamen, vi ne provu agordi ĝin tro malgranda, ĉar ĉi tio kaŭzos la eliro antaŭtempe de la bloka saltreĝimo. Por potencaj serviloj (ĉirkaŭ 20-40 fizikaj kernoj), estas optimuma agordi ĉirkaŭ 300-400 MB. Por meza klaso (~10 kernoj) 200-300 MB. Por malfortaj sistemoj (2-5 kernoj) 50-100 MB povas esti normalaj (ne provitaj sur ĉi tiuj).

Ni rigardu kiel tio funkcias: ni diru, ke ni agordas hbase.lru.cache.heavy.eviction.mb.size.limit = 500, estas ia ŝarĝo (legado) kaj tiam ĉiujn ~10 sekundojn ni kalkulas kiom da bajtoj estis forpelita uzante la formulon:

Supra = Liberigitaj Bitoj Sumo (MB) * 100 / Limo (MB) - 100;

Se fakte 2000 MB estis forpelitaj, tiam Overhead egalas al:

2000 * 100 / 500 - 100 = 300%

La algoritmoj provas konservi ne pli ol kelkajn dekojn da procentoj, do la funkcio reduktos la procenton de kaŝmemoritaj blokoj, tiel efektivigante aŭtomatan agordan mekanismon.

Tamen, se la ŝarĝo falas, ni diru, ke nur 200 MB estas forpelitaj kaj Overhead fariĝas negativa (la tiel nomata superfluo):

200 * 100 / 500 - 100 = -60%

Male, la funkcio pliigos la procenton de kaŝmemoritaj blokoj ĝis Overhead fariĝos pozitiva.

Malsupre estas ekzemplo de kiel tio aspektas sur realaj datumoj. Ne necesas provi atingi 0%, estas neeble. Ĝi estas tre bona kiam ĝi estas ĉirkaŭ 30 - 100%, ĉi tio helpas eviti antaŭtempan eliron de la optimumiga reĝimo dum mallongdaŭraj ŝprucoj.

hbase.lru.cache.heavy.eviction.overhead.koeficiento — fiksas kiom rapide ni ŝatus ricevi la rezulton. Se ni certe scias, ke niaj legadoj estas plejparte longaj kaj ne volas atendi, ni povas pliigi ĉi tiun proporcion kaj akiri altan rendimenton pli rapide.

Ekzemple, ni starigas ĉi tiun koeficienton = 0.01. Ĉi tio signifas, ke Overhead (vidu supre) estos multobligita per ĉi tiu nombro per la rezulta rezulto kaj la procento de kaŝmemoritaj blokoj estos reduktita. Ni supozu, ke Overhead = 300% kaj koeficiento = 0.01, tiam la procento de kaŝmemoritaj blokoj estos reduktita je 3%.

Simila "Kontropremo-" logiko ankaŭ estas efektivigita por negativaj Superkopaj (superpremaj) valoroj. Ĉar mallongdaŭraj fluktuoj en la volumo de legadoj kaj eldomigoj ĉiam eblas, ĉi tiu mekanismo ebligas al vi eviti antaŭtempan eliron de la optimumiga reĝimo. Kontraŭpremo havas inversan logikon: ju pli forta la superpado, des pli da blokoj estas kaŝitaj.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Efektiviga kodo

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

Ni nun rigardu ĉion ĉi uzante realan ekzemplon. Ni havas la sekvan testan skripton:

  1. Ni komencu fari Skanadon (25 fadenoj, aro = 100)
  2. Post 5 minutoj, aldonu multobjektojn (25 fadenoj, aro = 100)
  3. Post 5 minutoj, malŝaltu multobjektojn (nur skanado restas denove)

Ni faras du kurojn, unue hbase.lru.cache.heavy.eviction.count.limit = 10000 (kiu efektive malŝaltas la funkcion), kaj poste fiksas limon = 0 (ebligas ĝin).

En la subaj protokoloj ni vidas kiel la funkcio estas ŝaltita kaj restarigas Overshooting al 14-71%. De tempo al tempo la ŝarĝo malpliiĝas, kio ŝaltas Backpressure kaj HBase kaŝmemorigas pli da blokoj denove.

Log RegionServer
elpelita (MB): 0, rilatumo 0.0, supra kosto (%): -100, peza eldomigo nombrilo: 0, nuna kaŝmemoro DataBlock (%): 100
elpelita (MB): 0, rilatumo 0.0, supra kosto (%): -100, peza eldomigo nombrilo: 0, nuna kaŝmemoro DataBlock (%): 100
elpelita (MB): 2170, proporcio 1.09, superkompeto (%): 985, peza eldomigo nombrilo: 1, nuna kaŝmemoro DataBlock (%): 91 < komenco
elpelita (MB): 3763, rilatumo 1.08, superkapo (%): 1781, peza eldomigo nombrilo: 2, nuna kaŝmemoro DataBlock (%): 76
elpelita (MB): 3306, rilatumo 1.07, superkapo (%): 1553, peza eldomigo nombrilo: 3, nuna kaŝmemoro DataBlock (%): 61
elpelita (MB): 2508, rilatumo 1.06, superkapo (%): 1154, peza eldomigo nombrilo: 4, nuna kaŝmemoro DataBlock (%): 50
elpelita (MB): 1824, rilatumo 1.04, superkapo (%): 812, peza eldomigo nombrilo: 5, nuna kaŝmemoro DataBlock (%): 42
elpelita (MB): 1482, rilatumo 1.03, superkapo (%): 641, peza eldomigo nombrilo: 6, nuna kaŝmemoro DataBlock (%): 36
elpelita (MB): 1140, rilatumo 1.01, superkapo (%): 470, peza eldomigo nombrilo: 7, nuna kaŝmemoro DataBlock (%): 32
elpelita (MB): 913, rilatumo 1.0, superkapo (%): 356, peza eldomigo nombrilo: 8, nuna kaŝmemoro DataBlock (%): 29
elpelita (MB): 912, rilatumo 0.89, superkapo (%): 356, peza eldomigo nombrilo: 9, nuna kaŝmemoro DataBlock (%): 26
elpelita (MB): 684, rilatumo 0.76, superkapo (%): 242, peza eldomigo nombrilo: 10, nuna kaŝmemoro DataBlock (%): 24
elpelita (MB): 684, rilatumo 0.61, superkapo (%): 242, peza eldomigo nombrilo: 11, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 456, rilatumo 0.51, superkapo (%): 128, peza eldomigo nombrilo: 12, nuna kaŝmemoro DataBlock (%): 21
elpelita (MB): 456, rilatumo 0.42, superkapo (%): 128, peza eldomigo nombrilo: 13, nuna kaŝmemoro DataBlock (%): 20
elpelita (MB): 456, rilatumo 0.33, superkapo (%): 128, peza eldomigo nombrilo: 14, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 15, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 342, rilatumo 0.32, superkapo (%): 71, peza eldomigo nombrilo: 16, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 342, rilatumo 0.31, superkapo (%): 71, peza eldomigo nombrilo: 17, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.3, superkapo (%): 14, peza eldomigo nombrilo: 18, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.29, superkapo (%): 14, peza eldomigo nombrilo: 19, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.27, superkapo (%): 14, peza eldomigo nombrilo: 20, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.25, superkapo (%): 14, peza eldomigo nombrilo: 21, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.24, superkapo (%): 14, peza eldomigo nombrilo: 22, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.22, superkapo (%): 14, peza eldomigo nombrilo: 23, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.21, superkapo (%): 14, peza eldomigo nombrilo: 24, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.2, superkapo (%): 14, peza eldomigo nombrilo: 25, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 228, rilatumo 0.17, superkapo (%): 14, peza eldomigo nombrilo: 26, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 456, proporcio 0.17, superkompeto (%): 128, peza eldomigo nombrilo: 27, nuna kaŝmemoro DataBlock (%): 18 < aldonitaj gets (sed tablo sama)
elpelita (MB): 456, rilatumo 0.15, superkapo (%): 128, peza eldomigo nombrilo: 28, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 342, rilatumo 0.13, superkapo (%): 71, peza eldomigo nombrilo: 29, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 342, rilatumo 0.11, superkapo (%): 71, peza eldomigo nombrilo: 30, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 342, rilatumo 0.09, superkapo (%): 71, peza eldomigo nombrilo: 31, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.08, superkapo (%): 14, peza eldomigo nombrilo: 32, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.07, superkapo (%): 14, peza eldomigo nombrilo: 33, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.06, superkapo (%): 14, peza eldomigo nombrilo: 34, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.05, superkapo (%): 14, peza eldomigo nombrilo: 35, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.05, superkapo (%): 14, peza eldomigo nombrilo: 36, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 228, rilatumo 0.04, superkapo (%): 14, peza eldomigo nombrilo: 37, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 109, rilatumo 0.04, supra (%): -46, peza eldomigo nombrilo: 37, nuna kaŝmemoro DataBlock (%): 22 < malantaŭa premo
elpelita (MB): 798, rilatumo 0.24, superkapo (%): 299, peza eldomigo nombrilo: 38, nuna kaŝmemoro DataBlock (%): 20
elpelita (MB): 798, rilatumo 0.29, superkapo (%): 299, peza eldomigo nombrilo: 39, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 570, rilatumo 0.27, superkapo (%): 185, peza eldomigo nombrilo: 40, nuna kaŝmemoro DataBlock (%): 17
elpelita (MB): 456, rilatumo 0.22, superkapo (%): 128, peza eldomigo nombrilo: 41, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 342, rilatumo 0.16, superkapo (%): 71, peza eldomigo nombrilo: 42, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 342, rilatumo 0.11, superkapo (%): 71, peza eldomigo nombrilo: 43, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 228, rilatumo 0.09, superkapo (%): 14, peza eldomigo nombrilo: 44, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 228, rilatumo 0.07, superkapo (%): 14, peza eldomigo nombrilo: 45, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 228, rilatumo 0.05, superkapo (%): 14, peza eldomigo nombrilo: 46, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 222, rilatumo 0.04, superkapo (%): 11, peza eldomigo nombrilo: 47, nuna kaŝmemoro DataBlock (%): 16
elpelita (MB): 104, proporcio 0.03, superkompeto (%): -48, peza eldomigo nombrilo: 47, nuna kaŝmemoro DataBlock (%): 21 < interrompo ricevas
elpelita (MB): 684, rilatumo 0.2, superkapo (%): 242, peza eldomigo nombrilo: 48, nuna kaŝmemoro DataBlock (%): 19
elpelita (MB): 570, rilatumo 0.23, superkapo (%): 185, peza eldomigo nombrilo: 49, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 342, rilatumo 0.22, superkapo (%): 71, peza eldomigo nombrilo: 50, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 228, rilatumo 0.21, superkapo (%): 14, peza eldomigo nombrilo: 51, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 228, rilatumo 0.2, superkapo (%): 14, peza eldomigo nombrilo: 52, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 228, rilatumo 0.18, superkapo (%): 14, peza eldomigo nombrilo: 53, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 228, rilatumo 0.16, superkapo (%): 14, peza eldomigo nombrilo: 54, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 228, rilatumo 0.14, superkapo (%): 14, peza eldomigo nombrilo: 55, nuna kaŝmemoro DataBlock (%): 18
elpelita (MB): 112, rilatumo 0.14, supra (%): -44, peza eldomigo nombrilo: 55, nuna kaŝmemoro DataBlock (%): 23 < malantaŭa premo
elpelita (MB): 456, rilatumo 0.26, superkapo (%): 128, peza eldomigo nombrilo: 56, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.31, superkapo (%): 71, peza eldomigo nombrilo: 57, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 58, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 59, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 60, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 61, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 62, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 63, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.32, superkapo (%): 71, peza eldomigo nombrilo: 64, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 65, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 66, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.32, superkapo (%): 71, peza eldomigo nombrilo: 67, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 68, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.32, superkapo (%): 71, peza eldomigo nombrilo: 69, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.32, superkapo (%): 71, peza eldomigo nombrilo: 70, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 71, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 72, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 73, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 74, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 75, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 342, rilatumo 0.33, superkapo (%): 71, peza eldomigo nombrilo: 76, nuna kaŝmemoro DataBlock (%): 22
elpelita (MB): 21, rilatumo 0.33, supra kosto (%): -90, peza eldomigo nombrilo: 76, nuna kaŝmemoro DataBlock (%): 32
elpelita (MB): 0, rilatumo 0.0, supra kosto (%): -100, peza eldomigo nombrilo: 0, nuna kaŝmemoro DataBlock (%): 100
elpelita (MB): 0, rilatumo 0.0, supra kosto (%): -100, peza eldomigo nombrilo: 0, nuna kaŝmemoro DataBlock (%): 100

La skanadoj estis bezonataj por montri la saman procezon en la formo de grafeo de la rilato inter du kaŝmemorsekcioj - unuopaj (kie blokoj kiuj neniam estis petitaj antaŭe) kaj multnombraj (datumoj "petitaj" almenaŭ unufoje estas stokitaj ĉi tie):

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Kaj fine, kiel aspektas la funkciado de la parametroj en formo de grafeo. Por komparo, la kaŝmemoro estis tute malŝaltita komence, tiam HBase estis lanĉita kun kaŝmemoro kaj prokrastado de la komenco de optimumigo laboro je 5 minutoj (30 eldomigo cikloj).

Plena kodo troveblas en Pull Request HBASE 23887 sur github.

Tamen, 300 mil legoj sekundo ne estas ĉio, kio povas esti atingita sur ĉi tiu aparataro sub ĉi tiuj kondiĉoj. La fakto estas, ke kiam vi bezonas aliri datumojn per HDFS, la mekanismo ShortCircuitCache (ĉi-poste nomata SSC) estas uzata, kiu ebligas al vi rekte aliri la datumojn, evitante retajn interagojn.

Profilado montris, ke kvankam ĉi tiu mekanismo donas grandan gajnon, ĝi ankaŭ iam fariĝas botelo, ĉar preskaŭ ĉiuj pezaj operacioj okazas ene de seruro, kio kondukas al blokado plejofte.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Rimarkinte tion, ni rimarkis, ke la problemo povas esti evitita kreante aron de sendependaj SSC-oj:

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

Kaj poste laboru kun ili, ekskludante intersekciĝojn ankaŭ ĉe la lasta kompensa cifero:

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

Nun vi povas komenci testi. Por fari tion, ni legos dosierojn de HDFS per simpla plurfadena aplikaĵo. Agordu la parametrojn:

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

Kaj nur legu la dosierojn:

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

Ĉi tiu kodo estas ekzekutita en apartaj fadenoj kaj ni pliigos la nombron da samtempe legitaj dosieroj (de 10 ĝis 200 - horizontala akso) kaj la nombron da kaŝmemoroj (de 1 ĝis 10 - grafikaĵoj). La vertikala akso montras la akcelon kiu rezultas de pliiĝo en SSC relative al la kazo kiam ekzistas nur unu kaŝmemoro.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Kiel legi la grafeon: La ekzekuttempo por 100 mil legaĵoj en 64 KB-blokoj kun unu kaŝmemoro postulas 78 sekundojn. Dum kun 5 kaŝmemoroj ĝi daŭras 16 sekundojn. Tiuj. estas akcelo de ~5 fojojn. Kiel videblas el la grafeo, la efiko ne estas tre rimarkebla por malgranda nombro da paralelaj legadoj, ĝi komencas ludi rimarkindan rolon kiam estas pli ol 50 fadenlegadoj.Estas ankaŭ rimarkebla, ke pliigante la nombron da SSC-oj de 6. kaj supre donas signife pli malgrandan rendimentopliiĝon.

Noto 1: ĉar la testrezultoj estas sufiĉe volatilaj (vidu sube), 3 kuroj estis faritaj kaj la rezultaj valoroj estis averaĝigitaj.

Noto 2: La rendimenta gajno de agordo de hazarda aliro estas la sama, kvankam la aliro mem estas iomete pli malrapida.

Tamen, necesas klarigi, ke, male al la kazo kun HBase, ĉi tiu akcelo ne ĉiam estas senpaga. Ĉi tie ni "malŝlosas" la kapablon de la CPU fari pli laboron, anstataŭ pendi de seruroj.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Ĉi tie vi povas observi ke, ĝenerale, pliiĝo en la nombro da kaŝmemoroj donas proksimume proporcian pliiĝon en CPU-uzado. Tamen, estas iomete pli da venkaj kombinaĵoj.

Ekzemple, ni rigardu pli detale la agordon SSC = 3. La pliiĝo de rendimento sur la gamo estas ĉirkaŭ 3.3 fojojn. Malsupre estas la rezultoj de ĉiuj tri apartaj kuroj.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Dum CPU-konsumo pliiĝas ĉirkaŭ 2.8 fojojn. La diferenco ne estas tre granda, sed la eta Greta jam estas feliĉa kaj eble havas tempon por ĉeesti lernejon kaj lecioni.

Tiel, ĉi tio havos pozitivan efikon por iu ajn ilo kiu uzas amasan aliron al HDFS (ekzemple Spark, ktp.), kondiĉe ke la aplika kodo estas malpeza (t.e. la ŝtopilo estas ĉe la HDFS-klientflanko) kaj ekzistas senpaga CPU-potenco. . Por kontroli, ni provu kian efikon havos la kombinita uzo de BlockCache-optimumigo kaj SSC-agordado por legado de HBase.

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Videblas, ke en tiaj kondiĉoj la efiko ne estas tiel granda kiel en rafinitaj testoj (legado sen ajna prilaborado), sed estas tute eble elpremi pliajn 80K ĉi tie. Kune, ambaŭ optimumigoj provizas ĝis 4x plirapidigon.

PR ankaŭ estis farita por ĉi tiu optimumigo [HDFS-15202], kiu estis kunfandita kaj ĉi tiu funkcio estos disponebla en estontaj eldonoj.

Kaj fine, estis interese kompari la legadon de simila larĝa kolumna datumbazo, Cassandra kaj HBase.

Por fari tion, ni lanĉis ekzemplojn de la norma YCSB-ŝarĝa testado de du gastigantoj (800 fadenoj entute). Ĉe la servilo - 4 okazoj de RegionServer kaj Cassandra sur 4 gastigantoj (ne tiuj, kie la klientoj funkcias, por eviti ilian influon). Legoj venis de tabeloj de grandeco:

HBase - 300 GB sur HDFS (100 GB puraj datumoj)

Cassandra - 250 GB (reprodukta faktoro = 3)

Tiuj. la volumo estis proksimume sama (en HBase iom pli).

HBase-parametroj:

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

hbase.lru.cache.heavy.eviction.count.limit = 30 - tio signifas, ke la flikaĵo komencos funkcii post 30 eldomigoj (~5 minutoj)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — cela volumo de kaŝmemoro kaj eldomigo

YCSB-protokoloj estis analizitaj kaj kompilitaj en Excel-grafojn:

Kiel pliigi legan rapidon de HBase ĝis 3 fojojn kaj de HDFS ĝis 5 fojojn

Kiel vi povas vidi, ĉi tiuj optimumigoj ebligas kompari la agadon de ĉi tiuj datumbazoj sub ĉi tiuj kondiĉoj kaj atingi 450 mil legadojn por sekundo.

Ni esperas, ke ĉi tiu informo povas esti utila al iu dum la ekscita lukto por produktiveco.

fonto: www.habr.com

Aldoni komenton