Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Hohe Leistung ist eines der entscheidenden Kriterien bei der Verarbeitung großer Datenmengen. In der Datenmanagement-Abteilung von Sber beschĂ€ftigen wir uns mit der Verarbeitung nahezu aller Transaktionen in unsere Cloud-Datenbank auf Basis von Hadoop und haben daher mit wirklich großen Informationsströmen zu tun. NatĂŒrlich sind wir stĂ€ndig auf der Suche nach Wegen zur Leistungssteigerung und möchten nun darĂŒber berichten, wie wir den RegionServer von HBase und den HDFS-Client optimieren konnten, was zu einer erheblichen Erhöhung der Leseoperationen gefĂŒhrt hat.
Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Bevor wir jedoch auf die Details der Anpassungen eingehen, sollten wir die EinschrÀnkungen besprechen, die grundsÀtzlich unvermeidlich sind, wenn man auf HDD setzt.

Warum HDD und schnelle Random Access-LesevorgÀnge unvereinbar sind
Wie bekannt ist, speichern HBase und viele andere Datenbanken Daten in Blöcken von mehreren Dutzend Kilobyte. StandardmĂ€ĂŸig betrĂ€gt die BlockgrĂ¶ĂŸe etwa 64 KB. Stellen wir uns nun vor, dass wir nur 100 Byte abrufen mĂŒssen und HBase bitten, uns diese Daten anhand eines bestimmten SchlĂŒssels bereitzustellen. Da die BlockgrĂ¶ĂŸe in HFiles 64 KB betrĂ€gt, werden 640 Mal mehr Daten angefordert, als tatsĂ€chlich benötigt werden.

Da der Antrag ĂŒber HDFS und dessen Mechanismus zur zwischenspeicherung von Metadaten lĂ€uft, ShortCircuitCache (der einen direkten Zugriff auf Dateien ermöglicht), fĂŒhrt dies dazu, dass bereits 1 MB von der Festplatte gelesen wird. Dies kann jedoch durch den Parameter dfs.client.read.shortcircuit.buffer.size und in vielen FĂ€llen macht es Sinn, diesen Wert zu verringern, zum Beispiel auf 126 KB.

Nehmen wir an, wir tun dies, aber zusĂ€tzlich, wenn wir anfangen, Daten ĂŒber das Java-API zu lesen, wie zum Beispiel mit FileChannel.read, und die Betriebssystemanfrage, ein bestimmtes Datenvolumen zu lesen, wird als Sicherheitsmaßnahme doppelt so viel gelesen, d.h. in unserem Fall 256 KB. Das geschieht, weil es in Java keine einfache Möglichkeit gibt, das FADV_RANDOM-Flag zu setzen, das solches Verhalten verhindert.

Folglich wird, um unsere 100 Bytes zu erhalten, im Hintergrund 2600 Mal mehr ausgelesen. Die Lösung scheint klar, lassen Sie uns die BlockgrĂ¶ĂŸe auf ein Kilobyte reduzieren, das erwĂ€hnte Flag setzen und eine bedeutende Beschleunigung erreichen. Aber das Problem ist, dass wir durch die Halbierung der BlockgrĂ¶ĂŸe auch die Anzahl der ausgelesenen Bytes pro Zeiteinheit ebenfalls halbieren.

Ein gewisser Gewinn durch das Setzen des Flags FADV_RANDOM ist möglich, jedoch nur bei hoher ParallelitĂ€t und einer BlockgrĂ¶ĂŸe von mindestens 128 KB, was maximal einige Dutzend Prozent ausmacht.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Die Tests wurden mit 100 Dateien durchgefĂŒhrt, die jeweils 1 GB groß sind und auf 10 HDDs gespeichert wurden.

Schauen wir uns an, worauf wir mit dieser Geschwindigkeit grundsÀtzlich hoffen können:
Angenommen, wir lesen von 10 Laufwerken mit einer Geschwindigkeit von 280 MB/s, also 3 Millionen Mal 100 Byte. Doch wie wir wissen, sind die benötigten Daten 2600 Mal seltener als das Gelesene. Somit teilen wir 3 Millionen durch 2600 und erhalten 1100 EintrÀge pro Sekunde.

Erschreckend, nicht wahr? So ist die Natur des Random Access zum Datenzugriff auf HDDs — unabhĂ€ngig von der BlockgrĂ¶ĂŸe. Das ist die physische Grenze des zufĂ€lligen Zugriffs, die in diesen Bedingungen keine Datenbank ĂŒberschreiten kann.

Wie schaffen es Datenbanken also, viel höhere Geschwindigkeiten zu erreichen? Um diese Frage zu beantworten, schauen wir uns an, was im nÀchsten Bild passiert:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Hier sehen wir, dass die Geschwindigkeit in den ersten Minuten tatsÀchlich etwa tausend DatensÀtze pro Sekunde betrÀgt. Im weiteren Verlauf jedoch, da deutlich mehr Daten gelesen werden, setzen sich die Informationen im buff/cache des Betriebssystems (Linux ab) ab, und die Geschwindigkeit steigt auf respektable 60.000 pro Sekunde.

Daher werden wir uns kĂŒnftig mit der Beschleunigung des Zugriffs nur auf die Daten beschĂ€ftigen, die sich im OS-Cache befinden oder in vergleichbaren schnellen Speichern wie SSD/NVMe.

In unserem Fall werden wir Tests auf einer Konfiguration aus 4 Servern durchfĂŒhren, von denen jeder wie folgt ausgestattet ist:

CPU: Xeon E5-2680 v4 @ 2,40 GHz, 64 Threads.
Speicher: 730 GB.
Java-Version: 1.8.0_111

Hier ist der entscheidende Punkt – das Volumen der Daten in den Tabellen, die gelesen werden mĂŒssen. Denn wenn Daten aus einer Tabelle gelesen werden, die vollstĂ€ndig in den HBase-Cache passt, wird es sogar gar nicht notwendig sein, aus dem buff/cache des Betriebssystems zu lesen. StandardmĂ€ĂŸig reserviert HBase 40% des Speichers fĂŒr eine Struktur namens BlockCache. Dies ist im Wesentlichen ein ConcurrentHashMap, bei dem der SchlĂŒssel der Dateiname + der Offset des Blocks ist, und der Wert sind die Daten zu diesem Offset.

So wird, wenn nur aus dieser Struktur gelesen wird, wir erleben eine beeindruckende Geschwindigkeit, etwa eine Million Anfragen pro Sekunde. Doch stellen wir uns vor, dass wir nicht Hunderte von Gigabyte Speicher nur fĂŒr Datenbanken reservieren können, da auf diesen Servern auch viele andere nĂŒtzliche Dienste laufen.

In unserem Fall betrĂ€gt das Volumen des BlockCache auf einem RS etwa 12 GB. Wir haben zwei RS auf einem Knoten bereitgestellt, d.h. insgesamt stehen auf allen Knoten 96 GB fĂŒr den BlockCache zur VerfĂŒgung. Dabei gibt es jedoch erheblich mehr Daten, nehmen wir an, es handelt sich um 4 Tabellen mit 130 Regionen, in denen Dateien von jeweils 800 MB enthalten sind, komprimiert mit FAST_DIFF, d.h. insgesamt 410 GB (rein Daten, ohne BerĂŒcksichtigung des Replikationsfaktors).

Somit macht der BlockCache nur etwa 23 % des gesamten Datenvolumens aus, was viel nĂ€her an den realen Bedingungen dessen ist, was als Big Data bezeichnet wird. Und hier wird es besonders interessant — denn offensichtlich verschlechtert sich die Leistung, je weniger Treffer im Cache erzielt werden. Im Falle eines Fehlers mĂŒssen wir eine Menge Arbeit leisten — d.h. wir mĂŒssen auf Systemaufrufe zurĂŒckgreifen. Doch das ist unvermeidlich, also lassen Sie uns einen ganz anderen Aspekt betrachten — was passiert mit den Daten innerhalb des Caches?

Vereinfachen wir die Situation und nehmen wir an, dass wir einen Cache haben, der nur 1 Objekt aufnehmen kann. Hier ein Beispiel, was passiert, wenn wir mit einer Datenmenge arbeiten, die dreimal so groß ist wie der Cache:

1. Block 1 in den Cache legen
2. Block 1 aus dem Cache entfernen
3. Block 2 in den Cache legen
4. Block 2 aus dem Cache entfernen
5. Block 3 in den Cache legen

FĂŒnf Aktionen wurden durchgefĂŒhrt! Diese Situation kann jedoch nicht als normal bezeichnet werden, da wir HBase zwingen, eine Menge völlig nutzloser Arbeiten zu leisten. Es liest stĂ€ndig Daten aus dem Betriebssystem-Cache, legt sie im BlockCache ab, nur um sie fast sofort wieder zu verwerfen, weil neue Daten ankommen. Die Animation am Anfang des Beitrags zeigt das Problem — der Garbage Collector ĂŒberlastet, die AtmosphĂ€re erwĂ€rmt sich, das kleine Greta in Schweden ist verĂ€rgert. Und wir ITler mögen es sehr nicht, wenn Kinder traurig sind, also fangen wir an zu ĂŒberlegen, was wir dagegen tun können.

Was wĂ€re, wenn wir nicht alle Blöcke in den Cache legen, sondern nur einen bestimmten Prozentsatz davon, sodass der Cache nicht ĂŒberlĂ€uft? Lassen Sie uns zunĂ€chst einfach ein paar Zeilen Code am Anfang der Funktion hinzufĂŒgen, die Daten in den BlockCache legt:

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

Der Sinn liegt darin, dass der Offset die Position des Blocks in der Datei ist und die letzten Ziffern zufĂ€llig und gleichmĂ€ĂŸig von 00 bis 99 verteilt sind. Daher werden wir nur die Werte ĂŒberspringen, die in den gewĂŒnschten Bereich fallen.

Nehmen wir zum Beispiel an, wir setzen cacheDataBlockPercent = 20 und schauen, was passiert:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Das Ergebnis ist unĂŒbersehbar. Auf den folgenden Grafiken wird deutlich, wodurch diese Beschleunigung zustande kam — wir sparen viele Ressourcen des GC, indem wir uns nicht mit der mĂŒhsamen Platzierung von Daten im Cache beschĂ€ftigen, nur um sie sofort wieder an die marsianischen Hunde zu werfen:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Die CPU-Auslastung steigt dabei, jedoch viel weniger stark als die Leistung:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Es ist auch wichtig zu erwĂ€hnen, dass die Blöcke, die im BlockCache gespeichert werden, unterschiedlich sein können. Der grĂ¶ĂŸte Teil, etwa 95%, sind tatsĂ€chlich Daten. Und der Rest sind Metadaten, wie Bloom-Filter oder LEAF_INDEX und usw.. Es gibt zwar nur wenige Daten, aber sie sind Ă€ußerst nĂŒtzlich, da HBase vor dem Zugriff auf die eigentlichen Daten die Metadaten abfragt, um zu verstehen, ob weiter gesucht werden muss und falls ja, wo sich der gesuchte Block befindet.

Deshalb sehen wir im Code eine BedingungsprĂŒfung buf.getBlockType().isData() und dank dieser Metadaten werden wir diesen Block auf jeden Fall im Cache behalten.

Jetzt wollen wir die Last erhöhen und gleichzeitig ein paar Features optimieren. Im ersten Test haben wir einen Schrankenwert von 20% gesetzt, und der BlockCache war etwas unterlastet. Jetzt setzen wir 23% und fĂŒgen alle 5 Minuten 100 Threads hinzu, um zu sehen, wann die SĂ€ttigung eintritt:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Hier sehen wir, dass die ursprĂŒngliche Version fast sofort an die Grenze von etwa 100.000 Anfragen pro Sekunde stĂ¶ĂŸt. Der Patch hingegen ermöglicht eine Steigerung auf bis zu 300.000. Dabei ist klar, dass eine weitere Beschleunigung nicht mehr so „kostenlos“ ist und die CPU-Auslastung ebenfalls zunimmt.

Allerdings ist dies keine sehr elegante Lösung, da wir im Voraus nicht wissen, welcher Prozentsatz der Blöcke zwischengespeichert werden sollte. Das hÀngt vom Lastprofil ab. Deshalb wurde ein Mechanismus zur automatischen Anpassung dieses Parameters basierend auf der AktivitÀt der LesevorgÀnge implementiert.

Zur Steuerung wurden drei Parameter hinzugefĂŒgt:

hbase.lru.cache.heavy.eviction.count.limit — legt fest, wie oft der Prozess zur DatenrĂ€umung aus dem Cache ausgefĂŒhrt werden muss, bevor wir die Optimierung (d. h. das Überspringen von Blöcken) aktivieren. StandardmĂ€ĂŸig ist der Wert MAX_INT = 2147483647, was tatsĂ€chlich bedeutet, dass die Funktion bei diesem Wert nie aktiv wird. Denn der RĂ€umungsprozess wird alle 5 bis 10 Sekunden (je nach Last) gestartet, und 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 Jahre. Wir können jedoch diesen Parameter auf 0 setzen und die Funktion sofort nach dem Start aktivieren.

Es gibt jedoch auch einen praktischen Nutzen in diesem Parameter. Wenn unser Lastprofil so beschaffen ist, dass kurzfristige LesevorgĂ€nge (zum Beispiel tagsĂŒber) stĂ€ndig mit langfristigen LesevorgĂ€ngen (nachts) wechseln, können wir die Funktion so einstellen, dass sie nur bei lĂ€ngeren Leseoperationen aktiviert wird.

Wir wissen zum Beispiel, dass kurzfristige LesevorgĂ€nge normalerweise etwa 1 Minute dauern. Wir sollten keine Blöcke entfernen, da der Cache ansonsten nicht ablĂ€uft, und wir können diesen Parameter auf beispielsweise 10 setzen. Das bedeutet, dass die Optimierung erst dann aktiv wird, wenn ein lĂ€ngeres, aktives Lesen beginnt, also nach 100 Sekunden. Somit werden bei kurzfristigen LesevorgĂ€ngen alle Blöcke im Cache landen und verfĂŒgbar sein (außer denen, die durch den Standardalgorithmus entfernt werden). Bei langfristigen LesevorgĂ€ngen wird die Funktion aktiviert, wodurch wir eine deutlich höhere Leistung erzielen.

hbase.lru.cache.heavy.eviction.mb.size.limit — legt fest, wie viele Megabyte wir innerhalb von 10 Sekunden in den Cache speichern (und natĂŒrlich auch wieder entfernen) möchten. Die Funktion versucht, diesen Wert zu erreichen und aufrechtzuerhalten. Der Sinn ist folgender: Wenn wir Gigabytes in den Cache laden, mĂŒssen wir auch Gigabytes wieder entfernen, was, wie oben gesehen, ziemlich kostspielig sein kann. Allerdings sollte man es nicht zu klein einstellen, da dies zu einem vorzeitigen Verlassen des Blockauslassmodus fĂŒhren wĂŒrde. FĂŒr leistungsstarke Server (etwa 20–40 physische Kerne) ist es optimal, etwa 300–400 MB einzustellen. FĂŒr Mittelklasse-Systeme (~10 Kerne) 200–300 MB. FĂŒr schwache Systeme (2–5 Kerne) können 50–100 MB in Ordnung sein (darauf wurde nicht getestet).

Betrachten wir, wie das funktioniert: Angenommen, wir haben hbase.lru.cache.heavy.eviction.mb.size.limit = 500 eingestellt, es gibt eine gewisse Last (Lesen), und alle ~10 Sekunden berechnen wir, wie viele Bytes durch die folgende Formel entfernt wurden:

Überhang = Summe der freigegebenen Bytes (MB) * 100 / Limit (MB) — 100;

Wenn tatsĂ€chlich 2000 MB entfernt wurden, ergibt sich der Überhang folgendermassen:

2000 * 100 / 500 — 100 = 300%

Algorithmen versuchen, nicht mehr als einige Dutzend Prozent zu halten, sodass die Funktion den Prozentsatz der zwischengespeicherten Blöcke verringert und damit einen Selbstoptimierungsmechanismus realisiert.

Wenn die Last jedoch gesunken ist, beispielsweise nur 200 MB ausgelagert wurden und der Overhead negativ ist (das sogenannte Overshooting):

200 * 100 / 500 — 100 = -60%

Wird die Funktion im Gegenteil den Prozentsatz der zwischengespeicherten Blöcke erhöhen, bis der Overhead positiv wird.

Im Folgenden sehen Sie ein Beispiel, wie dies in realen Daten aussieht. Versuchen Sie nicht, 0 % zu erreichen, das ist unmöglich. Ein Ansatz von etwa 30 – 100 % ist sehr gut, da er hilft, ein vorzeitiges Verlassen des Optimierungsmodus bei kurzfristigen Spitzen zu vermeiden.

hbase.lru.cache.heavy.eviction.overhead.coefficient — legt fest, wie schnell wir das Ergebnis erhalten möchten. Wenn wir sicher wissen, dass unsere LesevorgĂ€nge hauptsĂ€chlich langwierig sind und wir nicht warten möchten, können wir diesen Koeffizienten erhöhen, um schneller eine hohe Leistung zu erzielen.

Wir haben beispielsweise diesen Faktor auf 0,01 gesetzt. Das bedeutet, dass der Overhead (siehe oben) mit dieser Zahl multipliziert wird, um das Ergebnis zu erhalten, und der Prozentsatz der zwischengespeicherten Blöcke verringert wird. Angenommen, der Overhead betrÀgt 300 %, und der Faktor ist 0,01, dann wird der Prozentsatz der zwischengespeicherten Blöcke um 3 % reduziert.

Eine Ă€hnliche Logik fĂŒr „Backpressure“ wurde auch fĂŒr negative Werte des Overheads (Overshooting) implementiert. Da es immer kurzfristige Schwankungen im Lese- und Schreibvolumen geben kann, ermöglicht dieser Mechanismus, einen vorzeitigen Ausstieg aus dem Optimierungsmodus zu vermeiden. Backpressure folgt einer umgekehrten Logik: Je stĂ€rker das Overshooting, desto mehr Blöcke werden zwischengespeichert.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Implementierungscode

        LruBlockCache cache = this.cache.get();
        if (cache == null) {
          break;
        }
        freedSumMb += cache.evict() / 1024 / 1024;
        /*
        * Manchmal lesen wir mehr Daten, als in den BlockCache passen,
        * was zu einer hohen Anzahl von Eviktionen fĂŒhrt.
        * Dies wiederum belastet den Garbage Collector stark.
        * Daher werden viele Blöcke in den BlockCache gelegt, aber nie gelesen,
        * wodurch viel CPU-Ressourcen verbraucht werden.
        * Hier analysieren wir, wie viele Bytes freigegeben wurden und entscheiden
        * ob es an der Zeit ist, die Anzahl der Cache-Blöcke zu reduzieren.
        * Das hilft, zu viele Blöcke in den BlockCache zu vermeiden,
        * wenn evict() sehr aktiv ist und CPU fĂŒr andere Aufgaben spart.
        * Mehr Details: https://issues.apache.org/jira/browse/HBASE-23887
        */

        // Zuerst mĂŒssen wir kontrollieren, wie viel Zeit vergangen ist,
        // seit die vorherige evict() gestartet wurde.
        // Dies sollte nahezu die gleiche Zeit sein (+/- 10s)
        // da wir jedes Mal vergleichbare Mengen freigegebener Bytes erhalten.
        // 10s, weil dies der Standardzeitraum fĂŒr die AusfĂŒhrung von evict() ist (siehe oben this.wait)
        long stopTime = System.currentTimeMillis();
        if ((stopTime - startTime) > 1000 * 10 - 1) {
          // Hier mĂŒssen wir berechnen, welche Situation wir haben.
          // Wir haben das Limit "hbase.lru.cache.heavy.eviction.bytes.size.limit"
          // und können die Überlast darauf berechnen.
          // Wir verwenden diese Information, um zu entscheiden,
          // wie wir den Prozentsatz der Cache-Blöcke Àndern.
          freedDataOverheadPercent =
            (int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
          if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
            // Jetzt befinden wir uns in einer Situation, in der wir ĂŒber dem Limit liegen.
            // Aber vielleicht ignorieren wir es, weil es bald enden wird.
            heavyEvictionCount++;
            if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
              // Es dauert schon lange, und wir mĂŒssen jetzt die Cache-Blöcke reduzieren.
              // Hier berechnen wir, wie viele Blöcke wir ĂŒberspringen möchten.
              // Es hÀngt von Folgendem ab:
              // 1. Überlast - wenn die Überlast groß ist, könnten wir aggressiver
              // die Anzahl der Cache-Blöcke reduzieren.
              // 2. Wie schnell wir das Ergebnis haben möchten. Wenn wir wissen, dass unser
              // starkes Lesen schon lange andauert, wollen wir nicht warten und können
              // den Koeffizienten erhöhen und eine gute Leistung recht schnell erzielen.
              // Wenn wir uns jedoch nicht sicher sind, können wir es langsam angehen und es könnte
              // einen vorzeitigen Ausstieg aus diesem Modus verhindern. Daher können wir bei
              // einem höheren Koeffizienten eine bessere Leistung erzielen, wenn das starke Lesen stabil ist.
              // Wenn das Lesen jedoch schwankt, können wir uns anpassen und den
              // Koeffizienten auf einen niedrigeren Wert setzen.
              int change =
                (int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
              // Die Praxis zeigt, dass eine Reduzierung um 15% ausreichend ist.
              // Wir sind nicht gierig (das könnte zu einem vorzeitigen Ausstieg fĂŒhren).
              change = Math.min(15, change);
              change = Math.max(0, change); // Ich denke, das wird nie passieren, aber vorsichtshalber prĂŒfen.
              // Dies ist der entscheidende Punkt, hier reduzieren wir % der Cache-Blöcke.
              cache.cacheDataBlockPercent -= change;
              // Wenn wir zu tief sinken, mĂŒssen wir hier aufhören; 1% sollte auf jeden Fall bleiben.
              cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
            }
          } else {
            // Nun, wir haben eine Überschreitung erhalten.
            // Vielleicht ist es nur eine kurzfristige Schwankung und wir können in diesem Modus bleiben.
            // Das hilft, vorzeitige Ausstiege wÀhrend kurzfristiger Schwankungen zu vermeiden.
            // Wenn die Überschreitung weniger als 90% betrĂ€gt, versuchen wir, den Prozentsatz der
            // Cache-Blöcke zu erhöhen und hoffen, dass das genug ist.
            if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
              // Einfache Logik: mehr Überschreitung - mehr Cache-Blöcke (RĂŒckstau)
              int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
              cache.cacheDataBlockPercent += change;
              // Aber es kann nicht mehr als 100% sein, also ĂŒberprĂŒfen wir das.
              cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
            } else {
              // Es sieht so aus, als ob das starke Lesen vorbei ist.
              // Einfach aus diesem Modus aussteigen.
              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;
       }

Betrachten wir nun alles an einem realen Beispiel. Wir haben folgendes Test-Szenario:

  1. Wir beginnen mit dem Scan (25 Threads, Batch = 100)
  2. Nach 5 Minuten fĂŒgen wir Multi-Get-Anfragen hinzu (25 Threads, Batch = 100)
  3. Nach 5 Minuten schalten wir die Multi-Get-Anfragen aus (es bleibt wieder nur der Scan)

Wir fĂŒhren zwei DurchlĂ€ufe durch, zuerst hbase.lru.cache.heavy.eviction.count.limit = 10000 (was die Funktion faktisch ausschaltet), und dann setzen wir das Limit auf 0 (was es aktiviert).

In den folgenden Logs sehen wir, wie die Funktion aktiviert wird, Overshooting auf 14–71% zurĂŒckgesetzt wird. Gelegentlich sinkt die Last, was Backpressure aktiviert und HBase erneut mehr Blöcke zwischenspeichert.

Log des RegionServers
evicted (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, aktueller Cache-Datenblock (%): 100
evicted (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, aktueller Cache-Datenblock (%): 100
evicted (MB): 2170, ratio 1.09, overhead (%): 985, heavy eviction counter: 1, aktueller Cache-Datenblock (%): 91 < start
evicted (MB): 3763, ratio 1.08, overhead (%): 1781, heavy eviction counter: 2, aktueller Cache-Datenblock (%): 76
evicted (MB): 3306, ratio 1.07, overhead (%): 1553, heavy eviction counter: 3, aktueller Cache-Datenblock (%): 61
evicted (MB): 2508, ratio 1.06, overhead (%): 1154, heavy eviction counter: 4, aktueller Cache-Datenblock (%): 50
evicted (MB): 1824, ratio 1.04, overhead (%): 812, heavy eviction counter: 5, aktueller Cache-Datenblock (%): 42
evicted (MB): 1482, ratio 1.03, overhead (%): 641, heavy eviction counter: 6, aktueller Cache-Datenblock (%): 36
evicted (MB): 1140, ratio 1.01, overhead (%): 470, heavy eviction counter: 7, aktueller Cache-Datenblock (%): 32
evicted (MB): 913, ratio 1.0, overhead (%): 356, heavy eviction counter: 8, aktueller Cache-Datenblock (%): 29
ausgeschlossene (MB): 912, VerhÀltnis 0,89, Overhead (%): 356, Schwerer AusschlusszÀhler: 9, Aktueller Cache DataBlock (%): 26
ausgeschlossene (MB): 684, VerhÀltnis 0,76, Overhead (%): 242, Schwerer AusschlusszÀhler: 10, Aktueller Cache DataBlock (%): 24
ausgeschlossene (MB): 684, VerhÀltnis 0,61, Overhead (%): 242, Schwerer AusschlusszÀhler: 11, Aktueller Cache DataBlock (%): 22
ausgeschlossene (MB): 456, VerhÀltnis 0,51, Overhead (%): 128, Schwerer AusschlusszÀhler: 12, Aktueller Cache DataBlock (%): 21
ausgeschlossene (MB): 456, VerhÀltnis 0,42, Overhead (%): 128, Schwerer AusschlusszÀhler: 13, Aktueller Cache DataBlock (%): 20
ausgeschlossene (MB): 456, VerhÀltnis 0,33, Overhead (%): 128, Schwerer AusschlusszÀhler: 14, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, Schwerer AusschlusszÀhler: 15, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 342, VerhÀltnis 0,32, Overhead (%): 71, Schwerer AusschlusszÀhler: 16, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 342, VerhÀltnis 0,31, Overhead (%): 71, Schwerer AusschlusszÀhler: 17, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,3, Overhead (%): 14, Schwerer AusschlusszÀhler: 18, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,29, Overhead (%): 14, Schwerer AusschlusszÀhler: 19, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,27, Overhead (%): 14, Schwerer AusschlusszÀhler: 20, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,25, Overhead (%): 14, Schwerer AusschlusszÀhler: 21, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,24, Overhead (%): 14, Schwerer AusschlusszÀhler: 22, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,22, Overhead (%): 14, Schwerer AusschlusszÀhler: 23, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,21, Overhead (%): 14, Schwerer AusschlusszÀhler: 24, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,2, Overhead (%): 14, Schwerer AusschlusszÀhler: 25, Aktueller Cache DataBlock (%): 19
ausgeschlossene (MB): 228, VerhÀltnis 0,17, Overhead (%): 14, schwerer AusschlusszÀhler: 26, aktueller Zwischenspeicherungs-Datenblock (%): 19
ausgeschlossene (MB): 456, VerhĂ€ltnis 0,17, Overhead (%): 128, schwerer AusschlusszĂ€hler: 27, aktueller Zwischenspeicherungs-Datenblock (%): 18 < hinzugefĂŒgte Abrufe (aber Tabelle gleich)
ausgeschlossene (MB): 456, VerhÀltnis 0,15, Overhead (%): 128, schwerer AusschlusszÀhler: 28, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 342, VerhÀltnis 0,13, Overhead (%): 71, schwerer AusschlusszÀhler: 29, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 342, VerhÀltnis 0,11, Overhead (%): 71, schwerer AusschlusszÀhler: 30, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 342, VerhÀltnis 0,09, Overhead (%): 71, schwerer AusschlusszÀhler: 31, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,08, Overhead (%): 14, schwerer AusschlusszÀhler: 32, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,07, Overhead (%): 14, schwerer AusschlusszÀhler: 33, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,06, Overhead (%): 14, schwerer AusschlusszÀhler: 34, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,05, Overhead (%): 14, schwerer AusschlusszÀhler: 35, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,05, Overhead (%): 14, schwerer AusschlusszÀhler: 36, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 228, VerhÀltnis 0,04, Overhead (%): 14, schwerer AusschlusszÀhler: 37, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 109, VerhĂ€ltnis 0,04, Overhead (%): -46, schwerer AusschlusszĂ€hler: 37, aktueller Zwischenspeicherungs-Datenblock (%): 22 < RĂŒckdruck
ausgeschlossene (MB): 798, VerhÀltnis 0,24, Overhead (%): 299, schwerer AusschlusszÀhler: 38, aktueller Zwischenspeicherungs-Datenblock (%): 20
ausgeschlossene (MB): 798, VerhÀltnis 0,29, Overhead (%): 299, schwerer AusschlusszÀhler: 39, aktueller Zwischenspeicherungs-Datenblock (%): 18
ausgeschlossene (MB): 570, VerhÀltnis 0,27, Overhead (%): 185, schwerer AusschlusszÀhler: 40, aktueller Zwischenspeicherungs-Datenblock (%): 17
ausgeschlossene (MB): 456, VerhÀltnis 0,22, Overhead (%): 128, schwerer AusschlusszÀhler: 41, aktueller Zwischenspeicherungs-Datenblock (%): 16
ausgeschlossene (MB): 342, VerhĂ€ltnis 0,16, Überkopf (%): 71, schwerer AusschlusszĂ€hler: 42, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 342, VerhĂ€ltnis 0,11, Überkopf (%): 71, schwerer AusschlusszĂ€hler: 43, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,09, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 44, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,07, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 45, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,05, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 46, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 222, VerhĂ€ltnis 0,04, Überkopf (%): 11, schwerer AusschlusszĂ€hler: 47, aktueller Cache-Datenblock (%): 16
ausgeschlossene (MB): 104, VerhĂ€ltnis 0,03, Überkopf (%): -48, schwerer AusschlusszĂ€hler: 47, aktueller Cache-Datenblock (%): 21 < interrupt gets
ausgeschlossene (MB): 684, VerhĂ€ltnis 0,2, Überkopf (%): 242, schwerer AusschlusszĂ€hler: 48, aktueller Cache-Datenblock (%): 19
ausgeschlossene (MB): 570, VerhĂ€ltnis 0,23, Überkopf (%): 185, schwerer AusschlusszĂ€hler: 49, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 342, VerhĂ€ltnis 0,22, Überkopf (%): 71, schwerer AusschlusszĂ€hler: 50, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,21, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 51, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,2, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 52, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,18, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 53, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,16, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 54, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 228, VerhĂ€ltnis 0,14, Überkopf (%): 14, schwerer AusschlusszĂ€hler: 55, aktueller Cache-Datenblock (%): 18
ausgeschlossene (MB): 112, VerhĂ€ltnis 0,14, Überkopf (%): -44, schwerer AusschlusszĂ€hler: 55, aktueller Cache-Datenblock (%): 23 < back pressure
ausgeschlossene (MB): 456, VerhĂ€ltnis 0,26, Überkopf (%): 128, schwerer AusschlusszĂ€hler: 56, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,31, Overhead (%): 71, schwerer Entfernung-ZÀhler: 57, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 58, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 59, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 60, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 61, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 62, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 63, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,32, Overhead (%): 71, schwerer Entfernung-ZÀhler: 64, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 65, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 66, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,32, Overhead (%): 71, schwerer Entfernung-ZÀhler: 67, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 68, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,32, Overhead (%): 71, schwerer Entfernung-ZÀhler: 69, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,32, Overhead (%): 71, schwerer Entfernung-ZÀhler: 70, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 71, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 72, aktueller Cache-Datenblock (%): 22
entfernt (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Entfernung-ZÀhler: 73, aktueller Cache-Datenblock (%): 22
ausgeschlossene (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Ausschluss-ZÀhler: 74, aktueller Cache-Datenblock (%): 22
ausgeschlossene (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Ausschluss-ZÀhler: 75, aktueller Cache-Datenblock (%): 22
ausgeschlossene (MB): 342, VerhÀltnis 0,33, Overhead (%): 71, schwerer Ausschluss-ZÀhler: 76, aktueller Cache-Datenblock (%): 22
ausgeschlossene (MB): 21, VerhÀltnis 0,33, Overhead (%): -90, schwerer Ausschluss-ZÀhler: 76, aktueller Cache-Datenblock (%): 32
evicted (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, aktueller Cache-Datenblock (%): 100
evicted (MB): 0, ratio 0.0, overhead (%): -100, heavy eviction counter: 0, aktueller Cache-Datenblock (%): 100

Die Scans waren notwendig, um denselben Prozess in Form eines Diagramms darzustellen, das das VerhĂ€ltnis zwischen zwei Cache-Bereichen zeigt — single (hier gelangen die Blöcke, die noch nie angefragt wurden) und multi (hier werden „nachgefragte“ Daten gespeichert):

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Und schließlich, wie die Parameter in Form eines Graphen aussehen. Zum Vergleich wurde der Cache zu Beginn vollstĂ€ndig deaktiviert, danach wurde HBase mit Caching gestartet und der Optimierungsbeginn um 5 Minuten verzögert (30 Ausschlussschleifen).

Der vollstĂ€ndige Code ist im Pull Request verfĂŒgbar HBASE 23887 auf GitHub.

Jedoch sind 300.000 LesevorgĂ€nge pro Sekunde nicht alles, was aus dieser Hardware unter diesen Bedingungen herausgeholt werden kann. Das liegt daran, dass beim Zugriff auf Daten ĂŒber HDFS der ShortCircuitCache (SSC) verwendet wird, der direkten Zugriff auf die Daten ermöglicht und dabei Netzwerkinteraktionen vermeidet.

Die Profilierung hat gezeigt, dass dieser Mechanismus zwar einen erheblichen Vorteil bietet, aber irgendwann selbst zum Flaschenhals wird, da nahezu alle schweren Operationen innerhalb des Locks stattfinden, was zu blockierenden ZustĂ€nden fĂŒhrt.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Als wir dies erkannten, wurde uns klar, dass wir das Problem umgehen können, indem wir ein Array unabhÀngiger SSCs erstellen:

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

Und dann arbeiten wir mit ihnen, indem wir Überschneidungen durch die letzte Ziffer des Offsets ausschließen:

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

Nun können wir mit den Tests beginnen. Dazu lesen wir Dateien aus HDFS mithilfe einer einfachen Multithread-Anwendung. Wir stellen die Parameter ein:

conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // Standard = 1 MB und dies verlangsamt das Lesen stark, daher ist es besser, es an die tatsĂ€chlichen BedĂŒrfnisse anzupassen
conf.set("dfs.client.short.circuit.num", num); // von 1 bis 10

Und wir lesen einfach die Dateien:

FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i  900000000)
        position = 0L;
    int res = in.read(position, byteBuffer, 0, 65536);
}

Dieser Code wird in separaten Threads ausgefĂŒhrt, und wir werden die Anzahl der gleichzeitig lesbaren Dateien (von 10 bis 200 – horizontale Achse) und die Anzahl der Caches (von 1 bis 10 – Grafiken) erhöhen. Die vertikale Achse zeigt die Beschleunigung, die durch die Erhöhung von SSC im Vergleich zum Fall, wenn nur ein Cache vorhanden ist, erzielt wird.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

So lesen Sie das Diagramm: Die AusfĂŒhrungszeit fĂŒr 100.000 LesevorgĂ€nge in Blöcken von 64 KB mit einem Cache betrĂ€gt 78 Sekunden. Mit 5 Caches erfolgt dies in 16 Sekunden. Das heißt, es ergibt sich eine Beschleunigung von ~5-fach. Wie aus dem Diagramm ersichtlich, ist der Effekt bei einer niedrigen Anzahl paralleler Lesungen nicht sehr auffĂ€llig; dies wird jedoch bedeutender, wenn die Anzahl der Lese-Threads ĂŒber 50 steigt. Ebenfalls auffĂ€llig ist, dass die Erhöhung der Anzahl von SSC ab 6 und mehr nur einen geringeren Leistungsschub bringt.

Hinweis 1: Da die Testergebnisse recht volatil sind (siehe unten), wurden 3 DurchlĂ€ufe durchgefĂŒhrt und die erhaltenen Werte wurden im Mittelwert berechnet.

Hinweis 2: Der Leistungsschub durch die Konfiguration fĂŒr den Zufallszugriff ist derselbe, obwohl der Zugriff selbst etwas langsamer ist.

Es ist jedoch wichtig zu beachten, dass diese Beschleunigung, im Gegensatz zu HBase, nicht immer kostenlos ist. Hier "entsperren" wir eher die Möglichkeiten der CPU, um Aufgaben zu erledigen, anstatt sie an den Locks hÀngen zu lassen.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Hier lĂ€sst sich feststellen, dass die allgemeine Erhöhung der Cache-Zahl zu einem mehr oder weniger proportionalen Anstieg der CPU-Auslastung fĂŒhrt. Es gibt jedoch einige Kombinationen, die vorteilhafter sind.

Betrachten wir zum Beispiel die Einstellung SSC = 3 genauer. Der Leistungszuwachs im Bereich betrĂ€gt etwa das 3,3-fache. Unten sind die Ergebnisse aller drei einzelnen DurchlĂ€ufe aufgefĂŒhrt.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

WĂ€hrend der CPU-Verbrauch ungefĂ€hr um das 2,8-fache steigt. Der Unterschied ist nicht sehr groß, aber es macht der kleinen Grete Freude, und möglicherweise bleibt Zeit fĂŒr den Besuch der Schule und der Unterrichtsstunden.

Daher wird dies positive Auswirkungen auf jedes Tool haben, das Massenzugriff auf HDFS nutzt (zum Beispiel Spark usw.), vorausgesetzt, der Anwendungscode ist leicht (d.h., der Engpass liegt tatsĂ€chlich auf der HDFS-Clientseite) und es stehen freie CPU-KapazitĂ€ten zur VerfĂŒgung. Um dies zu ĂŒberprĂŒfen, lassen Sie uns testen, welchen Effekt die gleichzeitige Anwendung der BlockCache-Optimierung und der SSC-Tuning fĂŒr das Lesen aus HBase haben wird.

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Es ist offensichtlich, dass der Effekt unter diesen Bedingungen nicht so stark ist wie bei raffinierten Tests (Lesen ohne jegliche Verarbeitung), aber zusÀtzliche 80K sind hier durchaus möglich. Zusammen bieten beide Optimierungen eine Beschleunigung von bis zu 4-fach.

Auch zu dieser Optimierung wurde ein PR erstellt [HDFS-15202], der ĂŒbernommen wurde, und diese FunktionalitĂ€t wird in den kommenden Releases verfĂŒgbar sein.

Und schließlich war es interessant, die Leseleistung einer Ă€hnlichen Wide-Column-Datenbank Cassandra mit HBase zu vergleichen.

Zu diesem Zweck wurden Instanzen des Standard-Belastungstest-Tools YCSB von zwei Hosts aus gestartet (800 Threads insgesamt). Auf der Serverseite wurden jeweils 4 Instanzen von RegionServer und Cassandra auf 4 Hosts ausgefĂŒhrt (nicht die, auf denen die Clients laufen, um deren Einfluss zu vermeiden). Die LesevorgĂ€nge erfolgten aus Tabellen mit folgenden GrĂ¶ĂŸen:

HBase — 300 GB auf HDFS (100 GB reine Daten)

Cassandra — 250 GB (Replikationsfaktor = 3)

Das heißt, das Volumen war ungefĂ€hr gleich (in HBase etwas mehr).

Parameter von HBase:

dfs.client.short.circuit.num = 5 (Optimierung des HDFS-Clients)

hbase.lru.cache.heavy.eviction.count.limit = 30 — das bedeutet, dass der Patch nach 30 Ausweisungen (~5 Minuten) zu wirken beginnt.

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — Zielvolumen fĂŒr Caching und Ausweisungen.

Die YCSB-Protokolle wurden analysiert und in Excel-Diagramme umgewandelt:

Wie man die Lesegeschwindigkeit von HBase um das Dreifache und von HDFS um das FĂŒnffache erhöht

Wie man sieht, ermöglichen die Optimierungsdaten, die Leistung dieser Datenbanken unter diesen Bedingungen auszugleichen und 450.000 LesevorgÀnge pro Sekunde zu erreichen.

Wir hoffen, dass diese Informationen fĂŒr jemanden im spannenden Wettbewerb um die Leistung nĂŒtzlich sein können.

Quelle: habr.com

ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen đŸ”„ ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster