Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

To nawet nie jest żart, wydaje się, że ten konkretny obraz najtrafniej oddaje istotę tych baz danych i ostatecznie stanie się jasne, dlaczego:

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

Według rankingu DB-Engines dwie najpopularniejsze kolumnowe bazy danych NoSQL to Cassandra (zwana dalej CS) i HBase (HB).

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

Z woli losu nasz zespół zarządzający ładowaniem danych w Sbierbanku już to zrobił długo i blisko współpracuje z HB. W tym czasie dość dobrze poznaliśmy jego mocne i słabe strony oraz nauczyliśmy się go gotować. Jednak obecność alternatywy w postaci CS zawsze zmuszała nas do dręczenia się wątpliwościami: czy dokonaliśmy właściwego wyboru? Co więcej, wyniki porównania, przeprowadzone przez DataStax, stwierdzili, że CS z łatwością pokonuje HB z niemal miażdżącym wynikiem. Z drugiej strony DataStax jest zainteresowaną stroną i nie powinieneś wierzyć im na słowo. Nas też zmyliła niewielka ilość informacji o warunkach testowania, dlatego postanowiliśmy sami przekonać się, kto jest królem BigData NoSql, a uzyskane wyniki okazały się bardzo ciekawe.

Zanim jednak przejdziemy do wyników przeprowadzonych testów, należy opisać istotne aspekty konfiguracji środowiska. Faktem jest, że CS można używać w trybie umożliwiającym utratę danych. Te. ma to miejsce wtedy, gdy za dane określonego klucza odpowiada tylko jeden serwer (węzeł) i jeśli z jakiegoś powodu ulegnie on awarii, wówczas wartość tego klucza zostanie utracona. Dla wielu zadań nie jest to krytyczne, ale dla sektora bankowego jest to raczej wyjątek niż reguła. W naszym przypadku ważne jest, aby mieć kilka kopii danych w celu niezawodnego ich przechowywania.

Dlatego też uwzględniono jedynie tryb pracy CS w trybie potrójnej replikacji, tj. Tworzenie przestrzeni obudowy zostało przeprowadzone przy następujących parametrach:

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

Następnie istnieją dwa sposoby zapewnienia wymaganego poziomu spójności. Główna zasada:
NW + NR > RF

Oznacza to, że liczba potwierdzeń z węzłów podczas zapisu (NW) plus liczba potwierdzeń z węzłów podczas odczytu (NR) musi być większa niż współczynnik replikacji. W naszym przypadku RF = 3, co oznacza, że ​​odpowiednie są następujące opcje:
2 + 2> 3
3 + 1> 3

Ponieważ dla nas najważniejsze jest możliwie niezawodne przechowywanie danych, zdecydowano się na schemat 3+1. Dodatkowo HB działa na podobnej zasadzie, tj. takie porównanie będzie bardziej sprawiedliwe.

Należy zauważyć, że DataStax w swoim badaniu postąpił odwrotnie, ustalił RF = 1 zarówno dla CS, jak i HB (dla tego ostatniego zmieniając ustawienia HDFS). Jest to naprawdę ważny aspekt, ponieważ wpływ na wydajność CS w tym przypadku jest ogromny. Przykładowo poniższy obrazek pokazuje wydłużenie czasu potrzebnego na załadowanie danych do CS:

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

Widzimy tutaj, co następuje: im więcej konkurujących wątków zapisuje dane, tym dłużej to trwa. Jest to naturalne, ale ważne jest, aby spadek wydajności dla RF=3 był znacznie większy. Innymi słowy, jeśli zapiszemy 4 wątków w 5 tabelach każda (w sumie 20), to RF=3 traci około 2 razy (150 sekund dla RF=3 w porównaniu do 75 dla RF=1). Ale jeśli zwiększymy obciążenie, ładując dane do 8 tabel po 5 wątków każda (w sumie 40), to utrata RF=3 jest już 2,7 razy (375 sekund w porównaniu do 138).

Być może w tym właśnie tkwi sekret udanych testów obciążeniowych wykonanych przez DataStax dla CS, gdyż dla HB na naszym stoisku zmiana współczynnika replikacji z 2 na 3 nie przyniosła żadnego efektu. Te. dyski nie są wąskim gardłem HB w naszej konfiguracji. Jednak jest tu wiele innych pułapek, bo należy zaznaczyć, że nasza wersja HB została nieco załatana i podrasowana, środowiska są zupełnie inne itp. Warto też zaznaczyć, że może po prostu nie wiem, jak poprawnie przygotować CS i istnieją skuteczniejsze sposoby na pracę z nim, o czym mam nadzieję, dowiemy się w komentarzach. Ale najpierw najważniejsze.

Wszystkie testy przeprowadzono na klastrze sprzętowym składającym się z 4 serwerów, każdy o następującej konfiguracji:

Procesor: Xeon E5-2680 v4 @ 2.40 GHz 64 wątki.
Dyski: 12 sztuk HDD SATA
wersja Java: 1.8.0_111

Wersja CS: 3.11.5

Parametry pliku cassandra.ymlliczba_tokenów: 256
podpowiedzi_handoff_enabled: prawda
podpowiedzi_handoff_throttle_in_kb: 1024
max_hints_delivery_threads: 2
katalog_wskazówek: /data10/cassandra/hints
podpowiedzi_flush_period_in_ms: 10000
max_hints_file_size_in_mb: 128
Batchlog_replay_throttle_in_kb: 1024
uwierzytelniacz: Zezwalaj na AllAuthenticator
autoryzator: Zezwalaj na AllAuthorizer
role_manager: CassandraRoleManager
role_validity_in_ms: 2000
uprawnienia_ważności_w_ms: 2000
referencje_validity_in_ms: 2000
partycjoner: org.apache.cassandra.dht.Murmur3Partitioner
katalogi_plików_danych:
- /data1/cassandra/data # każdy katalog dataN jest oddzielnym dyskiem
- /data2/cassandra/data
- /data3/cassandra/data
- /data4/cassandra/data
- /data5/cassandra/data
- /data6/cassandra/data
- /data7/cassandra/data
- /data8/cassandra/data
katalog_zatwierdzenia: /data9/cassandra/commitlog
cdc_enabled: fałsz
disc_failure_policy: zatrzymaj
commit_failure_policy: zatrzymaj
przygotowane_statements_cache_size_mb:
thrift_prepared_statements_cache_size_mb:
klucz_cache_size_in_mb:
key_cache_save_period: 14400
row_cache_size_in_mb: 0
row_cache_save_period: 0
licznik_cache_size_in_mb:
licznik_cache_save_period: 7200
katalog_zapisanych_skrzynek: /data10/cassandra/saved_cache
commitlog_sync: okresowe
commitlog_sync_period_in_ms: 10000
commitlog_segment_size_in_mb: 32
dostawca nasion:
- nazwa_klasy: org.apache.cassandra.locator.SimpleSeedProvider
parametry:
— nasiona: „*,*”
concurrent_reads: 256 # wypróbowano 64 - nie zauważono żadnej różnicy
concurrent_writes: 256 # wypróbowano 64 - nie zauważono żadnej różnicy
concurrent_counter_writes: 256 # wypróbowano 64 - nie zauważono żadnej różnicy
concurrent_materialized_view_writes: 32
memtable_heap_space_in_mb: 2048 # próbowałem 16 GB - było wolniej
memtable_allocation_type: bufory_sterty
indeks_summary_capacity_in_mb:
indeks_summary_resize_interval_in_minut: 60
trickle_fsync: fałsz
trickle_fsync_interval_in_kb: 10240
port_przechowywania: 7000
port_ssl_storage_port: 7001
adres_słuchania: *
adres transmisji: *
Listen_on_broadcast_address: true
internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: prawda
port_natywny_transportowy: 9042
start_rpc: prawda
adres_rpc: *
port_rpc: 9160
rpc_keepalive: prawda
rpc_server_type: synchronizacja
thrift_framed_transport_size_in_mb: 15
przyrostowe kopie zapasowe: fałsz
snapshot_before_compaction: fałsz
auto_snapshot: prawda
kolumna_indeks_rozmiar_w_kb: 64
kolumna_index_cache_size_in_kb: 2
współbieżne_kompaktory: 4
zagęszczanie_przepustowość_mb_per_sec: 1600
sstable_preemptive_open_interval_in_mb: 50
read_request_timeout_in_ms: 100000
range_request_timeout_in_ms: 200000
write_request_timeout_in_ms: 40000
licznik_write_request_timeout_in_ms: 100000
cas_contention_timeout_in_ms: 20000
truncate_request_timeout_in_ms: 60000
request_timeout_in_ms: 200000
slow_query_log_timeout_in_ms: 500
cross_node_timeout: fałsz
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
opcje_szyfrowania_serwera:
internode_encryption: brak
opcje_szyfrowania_klienta:
włączone: fałsz
internode_compression: dc
inter_dc_tcp_nodelay: fałsz
tracitype_query_ttl: 86400
tracitype_repair_ttl: 604800
włącz_użytkownik_zdefiniowane_funkcje: fałsz
Enable_scripted_user_definiowane_funkcje: fałsz
interwał_timera_okna: 1
opcje transparent_data_encryption_options:
włączone: fałsz
tombstone_warn_threshold: 1000
tombstone_failure_threshold: 100000
Batch_size_warn_threshold_in_kb: 200
Batch_size_fail_threshold_in_kb: 250
unlogged_batch_across_partitions_warn_threshold: 10
Compaction_large_partition_warning_threshold_mb: 100
gc_warn_threshold_in_ms: 1000
back_ Pressure_enabled: fałsz
Enable_materialized_views: prawda
Enable_sasi_indexes: prawda

Ustawienia GC:

### Ustawienia CMS-a-XX:+UżyjParNowyGC
-XX:+Użyj ConcMarkSweepGC
-XX:+CMSParallelUwagaWłączona
-XX:Stosunek ocalałych=8
-XX:MaxTenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+Użyj CMSIinicjowanie tylko obłożenia
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+Włączono rozładowywanie klasy CMS

Do pamięci jvm.options przydzielono 16 Gb (próbowaliśmy też 32 Gb, nie zauważyliśmy żadnej różnicy).

Tabele zostały utworzone za pomocą polecenia:

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

Wersja HB: 1.2.0-cdh5.14.2 (w klasie org.apache.hadoop.hbase.regionserver.HRegion wykluczyliśmy MetricsRegion, co prowadziło do GC, gdy liczba regionów na RegionServerze przekraczała 1000)

Parametry inne niż domyślne HBaselimit czasu sesji.opiekuna zoo: 120000
hbase.rpc.timeout: 2 minuty
hbase.client.scanner.timeout.period: 2 minuty
hbase.master.handler.count: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.period: 2 minuty
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.period: 4 godziny
hbase.regionserver.maxlogs: 200
hbase.hregion.memstore.flush.size: 1 GiB
hbase.hregion.memstore.block.multiplier: 6
hbase.hstore.compactionPróg: 5
hbase.hstore.blockingStoreFiles: 200
hbase.hregion.majorcompaction: 1 dzień(y)
Fragment zaawansowanej konfiguracji usługi HBase (zawór bezpieczeństwa) dla pliku hbase-site.xml:
hbase.regionserver.wal.codecorg.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec
hbase.master.namespace.init.timeout3600000
hbase.regionserver.opcjonalnycacheflushinterval18000000
hbase.regionserver.thread.compaction.large12
hbase.regionserver.wal.enablecompressiontrue
hbase.hstore.compaction.max.size1073741824
hbase.server.compactchecker.interval.multiplier200
Opcje konfiguracji Java dla serwera regionu HBase:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis: 2 minuty
hbase.snapshot.region.timeout: 2 minuty
hbase.snapshot.master.timeout.millis: 2 minuty
Maksymalny rozmiar dziennika serwera HBase REST: 100 MiB
Maksymalna liczba kopii zapasowych plików dziennika HBase REST Server: 5
Maksymalny rozmiar dziennika serwera HBase Thrift: 100 MiB
Maksymalna liczba kopii zapasowych plików dziennika HBase Thrift Server: 5
Główny maksymalny rozmiar dziennika: 100 MiB
Główne maksymalne kopie zapasowe plików dziennika: 5
Maksymalny rozmiar dziennika serwera RegionServer: 100 MiB
Maksymalna liczba kopii zapasowych plików dziennika RegionServer: 5
Okno wykrywania aktywnego wzorca HBase: 4 minuty
dfs.client.heged.read.threadpool.size: 40
dfs.client.heged.read.threshold.millis: 10 milisekund
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Maksymalna liczba deskryptorów plików procesowych: 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
Wątki dotyczące przenoszenia regionu: 6
Rozmiar sterty Java klienta w bajtach: 1 GiB
Domyślna grupa serwera HBase REST: 3 GiB
Domyślna grupa serwera HBase Thrift: 3 GiB
Rozmiar sterty Java głównego modułu HBase w bajtach: 16 GiB
Rozmiar sterty Java serwera regionu HBase w bajtach: 32 GiB

+Strażnik zoo
maxClientCnxns: 601
maxSessionTimeout: 120000
Tworzenie tabel:
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'}

Jest tu jedna ważna kwestia - opis DataStax nie mówi, ile regionów wykorzystano do utworzenia tabel HB, chociaż jest to krytyczne w przypadku dużych wolumenów. Dlatego do testów wybrano ilość = 64, co pozwala na przechowywanie aż 640 GB, czyli tj. stół średniej wielkości.

W momencie testu HBase miał 22 tysiące tabel i 67 tysięcy regionów (byłoby to zabójcze dla wersji 1.2.0, gdyby nie wspomniana wyżej łatka).

Teraz o kodzie. Ponieważ nie było jasne, która konfiguracja będzie korzystniejsza dla konkretnej bazy danych, przeprowadzono testy w różnych kombinacjach. Te. w niektórych testach ładowano jednocześnie 4 tabele (do połączenia wykorzystano wszystkie 4 węzły). W innych testach pracowaliśmy z 8 różnymi tabelami. W niektórych przypadkach wielkość partii wynosiła 100, w innych 200 (parametr partii - patrz kod poniżej). Rozmiar danych dla wartości wynosi 10 bajtów lub 100 bajtów (dataSize). W sumie za każdym razem zapisano i wczytano 5 milionów rekordów do każdej tabeli. Jednocześnie do każdej tabeli zostało zapisanych/odczytanych 5 wątków (numer wątku - thNum), z których każdy korzystał z własnego zakresu kluczy (liczba = 1 milion):

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

W związku z tym podobną funkcjonalność zapewniono dla HB:

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

Ponieważ w HB klient musi zadbać o równomierny rozkład danych, kluczowa funkcja solenia wyglądała następująco:

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

Teraz najciekawsza część - wyniki:

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

To samo w formie wykresu:

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku

Przewaga HB jest tak zaskakująca, że ​​istnieje podejrzenie, że w konfiguracji CS istnieje jakieś wąskie gardło. Jednak przeglądanie Google i wyszukiwanie najbardziej oczywistych parametrów (takich jak concurrent_writes lub memtable_heap_space_in_mb) nie przyspieszyło działania. Jednocześnie kłody są czyste i nie przeklinają niczego.

Dane zostały rozłożone równomiernie pomiędzy węzłami, statystyki ze wszystkich węzłów były w przybliżeniu takie same.

Tak wyglądają statystyki tabeli z jednego z węzłówPrzestrzeń kluczowa: ks
Przeczytaj liczbę: 9383707
Opóźnienie odczytu: 0.04287025042448576 ms
Liczba zapisów: 15462012
Opóźnienie zapisu: 0.1350068438699957 ms
Oczekujące kolory: 0
Tabela: t1
Liczba tabel SST: 16
Wykorzystana przestrzeń (na żywo): 148.59 MiB
Wykorzystane miejsce (łącznie): 148.59 MiB
Miejsce zajmowane przez migawki (łącznie): 0 bajtów
Używana pamięć poza stertą (łącznie): 5.17 MiB
Współczynnik kompresji SSTable: 0.5720989576459437
Liczba przegród (oszacowana): 3970323
Liczba komórek memtablelnych: 0
Rozmiar danych memtable: 0 bajtów
Używana pamięć sterty Memtable off: 0 bajtów
Liczba przełączników memtable: 5
Lokalna liczba odczytów: 2346045
Lokalne opóźnienie odczytu: NaN ms
Liczba zapisów lokalnych: 3865503
Lokalne opóźnienie zapisu: NaN ms
Oczekujące rzuty: 0
Procent naprawy: 0.0
Fałszywe alarmy filtra Blooma: 25
Fałszywy współczynnik filtra Blooma: 0.00000
Wykorzystane miejsce na filtr Blooma: 4.57 MiB
Używana pamięć sterty filtra Blooma: 4.57 MiB
Podsumowanie indeksu używanej pamięci sterty: 590.02 KiB
Metadane kompresji z użytej pamięci sterty: 19.45 KiB
Minimalna liczba bajtów skompaktowanej partycji: 36
Maksymalna liczba bajtów skompaktowanej partycji: 42
Średnia partycja skompaktowana w bajtach: 42
Średnia liczba żywych komórek na plasterek (ostatnie pięć minut): NaN
Maksymalna liczba żywych komórek na plasterek (ostatnie pięć minut): 0
Średnia liczba nagrobków na kawałek (ostatnie pięć minut): NaN
Maksymalna liczba nagrobków na kawałek (ostatnie pięć minut): 0
Porzucone mutacje: 0 bajtów

Próba zmniejszenia wielkości partii (nawet wysłanie jej pojedynczo) nie przyniosła efektu, było tylko gorzej. Możliwe, że faktycznie jest to rzeczywiście maksymalna wydajność dla CS, gdyż wyniki uzyskane dla CS są podobne do tych uzyskanych dla DataStax - około setek tysięcy operacji na sekundę. Ponadto, jeśli spojrzymy na wykorzystanie zasobów, zobaczymy, że CS zużywa znacznie więcej procesora i dysków:

Bitwa dwóch yakozuna, czyli Cassandra kontra HBase. Doświadczenie zespołu Sbierbanku
Rysunek przedstawia wykorzystanie podczas wykonywania wszystkich testów z rzędu dla obu baz danych.

Jeśli chodzi o potężną przewagę czytelniczą HB. Tutaj widać, że w przypadku obu baz danych wykorzystanie dysku podczas odczytu jest wyjątkowo niskie (testy odczytu są końcową częścią cyklu testowania każdej bazy danych, przykładowo dla CS jest to od 15:20 do 15:40). W przypadku HB powód jest jasny – większość danych wisi w pamięci, w memstore, a część jest buforowana w blockcache. Jeśli chodzi o CS to nie bardzo wiadomo jak to działa, ale recykling dysku też nie jest widoczny, ale na wszelki wypadek została podjęta próba włączenia pamięci podręcznej row_cache_size_in_mb = 2048 i ustawienia caching = {'keys': 'ALL', „rows_per_partition”: „2000000”}, ale to jeszcze bardziej pogorszyło sytuację.

Warto jeszcze raz wspomnieć o ważnej kwestii dotyczącej liczby regionów w HB. W naszym przypadku wartość została określona jako 64. Jeśli ją zmniejszymy i zrównamy np. z 4, to podczas odczytu prędkość spadnie 2-krotnie. Powodem jest to, że magazyn pamięci będzie się szybciej zapełniał, pliki będą częściej opróżniane, a podczas odczytu trzeba będzie przetworzyć więcej plików, co dla HB jest dość skomplikowaną operacją. W rzeczywistych warunkach można temu zaradzić, zastanawiając się nad strategią wstępnego podziału i kompaktowania; w szczególności używamy napisanego przez siebie narzędzia, które zbiera śmieci i stale kompresuje HFiles w tle. Jest całkiem możliwe, że do testów DataStax przydzielono tylko 1 region na tabelę (co nie jest poprawne), co w pewnym stopniu wyjaśniałoby, dlaczego HB był tak gorszy w testach czytania.

Z tego wynikają następujące wstępne wnioski. Zakładając, że podczas testów nie popełniono żadnych większych błędów, Cassandra wygląda jak kolos na glinianych nogach. A dokładniej, balansując na jednej nodze, jak na zdjęciu na początku artykułu, wykazuje stosunkowo dobre wyniki, jednak w walce w takich samych warunkach przegrywa od razu. Jednocześnie, biorąc pod uwagę niskie wykorzystanie procesora na naszym sprzęcie, nauczyliśmy się instalować po dwa moduły RegionServer HB na każdym hoście, co podwoiło wydajność. Te. Biorąc pod uwagę wykorzystanie zasobów, sytuacja CS jest jeszcze bardziej opłakana.

Oczywiście testy te mają charakter dość syntetyczny, a ilość danych, które tu wykorzystano, jest stosunkowo skromna. Możliwe, że gdybyśmy przeszli na terabajty, sytuacja byłaby inna, ale o ile w przypadku HB możemy ładować terabajty, to w przypadku CS okazało się to problematyczne. Często zgłaszał wyjątek OperationTimedOutException nawet w przypadku tych woluminów, chociaż parametry oczekiwania na odpowiedź zostały już kilkakrotnie zwiększone w porównaniu do parametrów domyślnych.

Mam nadzieję, że wspólnymi wysiłkami znajdziemy wąskie gardła CS i jeśli uda nam się to przyspieszyć, to na końcu wpisu na pewno dodam informację o końcowych wynikach.

UPD: Dzięki radom towarzyszy udało mi się przyspieszyć lekturę. Był:
159 644 operacji (4 tabele, 5 strumieni, partia 100).
Dodane przez:
.withLoadBalancingPolicy(nowy TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
I pobawiłem się liczbą wątków. Wynik jest następujący:
4 tabele, 100 wątków, partia = 1 (kawałek po kawałku): 301 969 operacji
4 tabele, 100 wątków, partia = 10: 447 608 ops
4 tabele, 100 wątków, partia = 100: 625 655 ops

Później zastosuję inne wskazówki dotyczące tuningu, przeprowadzę pełny cykl testów i wyniki dodam na końcu posta.

Źródło: www.habr.com

Dodaj komentarz