Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Das ist nicht einmal ein Witz, es scheint, dass dieses Bild die Essenz dieser DBs am genauesten widerspiegelt, und am Ende wird klar sein, warum:

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Laut DB-Engines Ranking sind die beiden beliebtesten NoSQL-Column-Store-Datenbanken Cassandra (im Folgenden CS) und HBase (HB).

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Durch den Willen des Schicksals hat unser Datenlademanagement-Team bei der Sberbank bereits lange und arbeitet eng mit HB zusammen. In dieser Zeit haben wir seine Stärken und Schwächen gut studiert und gelernt, wie man es zubereitet. Das Vorhandensein einer Alternative in Form von CS ließ uns jedoch ständig Zweifel hegen: Haben wir die richtige Wahl getroffen? Zumal die Ergebnisse Vergleiche, durchgeführt von DataStax, ergab, dass CS HB mit einem nahezu überwältigenden Ergebnis mühelos schlägt. Andererseits ist DataStax ein interessierter Partner, und man sollte sich nicht auf deren Worte verlassen. Auch die eher spärlichen Informationen zu den Testbedingungen waren verwirrend. Daher beschlossen wir, selbst herauszufinden, wer der König von BigData NoSql ist. Die Ergebnisse waren recht interessant.

Bevor wir jedoch zu den Ergebnissen der durchgeführten Tests kommen, müssen die wesentlichen Aspekte der Umgebungskonfigurationen beschrieben werden. Tatsächlich kann CS in einem Modus verwendet werden, der Datenverlust zulässt. Das heißt, wenn nur ein Server (Knoten) für die Daten eines bestimmten Schlüssels verantwortlich ist und dieser aus irgendeinem Grund ausfällt, geht der Wert dieses Schlüssels verloren. Für viele Aufgaben ist dies unkritisch, im Bankensektor jedoch eher die Ausnahme als die Regel. In unserem Fall ist es unerlässlich, mehrere Datenkopien für eine zuverlässige Speicherung zu haben.

Daher wurde nur der CS-Betriebsmodus im Triple-Replication-Modus betrachtet, d. h. die Erstellung des Keyspace wurde mit folgenden Parametern durchgeführt:

CREATE KEYSPACE ks WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'datacenter1' : 3};

Um die erforderliche Konsistenz zu gewährleisten, gibt es zwei Möglichkeiten. Die allgemeine Regel lautet:
NW + NR > RF

Das bedeutet, dass die Anzahl der Bestätigungen von Knoten beim Schreiben (NW) plus die Anzahl der Bestätigungen von Knoten beim Lesen (NR) größer sein muss als der Replikationsfaktor. In unserem Fall ist RF = 3, was bedeutet, dass folgende Optionen geeignet sind:
2 + 2 > 3
3 + 1 > 3

Da es für uns grundsätzlich wichtig ist, Daten so zuverlässig wie möglich zu speichern, wurde das 3+1-Schema gewählt. Darüber hinaus arbeitet HB nach einem ähnlichen Prinzip, d. h. ein solcher Vergleich ist fairer.

Es ist erwähnenswert, dass DataStax in seiner Studie das Gegenteil tat: Sie setzten RF = 1 sowohl für CS als auch für HB (für letzteres durch Änderung der HDFS-Einstellungen). Dies ist ein wirklich wichtiger Aspekt, da die Auswirkungen auf die CS-Leistung in diesem Fall enorm sind. Die folgende Abbildung zeigt beispielsweise die erhöhte Zeit, die zum Laden von Daten in CS benötigt wird:

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Hier sehen wir Folgendes: Je mehr konkurrierende Threads Daten schreiben, desto länger dauert es. Das ist natürlich, aber es ist wichtig, dass der Leistungsverlust bei RF=3 deutlich höher ist. Anders ausgedrückt: Wenn wir in vier Tabellen mit jeweils fünf Threads schreiben (insgesamt 4), verliert RF=5 etwa das Zweifache (20 Sekunden für RF=3 gegenüber 2 Sekunden für RF=150). Erhöhen wir jedoch die Last, indem wir Daten in acht Tabellen mit jeweils fünf Threads laden (insgesamt 3), verliert RF=75 1-mal (8 Sekunden gegenüber 5).

Vielleicht ist dies teilweise das Geheimnis erfolgreicher Lasttests für CS, die von DataStax durchgeführt wurden, denn für HB an unserem Stand hatte die Änderung des Replikationsfaktors von 2 auf 3 keine Auswirkungen. Das heißt, Festplatten stellen für HB in unserer Konfiguration keinen Engpass dar. Es gibt hier jedoch viele weitere Fallstricke, da unsere HB-Version leicht gepatcht und optimiert wurde, die Umgebungen völlig unterschiedlich sind usw. Es ist auch erwähnenswert, dass ich vielleicht einfach nicht weiß, wie man CS richtig vorbereitet, und es gibt effektivere Möglichkeiten, damit zu arbeiten. Ich hoffe, wir werden dies in den Kommentaren herausfinden. Aber der Reihe nach.

Alle Tests wurden auf einem Hardware-Cluster bestehend aus 4 Servern durchgeführt, jeweils in der Konfiguration:

CPU: Xeon E5-2680 v4 bei 2.40 GHz 64 Threads.
Festplatten: 12 Stück SATA HDD
Java-Version: 1.8.0_111

CS-Version: 3.11.5

cassandra.yml-ParameterAnzahl_Token: 256
hinted_handoff_enabled: wahr
hinted_handoff_throttle_in_kb: 1024
max_hints_delivery_threads: 2
Hinweise_Verzeichnis: /data10/cassandra/hints
Hinweise_Flush_Periode_in_ms: 10000
max_hints_file_size_in_mb: 128
batchlog_replay_throttle_in_kb: 1024
Authentifikator: AllowAllAuthenticator
Autorisierer: AllowAllAuthorizer
Rollenmanager: CassandraRoleManager
Rollengültigkeit in ms: 2000
Gültigkeit der Berechtigungen in ms: 2000
Gültigkeit der Anmeldeinformationen in ms: 2000
Partitionierer: org.apache.cassandra.dht.Murmur3Partitioner
Datendateiverzeichnisse:
— /data1/cassandra/data # jedes dataN-Verzeichnis ist eine separate Festplatte
— /data2/cassandra/data
— /data3/cassandra/data
— /data4/cassandra/data
— /data5/cassandra/data
— /data6/cassandra/data
— /data7/cassandra/data
— /data8/cassandra/data
Commitlog-Verzeichnis: /data9/cassandra/commitlog
cdc_enabled: falsch
disk_failure_policy: Stopp
commit_failure_policy: Stopp
Cachegröße_für_vorbereitete_Anweisungen_MB:
thrift_prepared_statements_cache_size_mb:
Schlüsselcachegröße in MB:
key_cache_save_period: 14400
row_cache_size_in_mb: 0
row_cache_save_period: 0
counter_cache_size_in_mb:
counter_cache_save_period: 7200
gespeicherte_Caches_Verzeichnis: /data10/cassandra/saved_caches
commitlog_sync: periodisch
commitlog_sync_period_in_ms: 10000
Commitlog-Segmentgröße in MB: 32
Seed-Anbieter:
- Klassenname: org.apache.cassandra.locator.SimpleSeedProvider
Parameter:
- Samen: "*,*"
concurrent_reads: 256 # 64 ausprobiert – kein Unterschied bemerkt
concurrent_writes: 256 # 64 ausprobiert – kein Unterschied bemerkt
concurrent_counter_writes: 256 # 64 ausprobiert – kein Unterschied bemerkt
gleichzeitige Schreibvorgänge in materialisierten Ansichten: 32
memtable_heap_space_in_mb: 2048 # 16 GB ausprobiert – war langsamer
memtable_allocation_type: Heap-Puffer
index_summary_capacity_in_mb:
index_summary_resize_interval_in_minutes: 60
trickle_fsync: falsch
trickle_fsync_interval_in_kb: 10240
Speicherport: 7000
SSL-Speicherport: 7001
Listenadresse: *
Broadcast-Adresse: *
listen_on_broadcast_address: wahr
Internode_Authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: true
nativer_transport_port: 9042
start_rpc: wahr
RPC-Adresse: *
RPC-Port: 9160
rpc_keepalive: wahr
rpc_server_type:sync
sparsamer_Rahmen_Transport_Größe_in_MB: 15
incremental_backups: false
snapshot_before_compaction: falsch
auto_snapshot: true
Spaltenindexgröße in KB: 64
Spaltenindex-Cachegröße in KB: 2
gleichzeitige_Kompaktoren: 4
Komprimierungsdurchsatz in MB pro Sekunde: 1600
sstable_preemptive_open_interval_in_mb: 50
Zeitüberschreitung der Leseanforderung in ms: 100000
range_request_timeout_in_ms: 200000
Zeitüberschreitung der Schreibanforderung in ms: 40000
Zeitüberschreitung der Schreibanforderung des Zählers in ms: 100000
cas_contention_timeout_in_ms: 20000
truncate_request_timeout_in_ms: 60000
Anforderungs-Timeout in ms: 200000
slow_query_log_timeout_in_ms: 500
cross_node_timeout: falsch
endpoint_snitch: GossipingPropertyFileSnitch
dynamic_snitch_update_interval_in_ms: 100
dynamic_snitch_reset_interval_in_ms: 600000
dynamic_snitch_badness_threshold: 0.1
request_scheduler: org.apache.cassandra.scheduler.NoScheduler
Serververschlüsselungsoptionen:
interne Verschlüsselung: keine
Client-Verschlüsselungsoptionen:
aktiviert: false
Internode-Kompression: dc
inter_dc_tcp_nodelay: falsch
tracetype_query_ttl: 86400
tracetype_repair_ttl: 604800
enable_user_defined_functions: falsch
enable_scripted_user_defined_functions: falsch
windows_timer_interval: 1
transparente Datenverschlüsselungsoptionen:
aktiviert: false
Tombstone_Warnschwelle: 1000
Tombstone-Fehlerschwelle: 100000
Batch-Größenwarnschwelle in KB: 200
Schwellenwert für Batchgröße-Fehler in KB: 250
unlogged_batch_across_partitions_warn_threshold: 10
Warnschwellenwert für Komprimierung großer Partitionen: 100
gc_warn_threshold_in_ms: 1000
back_pressure_enabled: falsch
enable_materialized_views: wahr
enable_sasi_indexes: wahr

GC-Einstellungen:

### CMS-Einstellungen-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:Überlebensrate=8
-XX:MaxTenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSWartedauer=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloadingEnabled

Dem jvm.options-Speicher wurden 16 GB zugewiesen (wir haben es auch mit 32 GB versucht, konnten keinen Unterschied feststellen).

Die Tabellen wurden mit dem folgenden Befehl erstellt:

CREATE TABLE ks.t1 (id bigint PRIMARY KEY, title text) WITH compression = {'sstable_compression': 'LZ4Compressor', 'chunk_length_kb': 64};

HB-Version: 1.2.0-cdh5.14.2 (in der Klasse org.apache.hadoop.hbase.regionserver.HRegion haben wir MetricsRegion ausgeschlossen, was zu GC führte, wenn die Anzahl der Regionen auf RegionServer mehr als 1000 betrug)

Nicht standardmäßige HBase-Parameterzookeeper.session.timeout: 120000
hbase.rpc.timeout: 2 Minute(n)
hbase.client.scanner.timeout.period: 2 Minute(n)
hbase.master.handler.count: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.period: 2 Minute(n)
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.period: 4 Stunde(n)
hbase.regionserver.maxlogs: 200
hbase.hregion.memstore.flush.size: 1 GiB
hbase.hregion.memstore.block.multiplier: 6
hbase.hstore.compactionThreshold: 5
hbase.hstore.blockingStoreFiles: 200
hbase.hregion.majorcompaction: 1 Tag(e)
Erweitertes Konfigurations-Snippet des HBase-Dienstes (Sicherheitsventil) für hbase-site.xml:
hbase.regionserver.wal.codecorg.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec
hbase.master.namespace.init.timeout3600000
hbase.regionserver.optionalcacheflushinterval18000000
hbase.regionserver.thread.compaction.large12
hbase.regionserver.wal.enablecompressiontrue
hbase.hstore.compaction.max.size1073741824
hbase.server.compactchecker.interval.multiplier200
Java-Konfigurationsoptionen für HBase RegionServer:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis: 2 Minute(n)
hbase.snapshot.region.timeout: 2 Minute(n)
hbase.snapshot.master.timeout.millis: 2 Minute(n)
Maximale Protokollgröße des HBase REST-Servers: 100 MiB
HBase REST-Server – Maximale Anzahl an Protokolldateisicherungen: 5
Maximale Protokollgröße des HBase Thrift-Servers: 100 MiB
HBase Thrift Server Maximale Protokolldateisicherungen: 5
Maximale Master-Protokollgröße: 100 MiB
Master Maximale Protokolldateisicherungen: 5
Maximale Protokollgröße des RegionServers: 100 MiB
RegionServer Maximale Protokolldateisicherungen: 5
HBase Active Master-Erkennungsfenster: 4 Minute(n)
dfs.client.hedged.read.threadpool.size: 40
dfs.client.hedged.read.threshold.millis: 10 Millisekunden
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Maximale Anzahl Prozessdateideskriptoren: 180000
hbase.thrift.minWorkerThreads: 200
hbase.master.executor.openregion.threads: 30
hbase.master.executor.closeregion.threads: 30
hbase.master.executor.serverops.threads: 60
hbase.regionserver.thread.compaction.small: 6
hbase.ipc.server.read.threadpool.size: 20
Region Mover-Threads: 6
Client-Java-Heap-Größe in Bytes: 1 GiB
HBase REST-Server-Standardgruppe: 3 GiB
HBase Thrift Server-Standardgruppe: 3 GiB
Java-Heap-Größe von HBase Master in Bytes: 16 GiB
Java-Heap-Größe des HBase RegionServer in Bytes: 32 GiB

+Tierpfleger
maxClientCnxns: 601
maxSessionTimeout: 120000
Erstellen von Tabellen:
hbase org.apache.hadoop.hbase.util.RegionSplitter ns:t1 UniformSplit -c 64 -f cf
alter 'ns:t1', {NAME => 'cf', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}

Hier ist ein wichtiger Punkt: Die DataStax-Beschreibung gibt nicht an, wie viele Regionen zum Erstellen von HB-Tabellen verwendet wurden, obwohl dies bei großen Datenmengen entscheidend ist. Daher wurde für die Tests die Zahl = 64 gewählt, die die Speicherung von bis zu 640 GB, also einer mittelgroßen Tabelle, ermöglicht.

Zum Zeitpunkt des Tests verfügte HBase über 22 Tabellen und 67 Regionen (ohne den oben erwähnten Patch wäre dies für Version 1.2.0 fatal gewesen).

Nun zum Code. Da unklar war, welche Konfigurationen für eine bestimmte Datenbank vorteilhafter waren, wurden die Tests in verschiedenen Kombinationen durchgeführt. Das heißt, in einigen Tests wurde gleichzeitig in vier Tabellen geladen (alle vier Knoten wurden für die Verbindung verwendet). In anderen Tests arbeiteten wir mit acht verschiedenen Tabellen. Die Batchgröße betrug teilweise 4, teilweise 4 (Batch-Parameter – siehe Code unten). Die Datengröße für den Wert beträgt 8 Bytes oder 100 Bytes (dataSize). Insgesamt wurden jeweils 200 Millionen Datensätze in jede Tabelle geschrieben und gelesen. In diesem Fall wurden fünf Threads in jede Tabelle geschrieben/gelesen (thNum-Threadnummer), von denen jeder seinen eigenen Schlüsselbereich verwendete (Anzahl = 10 Million):

if (opType.equals("insert")) {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        StringBuilder sb = new StringBuilder("BEGIN BATCH ");
        for (int i = 0; i < batch; i++) {
            String value = RandomStringUtils.random(dataSize, true, true);
            sb.append("INSERT INTO ")
                    .append(tableName)
                    .append("(id, title) ")
                    .append("VALUES (")
                    .append(key)
                    .append(", '")
                    .append(value)
                    .append("');");
            key++;
        }
        sb.append("APPLY BATCH;");
        final String query = sb.toString();
        session.execute(query);
    }
} else {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        StringBuilder sb = new StringBuilder("SELECT * FROM ").append(tableName).append(" WHERE id IN (");
        for (int i = 0; i < batch; i++) {
            sb = sb.append(key);
            if (i+1 < batch)
                sb.append(",");
            key++;
        }
        sb = sb.append(");");
        final String query = sb.toString();
        ResultSet rs = session.execute(query);
    }
}

Dementsprechend wurde für HB eine ähnliche Funktionalität bereitgestellt:

Configuration conf = getConf();
HTable table = new HTable(conf, keyspace + ":" + tableName);
table.setAutoFlush(false, false);
List<Get> lGet = new ArrayList<>();
List<Put> lPut = new ArrayList<>();
byte[] cf = Bytes.toBytes("cf");
byte[] qf = Bytes.toBytes("value");
if (opType.equals("insert")) {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        lPut.clear();
        for (int i = 0; i < batch; i++) {
            Put p = new Put(makeHbaseRowKey(key));
            String value = RandomStringUtils.random(dataSize, true, true);
            p.addColumn(cf, qf, value.getBytes());
            lPut.add(p);
            key++;
        }
        table.put(lPut);
        table.flushCommits();
    }
} else {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        lGet.clear();
        for (int i = 0; i < batch; i++) {
            Get g = new Get(makeHbaseRowKey(key));
            lGet.add(g);
            key++;
        }
        Result[] rs = table.get(lGet);
    }
}

Da bei HB der Client für die gleichmäßige Verteilung der Daten sorgen muss, sah die Key-Salting-Funktion folgendermaßen aus:

public static byte[] makeHbaseRowKey(long key) {
    byte[] nonSaltedRowKey = Bytes.toBytes(key);
    CRC32 crc32 = new CRC32();
    crc32.update(nonSaltedRowKey);
    long crc32Value = crc32.getValue();
    byte[] salt = Arrays.copyOfRange(Bytes.toBytes(crc32Value), 5, 7);
    return ArrayUtils.addAll(salt, nonSaltedRowKey);
}

Nun zum interessantesten Teil – die Ergebnisse:

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Dasselbe in grafischer Form:

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams

Der HB-Vorteil ist so erstaunlich, dass der Verdacht auf einen Engpass im CS-Setup besteht. Das Googeln und Optimieren der offensichtlichsten Parameter (wie concurrent_writes oder memtable_heap_space_in_mb) brachte jedoch keine Beschleunigung. Gleichzeitig sind die Protokolle sauber, es gibt keine Beschwerden.

Die Daten sind gleichmäßig auf die Knoten verteilt, die Statistiken aller Knoten sind ungefähr gleich.

So sieht die Statistik für die Tabelle mit einem der Knoten ausSchlüsselraum: ks
Anzahl der Leser: 9383707
Leselatenz: 0.04287025042448576 ms
Anzahl der Schreibvorgänge: 15462012
Schreiblatenz: 0.1350068438699957 ms
Ausstehende Flushes: 0
Tabelle: t1
SSTable-Anzahl: 16
Verwendeter Speicherplatz (live): 148.59 MiB
Verwendeter Speicherplatz (gesamt): 148.59 MiB
Von Snapshots verwendeter Speicherplatz (gesamt): 0 Byte
Verwendeter Off-Heap-Speicher (gesamt): 5.17 MiB
SSTable-Komprimierungsverhältnis: 0.5720989576459437
Anzahl der Partitionen (Schätzung): 3970323
Memtable-Zellenanzahl: 0
Memtable-Datengröße: 0 Bytes
Verwendeter Memtable-Off-Heap-Speicher: 0 Bytes
Anzahl der Memtable-Switches: 5
Lokale Leseanzahl: 2346045
Lokale Leselatenz: NaN ms
Anzahl lokaler Schreibvorgänge: 3865503
Lokale Schreiblatenz: NaN ms
Ausstehende Flushes: 0
Prozent repariert: 0.0
Bloom-Filter-Falschmeldungen: 25
Bloom-Filter-Falschverhältnis: 0.00000
Verwendeter Bloom-Filterspeicher: 4.57 MiB
Bloom-Filter aus Heap-Speicher verwendet: 4.57 MiB
Indexzusammenfassung des verwendeten Off-Heap-Speichers: 590.02 KiB
Verwendeter Speicher für komprimierte Metadaten außerhalb des Heaps: 19.45 KiB
Mindestanzahl Bytes für komprimierte Partition: 36
Maximale Byteanzahl der komprimierten Partition: 42
Durchschnittliche Byteanzahl der komprimierten Partition: 42
Durchschnittliche Lebendzellen pro Scheibe (letzte fünf Minuten): NaN
Maximale Anzahl lebender Zellen pro Scheibe (letzte fünf Minuten): 0
Durchschnittliche Anzahl von Tombstones pro Slice (letzte fünf Minuten): NaN
Maximale Anzahl von Tombstones pro Slice (letzte fünf Minuten): 0
Abgelegte Mutationen: 0 Bytes

Der Versuch, die Batchgröße zu reduzieren (bis hin zum Einzelversand), blieb wirkungslos, es verschlechterte sich nur. Möglicherweise ist dies tatsächlich die maximale Leistung für CS, da die für CS erzielten Ergebnisse denen von DataStax ähneln – etwa hunderttausend Operationen pro Sekunde. Betrachtet man außerdem die Ressourcenauslastung, wird deutlich, dass CS deutlich mehr CPU und Festplatten verbraucht:

Kampf zweier Yakozuna oder Cassandra gegen HBase. Erfahrung des Sberbank-Teams
Die Abbildung zeigt die Auslastung während des Durchlaufs aller Tests hintereinander für beide Datenbanken.

Was den leistungsstarken Vorteil von HB beim Lesen betrifft. Hier können Sie sehen, dass die Festplattenauslastung beim Lesen für beide DBs extrem niedrig ist (Lesetests sind der letzte Teil des Testzyklus für jede DB, für CS beispielsweise von 15:20 bis 15:40). Im Fall von HB ist der Grund klar – die meisten Daten hängen im Speicher, im Memstore und einige werden im Blockcache zwischengespeichert. Was CS betrifft, ist nicht ganz klar, wie es angeordnet ist, die Festplattenauslastung ist jedoch auch nicht sichtbar, aber nur für den Fall, dass versucht wurde, den Cache row_cache_size_in_mb = 2048 zu aktivieren und caching = {'keys': 'ALL', 'rows_per_partition': '2000000'} festzulegen, aber es wurde sogar noch ein wenig schlechter.

Es lohnt sich auch, noch einmal einen wichtigen Punkt bezüglich der Anzahl der Regionen in HB zu erwähnen. In unserem Fall betrug der Wert 64. Wenn wir ihn reduzieren und ihn beispielsweise auf 4 setzen, sinkt die Geschwindigkeit beim Lesen um das Zweifache. Der Grund dafür ist, dass sich der Memstore schneller füllt und Dateien häufiger geflasht werden. Beim Lesen müssen mehr Dateien verarbeitet werden, was für HB ein ziemlich komplexer Vorgang ist. In der Praxis wird dies durch eine durchdachte Voraufteilungs- und Kompaktierungsstrategie gelöst. Insbesondere verwenden wir ein selbstgeschriebenes Dienstprogramm, das im Hintergrund ständig Datenmüll sammelt und HFiles komprimiert. Es ist durchaus möglich, dass sie für die DataStax-Tests 2 Region pro Tabelle zugewiesen haben (was falsch ist), und dies würde einigermaßen erklären, warum HB bei den Lesetests so viel verlor.

Daraus lassen sich folgende vorläufige Schlussfolgerungen ziehen. Vorausgesetzt, dass beim Testen keine größeren Fehler gemacht wurden, gleicht Cassandra einem Koloss auf tönernen Füßen. Genauer gesagt: Während sie, wie im Bild am Anfang des Artikels, auf einem Bein balanciert, zeigt sie relativ gute Ergebnisse, verliert aber unter den gleichen Bedingungen vollständig. Gleichzeitig konnten wir aufgrund der geringen CPU-Auslastung unserer Hardware zwei RegionServer HB pro Host einsetzen und so die Leistung verdoppeln. Das heißt, unter Berücksichtigung der Ressourcenauslastung ist die Situation für Cassandra noch bedauerlicher.

Natürlich sind diese Tests recht synthetisch und das hier verwendete Datenvolumen relativ gering. Es ist möglich, dass die Situation beim Wechsel zu Terabyte anders wäre, aber wenn wir Terabyte für HB laden können, erwies es sich für CS als problematisch. Selbst bei diesen Volumina wurde häufig eine OperationTimedOutException ausgegeben, obwohl die Antwortwarteparameter im Vergleich zu den Standardwerten bereits um ein Vielfaches erhöht waren.

Ich hoffe, dass wir durch gemeinsame Anstrengungen die Engpässe von CS finden und, falls es möglich ist, es zu beschleunigen, am Ende des Beitrags werde ich auf jeden Fall Informationen über die endgültigen Ergebnisse hinzufügen.

UPD: Dank der Ratschläge meiner Kameraden konnte ich das Lesen beschleunigen. Es war:
159 Ops (644 Tabellen, 4 Threads, Batch 5).
Dobavleno:
.withLoadBalancingPolicy(neue TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
Und ich habe mit der Anzahl der Threads gespielt. Das Ergebnis war wie folgt:
4 Tabellen, 100 Threads, Batch = 1 (einzeln): 301 Ops
4 Tabellen, 100 Threads, Batch = 10: 447 Ops
4 Tabellen, 100 Threads, Batch = 100: 625 Ops

Später werde ich weitere Tuning-Tipps anwenden, einen vollständigen Testzyklus durchführen und die Ergebnisse am Ende des Beitrags hinzufügen.

Source: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz und VPS-VDS-Servern 🔥 Kaufen Sie zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server | ProHoster