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 Sberbank, ina-upload namin ang halos lahat ng mga transaksyon sa aming Data Cloud na nakabatay sa Hadoop, kaya pinangangasiwaan namin ang tunay na malalaking daloy ng data. Naturally, patuloy kaming naghahanap ng mga paraan para mapahusay ang performance, at gusto naming ibahagi kung paano namin na-patch ang RegionServer HBase at ang HDFS client, na makabuluhang pinapataas ang bilis ng pagbasa.
Paano taasan ang bilis ng pagbasa mula sa HBase hanggang 3 beses at mula sa HDFS hanggang 5 beses

Gayunpaman, bago tayo makarating sa puso ng mga pagpapabuti, sulit na talakayin ang mga limitasyon na mahalagang imposibleng iwasan kung gumagamit ka ng HDD.

Bakit hindi tugma ang HDD at mabilis na Random Access na pagbabasa
Tulad ng alam mo, ang HBase, tulad ng maraming iba pang mga database, ay nag-iimbak ng data sa mga bloke ng ilang sampu-sampung kilobytes. Bilang default, ito ay nasa 64 KB. Ngayon isipin na kailangan nating kunin ang 100 bytes lamang at hilingin sa HBase na ibalik 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 (sandali lang!) kaysa sa kinakailangan.

Dagdag pa, 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), nagreresulta ito sa pagbabasa ng 1 MB mula sa disk. Gayunpaman, maaari itong ayusin gamit ang 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 kapag sinimulan nating magbasa ng data sa pamamagitan ng Java API, gamit ang mga function tulad ng FileChannel.read at hilingin sa operating system na basahin ang tinukoy na dami ng data, ito ay magbabasa ng dalawang beses ng "kung sakali," ibig sabihin, 256 KB sa aming kaso. Nangyayari ito dahil walang simpleng paraan ang Java para itakda ang FADV_RANDOM flag para maiwasan ang pag-uugaling ito.

Kaya, upang makuha ang aming 100 byte, 2600 beses na mas marami ang binabasa sa ilalim ng hood. Mukhang malinaw ang solusyon: bawasan natin ang laki ng block sa isang kilobyte, itakda ang nabanggit na bandila, at makamit ang isang mahusay na bilis. Ngunit ang problema ay sa pamamagitan ng paghahati sa laki ng block, hinahati din namin ang bilang ng mga byte na nabasa sa bawat yunit ng oras.

Ang ilang mga pakinabang mula sa pagtatakda ng FADV_RANDOM flag ay maaaring makamit, ngunit lamang sa mataas na multithreading at isang bloke na laki ng 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 1 GB ang laki, na matatagpuan sa 10 HDD disk.

Kalkulahin natin kung ano ang maaari nating asahan sa bilis na ito:
Sabihin nating nagbabasa tayo mula sa 10 disk sa 280 MB/sec, o 3 milyong 100-byte na pagbabasa. Ngunit kung naaalala natin, ang data na kailangan natin ay 2600 beses na mas maliit kaysa sa halagang nabasa. Kaya, hinahati natin ang 3 milyon sa 2600 at makuha 1100 record kada segundo.

Nakaka-depress, di ba? Kalikasan yan. Random na Pag-access Access sa data ng HDD—anuman ang laki ng block. Ito ang pisikal na limitasyon ng random na pag-access, at walang database ang makakamit ng higit pa sa ilalim ng mga kundisyong ito.

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 ang bilis sa unang ilang minuto ay talagang humigit-kumulang isang libong mga rekord bawat segundo. Gayunpaman, sa paglaon, dahil sa katotohanang mas marami ang nabasa kaysa sa hiniling, ang data ay naninirahan sa buffer/cache ng operating system (Linux), at ang bilis ay tumataas sa isang mas kagalang-galang na 60 bawat segundo.

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

Sa aming kaso, magsasagawa kami ng mga pagsubok sa isang rig na binubuo ng 4 na server, na 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

Ang pangunahing isyu dito ay ang dami ng data sa mga talahanayan na kailangang basahin. Kung magbabasa ka ng data mula sa isang talahanayan na ganap na akma sa cache ng HBase, hindi mo na kakailanganing basahin ito mula sa buffer/cache ng operating system. Ito ay dahil ang HBase, bilang default, ay naglalaan ng 40% ng memorya sa isang istraktura na tinatawag na BlockCache. Ito ay mahalagang isang ConcurrentHashMap, kung saan ang susi ay ang filename + block offset, at ang halaga ay ang aktwal na data sa offset na iyon.

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 maaaring maglaan ng daan-daang gigabytes ng memorya lamang 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. Nag-deploy kami ng dalawang RS sa iisang node, ibig sabihin, 96 GB ang inilalaan sa BlockCache sa lahat ng node. Ang dami ng data ay mas malaki: halimbawa, sabihin nating mayroong apat na talahanayan, bawat isa ay may 130 rehiyon, bawat isa ay may 800 MB na mga file na na-compress gamit ang FAST_DIFF, para sa kabuuang 410 GB (ito ay purong data, ibig sabihin, nang hindi isinasaalang-alang ang pagtitiklop).

Sa gayon, ang BlockCache ay nagkakaloob lamang ng halos 23% ng kabuuang dami ng data, na mas malapit sa mga tunay na kondisyon ng tinatawag na Big Data. At narito kung saan nagiging kawili-wili ang mga bagay-malinaw, ang mas kaunting mga hit sa cache, mas malala ang pagganap. Pagkatapos ng lahat, kung may nangyaring cache miss, isang toneladang trabaho ang kailangang gawin—iyon ay, hanggang sa pagtawag sa mga function ng system. Gayunpaman, hindi ito maiiwasan, kaya isaalang-alang 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 mayroong isang bagay lamang. Narito ang isang halimbawa ng kung ano ang mangyayari kung sinubukan naming magtrabaho sa dami ng data nang tatlong beses na mas malaki kaysa sa cache:

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

Limang aksyon ang nagawa! Gayunpaman, ang sitwasyong ito ay hindi nangangahulugang normal; mahalagang pinipilit namin ang HBase na gumawa ng isang toneladang ganap na walang silbi na gawain. Patuloy itong nagbabasa ng data mula sa cache ng OS, iniimbak ito sa BlockCache, para lamang itapon ito kaagad kapag may dumating na bagong batch ng data. Ang animation sa simula ng post ay naglalarawan ng kakanyahan ng problema: ang Garbage Collector ay dumadaan sa bubong, ang kapaligiran ay umiinit, at ang maliit na Greta sa malayo, 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 kung ano ang maaari naming gawin tungkol dito.

Paano kung hindi namin ini-cache ang lahat ng mga bloke, ngunit isang tiyak na porsyento lamang ng mga ito, upang ang cache ay hindi umapaw? Magsimula tayo sa simpleng pagdaragdag ng ilang linya ng code sa simula ng function na nag-iimbak 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 ideya dito ay 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 loob ng hanay na kailangan namin.

Halimbawa, itakda natin 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 mga resulta ay malinaw. Nililinaw ng mga graph sa ibaba kung bakit nangyari ang pagpapabilis na ito—nagse-save kami ng isang toneladang mapagkukunan ng GC sa pamamagitan ng hindi kinakailangang pagdaan sa Sisyphean labor ng paglalagay ng data sa cache para lang agad itong itapon:

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

Tumataas ang paggamit ng CPU, ngunit mas mababa kaysa sa pagganap:

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 nag-iiba. Ang karamihan, mga 95%, ay aktwal na data. Ang natitira ay metadata, gaya ng mga filter ng Bloom o LEAF_INDEX. т.д.Ang data na ito ay maliit, ngunit ito ay lubhang kapaki-pakinabang, dahil bago i-access ang data mismo, ina-access ng HBase ang metadata upang matukoy kung kailangan pa nitong maghanap at, kung gayon, kung saan eksaktong matatagpuan ang bloke kung saan ito interesado.

Iyon ang dahilan kung bakit sa code nakakakita kami ng isang pagsusuri sa kondisyon buf.getBlockType().isData() at salamat sa meta na ito iiwan namin ito sa cache sa anumang kaso.

Ngayon taasan natin ang load at bahagyang i-tweak ang feature. Sa unang pagsubok, itinakda namin ang porsyento ng cutoff sa 20, at bahagyang hindi nagamit ang BlockCache. Ngayon, itakda natin ito sa 23% at magdagdag ng 100 mga thread bawat 5 minuto upang makita kung kailan nangyayari ang 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 mga kahilingan bawat segundo. Ang patch, gayunpaman, ay nagpapabilis ng pagganap sa 300. Malinaw na ang karagdagang acceleration ay hindi kasing "libre," dahil 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 sa load profile. Samakatuwid, nagpatupad kami ng mekanismo para awtomatikong isaayos ang parameter na ito batay sa aktibidad ng pagbabasa.

Tatlong parameter ang idinagdag upang kontrolin ito:

hbase.lru.cache.heavy.eviction.count.limit — itinatakda ang dami ng beses na dapat tumakbo ang proseso ng pagpapaalis ng cache bago natin simulan ang paggamit ng pag-optimize (ibig sabihin, paglaktaw sa mga bloke). Bilang default, nakatakda ito sa MAX_INT = 2147483647, na epektibong nangangahulugang hindi gagana ang feature sa halagang ito. Ito ay dahil ang proseso ng pagpapaalis ay tumatakbo bawat 5-10 segundo (depende sa pagkarga), at 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 taon. Gayunpaman, maaari naming itakda ang parameter na ito sa 0 at pilitin ang tampok na gumana kaagad pagkatapos ng startup.

Gayunpaman, mayroon ding kapaki-pakinabang na payload para sa parameter na ito. Kung ang aming workload ay tulad na ang mga panandaliang pagbabasa (sabihin, sa araw) at pangmatagalang pagbabasa (sa gabi) ay patuloy na kahalili, maaari naming i-configure ang feature na ma-enable lang kapag may nagaganap na mga operasyon ng mahabang pagbasa.

Halimbawa, alam namin na ang mga panandaliang pagbabasa ay karaniwang tumatagal ng humigit-kumulang 1 minuto. Kung hindi namin kailangang simulan ang pagpapaalis ng mga bloke, ang cache ay hindi magkakaroon ng oras upang maging lipas, pagkatapos ay maaari naming itakda ang parameter na ito sa, sabihin nating, 10. Ito ay magiging sanhi ng pag-optimize upang magsimula lamang kapag nagsimula ang pangmatagalang aktibong pagbabasa, ibig sabihin, pagkatapos ng 100 segundo. Kaya, kung mayroon kaming panandaliang pagbabasa, ang lahat ng mga bloke ay mai-cache at magagamit (maliban sa mga pinaalis ng karaniwang algorithm). At kapag nagsagawa kami ng pangmatagalang pagbabasa, pinapagana ang feature, na nagreresulta sa mas mataas na performance.

hbase.lru.cache.heavy.eviction.mb.size.limit — nagtatakda kung ilang megabyte ang gusto naming i-cache (at, natural, paalisin) bawat 10 segundo. Susubukan ng feature na maabot ang halagang ito at mapanatili ito. Ang ideya ay kung i-cram natin ang mga gigabyte sa cache, kakailanganin din nating paalisin ang mga gigabyte, na, tulad ng nakita natin sa itaas, ay medyo mahal. Gayunpaman, huwag subukang itakda ito nang masyadong mababa, dahil hahantong ito sa napaaga na paglabas mula sa block skipping mode. Para sa makapangyarihang mga server (mga 20-40 pisikal na core), ang setting na humigit-kumulang 300-400 MB ay pinakamainam. Para sa mga mid-range system (~10 core), 200-300 MB ang mainam. Para sa mga low-end na system (2-5 core), 50-100 MB ay maaaring maayos (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 load (nabasa), at pagkatapos bawat ~10 segundo kinakalkula namin kung gaano karaming mga byte ang pinaalis gamit ang formula:

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

Kung talagang pinaalis ang 2000 MB, ang Overhead ay katumbas ng:

2000 * 100 / 500 - 100 = 300%

Sinusubukan ng mga algorithm na suportahan 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 naalis at naging negatibo ang Overhead (ang tinatawag na overshooting):

200 * 100 / 500 - 100 = -60%

Ang tampok, sa kabaligtaran, ay tataas ang porsyento ng mga naka-cache na bloke hanggang sa maging positibo ang Overhead.

Nasa ibaba ang isang halimbawa ng kung ano ang hitsura nito sa totoong data. Huwag subukang maabot ang 0%; ito ay imposible. Ang isang magandang halaga ay nasa paligid ng 30-100%; nakakatulong ito na maiwasan ang maagang paglabas mula sa optimization mode sa mga panandaliang spike.

hbase.lru.cache.heavy.eviction.overhead.coefficient — nagtatakda kung gaano kabilis namin gustong makakuha ng mga resulta. Kung alam nating sigurado na ang ating mga nabasa ay halos mahaba at ayaw nating maghintay, maaari nating dagdagan ang salik na ito at mas mabilis na makamit ang mataas na pagganap.

Halimbawa, itinakda namin ang koepisyent na ito sa 0.01. Nangangahulugan ito na ang Overhead (tingnan sa itaas) ay i-multiply sa numerong ito at ang magreresultang halaga ay mababawasan, na binabawasan ang porsyento ng mga naka-cache na bloke. Sabihin nating ang Overhead ay 300% at ang coefficient ay 0.01, pagkatapos ay mababawasan ng 3% ang porsyento ng mga cacheable block.

Ang isang katulad na lohika ng "Backpressure" ay ipinatupad para sa mga negatibong Overhead (overshooting) na halaga. Dahil laging posible ang panandaliang pagbabagu-bago sa dami ng read/eviction, nakakatulong ang mekanismong ito na maiwasan ang maagang paglabas mula sa optimization mode. Ang backpressure ay may baligtad na lohika: mas mataas ang overshooting, 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 ang lahat ng ito gamit ang isang tunay na halimbawa sa mundo. Mayroon kaming sumusunod na senaryo 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 sa hbase.lru.cache.heavy.eviction.count.limit = 10000 (na epektibong hindi pinapagana ang feature), at pagkatapos ay nagtakda kami ng limitasyon = 0 (na nagbibigay-daan dito).

Sa mga log sa ibaba, nakikita namin kung paano pinagana ang feature, na binabawasan ang overshooting sa 14-71%. Paminsan-minsan, bumababa ang load, na nag-trigger ng Backpressure, at ang HBase ay nag-cache ng mas maraming blocks muli.

Log ng 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

Kinailangan ang mga pag-scan upang ipakita ang parehong prosesong ito bilang isang graph ng ugnayan sa pagitan ng dalawang seksyon ng cache—iisa (kung saan naka-imbak ang mga bloke na hindi pa kailanman hiniling) at marami (kung saan naka-imbak ang data na "hiniling" kahit isang beses):

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

Panghuli, narito ang isang graph kung paano gumagana ang mga parameter. Para sa paghahambing, ganap na hindi pinagana ang cache sa simula, pagkatapos ay inilunsad ang HBase na pinagana ang pag-cache at ang pagsisimula ng pag-optimize ay naantala ng 5 minuto (30 cycle ng pagpapalayas).

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

Gayunpaman, hindi lang 300 na pagbabasa bawat segundo ang maaaring makuha sa hardware na ito sa ilalim ng mga kundisyong ito. Kapag nag-a-access ng data sa pamamagitan ng HDFS, ginagamit ang mekanismo ng ShortCircuitCache (SSC), na nagbibigay-daan sa direktang pag-access sa data, pag-iwas sa mga pakikipag-ugnayan sa network.

Ipinakita ng pag-profile na ang mekanismong ito, bagama't nagbibigay ng makabuluhang pakinabang, ay nagiging isang bottleneck din sa isang punto, dahil halos lahat ng mabibigat na operasyon ay nangyayari sa loob ng 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 maiiwasan 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 ayon sa huling digit ng offset:

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

Ngayon ay maaari na nating simulan ang pagsubok. Gagamit kami ng simpleng multithreaded na application para magbasa ng mga file mula sa HDFS. Itatakda namin ang mga sumusunod na 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);
}

Gumagana ang code na ito sa magkahiwalay na mga thread, at dadagdagan namin ang bilang ng mga sabay-sabay na babasahin na file (mula 10 hanggang 200—horizontal axis) at ang bilang ng mga cache (mula 1 hanggang 10—graph). Ipinapakita ng vertical axis ang speedup na ibinigay sa pamamagitan ng pagtaas ng SSC na nauugnay sa kaso na may isang cache lamang.

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

Paano basahin ang graph: 100,000 reads sa 64 KB blocks na may isang cache ay tumatagal ng 78 segundo. Sa limang cache, ito ay nagagawa sa loob ng 16 na segundo. Ito ay kumakatawan sa isang ~5x speedup. Tulad ng ipinapakita ng graph, ang epekto ay hindi masyadong kapansin-pansin sa isang maliit na bilang ng parallel reads, ngunit ito ay nagiging kapansin-pansin kapag mayroong higit sa 50 read thread. Kapansin-pansin din na ang pagtaas ng bilang ng mga SSC mula 6 at pataas ay magbubunga ng mas maliit na pagpapalakas ng pagganap.

Tandaan 1: Dahil ang mga resulta ng pagsubok ay medyo pabagu-bago (tingnan sa ibaba), 3 pagpapatakbo ang isinagawa at ang nakuha na mga 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, mahalagang tandaan na, hindi katulad ng HBase, ang acceleration na ito ay hindi palaging libre. Dito, mahalagang "ina-unlock" namin ang kakayahan ng CPU na gumawa ng trabaho, sa halip na i-hogging ito dahil sa mga lockout.

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

Dito makikita natin na, sa pangkalahatan, ang pagtaas ng bilang ng mga cache ay nagreresulta sa halos proporsyonal na pagtaas sa paggamit ng CPU. Gayunpaman, mayroong ilang bahagyang mas kapaki-pakinabang na mga kumbinasyon.

Halimbawa, tingnan natin ang setting ng SSC = 3. Ang pagtaas ng pagganap sa buong saklaw ay humigit-kumulang 3.3 beses. Nasa ibaba ang mga resulta ng 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

Samantala, ang pagkonsumo ng CPU ay tumataas ng humigit-kumulang 2.8 beses. Ang pagkakaiba ay hindi malaki, ngunit ang batang si Greta ay masaya na, at marahil ay magkakaroon siya ng oras upang pumasok sa paaralan at gawin ang kanyang takdang-aralin.

Kaya, magkakaroon ito ng positibong epekto sa anumang tool na gumagamit ng mass access sa HDFS (hal., Spark, atbp.), basta magaan ang application code (ibig sabihin, ang bottleneck ay nasa panig ng kliyente ng HDFS) at mayroong available na kapasidad ng CPU. Para ma-verify ito, subukan natin ang pinagsamang epekto 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

Dito makikita mo na sa ilalim ng mga kundisyong ito, hindi gaanong kapansin-pansin ang epekto gaya ng sa mga pinong pagsubok (pagbabasa nang walang anumang pagpoproseso), ngunit medyo posible pa ring mag-ipit ng karagdagang 80K. Kung pinagsama, ang parehong pag-optimize ay nagbibigay ng hanggang 4x na bilis.

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

Sa wakas, kagiliw-giliw na ihambing ang pagganap ng nabasa ng isang katulad na database ng malawak na hanay, Cassandra at HBase.

Para sa layuning ito, ang mga pagkakataon ng karaniwang YCSB load testing utility ay inilunsad mula sa dalawang host (800 thread sa kabuuan). Sa panig ng server, apat na pagkakataon ng RegionServer at Cassandra ang pinatakbo sa apat na host (hindi ang nagpapatakbo ng mga kliyente, upang maiwasan ang kanilang epekto). Ang mga pagbabasa ay isinagawa mula sa mga talahanayan na may mga sumusunod na laki:

HBase — 300 GB sa HDFS (100 GB ng raw data)

Cassandra - 250 GB (replication factor = 3)

Iyon ay, ang volume ay humigit-kumulang pareho (sa HBase ito ay medyo mas malaki).

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 pag-cache at dami ng 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 makikita, ginagawang posible ng mga pag-optimize na ito na mapantayan 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 kapana-panabik na pakikibaka para sa pagiging produktibo.

Pinagmulan: www.habr.com