Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Ang mataas na pagganap ay isa sa mga pangunahing kinakailangan kapag nagtatrabaho sa malaking data. Sa departamento ng paglo-load ng data sa Sberbank, inilalagay namin ang halos lahat ng mga transaksyon sa aming Data Cloud na nakabatay sa Hadoop at samakatuwid ay humaharap sa talagang malalaking daloy ng impormasyon. Naturally, palagi kaming naghahanap ng mga paraan para mapahusay ang performance, at ngayon ay gusto naming sabihin sa iyo kung paano namin nagawang i-patch ang RegionServer HBase at ang HDFS client, salamat sa kung saan nagawa naming makabuluhang taasan ang bilis ng mga operasyon sa pagbasa.
Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Gayunpaman, bago lumipat sa kakanyahan ng mga pagpapabuti, sulit na pag-usapan ang tungkol sa mga paghihigpit na, sa prinsipyo, ay hindi maiiwasan kung nakaupo ka sa isang HDD.

Bakit hindi tugma ang HDD at mabilis na Random Access na pagbabasa
Tulad ng alam mo, ang HBase, at maraming iba pang mga database, ay nag-iimbak ng data sa mga bloke ng ilang sampu-sampung kilobytes ang laki. Bilang default, ito ay humigit-kumulang 64 KB. Ngayon isipin natin na kailangan lang nating makakuha ng 100 byte at hinihiling namin sa HBase na ibigay sa amin ang data na ito gamit ang isang partikular na key. Dahil ang laki ng block sa HFiles ay 64 KB, ang kahilingan ay magiging 640 beses na mas malaki (isang minuto lang!) kaysa sa kinakailangan.

Susunod, dahil dadaan ang kahilingan sa HDFS at ang mekanismo ng pag-cache ng metadata nito ShortCircuitCache (na nagbibigay-daan sa direktang pag-access sa mga file), humahantong ito sa pagbabasa ng 1 MB na mula sa disk. Gayunpaman, ito ay maaaring iakma sa parameter dfs.client.read.shortcircuit.buffer.size at sa maraming kaso, makatuwirang bawasan ang halagang ito, halimbawa sa 126 KB.

Sabihin nating ginagawa natin ito, ngunit bilang karagdagan, kapag sinimulan nating basahin ang data sa pamamagitan ng java api, tulad ng mga function tulad ng FileChannel.read at hilingin sa operating system na basahin ang tinukoy na dami ng data, ito ay nagbabasa ng "kung sakali" nang 2 beses pa. , ibig sabihin. 256 KB sa aming kaso. Ito ay dahil ang java ay walang madaling paraan upang itakda ang FADV_RANDOM flag upang maiwasan ang pag-uugaling ito.

Bilang resulta, upang makuha ang aming 100 byte, 2600 beses na mas marami ang binabasa sa ilalim ng hood. Tila malinaw na ang solusyon, bawasan natin ang laki ng bloke sa isang kilobyte, itakda ang nabanggit na bandila at makakuha ng mahusay na pagpabilis ng paliwanag. Ngunit ang problema ay sa pamamagitan ng pagbabawas ng laki ng block ng 2 beses, binabawasan din namin ang bilang ng mga byte na nabasa bawat yunit ng oras ng 2 beses.

Ang ilang mga pakinabang mula sa pagtatakda ng FADV_RANDOM na bandila ay maaaring makuha, ngunit lamang sa mataas na multi-threading at may isang bloke na sukat na 128 KB, ngunit ito ay isang maximum ng isang pares ng sampu-sampung porsyento:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Ang mga pagsubok ay isinagawa sa 100 mga file, bawat isa ay 1 GB ang laki at matatagpuan sa 10 HDD.

Kalkulahin natin kung ano ang maaari nating, sa prinsipyo, umasa sa bilis na ito:
Sabihin nating nagbabasa tayo mula sa 10 disk sa bilis na 280 MB/sec, i.e. 3 milyong beses 100 bytes. Ngunit tulad ng naaalala natin, ang data na kailangan natin ay 2600 beses na mas mababa kaysa sa nabasa. Kaya, hinahati namin ang 3 milyon sa 2600 at makuha 1100 record kada segundo.

Nakaka-depress, di ba? Kalikasan yan Random na Pag-access access sa data sa HDD - anuman ang laki ng block. Ito ang pisikal na limitasyon ng random na pag-access at walang database ang maaaring mag-squeeze out nang higit pa sa ilalim ng naturang mga kundisyon.

Paano kung gayon ang mga database ay nakakamit ng mas mataas na bilis? Upang masagot ang tanong na ito, tingnan natin kung ano ang nangyayari sa sumusunod na larawan:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Dito makikita natin na sa unang ilang minuto ang bilis ay talagang humigit-kumulang isang libong rekord bawat segundo. Gayunpaman, higit pa, dahil sa katotohanan na mas marami ang nabasa kaysa sa hiniling, ang data ay napupunta sa buff/cache ng operating system (linux) at ang bilis ay tumataas sa isang mas disenteng 60 bawat segundo

Sa gayon, haharapin pa natin ang pagpapabilis ng pag-access lamang sa data na nasa cache ng OS o matatagpuan sa SSD/NVMe na mga storage device na may maihahambing na bilis ng pag-access.

Sa aming kaso, magsasagawa kami ng mga pagsubok sa isang bench ng 4 na server, ang bawat isa ay sinisingil tulad ng sumusunod:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 na mga thread.
Memorya: 730 GB.
bersyon ng java: 1.8.0_111

At dito ang pangunahing punto ay ang dami ng data sa mga talahanayan na kailangang basahin. Ang katotohanan ay kung magbabasa ka ng data mula sa isang talahanayan na ganap na nakalagay sa cache ng HBase, hindi na ito makakarating sa pagbabasa mula sa buff/cache ng operating system. Dahil ang HBase bilang default ay naglalaan ng 40% ng memorya sa isang istraktura na tinatawag na BlockCache. Mahalaga ito ay isang ConcurrentHashMap, kung saan ang susi ay ang pangalan ng file + offset ng block, at ang halaga ay ang aktwal na data sa offset na ito.

Kaya, kapag nagbabasa lamang mula sa istrukturang ito, kami nakikita namin ang mahusay na bilis, tulad ng isang milyong kahilingan sa bawat segundo. Ngunit isipin natin na hindi tayo makakapaglaan ng daan-daang gigabytes ng memorya para lamang sa mga pangangailangan sa database, dahil marami pang ibang kapaki-pakinabang na bagay na tumatakbo sa mga server na ito.

Halimbawa, sa aming kaso, ang dami ng BlockCache sa isang RS ay humigit-kumulang 12 GB. Nakarating kami ng dalawang RS sa isang node, i.e. Ang 96 GB ay inilalaan para sa BlockCache sa lahat ng mga node. At maraming beses na mas maraming data, halimbawa, hayaan itong maging 4 na talahanayan, 130 rehiyon bawat isa, kung saan ang mga file ay 800 MB ang laki, na-compress ng FAST_DIFF, i.e. kabuuang 410 GB (ito ay purong data, ibig sabihin, nang hindi isinasaalang-alang ang kadahilanan ng pagtitiklop).

Kaya, ang BlockCache ay halos 23% lamang ng kabuuang dami ng data at ito ay mas malapit sa mga tunay na kondisyon ng tinatawag na BigData. At dito magsisimula ang saya - dahil malinaw naman, ang mas kaunting cache hits, mas malala ang performance. Pagkatapos ng lahat, kung makaligtaan ka, kailangan mong gumawa ng maraming trabaho - i.e. bumaba sa pagtawag sa mga function ng system. Gayunpaman, hindi ito maiiwasan, kaya tingnan natin ang isang ganap na naiibang aspeto - ano ang mangyayari sa data sa loob ng cache?

Pasimplehin natin ang sitwasyon at ipagpalagay na mayroon tayong cache na kasya lang sa 1 object. Narito ang isang halimbawa ng kung ano ang mangyayari kapag sinubukan naming magtrabaho sa dami ng data na 3 beses na mas malaki kaysa sa cache, kakailanganin naming:

1. Ilagay ang block 1 sa cache
2. Alisin ang block 1 mula sa cache
3. Ilagay ang block 2 sa cache
4. Alisin ang block 2 mula sa cache
5. Ilagay ang block 3 sa cache

5 aksyon ang natapos! Gayunpaman, ang sitwasyong ito ay hindi matatawag na normal; sa katunayan, pinipilit namin ang HBase na gumawa ng isang grupo ng mga ganap na walang silbi na gawain. Ito ay patuloy na nagbabasa ng data mula sa OS cache, inilalagay ito sa BlockCache, para lamang itapon ito kaagad dahil may dumating na bagong bahagi ng data. Ang animation sa simula ng post ay nagpapakita ng kakanyahan ng problema - ang Garbage Collector ay lumalabas sa sukat, ang kapaligiran ay umiinit, ang maliit na Greta sa malayo at mainit na Sweden ay nababalisa. At kaming mga taong IT ay talagang hindi gusto kapag ang mga bata ay malungkot, kaya nagsisimula kaming mag-isip tungkol sa kung ano ang maaari naming gawin tungkol dito.

Paano kung hindi mo inilagay ang lahat ng mga bloke sa cache, ngunit isang tiyak na porsyento lamang ng mga ito, upang ang cache ay hindi umapaw? Magsimula tayo sa simpleng pagdaragdag lamang ng ilang linya ng code sa simula ng function para sa paglalagay ng data sa BlockCache:

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

Ang punto dito ay ang mga sumusunod: ang offset ay ang posisyon ng block sa file at ang mga huling digit nito ay random at pantay na ipinamamahagi mula 00 hanggang 99. Samakatuwid, laktawan lang namin ang mga nasa hanay na kailangan namin.

Halimbawa, itakda ang cacheDataBlockPercent = 20 at tingnan kung ano ang mangyayari:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Ang resulta ay halata. Sa mga graph sa ibaba, nagiging malinaw kung bakit nangyari ang ganoong acceleration - nakakatipid kami ng maraming mapagkukunan ng GC nang hindi ginagawa ang gawaing Sisyphean ng paglalagay ng data sa cache upang agad na itapon ito sa drain ng mga asong Martian:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Kasabay nito, tumataas ang paggamit ng CPU, ngunit mas mababa kaysa sa pagiging produktibo:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Dapat ding tandaan na ang mga bloke na nakaimbak sa BlockCache ay iba. Karamihan, mga 95%, ay data mismo. At ang natitira ay metadata, tulad ng mga filter ng Bloom o LEAF_INDEX at Ρ‚.Π΄.. Ang data na ito ay hindi sapat, ngunit ito ay lubhang kapaki-pakinabang, dahil bago i-access ang data nang direkta, ang HBase ay lumiliko sa meta upang maunawaan kung ito ay kinakailangan upang maghanap pa dito at, kung gayon, kung saan eksaktong matatagpuan ang bloke ng interes.

Samakatuwid, sa code nakikita namin ang isang kondisyon ng tseke buf.getBlockType().isData() at salamat sa meta na ito, iiwan namin ito sa cache sa anumang kaso.

Ngayon, taasan natin ang pagkarga at bahagyang higpitan ang feature nang sabay-sabay. Sa unang pagsubok ginawa namin ang porsyento ng cutoff = 20 at ang BlockCache ay bahagyang hindi nagamit. Ngayon, itakda natin ito sa 23% at magdagdag ng 100 mga thread bawat 5 minuto upang makita kung anong punto ang nangyayaring saturation:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Dito makikita natin na ang orihinal na bersyon ay halos agad na tumama sa kisame sa humigit-kumulang 100 libong mga kahilingan bawat segundo. Samantalang ang patch ay nagbibigay ng acceleration ng hanggang 300 thousand. Kasabay nito, malinaw na ang karagdagang acceleration ay hindi na "libre"; tumataas din ang paggamit ng CPU.

Gayunpaman, hindi ito isang napaka-eleganteng solusyon, dahil hindi namin alam nang maaga kung anong porsyento ng mga bloke ang kailangang i-cache, depende ito sa profile ng pag-load. Samakatuwid, isang mekanismo ang ipinatupad upang awtomatikong ayusin ang parameter na ito depende sa aktibidad ng mga operasyon sa pagbabasa.

Tatlong opsyon ang naidagdag para makontrol ito:

hbase.lru.cache.heavy.eviction.count.limit β€” nagtatakda kung gaano karaming beses dapat tumakbo ang proseso ng pagpapaalis ng data mula sa cache bago natin simulan ang paggamit ng pag-optimize (ibig sabihin, paglaktaw ng mga bloke). Bilang default, ito ay katumbas ng MAX_INT = 2147483647 at sa katunayan ay nangangahulugan na ang tampok ay hindi magsisimulang gumana sa halagang ito. Dahil ang proseso ng pagpapaalis ay nagsisimula kada 5 - 10 segundo (depende ito sa load) at 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 taon. Gayunpaman, maaari naming itakda ang parameter na ito sa 0 at gawin ang tampok na gumana kaagad pagkatapos ng paglunsad.

Gayunpaman, mayroon ding payload sa parameter na ito. Kung ang aming load ay ganoong ang mga panandaliang pagbabasa (sabihin sa araw) at pangmatagalang pagbabasa (sa gabi) ay patuloy na pinagsalubungan, pagkatapos ay maaari naming siguraduhin na ang tampok ay naka-on lamang kapag ang mga operasyon ng mahabang pagbasa ay isinasagawa.

Halimbawa, alam namin na ang mga panandaliang pagbabasa ay karaniwang tumatagal ng mga 1 minuto. Hindi na kailangang magsimulang magtapon ng mga bloke, ang cache ay hindi magkakaroon ng oras upang maging lipas na at pagkatapos ay maaari naming itakda ang parameter na ito na katumbas ng, halimbawa, 10. Ito ay hahantong sa katotohanan na ang pag-optimize ay magsisimulang gumana lamang kapag matagal- nagsimula na ang term active reading, i.e. sa loob ng 100 segundo. Kaya, kung mayroon tayong panandaliang pagbabasa, ang lahat ng mga bloke ay mapupunta sa cache at magiging available (maliban sa mga aalisin ng karaniwang algorithm). At kapag gumawa kami ng pangmatagalang pagbabasa, naka-on ang feature at magkakaroon kami ng mas mataas na performance.

hbase.lru.cache.heavy.eviction.mb.size.limit β€” nagtatakda kung gaano karaming megabytes ang gusto naming ilagay sa cache (at, siyempre, paalisin) sa loob ng 10 segundo. Susubukan ng feature na maabot ang halagang ito at mapanatili ito. Ang punto ay ito: kung itutulak natin ang mga gigabytes sa cache, kakailanganin nating paalisin ang mga gigabyte, at ito, tulad ng nakita natin sa itaas, ay napakamahal. Gayunpaman, hindi mo dapat subukang itakda ito nang masyadong maliit, dahil ito ay magiging sanhi ng pag-alis ng block skip mode nang maaga. Para sa makapangyarihang mga server (mga 20-40 pisikal na core), pinakamainam na magtakda ng mga 300-400 MB. Para sa middle class (~10 core) 200-300 MB. Para sa mga mahihinang system (2-5 core) 50-100 MB ay maaaring normal (hindi nasubok sa mga ito).

Tingnan natin kung paano ito gumagana: sabihin nating itinakda natin ang hbase.lru.cache.heavy.eviction.mb.size.limit = 500, mayroong ilang uri ng pag-load (pagbabasa) at pagkatapos bawat ~10 segundo kinakalkula namin kung gaano karaming mga byte ang naging pinaalis gamit ang formula:

Overhead = Freed Bytes Sum (MB) * 100 / Limitasyon (MB) - 100;

Kung sa katunayan ay 2000 MB ang pinaalis, ang Overhead ay katumbas ng:

2000 * 100 / 500 - 100 = 300%

Sinusubukan ng mga algorithm na mapanatili ang hindi hihigit sa ilang sampu-sampung porsyento, kaya babawasan ng tampok ang porsyento ng mga naka-cache na bloke, sa gayon ay nagpapatupad ng mekanismo ng auto-tuning.

Gayunpaman, kung bumaba ang load, sabihin nating 200 MB lang ang pinaalis at ang Overhead ay nagiging negatibo (ang tinatawag na overshooting):

200 * 100 / 500 - 100 = -60%

Sa kabaligtaran, tataas ng feature ang porsyento ng mga naka-cache na bloke hanggang sa maging positibo ang Overhead.

Nasa ibaba ang isang halimbawa ng hitsura nito sa totoong data. Hindi na kailangang subukang maabot ang 0%, imposible. Ito ay napakahusay kapag ito ay humigit-kumulang 30 - 100%, nakakatulong ito upang maiwasan ang napaaga na paglabas mula sa mode ng pag-optimize sa panahon ng panandaliang pag-alon.

hbase.lru.cache.heavy.eviction.overhead.coefficient β€” nagtatakda kung gaano kabilis namin gustong makuha ang resulta. Kung alam naming sigurado na ang aming mga nabasa ay halos mahaba at ayaw naming maghintay, maaari naming taasan ang ratio na ito at makakuha ng mataas na pagganap nang mas mabilis.

Halimbawa, itinakda namin ang koepisyent na ito = 0.01. Nangangahulugan ito na ang Overhead (tingnan sa itaas) ay i-multiply sa numerong ito sa magreresultang resulta at ang porsyento ng mga naka-cache na bloke ay mababawasan. Ipagpalagay natin na ang Overhead = 300% at coefficient = 0.01, kung gayon ang porsyento ng mga naka-cache na bloke ay mababawasan ng 3%.

Ang isang katulad na lohika ng "Backpressure" ay ipinatupad din para sa mga negatibong Overhead (overshooting) na halaga. Dahil laging posible ang panandaliang pagbabagu-bago sa dami ng mga nabasa at pagpapaalis, pinapayagan ka ng mekanismong ito na maiwasan ang napaaga na paglabas mula sa mode ng pag-optimize. Ang backpressure ay may baligtad na lohika: kung mas malakas ang pag-overshoot, mas maraming mga bloke ang naka-cache.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Code ng pagpapatupad

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

Tingnan natin ngayon ang lahat ng ito gamit ang isang tunay na halimbawa. Mayroon kaming sumusunod na script ng pagsubok:

  1. Simulan natin ang pag-scan (25 thread, batch = 100)
  2. Pagkatapos ng 5 minuto, magdagdag ng multi-gets (25 thread, batch = 100)
  3. Pagkalipas ng 5 minuto, i-off ang multi-gets (nananatili na lang ulit ang pag-scan)

Gumagawa kami ng dalawang pagpapatakbo, una hbase.lru.cache.heavy.eviction.count.limit = 10000 (na talagang hindi pinapagana ang tampok), at pagkatapos ay itakda ang limitasyon = 0 (pinagana ito).

Sa mga log sa ibaba makikita natin kung paano naka-on ang feature at ni-reset ang Overshooting sa 14-71%. Paminsan-minsan ay bumababa ang load, na nag-o-on sa Backpressure at ang HBase ay nag-cache ng mas maraming blocks muli.

Log RegionServer
pinaalis (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, kasalukuyang pag-cache ng DataBlock (%): 100
pinaalis (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, kasalukuyang pag-cache ng DataBlock (%): 100
pinaalis (MB): 2170, ratio 1.09, overhead (%): 985, heavy eviction counter: 1, kasalukuyang pag-cache ng DataBlock (%): 91 < simula
pinaalis (MB): 3763, ratio 1.08, overhead (%): 1781, heavy eviction counter: 2, kasalukuyang pag-cache ng DataBlock (%): 76
pinaalis (MB): 3306, ratio 1.07, overhead (%): 1553, heavy eviction counter: 3, kasalukuyang pag-cache ng DataBlock (%): 61
pinaalis (MB): 2508, ratio 1.06, overhead (%): 1154, heavy eviction counter: 4, kasalukuyang pag-cache ng DataBlock (%): 50
pinaalis (MB): 1824, ratio 1.04, overhead (%): 812, heavy eviction counter: 5, kasalukuyang pag-cache ng DataBlock (%): 42
pinaalis (MB): 1482, ratio 1.03, overhead (%): 641, heavy eviction counter: 6, kasalukuyang pag-cache ng DataBlock (%): 36
pinaalis (MB): 1140, ratio 1.01, overhead (%): 470, heavy eviction counter: 7, kasalukuyang pag-cache ng DataBlock (%): 32
pinaalis (MB): 913, ratio 1.0, overhead (%): 356, heavy eviction counter: 8, kasalukuyang pag-cache ng DataBlock (%): 29
pinaalis (MB): 912, ratio 0.89, overhead (%): 356, heavy eviction counter: 9, kasalukuyang pag-cache ng DataBlock (%): 26
pinaalis (MB): 684, ratio 0.76, overhead (%): 242, heavy eviction counter: 10, kasalukuyang pag-cache ng DataBlock (%): 24
pinaalis (MB): 684, ratio 0.61, overhead (%): 242, heavy eviction counter: 11, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 456, ratio 0.51, overhead (%): 128, heavy eviction counter: 12, kasalukuyang pag-cache ng DataBlock (%): 21
pinaalis (MB): 456, ratio 0.42, overhead (%): 128, heavy eviction counter: 13, kasalukuyang pag-cache ng DataBlock (%): 20
pinaalis (MB): 456, ratio 0.33, overhead (%): 128, heavy eviction counter: 14, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 15, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 16, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 342, ratio 0.31, overhead (%): 71, heavy eviction counter: 17, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.3, overhead (%): 14, heavy eviction counter: 18, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.29, overhead (%): 14, heavy eviction counter: 19, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.27, overhead (%): 14, heavy eviction counter: 20, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.25, overhead (%): 14, heavy eviction counter: 21, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.24, overhead (%): 14, heavy eviction counter: 22, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.22, overhead (%): 14, heavy eviction counter: 23, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.21, overhead (%): 14, heavy eviction counter: 24, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.2, overhead (%): 14, heavy eviction counter: 25, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 228, ratio 0.17, overhead (%): 14, heavy eviction counter: 26, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 456, ratio 0.17, overhead (%): 128, heavy eviction counter: 27, kasalukuyang pag-cache ng DataBlock (%): 18 < idinagdag na nakukuha (ngunit pareho ang talahanayan)
pinaalis (MB): 456, ratio 0.15, overhead (%): 128, heavy eviction counter: 28, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 342, ratio 0.13, overhead (%): 71, heavy eviction counter: 29, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 342, ratio 0.11, overhead (%): 71, heavy eviction counter: 30, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 342, ratio 0.09, overhead (%): 71, heavy eviction counter: 31, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.08, overhead (%): 14, heavy eviction counter: 32, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.07, overhead (%): 14, heavy eviction counter: 33, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.06, overhead (%): 14, heavy eviction counter: 34, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 35, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 36, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 228, ratio 0.04, overhead (%): 14, heavy eviction counter: 37, kasalukuyang pag-cache ng DataBlock (%): 17
pinalayas (MB): 109, ratio 0.04, overhead (%): -46, heavy eviction counter: 37, kasalukuyang caching DataBlock (%): 22 < back pressure
pinaalis (MB): 798, ratio 0.24, overhead (%): 299, heavy eviction counter: 38, kasalukuyang pag-cache ng DataBlock (%): 20
pinaalis (MB): 798, ratio 0.29, overhead (%): 299, heavy eviction counter: 39, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 570, ratio 0.27, overhead (%): 185, heavy eviction counter: 40, kasalukuyang pag-cache ng DataBlock (%): 17
pinaalis (MB): 456, ratio 0.22, overhead (%): 128, heavy eviction counter: 41, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 342, ratio 0.16, overhead (%): 71, heavy eviction counter: 42, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 342, ratio 0.11, overhead (%): 71, heavy eviction counter: 43, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 228, ratio 0.09, overhead (%): 14, heavy eviction counter: 44, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 228, ratio 0.07, overhead (%): 14, heavy eviction counter: 45, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 228, ratio 0.05, overhead (%): 14, heavy eviction counter: 46, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 222, ratio 0.04, overhead (%): 11, heavy eviction counter: 47, kasalukuyang pag-cache ng DataBlock (%): 16
pinaalis (MB): 104, ratio 0.03, overhead (%): -48, heavy eviction counter: 47, kasalukuyang pag-cache ng DataBlock (%): 21 < interrupt gets
pinaalis (MB): 684, ratio 0.2, overhead (%): 242, heavy eviction counter: 48, kasalukuyang pag-cache ng DataBlock (%): 19
pinaalis (MB): 570, ratio 0.23, overhead (%): 185, heavy eviction counter: 49, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 342, ratio 0.22, overhead (%): 71, heavy eviction counter: 50, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 228, ratio 0.21, overhead (%): 14, heavy eviction counter: 51, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 228, ratio 0.2, overhead (%): 14, heavy eviction counter: 52, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 228, ratio 0.18, overhead (%): 14, heavy eviction counter: 53, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 228, ratio 0.16, overhead (%): 14, heavy eviction counter: 54, kasalukuyang pag-cache ng DataBlock (%): 18
pinaalis (MB): 228, ratio 0.14, overhead (%): 14, heavy eviction counter: 55, kasalukuyang pag-cache ng DataBlock (%): 18
pinalayas (MB): 112, ratio 0.14, overhead (%): -44, heavy eviction counter: 55, kasalukuyang caching DataBlock (%): 23 < back pressure
pinaalis (MB): 456, ratio 0.26, overhead (%): 128, heavy eviction counter: 56, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.31, overhead (%): 71, heavy eviction counter: 57, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 58, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 59, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 60, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 61, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 62, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 63, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 64, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 65, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 66, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 67, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 68, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 69, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 70, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 71, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 72, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 73, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 74, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 75, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 76, kasalukuyang pag-cache ng DataBlock (%): 22
pinaalis (MB): 21, ratio 0.33, overhead (%): -90, heavy eviction counter: 76, kasalukuyang pag-cache ng DataBlock (%): 32
pinaalis (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, kasalukuyang pag-cache ng DataBlock (%): 100
pinaalis (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, kasalukuyang pag-cache ng DataBlock (%): 100

Ang mga pag-scan ay kinakailangan upang ipakita ang parehong proseso sa anyo ng isang graph ng ugnayan sa pagitan ng dalawang mga seksyon ng cache - iisa (kung saan ang mga bloke na hindi pa hiniling dati) at marami (data na "hiniling" ay naka-imbak dito ng hindi bababa sa isang beses):

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

At sa wakas, ano ang hitsura ng pagpapatakbo ng mga parameter sa anyo ng isang graph. Para sa paghahambing, ganap na naka-off ang cache sa simula, pagkatapos ay inilunsad ang HBase na may pag-cache at pagkaantala sa pagsisimula ng trabaho sa pag-optimize ng 5 minuto (30 na mga siklo ng pagpapalayas).

Ang buong code ay makikita sa Pull Request HBASE 23887 sa github.

Gayunpaman, ang 300 libong mga pagbabasa sa bawat segundo ay hindi lahat na maaaring makamit sa hardware na ito sa ilalim ng mga kundisyong ito. Ang katotohanan ay kapag kailangan mong mag-access ng data sa pamamagitan ng HDFS, ang ShortCircuitCache (simula dito ay tinutukoy bilang SSC) na mekanismo ay ginagamit, na nagpapahintulot sa iyo na ma-access ang data nang direkta, pag-iwas sa mga pakikipag-ugnayan sa network.

Ipinakita ng pag-profile na kahit na ang mekanismong ito ay nagbibigay ng malaking pakinabang, ito rin sa ilang mga punto ay nagiging isang bottleneck, dahil halos lahat ng mabibigat na operasyon ay nangyayari sa loob ng isang lock, na humahantong sa pagharang sa halos lahat ng oras.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Nang mapagtanto ito, napagtanto namin na ang problema ay maaaring iwasan sa pamamagitan ng paglikha ng isang hanay ng mga independiyenteng SSC:

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

At pagkatapos ay makipagtulungan sa kanila, hindi kasama ang mga intersection din sa huling offset na digit:

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

Ngayon ay maaari mong simulan ang pagsubok. Upang gawin ito, magbabasa kami ng mga file mula sa HDFS gamit ang isang simpleng multi-threaded na application. Itakda ang mga parameter:

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

At basahin lamang ang mga file:

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

Ang code na ito ay isinagawa sa magkahiwalay na mga thread at madaragdagan namin ang bilang ng sabay-sabay na pagbabasa ng mga file (mula 10 hanggang 200 - pahalang na axis) at ang bilang ng mga cache (mula 1 hanggang 10 - graphics). Ipinapakita ng vertical axis ang acceleration na nagreresulta mula sa pagtaas ng SSC kaugnay ng kaso kapag mayroon lamang isang cache.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Paano basahin ang graph: Ang oras ng pagpapatupad para sa 100 libong pagbabasa sa 64 KB na mga bloke na may isang cache ay nangangailangan ng 78 segundo. Samantalang sa 5 cache ay tumatagal ng 16 segundo. Yung. mayroong isang acceleration ng ~5 beses. Tulad ng makikita mula sa graph, ang epekto ay hindi masyadong kapansin-pansin para sa isang maliit na bilang ng parallel reads, ito ay nagsisimula upang gumanap ng isang kapansin-pansing papel kapag mayroong higit sa 50 thread reads. Ito rin ay kapansin-pansin na ang pagtaas ng bilang ng mga SSC mula sa 6 at sa itaas ay nagbibigay ng isang makabuluhang mas maliit na pagtaas ng pagganap.

Tandaan 1: dahil ang mga resulta ng pagsubok ay medyo pabagu-bago (tingnan sa ibaba), 3 pagpapatakbo ang isinagawa at ang mga resultang halaga ay na-average.

Tandaan 2: Ang pakinabang ng pagganap mula sa pag-configure ng random na pag-access ay pareho, kahit na ang pag-access mismo ay bahagyang mas mabagal.

Gayunpaman, kinakailangang linawin na, hindi katulad ng kaso sa HBase, ang pagbilis na ito ay hindi palaging libre. Dito namin "i-unlock" ang kakayahan ng CPU na gumawa ng higit na trabaho, sa halip na mag-hang sa mga kandado.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Dito maaari mong obserbahan na, sa pangkalahatan, ang pagtaas sa bilang ng mga cache ay nagbibigay ng humigit-kumulang na proporsyonal na pagtaas sa paggamit ng CPU. Gayunpaman, mayroong bahagyang higit pang mga panalong kumbinasyon.

Halimbawa, tingnan natin ang setting na SSC = 3. Ang pagtaas ng performance sa range ay humigit-kumulang 3.3 beses. Nasa ibaba ang mga resulta mula sa lahat ng tatlong magkakahiwalay na pagtakbo.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Habang ang pagkonsumo ng CPU ay tumataas ng humigit-kumulang 2.8 beses. Ang pagkakaiba ay hindi masyadong malaki, ngunit ang maliit na si Greta ay masaya na at maaaring magkaroon ng oras upang pumasok sa paaralan at kumuha ng mga aralin.

Kaya, magkakaroon ito ng positibong epekto para sa anumang tool na gumagamit ng maramihang pag-access sa HDFS (halimbawa, Spark, atbp.), sa kondisyon na magaan ang application code (ibig sabihin, ang plug ay nasa gilid ng kliyente ng HDFS) at mayroong libreng CPU power. . Upang suriin, subukan natin kung ano ang magiging epekto ng pinagsamang paggamit ng BlockCache optimization at SSC tuning para sa pagbabasa mula sa HBase.

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Makikita na sa ilalim ng gayong mga kundisyon ang epekto ay hindi kasinghusay ng sa mga pinong pagsubok (pagbabasa nang walang anumang pagproseso), ngunit medyo posible na mag-squeeze out ng karagdagang 80K dito. Magkasama, ang parehong pag-optimize ay nagbibigay ng hanggang 4x na speedup.

Isang PR din ang ginawa para sa optimization na ito [HDFS-15202], na pinagsama-sama at magiging available ang functionality na ito sa mga release sa hinaharap.

At sa wakas, kawili-wiling ihambing ang pagganap ng pagbabasa ng isang katulad na database ng malawak na hanay, Cassandra at HBase.

Para magawa ito, naglunsad kami ng mga pagkakataon ng karaniwang YCSB load testing utility mula sa dalawang host (800 thread sa kabuuan). Sa panig ng server - 4 na pagkakataon ng RegionServer at Cassandra sa 4 na host (hindi ang kung saan tumatakbo ang mga kliyente, upang maiwasan ang kanilang impluwensya). Ang mga pagbabasa ay nagmula sa mga talahanayan ng laki:

HBase – 300 GB sa HDFS (100 GB purong data)

Cassandra - 250 GB (replication factor = 3)

Yung. ang volume ay humigit-kumulang pareho (sa HBase nang kaunti pa).

Mga parameter ng HBase:

dfs.client.short.circuit.num = 5 (Pag-optimize ng kliyente ng HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - nangangahulugan ito na ang patch ay magsisimulang gumana pagkatapos ng 30 pagpapaalis (~5 minuto)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 β€” target na dami ng pag-cache at pagpapaalis

Ang mga log ng YCSB ay na-parse at pinagsama-sama sa mga Excel graph:

Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Tulad ng nakikita mo, ginagawang posible ng mga pag-optimize na ito na ihambing ang pagganap ng mga database na ito sa ilalim ng mga kundisyong ito at makamit ang 450 libong pagbabasa bawat segundo.

Umaasa kami na ang impormasyong ito ay maaaring maging kapaki-pakinabang sa isang tao sa panahon ng kapana-panabik na pakikibaka para sa pagiging produktibo.

Pinagmulan: www.habr.com

Magdagdag ng komento