KĂ”rge jĂ”udlus on ĂŒks peamisi nĂ”udmisi suurte andmete töötlemisel. Meie andmehalduse meeskond Sberis tegeleb praktiliselt kĂ”igi tehingute edastamisega meie Hadoopi andmepilve ja seega puutume kokku tĂ”eliselt suurte infovoogudega. Loomulikult otsime me pidevalt vĂ”imalusi jĂ”udluse parandamiseks ning nĂŒĂŒd tahame rÀÀkida, kuidas suutsime parandada RegionServer HBase ja HDFS-kliendi, mille abil suutsime oluliselt suurendada lugemisoperatsiooni kiirus.

Kuid enne, kui lĂ€heme edasi muudatuste tĂ”elise olemuse juurde, tasub rÀÀkida piirangutest, mida on pĂ”himĂ”tteliselt vĂ”imatu ĂŒletada, kui kasutada HDD-d.
Miks HDD ja kiired juhuslikud lugemised ei sobi kokku
Nagu teada, salvestavad HBase ja paljud muud andmebaasid andmeid plokkidena, mille suurus on mitukĂŒmmend kilobaiti. Vaikimisi on see umbes 64 KB. Kujutame nĂŒĂŒd ette, et peame vĂ€lja tooma vaid 100 baiti ja palume HBase'il need andmed teatud vĂ”tme alusel vĂ€lja anda. Kuna HFiles'i ploki suurus on 64 KB, tellitakse 640 korda rohkem (pange tĂ€hele!) kui vajalik.
Edasi liikudes, kuna pÀring liigub HDFS-i ja selle metaanmete vahemÀlu mehhanismi kaudu ShortCircuitCache (mis vÔimaldab otsefailidele juurde pÀÀseda), toob see kaasa lugemise juba 1 MB andmeid kettalt. Siiski saab seda reguleerida parameetriga dfs.client.read.shortcircuit.buffer.size ja paljudes juhtudes on mÔistlik seda vÀÀrtust vÀhendada, nÀiteks 126 KB-ni.
Oletame, et me teeme seda, kuid kui hakkame lugema andmeid java api kaudu, nĂ€iteks funktsioonide nagu FileChannel.read abil, ja palume operatsioonisĂŒsteemil lugeda mÀÀratud andmemahtu, siis loetakse "igaks juhuks" 2 korda rohkem, st 256 KB meie puhul. See juhtub sellepĂ€rast, et javast puudub lihtne vĂ”imalus seada lippu FADV_RANDOM, mis takistaks sellist kĂ€itumist.
KokkuvÔttes, et saada meie 100 baiti, loetakse mastaabis 2600 korda rohkem. Tundub, et lahendus on ilmne, vÀhendame ploki suurust kilobaitide kaupa, seame nimetatud lipu ja saavutame suurepÀrase kiirenduse. Kuid probleem on selles, et ploki suurust vÀhendades vÀhendame ka lugemisvÔimekust ajavahemiku jooksul sama palju.
MÔningast kasu FADV_RANDOM lipu seadmisest vÔib saada, kuid ainult suure mitme lÔime korral ja ploki suuruse korral alates 128 Kb, kuid see on maksimaalselt paar tosinat protsenti:

Testid viidi lĂ€bi 100 failiga, igaĂŒks 1 GB suurune ja paigutatud 10 HDD kettale.
Arvestagem, millega me sellise kiiruseta ĂŒldse arvestada saame:
Oletame, et loeme 10 kettalt kiirusel 280 MB/s, st 3 miljonit korda 100 baiti. Kuid nagu me mÀletame, on andmed, mida me vajame, 2600 korda vÀhem, kui loetud. Seega jagame 3 miljonit 2600-ga ja saame 1100 kirjet sekundis.
Kurvastav, eks? Nii on lood Juhusliku juurdepÀÀsu andmetele HDD-l â sĂ”ltumata ploki suurusest. See on fĂŒĂŒsiline piir juhusliku juurdepÀÀsu puhul ja suuremat ei suuda sellistes tingimustes ĂŒkski andmebaas vĂ€lja pigistada.
Kuidas siis andmebaasid suudavad palju kĂ”rgemat kiirus saavutada? Selle kĂŒsimuse vastamiseks vaatame, mis toimub jĂ€rgmisel pildil:

Siin nĂ€eme, et esimestel minutitel on kiirus tĂ”epoolest ligikaudu tuhat kirjet sekundis. Kuid hiljem, kuna loetakse mĂ€rkimisvÀÀrselt rohkem, kui on seadistatud, jÀÀvad andmed operatsioonisĂŒsteemi (Linux) buff/cache'i ning kiirus tĂ”useb enam-vĂ€hem 60 000-ni sekundis.
Seega vaatame edaspidi, kuidas kiirusel juurde pÀÀseda ainult neile andmetele, mis on operatsioonisĂŒsteemi cache'is vĂ”i samalaadsetes, nĂ€iteks SSD/NVMe salvestites.
Meie puhul teeme teste nelja serveri seadistusel, millest igaĂŒhes on jĂ€rgmine konfiguratsioon:
CPU: Xeon E5-2680 v4 @ 2.40GHz 64 lÔime.
MĂ€lu: 730 GB.
java versioon: 1.8.0_111
Ja siin on tĂ”eliselt oluline hetk â andmete maht tabelites, mida tuleb lugeda. Asi on selles, et kui lugeda andmeid tabelist, mis mahub tĂ€ielikult HBase'i cache'i, siis ei jĂ”ua isegi operatsioonisĂŒsteemi buff/cache'i lugemine. Sest HBase eraldab vaikimisi 40% mĂ€lust struktuurile, mida nimetatakse BlockCache'iks. Sisuliselt on see ConcurrentHashMap, kus vĂ”ti on failinimi + ploki hĂŒvitamine ja vÀÀrtus on tegelikult andmed selle hĂŒvitamise kohta.
SeetÔttu, kui lugemine toimub ainult sellest struktuurist, me , umbes miljoni pÀringuga sekundis. Kuid kujutame ette, et me ei saa anda sadu gigabaiti mÀlu ainult andmebaasi vajadustele, kuna nendel serveritel töötab ka palju muud kasulikku.
NĂ€iteks meie juhul on ĂŒhe RS BlockCache'i maht umbes 12 GB. Me oleme kaks RS-d ĂŒhele sĂ”lmele installinud, st BlockCache'ile on eraldatud kokku 96 GB kĂ”igil sĂ”lmedel. Andmeid on aga palju rohkem, oletame, et need on 4 tabelit, 130 piirkonnas, milles failide suurus on 800 MB, kokkutĂ”mmatud FAST_DIFF meetodil, st kokku 410 GB (need on puhtad andmed, st replikatsiooni tegurit arvesse vĂ”tmata).
Seega moodustavad BlockCache vaid umbes 23% kogu andmete mahust ja see on palju lĂ€hemal reaalsetele tingimustele, mida nimetatakse BigData'ks. Ja siin algab kĂ”ige huvitavam â on ju selge, et mida vĂ€hem on pÀÀsukesi vahemĂ€lus, seda halvem on jĂ”udlus. Sest kui vahemĂ€lu ei toimi, tuleb teha palju tööd â st minna sĂŒsteemsete funktsioonide kutsumise tasemeni. Kuid sellest ei saa mööda ja seega vaatame hoopis teist aspekti â mis juhtub andmetega vahemĂ€lus?
Lihtsustame olukorda ja oletame, et meil on vahemÀlu, kuhu mahtub ainult 1 objekt. Siin on nÀide sellest, mis juhtub, kui proovime töötada andmemahtudega, mis on 3 korda suuremad kui vahemÀlu, peame:
1. Asetama ploki 1 vahemÀllu
2. Eemaldama ploki 1 vahemÀlust
3. Asetama ploki 2 vahemÀllu
4. Eemaldama ploki 2 vahemÀlust
5. Asetama ploki 3 vahemÀllu
Tehti 5 toimingut! Kuid normaalne ei saa seda olukorda kuidagi nimetada, pĂ”himĂ”tteliselt paneme HBase'i tegema hunniku tĂ€iesti kasutut tööd. Ta loeb pidevalt andmeid operatsioonisĂŒsteemi vahemĂ€lust, asetab need endale BlockCache'i, et peaaegu kohe need kĂ”rvale visata, kuna saabub uus andmepakett. Postituse alguses olev animatsioon nĂ€itab probleemi olemust â Garbage Collector töötab ĂŒle, atmosfÀÀr soojeneb, vĂ€ike Greta kaugel kuumas Rootsis on mures. Ja meie, IT spetsialistid, ei armasta vĂ€ga, kui lapsed on kurvad, seega hakkame mĂ”tlema, mida selle probleemiga ette vĂ”tta.
Aga mis siis, kui asetada vahemĂ€llu mitte kĂ”iki plokkide, vaid ainult teatud protsent neist, et vahemĂ€lu ei oleks ĂŒletĂ€idetud? Alustame lihtsalt paar rida koodi lisamisega funktsiooni alguses, mis asetab andmed BlockCache'i:
public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
if (cacheDataBlockPercent != 100 && buf.getBlockType().isData()) {
if (cacheKey.getOffset() % 100 >= cacheDataBlockPercent) {
return;
}
}
...
Siin on mĂ”te jĂ€rgmine: ofset on ploki asukoht failis ja viimased numbrid on juhuslikult ja ĂŒhtlaselt jaotatud vahemikus 00 kuni 99. SeetĂ”ttu jĂ€tame vahele vaid need, mis jÀÀvad soovitud vahemikku.
NĂ€iteks seadke cacheDataBlockPercent = 20 ja vaatame, mis juhtub:

Tulemus on kĂ€es. Allolevatelt graafikutelt nĂ€htub, kuidas kiirus on saavutatud â sÀÀstame hulga GC ressursse, et mitte tegeleda sisefilosoofia tööga, et andmed vahemĂ€lu paigutada, ainult selleks, et visata need marsi koertele saba alla:

CPU kasutamine kasvab, kuid oluliselt vÀhem kui tootlikkus:

Siin tasub veel mĂ€rkida, et BlockCache'is hoitavad plokid vĂ”ivad olla erinevad. Enamik, umbes 95%, on tegelikult andmed. ĂlejÀÀnud on metaandmed, nĂ€iteks Bloom filtri vĂ”i LEAF_INDEX ja . Nende andmete on vĂ€he, kuid need on vĂ€ga kasulikud, kuna enne, kui pöörduda otse andmete poole, pöördub HBase meta poole, et mĂ”ista, kas on vaja otsida edasi ja kui jah, siis kus tĂ€pselt asub huvipakkuv plokk.
SeetÔttu nÀeme koodis tingimust, mis kontrollib buf.getBlockType().isData() ja tÀnu sellele metale jÀtame me vahemÀlu igal juhul.
NĂŒĂŒd suurendame koormust ja samal ajal pisut kohandame funktsiooni. Esimeses testis seadsime lĂ”ikeprotsendi = 20 ja BlockCache oli veidi alakoormatud. NĂŒĂŒd seadke 23% ja lisame 100 niiti iga 5 minuti jĂ€rel, et nĂ€ha, millal toimub kĂŒllastus:

Siin nÀeme, et algversioon jÔuab peaaegu kohe piiri 100 tuhat pÀringut sekundis. Samas, patch annab kiiruseni 300 tuhat. Samal ajal on selge, et edasine kiirus ei ole enam nii 'tasuta', CPU kasutamine kasvab samuti.
Kuid see pole vÀga elegantne lahendus, kuna me ei tea ette, kui suur protsent plokke on vaja vahemÀlu salvestada, see sÔltub koormuse profiilist. SeetÔttu on realiseeritud mehhanism selle parameetri automaatseks kohandamiseks lugemisoperatsioonide aktiivsuse pÔhjal.
Selle haldamiseks on lisatud kolm parameetrit:
hbase.lru.cache.heavy.eviction.count.limit â seabestab, mitu korda tuleb andmete eemaldamise protsess töösse vĂ”tta enne, kui hakkame kasutama optimeerimist (st vahevĂ€listama plokke). Vaikimisi on see MAX_INT = 2147483647 ja tĂ€hendab tegelikult, et funktsioon ei hakka kunagi nii suure vÀÀrtuse korral tööle. Kuna andmete eemaldamine kĂ€ivitub iga 5â10 sekundi jĂ€rel (sĂ”ltub koormusest) ja 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 aastat. Siiski saame selle parameetri seadistada 0 peale, mis sunnib funktsiooni kohe pĂ€rast kĂ€ivitamist tööle.
Kuid selle parameetri puhul on ka kasulik koormus. Kui meie koormusetĂŒĂŒp tĂ€hendab, et lĂŒhiajalised lugemised (nĂ€iteks pĂ€eval) ja pikaajalised (öösiti) vahelduvad pidevalt, saame teha nii, et funktsioon aktiveerub ainult siis, kui kĂ€ivad pikaajalised lugemised.
NĂ€iteks me teame, et lĂŒhiajalised lugemised kestavad tavaliselt umbes 1 minuti. Siis ei tohiks alustada plokkide eemaldamist, kuna vahemĂ€lu ei jĂ”ua aeguda ja saame mÀÀrata selle parameetri nĂ€iteks 10. See tĂ€hendab, et optimeerimine hakkab tööle alles siis, kui on alanud pikaajaline aktiivne lugemine, st 100 sekundi pĂ€rast. Seega, kui meil on lĂŒhiajaline lugemine, pÀÀsevad kĂ”ik plokid vahemĂ€llu ja on kergesti kĂ€ttesaadavad (vĂ€lja arvatud need, mis eemaldatakse standardse algoritmi poolt). Kui aga teeme pikaajalisi lugemisi, hakkab funktsioon tööle ja meil on tunduvalt kĂ”rgem jĂ”udlus.
hbase.lru.cache.heavy.eviction.mb.size.limit â seab, kui palju megabaiti soovime vahemĂ€llu paigutada (ja loomulikult eemaldada) 10 sekundi jooksul. Funktsioon pĂŒĂŒab saavutada seda vÀÀrtust ja hoida seda. Idee on jĂ€rgmine: kui paneme vahemĂ€llu gigabaite, peame ka eemaldama gigabaite, mis nagu me eespool nĂ€gime, on ĂŒsna kulukas. Samuti ei tohiks proovida seda mÀÀrata liiga vĂ€ikseks, kuna see viib plokkide vahelt Ă€ra jÀÀmise reĆŸiimist varakult vĂ€ljumiseni. Tugevatele serveritele (20-40 fĂŒĂŒsilise tuumaga) on ideaalne seada umbes 300-400 MB. Keskmise klassi (~10 tuuma) puhul 200-300 MB. NĂ”rgematele sĂŒsteemidele (2-5 tuuma) vĂ”ib olla normaalne 50-100 MB (selliseid pole testitud).
Vaatame, kuidas see töötab: oletame, et oleme seadnud hbase.lru.cache.heavy.eviction.mb.size.limit = 500, toimub mingi koormus (lugemine) ja siis iga ~10 sekundi jÀrel arvutame, kui palju baite on vÀlja visatud jÀrgmise valemi jÀrgi:
Overhead = Vabastatud baitide kogus (MB) * 100 / Limiit (MB) â 100;
Kui tegelikult on vÀlja visatud 2000 MB, siis Overhead on:
2000 * 100 / 500 â 100 = 300%
Algoritmid pĂŒĂŒavad hoida ĂŒle 10% ei rohkem, nii et funktsioon vĂ€hendab vahemĂ€lu blokeeringute protsenti, rakendades sellega automaatse hÀÀlestamise mehhanismi.
Kuid kui koormus on langenud, oletame, et vÀlja on visatud ainult 200 MB ja Overhead on muutunud negatiivseks (nn overshooting):
200 * 100 / 500 â 100 = -60%
Siis funktsioon vastupidi, suurendab vahemÀlu blokeeringute protsenti, kuni Overhead muutub positiivseks.
Allpool on nĂ€ide, kuidas see tegelike andmete pĂ”hjal vĂ€lja nĂ€eb. Ei tohiks pĂŒĂŒda saavutada 0%, see on vĂ”imatu. Ăsna hea, kui see on umbes 30â100%, see aitab vĂ€ltida enneaegset eemaldumist optimeerimisreĆŸiimist lĂŒhiajaliste tĂ”usude korral.
hbase.lru.cache.heavy.eviction.overhead.coefficient â mÀÀrab, kui kiiresti soovime tulemust saada. Kui me teame kindlasti, et meie lugemine on peamiselt pikk ja me ei soovi oodata, saame seda koefitsienti suurendada ja saavutada kĂ”rge jĂ”udluse kiiremini.
NĂ€iteks oleme seadnud selle koefitsiendi = 0.01. See tĂ€hendab, et Overhead (vt ĂŒlal) korrutatakse selle numbriga ja saadud tulemusena vĂ€heneb vahemĂ€lu blokeeringute protsent. Oletame, et Overhead = 300%, ja koefitsient = 0.01, siis vahemĂ€lu blokeeringute protsent vĂ€heneb 3%.
Sarnane «Backpressure» loogika on rakendatud ka negatiivsete Overhead vÀÀrtuste (overshooting) jaoks. Kuna lĂŒhiajalised kĂ”ikumised lugemiste ja vĂ€lja viskamiste mahtudes on alati vĂ”imalikud, vĂ”imaldab see mehhanism vĂ€ltida enneaegset eemaldumist optimeerimisreĆŸiimist. Backpressure'l on pööratud loogika: mida tugevam on overshooting, seda rohkem blokeeringuid on vahemĂ€lus.

Rakenduskood
LruBlockCache cache = this.cache.get();
if (cache == null) {
break;
}
freedSumMb += cache.evict() / 1024 / 1024;
/*
* MÔnikord loeme me rohkem andmeid, kui BlockCache mahutab
* ja see on pÔhjus kÔrgele evakueerimise mÀÀrale.
* See omakorda viib tugeva prĂŒgikoguja töödeni.
* Nii et palju plokke pannakse BlockCache'i, aga ei loeta,
* kulutades palju CPU ressursse.
* Siin analĂŒĂŒsime, kui palju octette on vabastatud ja otsustame,
* kas on aeg vÀhendada vaheblokkide kogust.
* See aitab vÀltida liigset vaheblokkide kogumist BlockCache'i,
* kui evict() töötab vÀga aktiivselt ja sÀÀstab CPU-d teiste tööde jaoks.
* Lisainfot: https://issues.apache.org/jira/browse/HBASE-23887
*/
// Esiteks peame kontrollima, kui kaua aega
// on möödunud eelmisest evict() teostamisest
// See peaks olema peaaegu sama aeg (+/- 10s)
// kuna saame iga kord vabastatud baitide sarnase mahu.
// 10s, kuna see on vaikimisi periood evict() kÀivitamiseks (vt eespool this.wait)
long stopTime = System.currentTimeMillis();
if ((stopTime - startTime) > 1000 * 10 - 1) {
// Siin peame arvutama, milline olukord meil on.
// Meil on piir "hbase.lru.cache.heavy.eviction.bytes.size.limit"
// ja saame selle ĂŒlehinnangu arvutada.
// Kasutame seda teavet, et otsustada,
// kuidas muuta vaheblokkide protsenti.
freedDataOverheadPercent =
(int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
// NĂŒĂŒd oleme olukorras, kus oleme piirist ĂŒle
// Kuid vĂ”ib-olla ignoreerime seda, sest see lĂ”ppeb ĂŒsna pea
heavyEvictionCount++;
if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
// See kestab kaua ja peame nĂŒĂŒd vaheblokkide
// kogust vÀhendama.
// Siin arvutame, kui palju plokke soovime vahele jÀtta.
// See sÔltub:
// 1. Ălehindamine - kui ĂŒlehindamine on suur, saame olla agressiivsemad
// vaheblokkide vÀhendamisel.
// 2. Kui kiiresti soovime tulemust saada. Kui teame, et meie
// raske lugemine kestab kaua, ei soovi me oodata ja saame
// koefitsienti suurendada ning head tulemuslikkust ĂŒsna pea.
// Kuid kui me ei ole kindlad, saame teha seda aeglaselt ja see vÔib
// vĂ€ltida enneaegset lahkumist sellest reĆŸiimist. Seega, kui koefitsient on
// kÔrgem, saame paremat tulemuslikkust, kui raske lugemine on stabiilne.
// Aga kui lugemine muutub, saame sellele kohanduda ja seada
// koefitsient madalamale vÀÀrtusele.
int change =
(int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
// Kuid praktika nÀitab, et 15% vÀhendamine on tÀiesti piisav.
// Me ei ole ahned (see vÔib viia enneaegsele lahkumisele).
change = Math.min(15, change);
change = Math.max(0, change); // Ma arvan, et see ei juhtu kunagi, kuid kontrolli igaks juhuks
// Nii et see on vÔtmepunkt, siin vÀhendame % vaheblokkidest
cache.cacheDataBlockPercent -= change;
// Kui me lÀheme liiga madalale, peame siin peatuma, 1% peaks olema igal juhul.
cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
}
} else {
// Noh, oleme saanud ĂŒletĂ€itmise.
// VĂ”ib-olla on see lihtsalt lĂŒhiajaline kĂ”ikumine ja saame jÀÀda sellesse reĆŸiimi.
// See aitab vĂ€ltida enneaegset lahkumist lĂŒhiajaliste kĂ”ikumiste tĂ”ttu.
// Kui ĂŒletĂ€itmine on vĂ€hem kui 90%, proovime tĂ”sta vaheblokkide
// protsenti ja loodame, et see on piisav.
if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
// Lihtne loogika: rohkem ĂŒletĂ€itmist - rohkem vahebloke (tagasisurve)
int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
cache.cacheDataBlockPercent += change;
// Kuid see ei saa olla rohkem kui 100%, seega kontrollime seda.
cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
} else {
// Tundub, et raske lugemine on lÀbi.
// Lihtsalt lahkume sellest reĆŸiimist.
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;
}
Vaatame nĂŒĂŒd kĂ”ike seda reaalse nĂ€ite kaudu. Meil on jĂ€rgmine teststsenaarium:
- Alustame Scan'i tegemisega (25 lÔime, partii = 100)
- Viie minuti pÀrast lisame multi-gets (25 lÔime, partii = 100)
- Viie minuti pÀrast peatame multi-gets'i (jÀÀb jÀlle ainult scan)
Teeme kaks katset, esiteks hbase.lru.cache.heavy.eviction.count.limit = 10000 (mis pĂ”himĂ”tteliselt keelab funktsiooni), seejĂ€rel seadistame limiidi = 0 (lĂŒlitab sisse).
Allpool logides nÀeme, kuidas funktsioon aktiveeritakse, Overshooting langeb 14-71% peale. Aeg-ajalt koormus vÀheneb, mis aktiveerib Backpressure ja HBase salvestab rohkem bloke.
RegionServeri logi
evicted (MB): 0, suhe 0.0, lisakulu (%): -100, raske evakueerimise arvesti: 0, praegune andmeploki salvestamine (%): 100
evicted (MB): 0, suhe 0.0, lisakulu (%): -100, raske evakueerimise arvesti: 0, praegune andmeploki salvestamine (%): 100
evicted (MB): 2170, suhe 1.09, lisakulu (%): 985, raske evakueerimise arvesti: 1, praegune andmeploki salvestamine (%): 91 < start
evicted (MB): 3763, suhe 1.08, lisakulu (%): 1781, raske evakueerimise arvesti: 2, praegune andmeploki salvestamine (%): 76
evicted (MB): 3306, suhe 1.07, lisakulu (%): 1553, raske evakueerimise arvesti: 3, praegune andmeploki salvestamine (%): 61
evicted (MB): 2508, suhe 1.06, lisakulu (%): 1154, raske evakueerimise arvesti: 4, praegune andmeploki salvestamine (%): 50
evicted (MB): 1824, suhe 1.04, lisakulu (%): 812, raske evakueerimise arvesti: 5, praegune andmeploki salvestamine (%): 42
evicted (MB): 1482, suhe 1.03, lisakulu (%): 641, raske evakueerimise arvesti: 6, praegune andmeploki salvestamine (%): 36
evicted (MB): 1140, suhe 1.01, lisakulu (%): 470, raske evakueerimise arvesti: 7, praegune andmeploki salvestamine (%): 32
evicted (MB): 913, suhe 1.0, lisakulu (%): 356, raske evakueerimise arvesti: 8, praegune andmeploki salvestamine (%): 29
evicted (MB): 912, suhe 0.89, lisakulu (%): 356, raske evakueerimise arvesti: 9, praegune andmeploki salvestamine (%): 26
evicted (MB): 684, suhe 0.76, lisakulu (%): 242, raske evakueerimise arvesti: 10, praegune andmeploki salvestamine (%): 24
evicted (MB): 684, suhe 0.61, lisakulu (%): 242, raske evakueerimise arvesti: 11, praegune andmeploki salvestamine (%): 22
evicted (MB): 456, suhe 0.51, lisakulu (%): 128, raske evakueerimise arvesti: 12, praegune andmeploki salvestamine (%): 21
evicted (MB): 456, suhe 0.42, lisakulu (%): 128, raske evakueerimise arvesti: 13, praegune andmeploki salvestamine (%): 20
evicted (MB): 456, suhe 0.33, lisakulu (%): 128, raske evakueerimise arvesti: 14, praegune andmeploki salvestamine (%): 19
evicted (MB): 342, suhe 0.33, lisakulu (%): 71, raske evakueerimise arvesti: 15, praegune andmeploki salvestamine (%): 19
evicted (MB): 342, suhe 0.32, lisakulu (%): 71, raske evakueerimise arvesti: 16, praegune andmeploki salvestamine (%): 19
evicted (MB): 342, suhe 0.31, lisakulu (%): 71, raske evakueerimise arvesti: 17, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.3, lisakulu (%): 14, raske evakueerimise arvesti: 18, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.29, lisakulu (%): 14, raske evakueerimise arvesti: 19, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.27, lisakulu (%): 14, raske evakueerimise arvesti: 20, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.25, lisakulu (%): 14, raske evakueerimise arvesti: 21, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.24, lisakulu (%): 14, raske evakueerimise arvesti: 22, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.22, lisakulu (%): 14, raske evakueerimise arvesti: 23, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.21, lisakulu (%): 14, raske evakueerimise arvesti: 24, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.2, lisakulu (%): 14, raske evakueerimise arvesti: 25, praegune andmeploki salvestamine (%): 19
evicted (MB): 228, suhe 0.17, lisakulu (%): 14, raske evakueerimise arvesti: 26, praegune andmeploki salvestamine (%): 19
kustutatud (MB): 456, suhe 0.17, ĂŒlekoormus (%): 128, suur kustutamise arv: 27, praegune vahemĂ€lu DataBlock (%): 18 < lisatud saadetised (aga tabel sama)
kustutatud (MB): 456, suhe 0.15, ĂŒlekoormus (%): 128, suur kustutamise arv: 28, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 342, suhe 0.13, ĂŒlekoormus (%): 71, suur kustutamise arv: 29, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 342, suhe 0.11, ĂŒlekoormus (%): 71, suur kustutamise arv: 30, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 342, suhe 0.09, ĂŒlekoormus (%): 71, suur kustutamise arv: 31, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.08, ĂŒlekoormus (%): 14, suur kustutamise arv: 32, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.07, ĂŒlekoormus (%): 14, suur kustutamise arv: 33, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.06, ĂŒlekoormus (%): 14, suur kustutamise arv: 34, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.05, ĂŒlekoormus (%): 14, suur kustutamise arv: 35, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.05, ĂŒlekoormus (%): 14, suur kustutamise arv: 36, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 228, suhe 0.04, ĂŒlekoormus (%): 14, suur kustutamise arv: 37, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 109, suhe 0.04, ĂŒlekoormus (%): -46, suur kustutamise arv: 37, praegune vahemĂ€lu DataBlock (%): 22 < tagasisurve
kustutatud (MB): 798, suhe 0.24, ĂŒlekoormus (%): 299, suur kustutamise arv: 38, praegune vahemĂ€lu DataBlock (%): 20
kustutatud (MB): 798, suhe 0.29, ĂŒlekoormus (%): 299, suur kustutamise arv: 39, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 570, suhe 0.27, ĂŒlekoormus (%): 185, suur kustutamise arv: 40, praegune vahemĂ€lu DataBlock (%): 17
kustutatud (MB): 456, suhe 0.22, ĂŒlekoormus (%): 128, suur kustutamise arv: 41, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 342, suhe 0.16, ĂŒlekoormus (%): 71, suur kustutamise arv: 42, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 342, suhe 0.11, ĂŒlekoormus (%): 71, suur kustutamise arv: 43, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 228, suhe 0.09, ĂŒlekoormus (%): 14, suur kustutamise arv: 44, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 228, suhe 0.07, ĂŒlekoormus (%): 14, suur kustutamise arv: 45, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 228, suhe 0.05, ĂŒlekoormus (%): 14, suur kustutamise arv: 46, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 222, suhe 0.04, ĂŒlekoormus (%): 11, suur kustutamise arv: 47, praegune vahemĂ€lu DataBlock (%): 16
kustutatud (MB): 104, suhe 0.03, ĂŒlekoormus (%): -48, suur kustutamise arv: 47, praegune vahemĂ€lu DataBlock (%): 21 < katkendlik saadetis
kustutatud (MB): 684, suhe 0.2, ĂŒlekoormus (%): 242, suur kustutamise arv: 48, praegune vahemĂ€lu DataBlock (%): 19
kustutatud (MB): 570, suhe 0.23, ĂŒlekoormus (%): 185, suur kustutamise arv: 49, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 342, suhe 0.22, ĂŒlekoormus (%): 71, suur kustutamise arv: 50, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 228, suhe 0.21, ĂŒlekoormus (%): 14, suur kustutamise arv: 51, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 228, suhe 0.2, ĂŒlekoormus (%): 14, suur kustutamise arv: 52, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 228, suhe 0.18, ĂŒlekoormus (%): 14, suur kustutamise arv: 53, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 228, suhe 0.16, ĂŒlekoormus (%): 14, suur kustutamise arv: 54, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 228, suhe 0.14, ĂŒlekoormus (%): 14, suur kustutamise arv: 55, praegune vahemĂ€lu DataBlock (%): 18
kustutatud (MB): 112, suhe 0.14, ĂŒlekoormus (%): -44, suur kustutamise arv: 55, praegune vahemĂ€lu DataBlock (%): 23 < tagasisurve
kustutatud (MB): 456, suhe 0.26, ĂŒlekoormus (%): 128, suur kustutamise arv: 56, praegune vahemĂ€lu DataBlock (%): 22
kustutatud (MB): 342, suhe 0.31, ĂŒlekoormus (%): 71, suur kustutamise arv: 57, praegune vahemĂ€lu DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 58, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 59, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 60, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 61, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 62, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 63, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 64, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 65, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 66, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 67, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 68, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 69, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.32, overhead (%): 71, heavy eviction counter: 70, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 71, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 72, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 73, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 74, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 75, current caching DataBlock (%): 22
evicted (MB): 342, ratio 0.33, overhead (%): 71, heavy eviction counter: 76, current caching DataBlock (%): 22
evicted (MB): 21, ratio 0.33, overhead (%): -90, heavy eviction counter: 76, current caching DataBlock (%): 32
evicted (MB): 0, suhe 0.0, lisakulu (%): -100, raske evakueerimise arvesti: 0, praegune andmeploki salvestamine (%): 100
evicted (MB): 0, suhe 0.0, lisakulu (%): -100, raske evakueerimise arvesti: 0, praegune andmeploki salvestamine (%): 100
Skane oli vajalikud, et nĂ€idata sama protsessi diagrammina kahe vahe peatĂŒkiga â single (kuhu pÀÀsevad plokid, mida pole kunagi keegi kĂŒsinud) ja multi (siin hoitakse andmeid, mida on vĂ€hemalt korra nĂ”utud):

Ja lĂ”puks, kuidas nĂ€evad parameetrite tulemused vĂ€lja diagrammina. VĂ”rdlemiseks oli vahe esimese etapi alguses tĂ€iesti vĂ€lja lĂŒlitatud, seejĂ€rel kĂ€ivitati HBase koos vahemĂ€lu ja 5-minutilise optimeerimise viivitusega (30 vĂ€ljaĂŒĂŒrimist).
TĂ€iskood on saadaval Pull Requestis githubis.
Kuid 300 tuhat lugemist sekundis pole kÔik, mida sellistes tingimustes selle riistvara pealt vÀlja pigistada saab. Asi on selles, et kui andmetele tuleb pöörduda lÀbi HDFS-i, kasutatakse mehhanismi ShortCircuitCache (edaspidi SSC), mis vÔimaldab andmetele otsese juurdepÀÀsu, vÀltides vÔrgusuhtluse kasutamist.
Profiling showed that although this mechanism provides a significant gain, it also becomes a bottleneck at some point, as nearly all heavy operations occur within the lock, leading to blocks most of the time.

Realizing this, we understood that the problem could be circumvented by creating an array of independent SSC:
private final ShortCircuitCache[] shortCircuitCache;
...
shortCircuitCache = new ShortCircuitCache[this.clientShortCircuitNum];
for (int i = 0; i < this.clientShortCircuitNum; i++)
this.shortCircuitCache[i] = new ShortCircuitCache(âŠ);
And then work with them, excluding overlaps also by the last digit of the offset:
public ShortCircuitCache getShortCircuitCache(long idx) {
return shortCircuitCache[(int) (idx % clientShortCircuitNum)];
}
Now we can proceed to the tests. For this, we will read files from HDFS using a simple multithreaded application. We set the parameters:
conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // default = 1 MB and this significantly slows down reading, so itâs better to adjust it according to actual needs
conf.set("dfs.client.short.circuit.num", num); // from 1 to 10
And just read the files:
FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i 900000000)
position = 0L;
int res = in.read(position, byteBuffer, 0, 65536);
}
This code runs in separate threads and we will increase the number of simultaneously read files (from 10 to 200 â horizontal axis) and the number of caches (from 1 to 10 â graphs). The vertical axis shows the acceleration gained from increasing SSC compared to the case when there is only one cache.

How to read the graph: the time taken for 100 thousand reads in blocks of 64 KB with one cache requires 78 seconds. Whereas with 5 caches, it is performed in 16 seconds. That is, thereâs an acceleration of about 5 times. As seen from the graph, at a small number of parallel reads, the effect is not very noticeable; it begins to play a significant role when the number of reading threads exceeds 50. It is also noticeable that increasing the number of SSCs from 6 and above gives significantly less performance gain.
Note 1: Since the test results are quite volatile (see below), 3 runs were conducted and the obtained values were averaged.
Note 2: The performance increase from tuning for random access is the same, although the access itself is slightly slower.
Siiski on oluline mĂ€rkida, et erinevalt HBase'ist ei ole see kiirusetĂ”us alati tasuta. Siin me rohkem âvabastameâ CPU vĂ”imalusi töö tegemiseks, selle asemel et jÀÀda lukustatud olukordadesse.

Siin on nĂ€ha, et ĂŒldiselt toob vahemike arvu suurenemine ligikaudu proportsionaalse kasvu CPU-utiliseerimises. Siiski on mĂ”ned kombinatsioonid, mis on kasumlikumad.
NÀiteks vaatame lÀhemalt SSC = 3 seadistust. JÔudluse kasv vahemikus on umbes 3,3 korda. Allpool on tulemused kolmest eraldi kÀivitamisest.

Samas suureneb CPU tarbimine ligikaudu 2,8 korda. Erinevus pole vÀga suur, kuid vÀikese Grete jaoks on see rÔÔm ja ehk tekib aeg, et osaleda koolitundides.
Seega on sellel positiivne mĂ”ju igale tööriistale, mis kasutab masstest HDFS-i (nĂ€iteks Spark jne), eeldusel et rakenduskood on kerge (st tĂ”ke on HDFS-i kliendi poolel) ning CPU-l on vaba vĂ”imsus. Kontrollimiseks testime, milline efekt on BlockCache optimeerimise ja SSC-tuning'i ĂŒhisrakendamisel HBase'ist lugemisel.

Siin on nÀha, et sellistes tingimustes pole efekt nii suur kui rafineeritud testides (lugemine ilma igasuguse töötlemiseta), kuid veel 80k saavutamine on tÀiesti vÔimalik. Koos annavad mÔlemad optimeerimised kuni 4-kordse kiirusetÔusu.
Selle optimeerimise kohta tehti ka PR , mis sai kinnitatud ja see funktsionaalsus on jÀrgmistes versioonides saadaval.
Viimaseks oli huvitav vÔrrelda sarnaste wide-column andmebaaside Cassandra ja HBase lugemisjÔudlust.
Selleks kÀivitati standardse koormustestimise utiliidi YCSB eksemplarid kahest hostist (kokku 800 lÔime). Serveripooles oli 4 RegionServeri ja Cassandra eksemplari 4 hostis (mitte mingites, kus kliendid on kÀimas, et vÀltida nende mÔju). Lugemine toimus tabelitest, mille suurus oli:
HBase â 300 GB HDFS-is (100 GB puhast andmeid)
Cassandra â 250 GB (replication factor = 3)
St, maht oli umbes sama (HBase'is oli veidi rohkem).
HBase parameetrid:
dfs.client.short.circuit.num = 5 (HDFS kliendi optimeerimine)
hbase.lru.cache.heavy.eviction.count.limit = 30 â see tĂ€hendab, et plaastrid hakkavad töötama 30 vĂ€lja viskamise jĂ€rel (~5 minutit)
hbase.lru.cache.heavy.eviction.mb.size.limit = 300 â sihtmaht, mida vahetatakse ja visatakse vĂ€lja
YCSB logid töödeldi ja tuvastati Exceli graafikutesse:

Nagu nÀha, vÔimaldavad optimeerimisandmed saavutada nende andmebaaside vÔrdse jÔudluse nii tingimustes ja jÔuda 450 tuhandeni lugemistes sekundis.
Loodame, et see teave on kedagi aidanud pÔnevas madinas jÔudluse nimel.
Allikas: habr.com
