Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

L'alt rendiment és un dels requisits clau quan es treballa amb big data. Al departament de càrrega de dades de Sberbank, bombem gairebé totes les transaccions al nostre núvol de dades basat en Hadoop i, per tant, tractem fluxos d'informació molt grans. Naturalment, sempre busquem maneres de millorar el rendiment, i ara us volem explicar com hem aconseguit pedaçar RegionServer HBase i el client HDFS, gràcies als quals hem pogut augmentar significativament la velocitat de les operacions de lectura.
Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Tanmateix, abans de passar a l'essència de les millores, val la pena parlar de restriccions que, en principi, no es poden eludir si us asseieu en un disc dur.

Per què el disc dur i les lectures ràpides d'accés aleatori són incompatibles
Com sabeu, HBase, i moltes altres bases de dades, emmagatzemen dades en blocs de diverses desenes de kilobytes de mida. Per defecte és d'uns 64 KB. Ara imaginem que només necessitem obtenir 100 bytes i li demanem a HBase que ens doni aquestes dades amb una determinada clau. Com que la mida del bloc a HFiles és de 64 KB, la sol·licitud serà 640 vegades més gran (només un minut!) del necessari.

A continuació, ja que la sol·licitud passarà per HDFS i el seu mecanisme de memòria cau de metadades ShortCircuitCache (que permet l'accés directe als fitxers), això porta a llegir ja 1 MB del disc. Tanmateix, això es pot ajustar amb el paràmetre dfs.client.read.shortcircuit.buffer.size i en molts casos té sentit reduir aquest valor, per exemple a 126 KB.

Suposem que ho fem, però a més, quan comencem a llegir dades a través de l'API java, com ara funcions com FileChannel.read i demanem al sistema operatiu que llegeixi la quantitat especificada de dades, es llegeix "per si de cas" 2 vegades més. , és a dir 256 KB en el nostre cas. Això es deu al fet que Java no té una manera fàcil d'establir el senyalador FADV_RANDOM per evitar aquest comportament.

Com a resultat, per obtenir els nostres 100 bytes, es llegeixen 2600 vegades més sota el capó. Sembla que la solució és òbvia, reduïm la mida del bloc a un kilobyte, posem la bandera esmentada i obtenim una gran acceleració de la il·luminació. Però el problema és que en reduir la mida del bloc en 2 vegades, també reduïm el nombre de bytes llegits per unitat de temps en 2 vegades.

Es poden obtenir alguns guanys de la configuració de la marca FADV_RANDOM, però només amb multiprocés elevat i amb una mida de bloc de 128 KB, però això és un màxim d'un parell de desenes de per cent:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Es van realitzar proves en 100 fitxers, cadascun d'1 GB de mida i situats en 10 HDD.

Calculem amb què podem comptar, en principi, a aquesta velocitat:
Suposem que llegim de 10 discos a una velocitat de 280 MB/s, és a dir. 3 milions de vegades 100 bytes. Però com recordem, les dades que necessitem són 2600 vegades menys que les que es llegeixen. Així, dividim 3 milions per 2600 i obtenim 1100 registres per segon.

Depriment, no? Això és la natura Accés aleatori accés a les dades del disc dur, independentment de la mida del bloc. Aquest és el límit físic de l'accés aleatori i cap base de dades pot esprémer més en aquestes condicions.

Aleshores, com aconsegueixen les bases de dades velocitats molt més altes? Per respondre aquesta pregunta, mirem el que passa a la imatge següent:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Aquí veiem que durant els primers minuts la velocitat és realment d'uns mil registres per segon. Tanmateix, a més, a causa del fet que es llegeix molt més del que es va demanar, les dades acaben a la memòria cau del sistema operatiu (linux) i la velocitat augmenta fins a 60 mil per segon més decents.

Així, a més, tractarem l'acceleració de l'accés només a les dades que es troben a la memòria cau del sistema operatiu o que es troben en dispositius d'emmagatzematge SSD/NVMe de velocitat d'accés comparable.

En el nostre cas, realitzarem proves en un banc de 4 servidors, cadascun dels quals es cobra de la següent manera:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 fils.
Memòria: 730 GB.
versió de java: 1.8.0_111

I aquí el punt clau és la quantitat de dades de les taules que cal llegir. El fet és que si llegiu dades d'una taula que es troba completament a la memòria cau d'HBase, ni tan sols arribarà a llegir des del buff/caché del sistema operatiu. Perquè HBase de manera predeterminada assigna el 40% de la memòria a una estructura anomenada BlockCache. Bàsicament, es tracta d'un ConcurrentHashMap, on la clau és el nom del fitxer + el desplaçament del bloc, i el valor són les dades reals en aquest desplaçament.

Així, en llegir només des d'aquesta estructura, nosaltres veiem una velocitat excel·lent, com un milió de sol·licituds per segon. Però imaginem que no podem assignar centenars de gigabytes de memòria només per a necessitats de bases de dades, perquè hi ha moltes altres coses útils que s'executen en aquests servidors.

Per exemple, en el nostre cas, el volum de BlockCache en un RS és d'uns 12 GB. Vam aterrar dos RS en un node, és a dir. S'assignen 96 GB per a BlockCache a tots els nodes. I hi ha moltes vegades més dades, per exemple, que siguin 4 taules, de 130 regions cadascuna, en les quals els fitxers tenen una mida de 800 MB, comprimits per FAST_DIFF, és a dir. un total de 410 GB (es tracta de dades pures, és a dir, sense tenir en compte el factor de replicació).

Així, BlockCache és només al voltant del 23% del volum total de dades i això s'acosta molt més a les condicions reals del que s'anomena BigData. I aquí és on comença la diversió, perquè òbviament, com menys visites de memòria cau, pitjor serà el rendiment. Després de tot, si us perdeu, haureu de fer molta feina, és a dir. aneu a trucar a les funcions del sistema. Tanmateix, això no es pot evitar, així que mirem un aspecte completament diferent: què passa amb les dades dins de la memòria cau?

Simplificam la situació i suposem que tenim una memòria cau que només s'adapta a 1 objecte. Aquí teniu un exemple del que passarà quan intentem treballar amb un volum de dades 3 vegades més gran que la memòria cau, haurem de:

1. Col·loqueu el bloc 1 a la memòria cau
2. Elimina el bloc 1 de la memòria cau
3. Col·loqueu el bloc 2 a la memòria cau
4. Elimina el bloc 2 de la memòria cau
5. Col·loqueu el bloc 3 a la memòria cau

5 accions completades! Tanmateix, aquesta situació no es pot dir normal; de fet, estem obligant a HBase a fer un munt de treballs completament inútils. Llegeix constantment dades de la memòria cau del sistema operatiu, les col·loca a BlockCache, només per llençar-les gairebé immediatament perquè ha arribat una nova part de dades. L'animació de l'inici de la publicació mostra l'essència del problema: Garbage Collector s'escalfa, l'atmosfera s'escalfa, la petita Greta a la llunyana i calenta Suècia s'enfada. I als informàtics no ens agrada quan els nens estan tristos, així que comencem a pensar què podem fer-hi.

Què passa si no poses tots els blocs a la memòria cau, sinó només un percentatge determinat, perquè la memòria cau no es desbordi? Comencem simplement afegint unes quantes línies de codi al començament de la funció per posar dades a BlockCache:

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

El punt aquí és el següent: l'offset és la posició del bloc al fitxer i els seus últims dígits es distribueixen de manera aleatòria i uniforme del 00 al 99. Per tant, només saltarem els que entren dins l'interval que necessitem.

Per exemple, establiu cacheDataBlockPercent = 20 i mireu què passa:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

El resultat és evident. Als gràfics següents, queda clar per què es va produir aquesta acceleració: estalviem molts recursos de GC sense fer el treball de Sísif de col·locar dades a la memòria cau només per llançar-les immediatament al desguàs dels gossos marcians:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Al mateix temps, la utilització de la CPU augmenta, però és molt inferior a la productivitat:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

També val la pena assenyalar que els blocs emmagatzemats a BlockCache són diferents. La majoria, al voltant del 95%, són dades en si. I la resta són metadades, com ara filtres Bloom o LEAF_INDEX i т.д.. Aquestes dades no són suficients, però són molt útils, perquè abans d'accedir directament a les dades, HBase recorre al meta per entendre si cal cercar més aquí i, si és així, on es troba exactament el bloc d'interès.

Per tant, al codi veiem una condició de verificació buf.getBlockType().isData() i gràcies a aquest meta, el deixarem a la memòria cau en qualsevol cas.

Ara augmentem la càrrega i augmentem lleugerament la funció d'una vegada. A la primera prova vam fer el percentatge de tall = 20 i BlockCache va ser lleugerament infrautilitzat. Ara posem-lo al 23% i afegim 100 fils cada 5 minuts per veure en quin punt es produeix la saturació:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Aquí veiem que la versió original arriba gairebé immediatament al sostre amb unes 100 mil sol·licituds per segon. Mentre que el pegat dóna una acceleració de fins a 300 mil. Al mateix temps, és evident que l'acceleració addicional ja no és tan "gratuïta"; la utilització de la CPU també augmenta.

Tanmateix, aquesta no és una solució molt elegant, ja que no sabem per endavant quin percentatge de blocs s'han d'emmagatzemar en memòria cau, depèn del perfil de càrrega. Per tant, es va implementar un mecanisme per ajustar automàticament aquest paràmetre en funció de l'activitat de les operacions de lectura.

S'han afegit tres opcions per controlar-ho:

hbase.lru.cache.heavy.expulsion.count.limit — estableix quantes vegades s'ha d'executar el procés d'expulsió de dades de la memòria cau abans de començar a utilitzar l'optimització (és a dir, saltar blocs). Per defecte és igual a MAX_INT = 2147483647 i, de fet, significa que la funció mai començarà a funcionar amb aquest valor. Perquè el procés de desallotjament comença cada 5 - 10 segons (depèn de la càrrega) i 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 anys. Tanmateix, podem establir aquest paràmetre a 0 i fer que la funció funcioni immediatament després del llançament.

Tanmateix, també hi ha una càrrega útil en aquest paràmetre. Si la nostra càrrega és tal que les lectures a curt termini (per exemple, durant el dia) i les lectures a llarg termini (a la nit) s'intercalen constantment, podem assegurar-nos que la funció només s'activa quan hi ha operacions de lectura llarga en curs.

Per exemple, sabem que les lectures a curt termini solen durar aproximadament 1 minut. No cal començar a llençar blocs, la memòria cau no tindrà temps per quedar-se obsoleta i llavors podem establir aquest paràmetre igual, per exemple, 10. Això comportarà que l'optimització començarà a funcionar només quan sigui llarg. ha començat la lectura activa del trimestre, és a dir. en 100 segons. Així, si tenim una lectura a curt termini, tots els blocs aniran a la memòria cau i estaran disponibles (excepte els que seran desallotjats per l'algorisme estàndard). I quan fem lectures a llarg termini, la funció està activada i tindríem un rendiment molt superior.

hbase.lru.cache.heavy.eviction.mb.size.limit — estableix quants megabytes voldríem posar a la memòria cau (i, per descomptat, desallotjar) en 10 segons. La funció intentarà assolir aquest valor i mantenir-lo. La qüestió és aquesta: si introduïm gigabytes a la memòria cau, haurem de desallotjar gigabytes, i això, com hem vist més amunt, és molt car. Tanmateix, no hauríeu d'intentar configurar-lo massa petit, ja que això farà que el mode d'omissió de bloc surti prematurament. Per a servidors potents (uns 20-40 nuclis físics), és òptim establir uns 300-400 MB. Per a la classe mitjana (~10 nuclis) 200-300 MB. Per a sistemes febles (2-5 nuclis) 50-100 MB poden ser normals (no provat en aquests).

Vegem com funciona això: suposem que establim hbase.lru.cache.heavy.eviction.mb.size.limit = 500, hi ha algun tipus de càrrega (lectura) i després cada ~10 segons calculem quants bytes hi havia. desallotjat amb la fórmula:

Sobrecàrrega = Suma de bytes alliberats (MB) * 100 / Límit (MB) - 100;

Si de fet s'han desallotjat 2000 MB, la sobrecàrrega és igual a:

2000 * 100/500 - 100 = 300 %

Els algorismes intenten mantenir no més d'unes desenes de per cent, de manera que la funció reduirà el percentatge de blocs en memòria cau, implementant així un mecanisme d'ajust automàtic.

Tanmateix, si la càrrega baixa, posem per cas que només 200 MB són desallotjats i Overhead es converteix en negatiu (l'anomenat overshooting):

200 * 100/500 - 100 = -60 %

Al contrari, la funció augmentarà el percentatge de blocs a la memòria cau fins que Overhead sigui positiu.

A continuació es mostra un exemple de com es veu això amb dades reals. No cal intentar arribar al 0%, és impossible. És molt bo quan és d'un 30 - 100%, això ajuda a evitar la sortida prematura del mode d'optimització durant les pujades a curt termini.

hbase.lru.cache.heavy.eviction.overhead.coeficient — estableix la rapidesa amb què ens agradaria obtenir el resultat. Si sabem del cert que les nostres lectures són majoritàriament llargues i no volem esperar, podem augmentar aquesta ràtio i obtenir un alt rendiment més ràpidament.

Per exemple, establim aquest coeficient = 0.01. Això vol dir que la sobrecàrrega (vegeu més amunt) es multiplicarà per aquest nombre pel resultat resultant i es reduirà el percentatge de blocs en memòria cau. Suposem que Overhead = 300% i coeficient = 0.01, llavors el percentatge de blocs en memòria cau es reduirà un 3%.

També s'implementa una lògica de "contrapressió" similar per als valors negatius de sobrecàrrega (superació). Com que sempre són possibles fluctuacions a curt termini en el volum de lectures i desnonaments, aquest mecanisme permet evitar la sortida prematura del mode d'optimització. La contrapressió té una lògica invertida: com més fort és el desbordament, més blocs s'emmagatzemen a la memòria cau.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Codi d'implementació

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

Vegem-ho ara amb un exemple real. Tenim el següent script de prova:

  1. Comencem a fer escaneig (25 fils, lot = 100)
  2. Després de 5 minuts, afegiu multi-obtencions (25 fils, lot = 100)
  3. Al cap de 5 minuts, desactiveu les multi-obtencions (només queda l'escaneig de nou)

Fem dues execucions, primer hbase.lru.cache.heavy.eviction.count.limit = 10000 (que en realitat desactiva la funció) i després establim limit = 0 (l'activa).

Als registres de sota veiem com s'activa la funció i restableix Overshooting al 14-71%. De tant en tant la càrrega disminueix, la qual cosa activa la contrapressió i HBase torna a guardar més blocs a la memòria cau.

Registre RegionServer
desallotjats (MB): 0, proporció 0.0, sobrecàrrega (%): -100, comptador de desnonaments pesats: 0, memòria cau actual DataBlock (%): 100
desallotjats (MB): 0, proporció 0.0, sobrecàrrega (%): -100, comptador de desnonaments pesats: 0, memòria cau actual DataBlock (%): 100
desallotjats (MB): 2170, proporció 1.09, sobrecàrrega (%): 985, comptador de desnonaments pesats: 1, memòria cau actual DataBlock (%): 91 < inici
desallotjats (MB): 3763, proporció 1.08, sobrecàrrega (%): 1781, comptador de desnonaments pesats: 2, memòria cau actual DataBlock (%): 76
desallotjats (MB): 3306, proporció 1.07, sobrecàrrega (%): 1553, comptador de desnonaments pesats: 3, memòria cau actual DataBlock (%): 61
desallotjats (MB): 2508, proporció 1.06, sobrecàrrega (%): 1154, comptador de desnonaments pesats: 4, memòria cau actual DataBlock (%): 50
desallotjats (MB): 1824, proporció 1.04, sobrecàrrega (%): 812, comptador de desnonaments pesats: 5, memòria cau actual DataBlock (%): 42
desallotjats (MB): 1482, proporció 1.03, sobrecàrrega (%): 641, comptador de desnonaments pesats: 6, memòria cau actual DataBlock (%): 36
desallotjats (MB): 1140, proporció 1.01, sobrecàrrega (%): 470, comptador de desnonaments pesats: 7, memòria cau actual DataBlock (%): 32
desallotjats (MB): 913, proporció 1.0, sobrecàrrega (%): 356, comptador de desnonaments pesats: 8, memòria cau actual DataBlock (%): 29
desallotjats (MB): 912, proporció 0.89, sobrecàrrega (%): 356, comptador de desnonaments pesats: 9, memòria cau actual DataBlock (%): 26
desallotjats (MB): 684, proporció 0.76, sobrecàrrega (%): 242, comptador de desnonaments pesats: 10, memòria cau actual DataBlock (%): 24
desallotjats (MB): 684, proporció 0.61, sobrecàrrega (%): 242, comptador de desnonaments pesats: 11, memòria cau actual DataBlock (%): 22
desallotjats (MB): 456, proporció 0.51, sobrecàrrega (%): 128, comptador de desnonaments pesats: 12, memòria cau actual DataBlock (%): 21
desallotjats (MB): 456, proporció 0.42, sobrecàrrega (%): 128, comptador de desnonaments pesats: 13, memòria cau actual DataBlock (%): 20
desallotjats (MB): 456, proporció 0.33, sobrecàrrega (%): 128, comptador de desnonaments pesats: 14, memòria cau actual DataBlock (%): 19
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 15, memòria cau actual DataBlock (%): 19
desallotjats (MB): 342, proporció 0.32, sobrecàrrega (%): 71, comptador de desnonaments pesats: 16, memòria cau actual DataBlock (%): 19
desallotjats (MB): 342, proporció 0.31, sobrecàrrega (%): 71, comptador de desnonaments pesats: 17, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.3, sobrecàrrega (%): 14, comptador de desnonaments pesats: 18, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.29, sobrecàrrega (%): 14, comptador de desnonaments pesats: 19, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.27, sobrecàrrega (%): 14, comptador de desnonaments pesats: 20, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.25, sobrecàrrega (%): 14, comptador de desnonaments pesats: 21, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.24, sobrecàrrega (%): 14, comptador de desnonaments pesats: 22, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.22, sobrecàrrega (%): 14, comptador de desnonaments pesats: 23, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.21, sobrecàrrega (%): 14, comptador de desnonaments pesats: 24, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.2, sobrecàrrega (%): 14, comptador de desnonaments pesats: 25, memòria cau actual DataBlock (%): 19
desallotjats (MB): 228, proporció 0.17, sobrecàrrega (%): 14, comptador de desnonaments pesats: 26, memòria cau actual DataBlock (%): 19
desallotjats (MB): 456, proporció 0.17, sobrecàrrega (%): 128, comptador de desnonaments pesats: 27, memòria cau actual DataBlock (%): 18 < obtinguts afegits (però la taula és igual)
desallotjats (MB): 456, proporció 0.15, sobrecàrrega (%): 128, comptador de desnonaments pesats: 28, memòria cau actual DataBlock (%): 17
desallotjats (MB): 342, proporció 0.13, sobrecàrrega (%): 71, comptador de desnonaments pesats: 29, memòria cau actual DataBlock (%): 17
desallotjats (MB): 342, proporció 0.11, sobrecàrrega (%): 71, comptador de desnonaments pesats: 30, memòria cau actual DataBlock (%): 17
desallotjats (MB): 342, proporció 0.09, sobrecàrrega (%): 71, comptador de desnonaments pesats: 31, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.08, sobrecàrrega (%): 14, comptador de desnonaments pesats: 32, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.07, sobrecàrrega (%): 14, comptador de desnonaments pesats: 33, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.06, sobrecàrrega (%): 14, comptador de desnonaments pesats: 34, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.05, sobrecàrrega (%): 14, comptador de desnonaments pesats: 35, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.05, sobrecàrrega (%): 14, comptador de desnonaments pesats: 36, memòria cau actual DataBlock (%): 17
desallotjats (MB): 228, proporció 0.04, sobrecàrrega (%): 14, comptador de desnonaments pesats: 37, memòria cau actual DataBlock (%): 17
desallotjats (MB): 109, proporció 0.04, sobrecàrrega (%): -46, comptador de desnonaments pesats: 37, memòria cau actual DataBlock (%): 22 < contrapressió
desallotjats (MB): 798, proporció 0.24, sobrecàrrega (%): 299, comptador de desnonaments pesats: 38, memòria cau actual DataBlock (%): 20
desallotjats (MB): 798, proporció 0.29, sobrecàrrega (%): 299, comptador de desnonaments pesats: 39, memòria cau actual DataBlock (%): 18
desallotjats (MB): 570, proporció 0.27, sobrecàrrega (%): 185, comptador de desnonaments pesats: 40, memòria cau actual DataBlock (%): 17
desallotjats (MB): 456, proporció 0.22, sobrecàrrega (%): 128, comptador de desnonaments pesats: 41, memòria cau actual DataBlock (%): 16
desallotjats (MB): 342, proporció 0.16, sobrecàrrega (%): 71, comptador de desnonaments pesats: 42, memòria cau actual DataBlock (%): 16
desallotjats (MB): 342, proporció 0.11, sobrecàrrega (%): 71, comptador de desnonaments pesats: 43, memòria cau actual DataBlock (%): 16
desallotjats (MB): 228, proporció 0.09, sobrecàrrega (%): 14, comptador de desnonaments pesats: 44, memòria cau actual DataBlock (%): 16
desallotjats (MB): 228, proporció 0.07, sobrecàrrega (%): 14, comptador de desnonaments pesats: 45, memòria cau actual DataBlock (%): 16
desallotjats (MB): 228, proporció 0.05, sobrecàrrega (%): 14, comptador de desnonaments pesats: 46, memòria cau actual DataBlock (%): 16
desallotjats (MB): 222, proporció 0.04, sobrecàrrega (%): 11, comptador de desnonaments pesats: 47, memòria cau actual DataBlock (%): 16
desallotjats (MB): 104, proporció 0.03, sobrecàrrega (%): -48, comptador de desnonaments pesats: 47, bloc de dades de memòria cau actual (%): 21 < interrupció obté
desallotjats (MB): 684, proporció 0.2, sobrecàrrega (%): 242, comptador de desnonaments pesats: 48, memòria cau actual DataBlock (%): 19
desallotjats (MB): 570, proporció 0.23, sobrecàrrega (%): 185, comptador de desnonaments pesats: 49, memòria cau actual DataBlock (%): 18
desallotjats (MB): 342, proporció 0.22, sobrecàrrega (%): 71, comptador de desnonaments pesats: 50, memòria cau actual DataBlock (%): 18
desallotjats (MB): 228, proporció 0.21, sobrecàrrega (%): 14, comptador de desnonaments pesats: 51, memòria cau actual DataBlock (%): 18
desallotjats (MB): 228, proporció 0.2, sobrecàrrega (%): 14, comptador de desnonaments pesats: 52, memòria cau actual DataBlock (%): 18
desallotjats (MB): 228, proporció 0.18, sobrecàrrega (%): 14, comptador de desnonaments pesats: 53, memòria cau actual DataBlock (%): 18
desallotjats (MB): 228, proporció 0.16, sobrecàrrega (%): 14, comptador de desnonaments pesats: 54, memòria cau actual DataBlock (%): 18
desallotjats (MB): 228, proporció 0.14, sobrecàrrega (%): 14, comptador de desnonaments pesats: 55, memòria cau actual DataBlock (%): 18
desallotjats (MB): 112, proporció 0.14, sobrecàrrega (%): -44, comptador de desnonaments pesats: 55, memòria cau actual DataBlock (%): 23 < contrapressió
desallotjats (MB): 456, proporció 0.26, sobrecàrrega (%): 128, comptador de desnonaments pesats: 56, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.31, sobrecàrrega (%): 71, comptador de desnonaments pesats: 57, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 58, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 59, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 60, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 61, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 62, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 63, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.32, sobrecàrrega (%): 71, comptador de desnonaments pesats: 64, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 65, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 66, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.32, sobrecàrrega (%): 71, comptador de desnonaments pesats: 67, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 68, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.32, sobrecàrrega (%): 71, comptador de desnonaments pesats: 69, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.32, sobrecàrrega (%): 71, comptador de desnonaments pesats: 70, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 71, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 72, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 73, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 74, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 75, memòria cau actual DataBlock (%): 22
desallotjats (MB): 342, proporció 0.33, sobrecàrrega (%): 71, comptador de desnonaments pesats: 76, memòria cau actual DataBlock (%): 22
desallotjats (MB): 21, proporció 0.33, sobrecàrrega (%): -90, comptador de desnonaments pesats: 76, memòria cau actual DataBlock (%): 32
desallotjats (MB): 0, proporció 0.0, sobrecàrrega (%): -100, comptador de desnonaments pesats: 0, memòria cau actual DataBlock (%): 100
desallotjats (MB): 0, proporció 0.0, sobrecàrrega (%): -100, comptador de desnonaments pesats: 0, memòria cau actual DataBlock (%): 100

Les exploracions eren necessàries per mostrar el mateix procés en forma d'un gràfic de la relació entre dues seccions de memòria cau: única (on els blocs que mai s'havien sol·licitat abans) i múltiples (les dades "sol·licitades" almenys una vegada s'emmagatzemen aquí):

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

I, finalment, quin aspecte té el funcionament dels paràmetres en forma de gràfic. Per comparar, la memòria cau es va apagar completament al principi, després es va llançar HBase amb la memòria cau i retardant l'inici del treball d'optimització en 5 minuts (30 cicles de desallotjament).

El codi complet es pot trobar a Pull Request HBASE 23887 a github.

Tanmateix, 300 mil lectures per segon no és tot el que es pot aconseguir amb aquest maquinari en aquestes condicions. El cas és que quan cal accedir a les dades mitjançant HDFS, s'utilitza el mecanisme ShortCircuitCache (en endavant SSC), que permet accedir directament a les dades, evitant les interaccions de xarxa.

L'elaboració de perfils va demostrar que, tot i que aquest mecanisme dóna un gran guany, també en algun moment es converteix en un coll d'ampolla, perquè gairebé totes les operacions pesades es produeixen dins d'un pany, la qual cosa comporta el bloqueig la majoria de les vegades.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

En adonar-nos d'això, ens vam adonar que el problema es pot evitar creant una sèrie de SSC independents:

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

I després treballeu amb ells, excloent les interseccions també a l'últim dígit de compensació:

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

Ara podeu començar a provar. Per fer-ho, llegirem fitxers d'HDFS amb una senzilla aplicació multiprocés. Estableix els paràmetres:

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

I només cal llegir els fitxers:

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

Aquest codi s'executa en fils separats i augmentarem el nombre de fitxers llegits simultàniament (de 10 a 200 - eix horitzontal) i el nombre de memòria cau (d'1 a 10 - gràfics). L'eix vertical mostra l'acceleració que resulta d'un augment de SSC en relació amb el cas quan només hi ha una memòria cau.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Com llegir el gràfic: el temps d'execució de 100 mil lectures en blocs de 64 KB amb una memòria cau requereix 78 segons. Mentre que amb 5 cachés triguen 16 segons. Aquells. hi ha una acceleració de ~5 vegades. Com es pot veure al gràfic, l'efecte no es nota gaire per a un nombre reduït de lectures paral·leles, comença a tenir un paper notable quan hi ha més de 50 lectures de fils. També es nota que augmenta el nombre de SSC a partir de 6. i superior dóna un augment de rendiment significativament menor.

Nota 1: com que els resultats de les proves són força volàtils (vegeu més avall), es van realitzar 3 execucions i es van promediar els valors resultants.

Nota 2: el guany de rendiment de la configuració de l'accés aleatori és el mateix, tot i que l'accés en si és una mica més lent.

Tanmateix, cal aclarir que, a diferència del cas de l'HBase, aquesta acceleració no sempre és gratuïta. Aquí "desbloquegem" la capacitat de la CPU per fer més feina, en lloc de penjar-se als panys.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Aquí podeu observar que, en general, un augment del nombre de memòria cau dóna un augment aproximadament proporcional de la utilització de la CPU. Tanmateix, hi ha una mica més de combinacions guanyadores.

Per exemple, mirem més de prop la configuració SSC = 3. L'augment del rendiment a l'interval és d'aproximadament 3.3 vegades. A continuació es mostren els resultats de les tres tirades separades.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Mentre que el consum de CPU augmenta unes 2.8 vegades. La diferència no és molt gran, però la petita Greta ja està contenta i pot tenir temps per anar a l'escola i fer classes.

Així, això tindrà un efecte positiu per a qualsevol eina que utilitzi accés massiu a HDFS (per exemple, Spark, etc.), sempre que el codi de l'aplicació sigui lleuger (és a dir, l'endoll estigui al costat del client HDFS) i hi hagi energia de CPU gratuïta. . Per comprovar-ho, provem quin efecte tindrà l'ús combinat de l'optimització de BlockCache i l'ajustament SSC per llegir des d'HBase.

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Es pot veure que en aquestes condicions l'efecte no és tan gran com en les proves refinades (lectura sense cap processament), però aquí és molt possible extreure 80K addicionals. En conjunt, ambdues optimitzacions proporcionen una velocitat de fins a 4 vegades.

També es va fer un PR per a aquesta optimització [HDFS-15202], que s'ha fusionat i aquesta funcionalitat estarà disponible en futures versions.

I, finalment, va ser interessant comparar el rendiment de lectura d'una base de dades similar de columnes amples, Cassandra i HBase.

Per fer-ho, vam llançar instàncies de la utilitat estàndard de prova de càrrega YCSB des de dos amfitrions (800 fils en total). Al costat del servidor: 4 instàncies de RegionServer i Cassandra en 4 amfitrions (no els que s'executen els clients, per evitar la seva influència). Les lectures provenien de taules de mida:

HBase: 300 GB en HDFS (100 GB de dades pures)

Cassandra: 250 GB (factor de replicació = 3)

Aquells. el volum era aproximadament el mateix (a HBase una mica més).

Paràmetres HBase:

dfs.client.short.circuit.num = 5 (Optimització del client HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - això significa que el pegat començarà a funcionar després de 30 desnonaments (~ 5 minuts)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — volum objectiu de la memòria cau i el desallotjament

Els registres YCSB es van analitzar i compilar en gràfics d'Excel:

Com augmentar la velocitat de lectura des d'HBase fins a 3 vegades i des de HDFS fins a 5 vegades

Com podeu veure, aquestes optimitzacions permeten comparar el rendiment d'aquestes bases de dades en aquestes condicions i aconseguir 450 mil lectures per segon.

Esperem que aquesta informació pugui ser útil a algú durant l'apassionant lluita per la productivitat.

Font: www.habr.com

Afegeix comentari