Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Високе перформансе су један од кључних захтева за рад са великим подацима. У одељењу за учитавање података Сбербанке, ми пумпамо скоро све трансакције у наш Дата Цлоуд заснован на Хадооп-у и стога се бавимо заиста великим токовима информација. Наравно, увек тражимо начине да побољшамо перформансе, а сада желимо да вам кажемо како смо успели да закрпимо РегионСервер ХБасе и ХДФС клијент, захваљујући чему смо успели значајно да повећамо брзину операција читања.
Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Међутим, пре него што пређемо на суштину побољшања, вреди разговарати о ограничењима која се у принципу не могу заобићи ако седите на ХДД.

Зашто ХДД и брзо Рандом Аццесс читање нису компатибилни
Као што знате, ХБасе и многе друге базе података чувају податке у блоковима величине неколико десетина килобајта. Подразумевано је око 64 КБ. Сада замислимо да треба да добијемо само 100 бајтова и тражимо од ХБасе-а да нам да ове податке користећи одређени кључ. Пошто је величина блока у ХФилес-у 64 КБ, захтев ће бити 640 пута већи (само минут!) него што је потребно.

Затим, пошто ће захтев проћи кроз ХДФС и његов механизам за кеширање метаподатака СхортЦирцуитЦацхе (што омогућава директан приступ датотекама), ово доводи до читања већ 1 МБ са диска. Међутим, ово се може подесити помоћу параметра дфс.цлиент.реад.схортцирцуит.буффер.сизе и у многим случајевима има смисла смањити ову вредност, на пример на 126 КБ.

Рецимо да урадимо ово, али поред тога, када почнемо да читамо податке преко јава АПИ-ја, као што су функције попут ФилеЦханнел.реад и затражимо од оперативног система да прочита наведену количину података, он чита „за сваки случај“ 2 пута више , тј. 256 КБ у нашем случају. То је зато што јава нема једноставан начин да подеси ФАДВ_РАНДОМ заставицу да спречи ово понашање.

Као резултат тога, да бисмо добили наших 100 бајтова, 2600 пута више се чита испод хаубе. Чини се да је решење очигледно, смањимо величину блока на килобајт, поставимо поменуту заставицу и добијемо велико убрзање просветљења. Али проблем је у томе што смањењем величине блока за 2 пута, такође смањујемо број читаних бајтова по јединици времена за 2 пута.

Неки добитак од постављања заставице ФАДВ_РАНДОМ се може добити, али само уз високу вишенитост и са величином блока од 128 КБ, али то је максимално неколико десетина процената:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Тестови су спроведени на 100 датотека, свака величине 1 ГБ и смештена на 10 ХДД-а.

Хајде да израчунамо на шта, у принципу, можемо да рачунамо овом брзином:
Рецимо да читамо са 10 дискова брзином од 280 МБ/сец, тј. 3 милиона пута 100 бајтова. Али као што се сећамо, подаци који су нам потребни су 2600 пута мањи од онога што се чита. Дакле, делимо 3 милиона са 2600 и добијамо 1100 записа у секунди.

Депресивно, зар не? То је природа Директног приступа приступ подацима на ХДД-у - без обзира на величину блока. Ово је физичко ограничење случајног приступа и ниједна база података не може да истисне више под таквим условима.

Како онда базе података постижу много веће брзине? Да бисмо одговорили на ово питање, погледајмо шта се дешава на следећој слици:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Овде видимо да је за првих неколико минута брзина заиста око хиљаду записа у секунди. Међутим, даље, због чињенице да се чита много више него што је тражено, подаци завршавају у бафф/цацхе оперативном систему (линук) и брзина се повећава на пристојнијих 60 хиљада у секунди

Дакле, даље ћемо се бавити убрзавањем приступа само подацима који се налазе у кешу ОС-а или који се налазе у ССД/НВМе уређајима за складиштење упоредиве брзине приступа.

У нашем случају, ми ћемо спровести тестове на клупи од 4 сервера, од којих се сваки наплаћује на следећи начин:

ЦПУ: Ксеон Е5-2680 в4 @ 2.40 ГХз 64 нити.
Меморија: 730 ГБ.
јава верзија: 1.8.0_111

И овде је кључна тачка количина података у табелама које треба прочитати. Чињеница је да ако читате податке из табеле која је у потпуности смештена у ХБасе кеш меморију, онда неће доћи ни до читања из бафф/цацхе оперативног система. Зато што ХБасе подразумевано додељује 40% меморије структури која се зове БлоцкЦацхе. У суштини ово је ЦонцуррентХасхМап, где је кључ име датотеке + помак блока, а вредност су стварни подаци на овом офсету.

Дакле, када читамо само из ове структуре, ми видимо одличну брзину, као милион захтева у секунди. Али замислимо да не можемо да издвојимо стотине гигабајта меморије само за потребе базе података, јер постоји много других корисних ствари које раде на овим серверима.

На пример, у нашем случају, обим БлоцкЦацхе-а на једном РС је око 12 ГБ. Спустили смо два РС на један чвор, тј. 96 ГБ је додељено за БлоцкЦацхе на свим чворовима. А података има вишеструко више, на пример, нека то буду 4 табеле, по 130 региона, у којима су фајлови величине 800 МБ, компримовани ФАСТ_ДИФФ, тј. укупно 410 ГБ (ово су чисти подаци, тј. без узимања у обзир фактора репликације).

Дакле, БлоцкЦацхе чини само око 23% укупног обима података и то је много ближе стварним условима онога што се зове БигДата. И ту почиње забава – јер очигледно, што је мање погодака у кеш меморији, то су перформансе лошије. На крају крајева, ако пропустите, мораћете да урадите много посла – тј. спустите се на позивање системских функција. Међутим, ово се не може избећи, па хајде да погледамо потпуно другачији аспект – шта се дешава са подацима унутар кеша?

Хајде да поједноставимо ситуацију и претпоставимо да имамо кеш меморију која одговара само 1 објекту. Ево примера шта ће се десити када покушамо да радимо са обимом података 3 пута већим од кеша, мораћемо:

1. Поставите блок 1 у кеш меморију
2. Уклоните блок 1 из кеша
3. Поставите блок 2 у кеш меморију
4. Уклоните блок 2 из кеша
5. Поставите блок 3 у кеш меморију

5 акција завршено! Међутим, ова ситуација се не може назвати нормалном; у ствари, ми терамо ХБасе да ради гомилу потпуно бескорисног посла. Стално чита податке из кеша ОС-а, смешта их у БлоцкЦацхе, да би их скоро одмах избацио јер је стигао нови део података. Анимација на почетку поста показује суштину проблема - Сакупљач смећа се захуктава, атмосфера се захуктава, мала Грета у далекој и врућој Шведској се узнемирује. А ми ИТ људи заиста не волимо када су деца тужна, па почињемо да размишљамо шта можемо да урадимо поводом тога.

Шта ако ставите не све блокове у кеш, већ само одређени проценат њих, да се кеш не би препунио? Почнимо једноставним додавањем само неколико линија кода на почетак функције за стављање података у БлоцкЦацхе:

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

Овде се ради о следећем: офсет је позиција блока у датотеци и његове последње цифре су насумично и равномерно распоређене од 00 до 99. Стога ћемо прескочити само оне које спадају у опсег који нам је потребан.

На пример, поставите цацхеДатаБлоцкПерцент = 20 и погледајте шта се дешава:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Резултат је очигледан. На графиконима испод, постаје јасно зашто је дошло до таквог убрзања - штедимо много ГЦ ресурса без обављања сизифовског посла стављања података у кеш само да бисмо их одмах бацили у канализацију марсовских паса:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Истовремено, коришћење ЦПУ-а се повећава, али је много мање од продуктивности:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Такође је вредно напоменути да су блокови ускладиштени у БлоцкЦацхе-у различити. Већина, око 95%, су сами подаци. А остало су метаподаци, као што су Блум филтери или ЛЕАФ_ИНДЕКС и итд.. Ови подаци нису довољни, али су веома корисни, јер пре директног приступа подацима, ХБасе се окреће мета да би разумео да ли је потребно даље тражити овде и, ако јесте, где се тачно налази блок од интереса.

Дакле, у коду видимо услов провере буф.гетБлоцкТипе().исДата() а захваљујући овој мета, у сваком случају ћемо га оставити у кешу.

Хајде сада да повећамо оптерећење и мало затегнемо функцију у једном потезу. У првом тесту направили смо гранични проценат = 20 и БлоцкЦацхе је био мало недовољно искоришћен. Сада поставимо на 23% и додајмо 100 нити сваких 5 минута да видимо у ком тренутку долази до засићења:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Овде видимо да оригинална верзија скоро одмах достиже плафон са око 100 хиљада захтева у секунди. Док закрпа даје убрзање до 300 хиљада. У исто време, јасно је да даље убрзање више није тако „бесплатно“; коришћење ЦПУ-а се такође повећава.

Међутим, ово није баш елегантно решење, пошто не знамо унапред који проценат блокова треба да буде кеширан, зависи од профила оптерећења. Због тога је имплементиран механизам за аутоматско подешавање овог параметра у зависности од активности операција читања.

Додате су три опције за контролу овога:

хбасе.лру.цацхе.хеави.евицтион.цоунт.лимит — поставља колико пута треба да се покрене процес избацивања података из кеша пре него што почнемо да користимо оптимизацију (тј. прескакање блокова). Подразумевано је једнако МАКС_ИНТ = 2147483647 и у ствари значи да функција никада неће почети да ради са овом вредношћу. Зато што процес исељења почиње сваких 5 - 10 секунди (зависи од оптерећења) и 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 година. Међутим, можемо поставити овај параметар на 0 и учинити да функција ради одмах након покретања.

Међутим, у овом параметру постоји и оптерећење. Ако је наше оптерећење такво да се краткорочна читања (рецимо током дана) и дуготрајна читања (ноћу) стално мешају, онда можемо да се уверимо да је функција укључена само када су у току операције дугог читања.

На пример, знамо да краткорочна читања обично трају око 1 минут. Нема потребе да почнете да избацујете блокове, кеш неће имати времена да застари и тада можемо подесити овај параметар једнак, на пример, 10. То ће довести до чињенице да ће оптимизација почети да ради тек када дуго- почео је термин активно читање, тј. за 100 секунди. Дакле, ако имамо краткотрајно читање, онда ће сви блокови отићи у кеш и биће доступни (осим оних који ће бити избачени стандардним алгоритмом). А када радимо дуготрајна читања, функција је укључена и имали бисмо много боље перформансе.

хбасе.лру.цацхе.хеави.евицтион.мб.сизе.лимит — поставља колико мегабајта желимо да ставимо у кеш (и, наравно, избацимо) за 10 секунди. Функција ће покушати да достигне ову вредност и одржи је. Поента је следећа: ако гурнемо гигабајте у кеш, онда ћемо морати да избацимо гигабајте, а ово је, као што смо видели горе, веома скупо. Међутим, не би требало да покушавате да га поставите премало, јер ће то довести до прераног изласка из режима прескакања блока. За моћне сервере (око 20-40 физичких језгара) оптимално је подесити око 300-400 МБ. За средњу класу (~10 језгара) 200-300 МБ. За слабе системе (2-5 језгара) 50-100 МБ може бити нормално (није тестирано на овим).

Хајде да погледамо како ово функционише: рецимо да поставимо хбасе.лру.цацхе.хеави.евицтион.мб.сизе.лимит = 500, постоји нека врста оптерећења (читања) и онда сваких ~10 секунди израчунамо колико је бајтова било исељени по формули:

Оверхеад = збир ослобођених бајтова (МБ) * 100 / ограничење (МБ) - 100;

Ако је у ствари 2000 МБ избачено, онда су режијски трошкови једнаки:

2000 * 100 / 500 - 100 = 300%

Алгоритми покушавају да одрже не више од неколико десетина процената, тако да ће функција смањити проценат кешираних блокова, чиме ће имплементирати механизам за аутоматско подешавање.

Међутим, ако оптерећење падне, рецимо да је само 200 МБ избачено и Оверхеад постане негативан (тзв. прекорачење):

200 * 100 / 500 - 100 = -60%

Напротив, ова функција ће повећати проценат кешираних блокова све док Оверхеад не постане позитиван.

Испод је пример како ово изгледа на стварним подацима. Нема потребе да покушавате да достигнете 0%, то је немогуће. Веома је добро када је око 30 - 100%, ово помаже да се избегне превремени излазак из режима оптимизације током краткотрајних пренапона.

хбасе.лру.цацхе.хеави.евицтион.оверхеад.цоеффициент — одређује колико брзо желимо да добијемо резултат. Ако са сигурношћу знамо да су наша читања углавном дуга и не желимо да чекамо, можемо повећати овај однос и брже постићи високе перформансе.

На пример, постављамо овај коефицијент = 0.01. То значи да ће Оверхеад (види горе) бити помножен овим бројем са резултујућим резултатом и проценат кешираних блокова ће бити смањен. Претпоставимо да је Оверхеад = 300% и коефицијент = 0.01, тада ће проценат кешираних блокова бити смањен за 3%.

Слична логика „повратног притиска“ је такође имплементирана за негативне вредности прекорачења (прекорачивања). Пошто су краткорочне флуктуације у обиму читања и избацивања увек могуће, овај механизам вам омогућава да избегнете превремени излазак из режима оптимизације. Повратни притисак има обрнуту логику: што је јаче прекорачење, то се више блокова кешује.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Код за имплементацију

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

Погледајмо сада све ово користећи прави пример. Имамо следећу тест скрипту:

  1. Почнимо да радимо скенирање (25 нити, група = 100)
  2. Након 5 минута, додајте вишеструке добити (25 нити, група = 100)
  3. Након 5 минута, искључите вишеструке добијање (поново остаје само скенирање)

Радимо два покретања, прво хбасе.лру.цацхе.хеави.евицтион.цоунт.лимит = 10000 (што заправо онемогућава функцију), а затим поставља лимит = 0 (омогућава).

У евиденцији испод видимо како је функција укључена и ресетује Оверсхоотинг на 14-71%. С времена на време оптерећење се смањује, што укључује Бацкпрессуре и ХБасе поново кешира више блокова.

Лог РегионСервер
избачен (МБ): 0, однос 0.0, додатни трошкови (%): -100, тежак бројач избацивања: 0, тренутно кеширање ДатаБлоцк (%): 100
избачен (МБ): 0, однос 0.0, додатни трошкови (%): -100, тежак бројач избацивања: 0, тренутно кеширање ДатаБлоцк (%): 100
избачен (МБ): 2170, однос 1.09, додатни трошкови (%): 985, тежак бројач избацивања: 1, тренутно кеширање ДатаБлоцк (%): 91 < почетак
исељено (МБ): 3763, однос 1.08, додатни трошкови (%): 1781, тежак бројач избацивања: 2, тренутно кеширање ДатаБлоцк (%): 76
исељено (МБ): 3306, однос 1.07, додатни трошкови (%): 1553, тежак бројач избацивања: 3, тренутно кеширање ДатаБлоцк (%): 61
исељено (МБ): 2508, однос 1.06, додатни трошкови (%): 1154, тежак бројач избацивања: 4, тренутно кеширање ДатаБлоцк (%): 50
исељено (МБ): 1824, однос 1.04, додатни трошкови (%): 812, тежак бројач избацивања: 5, тренутно кеширање ДатаБлоцк (%): 42
исељено (МБ): 1482, однос 1.03, додатни трошкови (%): 641, тежак бројач избацивања: 6, тренутно кеширање ДатаБлоцк (%): 36
исељено (МБ): 1140, однос 1.01, додатни трошкови (%): 470, тежак бројач избацивања: 7, тренутно кеширање ДатаБлоцк (%): 32
исељено (МБ): 913, однос 1.0, додатни трошкови (%): 356, тежак бројач избацивања: 8, тренутно кеширање ДатаБлоцк (%): 29
исељено (МБ): 912, однос 0.89, додатни трошкови (%): 356, тежак бројач избацивања: 9, тренутно кеширање ДатаБлоцк (%): 26
исељено (МБ): 684, однос 0.76, додатни трошкови (%): 242, тежак бројач избацивања: 10, тренутно кеширање ДатаБлоцк (%): 24
исељено (МБ): 684, однос 0.61, додатни трошкови (%): 242, тежак бројач избацивања: 11, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 456, однос 0.51, додатни трошкови (%): 128, тежак бројач избацивања: 12, тренутно кеширање ДатаБлоцк (%): 21
исељено (МБ): 456, однос 0.42, додатни трошкови (%): 128, тежак бројач избацивања: 13, тренутно кеширање ДатаБлоцк (%): 20
исељено (МБ): 456, однос 0.33, додатни трошкови (%): 128, тежак бројач избацивања: 14, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 15, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 342, однос 0.32, додатни трошкови (%): 71, тежак бројач избацивања: 16, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 342, однос 0.31, додатни трошкови (%): 71, тежак бројач избацивања: 17, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.3, додатни трошкови (%): 14, тежак бројач избацивања: 18, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.29, додатни трошкови (%): 14, тежак бројач избацивања: 19, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.27, додатни трошкови (%): 14, тежак бројач избацивања: 20, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.25, додатни трошкови (%): 14, тежак бројач избацивања: 21, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.24, додатни трошкови (%): 14, тежак бројач избацивања: 22, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.22, додатни трошкови (%): 14, тежак бројач избацивања: 23, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.21, додатни трошкови (%): 14, тежак бројач избацивања: 24, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.2, додатни трошкови (%): 14, тежак бројач избацивања: 25, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 228, однос 0.17, додатни трошкови (%): 14, тежак бројач избацивања: 26, тренутно кеширање ДатаБлоцк (%): 19
избачен (МБ): 456, однос 0.17, додатни трошкови (%): 128, тежак бројач избацивања: 27, тренутно кеширање ДатаБлоцк (%): 18 < додато добија (али табела иста)
исељено (МБ): 456, однос 0.15, додатни трошкови (%): 128, тежак бројач избацивања: 28, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 342, однос 0.13, додатни трошкови (%): 71, тежак бројач избацивања: 29, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 342, однос 0.11, додатни трошкови (%): 71, тежак бројач избацивања: 30, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 342, однос 0.09, додатни трошкови (%): 71, тежак бројач избацивања: 31, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.08, додатни трошкови (%): 14, тежак бројач избацивања: 32, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.07, додатни трошкови (%): 14, тежак бројач избацивања: 33, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.06, додатни трошкови (%): 14, тежак бројач избацивања: 34, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.05, додатни трошкови (%): 14, тежак бројач избацивања: 35, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.05, додатни трошкови (%): 14, тежак бројач избацивања: 36, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 228, однос 0.04, додатни трошкови (%): 14, тежак бројач избацивања: 37, тренутно кеширање ДатаБлоцк (%): 17
избачен (МБ): 109, однос 0.04, додатни трошкови (%): -46, тежак бројач избацивања: 37, тренутно кеширање ДатаБлоцк (%): 22 < повратни притисак
исељено (МБ): 798, однос 0.24, додатни трошкови (%): 299, тежак бројач избацивања: 38, тренутно кеширање ДатаБлоцк (%): 20
исељено (МБ): 798, однос 0.29, додатни трошкови (%): 299, тежак бројач избацивања: 39, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 570, однос 0.27, додатни трошкови (%): 185, тежак бројач избацивања: 40, тренутно кеширање ДатаБлоцк (%): 17
исељено (МБ): 456, однос 0.22, додатни трошкови (%): 128, тежак бројач избацивања: 41, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 342, однос 0.16, додатни трошкови (%): 71, тежак бројач избацивања: 42, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 342, однос 0.11, додатни трошкови (%): 71, тежак бројач избацивања: 43, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 228, однос 0.09, додатни трошкови (%): 14, тежак бројач избацивања: 44, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 228, однос 0.07, додатни трошкови (%): 14, тежак бројач избацивања: 45, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 228, однос 0.05, додатни трошкови (%): 14, тежак бројач избацивања: 46, тренутно кеширање ДатаБлоцк (%): 16
исељено (МБ): 222, однос 0.04, додатни трошкови (%): 11, тежак бројач избацивања: 47, тренутно кеширање ДатаБлоцк (%): 16
избачен (МБ): 104, однос 0.03, додатни трошкови (%): -48, тежак бројач избацивања: 47, тренутно кеширање ДатаБлоцк (%): 21 < прекид добија
исељено (МБ): 684, однос 0.2, додатни трошкови (%): 242, тежак бројач избацивања: 48, тренутно кеширање ДатаБлоцк (%): 19
исељено (МБ): 570, однос 0.23, додатни трошкови (%): 185, тежак бројач избацивања: 49, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 342, однос 0.22, додатни трошкови (%): 71, тежак бројач избацивања: 50, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 228, однос 0.21, додатни трошкови (%): 14, тежак бројач избацивања: 51, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 228, однос 0.2, додатни трошкови (%): 14, тежак бројач избацивања: 52, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 228, однос 0.18, додатни трошкови (%): 14, тежак бројач избацивања: 53, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 228, однос 0.16, додатни трошкови (%): 14, тежак бројач избацивања: 54, тренутно кеширање ДатаБлоцк (%): 18
исељено (МБ): 228, однос 0.14, додатни трошкови (%): 14, тежак бројач избацивања: 55, тренутно кеширање ДатаБлоцк (%): 18
избачен (МБ): 112, однос 0.14, додатни трошкови (%): -44, тежак бројач избацивања: 55, тренутно кеширање ДатаБлоцк (%): 23 < повратни притисак
исељено (МБ): 456, однос 0.26, додатни трошкови (%): 128, тежак бројач избацивања: 56, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.31, додатни трошкови (%): 71, тежак бројач избацивања: 57, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 58, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 59, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 60, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 61, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 62, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 63, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.32, додатни трошкови (%): 71, тежак бројач избацивања: 64, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 65, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 66, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.32, додатни трошкови (%): 71, тежак бројач избацивања: 67, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 68, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.32, додатни трошкови (%): 71, тежак бројач избацивања: 69, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.32, додатни трошкови (%): 71, тежак бројач избацивања: 70, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 71, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 72, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 73, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 74, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 75, тренутно кеширање ДатаБлоцк (%): 22
исељено (МБ): 342, однос 0.33, додатни трошкови (%): 71, тежак бројач избацивања: 76, тренутно кеширање ДатаБлоцк (%): 22
избачен (МБ): 21, однос 0.33, додатни трошкови (%): -90, тежак бројач избацивања: 76, тренутно кеширање ДатаБлоцк (%): 32
избачен (МБ): 0, однос 0.0, додатни трошкови (%): -100, тежак бројач избацивања: 0, тренутно кеширање ДатаБлоцк (%): 100
избачен (МБ): 0, однос 0.0, додатни трошкови (%): -100, тежак бројач избацивања: 0, тренутно кеширање ДатаБлоцк (%): 100

Скенирања су била потребна да би се приказао исти процес у облику графикона односа између два одељка кеша - једноструког (где су блокови који никада раније нису били тражени) и вишеструки (подаци „затражени“ бар једном се чувају овде):

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

И на крају, како изгледа рад параметара у облику графикона. Поређења ради, кеш је на почетку потпуно искључен, затим је покренут ХБасе са кеширањем и одлагањем почетка рада оптимизације за 5 минута (30 циклуса избацивања).

Комплетан код се може наћи у захтеву за повлачење ХБАСЕ 23887 на гитхуб-у.

Међутим, 300 хиљада читања у секунди није све што се може постићи на овом хардверу под овим условима. Чињеница је да када треба да приступите подацима преко ХДФС-а, користи се механизам СхортЦирцуитЦацхе (у даљем тексту ССЦ), који вам омогућава да директно приступите подацима, избегавајући мрежне интеракције.

Профилисање је показало да иако овај механизам даје велику добит, он такође у неком тренутку постаје уско грло, јер се скоро све тешке операције дешавају унутар браве, што доводи до блокирања већину времена.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Пошто смо ово схватили, схватили смо да се проблем може заобићи стварањем низа независних ССЦ-ова:

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

И онда радите са њима, искључујући раскрснице такође на последњој офсет цифри:

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

Сада можете почети са тестирањем. Да бисмо то урадили, читаћемо датотеке са ХДФС-а помоћу једноставне апликације са више нити. Подесите параметре:

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

И само прочитајте датотеке:

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

Овај код се извршава у одвојеним нитима и повећаћемо број истовремено читаних датотека (са 10 на 200 - хоризонтална оса) и број кеш меморија (са 1 на 10 - графика). Вертикална оса показује убрзање које је резултат повећања ССЦ у односу на случај када постоји само један кеш.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Како читати графикон: Време извршења за 100 хиљада читања у блоковима од 64 КБ са једним кешом захтева 78 секунди. Док са 5 кеш меморија потребно је 16 секунди. Оне. долази до убрзања од ~5 пута. Као што се види из графикона, ефекат није много приметан за мали број паралелних читања, почиње да игра приметну улогу када има више од 50 читања нити. Приметно је и повећање броја ССЦ-ова са 6 и изнад даје знатно мањи пораст перформанси.

Напомена 1: пошто су резултати теста прилично променљиви (погледајте доле), извршена су 3 рада и добијене вредности су усредњене.

Напомена 2: Добитак перформанси од конфигурисања случајног приступа је исти, иако је сам приступ нешто спорији.

Међутим, потребно је појаснити да, за разлику од случаја са ХБасе, ово убрзање није увек бесплатно. Овде „откључавамо“ способност ЦПУ-а да ради више, уместо да виси на бравама.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Овде можете приметити да, генерално, повећање броја кеш меморија даје приближно пропорционално повећање искоришћења ЦПУ-а. Ипак, добитних комбинација има нешто више.

На пример, погледајмо ближе поставку ССЦ = 3. Повећање перформанси на опсегу је око 3.3 пута. Испод су резултати из све три одвојене вожње.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Док се потрошња процесора повећава за око 2.8 пута. Разлика није велика, али мала Грета је већ срећна и можда има времена да иде у школу и иде на часове.

Дакле, ово ће имати позитиван ефекат за било коју алатку која користи масовни приступ ХДФС-у (на пример Спарк, итд.), под условом да је код апликације лаган (тј. утикач је на страни ХДФС клијента) и да постоји бесплатна ЦПУ снага . Да проверимо, хајде да тестирамо какав ће ефекат имати комбинована употреба БлоцкЦацхе оптимизације и ССЦ подешавања за читање са ХБасе-а.

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Види се да у оваквим условима ефекат није тако велики као у рафинираним тестовима (читање без икакве обраде), али је овде сасвим могуће истиснути додатних 80К. Заједно, обе оптимизације обезбеђују до 4к убрзање.

Такође је направљен ПР за ову оптимизацију [ХДФС-15202], који је спојен и ова функционалност ће бити доступна у будућим издањима.

И на крају, било је занимљиво упоредити перформансе читања сличне базе података са широким колонама, Цассандра и ХБасе.

Да бисмо то урадили, покренули смо инстанце стандардног ИЦСБ услужног програма за тестирање оптерећења са два хоста (укупно 800 нити). На страни сервера - 4 инстанце РегионСервер-а и Цассандра на 4 хоста (не на онима на којима клијенти раде, да би се избегао њихов утицај). Очитавања су долазила из табела величине:

ХБасе – 300 ГБ на ХДФС (100 ГБ чисти подаци)

Цассандра - 250 ГБ (фактор репликације = 3)

Оне. запремина је била приближно иста (у ХБасе мало више).

ХБасе параметри:

дфс.цлиент.схорт.цирцуит.нум = 5 (ХДФС оптимизација клијента)

хбасе.лру.цацхе.хеави.евицтион.цоунт.лимит = 30 - то значи да ће закрпа почети да ради након 30 избацивања (~5 минута)

хбасе.лру.цацхе.хеави.евицтион.мб.сизе.лимит = 300 — циљни обим кеширања и избацивања

ИЦСБ евиденције су рашчлањене и компајлиране у Екцел графиконе:

Како повећати брзину читања са ХБасе-а до 3 пута и са ХДФС-а до 5 пута

Као што видите, ове оптимизације омогућавају упоређивање перформанси ових база података под овим условима и постизање 450 хиљада читања у секунди.

Надамо се да ће ове информације некоме бити корисне током узбудљиве борбе за продуктивност.

Извор: ввв.хабр.цом

Додај коментар