Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Detta är inte ens ett skämt, det verkar som om den här bilden mest exakt återspeglar kärnan i dessa databaser, och i slutändan kommer det att vara klart varför:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Enligt DB-Engines Ranking är de två mest populära NoSQL kolumnära databaserna Cassandra (nedan kallat CS) och HBase (HB).

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Genom ödets vilja har vår dataladdningsledning på Sberbank redan gjort det för länge sedan och har ett nära samarbete med HB. Under den här tiden studerade vi dess styrkor och svagheter ganska väl och lärde oss hur man lagar den. Men närvaron av ett alternativ i form av CS tvingade oss alltid att plåga oss själva lite med tvivel: gjorde vi rätt val? Dessutom resultaten jämförelser, utförd av DataStax, sa de att CS lätt slår HB med nästan en förkrossande poäng. Å andra sidan är DataStax en intresserad part, och du bör inte ta deras ord för det. Vi blev också förvirrade av den ganska lilla mängden information om testförhållandena, så vi bestämde oss för att ta reda på på egen hand vem som är kungen av BigData NoSql, och de erhållna resultaten visade sig vara mycket intressanta.

Innan man går vidare till resultaten av de utförda testerna är det dock nödvändigt att beskriva de betydande aspekterna av miljökonfigurationerna. Faktum är att CS kan användas i ett läge som tillåter dataförlust. De där. detta är när endast en server (nod) är ansvarig för data för en viss nyckel, och om den av någon anledning misslyckas, kommer värdet på denna nyckel att gå förlorat. För många uppgifter är detta inte kritiskt, men för banksektorn är detta undantaget snarare än regeln. I vårt fall är det viktigt att ha flera kopior av data för tillförlitlig lagring.

Därför övervägdes endast CS-driftläget i trippelreplikeringsläge, dvs. Skapandet av casespace utfördes med följande parametrar:

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

Därefter finns det två sätt att säkerställa den nödvändiga nivån av konsekvens. Allmän regel:
NW + NR > RF

Vilket innebär att antalet bekräftelser från noder vid skrivning (NW) plus antalet bekräftelser från noder vid läsning (NR) måste vara större än replikeringsfaktorn. I vårt fall är RF = 3, vilket betyder att följande alternativ är lämpliga:
2 + 2 > 3
3 + 1 > 3

Eftersom det är fundamentalt viktigt för oss att lagra data så tillförlitligt som möjligt valdes 3+1-schemat. Dessutom arbetar HB efter en liknande princip, d.v.s. en sådan jämförelse blir mer rättvis.

Det bör noteras att DataStax gjorde tvärtom i sin studie, de satte RF = 1 för både CS och HB (för de senare genom att ändra HDFS-inställningarna). Detta är en riktigt viktig aspekt eftersom påverkan på CS-prestanda i det här fallet är enorm. Till exempel visar bilden nedan ökningen av den tid som krävs för att ladda data till CS:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Här ser vi följande: ju fler konkurrerande trådar skriver data, desto längre tid tar det. Detta är naturligt, men det är viktigt att prestandaförsämringen för RF=3 är betydligt högre. Med andra ord, om vi skriver 4 trådar i 5 tabeller vardera (20 totalt), så förlorar RF=3 med cirka 2 gånger (150 sekunder för RF=3 mot 75 för RF=1). Men om vi ökar belastningen genom att ladda data i 8 tabeller med 5 trådar vardera (40 totalt), så är förlusten av RF=3 redan 2,7 gånger (375 sekunder mot 138).

Kanske är detta delvis hemligheten bakom det framgångsrika belastningstestet som utförts av DataStax för CS, för HB i vår monter hade ingen effekt att ändra replikeringsfaktorn från 2 till 3. De där. diskar är inte HB-flaskhalsen för vår konfiguration. Det finns dock många andra fallgropar här, för det ska noteras att vår version av HB var lite lappad och tweakad, miljöerna är helt annorlunda osv. Det är också värt att notera att jag kanske helt enkelt inte vet hur man förbereder CS korrekt och det finns några mer effektiva sätt att arbeta med det, och jag hoppas att vi får reda på det i kommentarerna. Men först till kvarn.

Alla tester utfördes på ett hårdvarukluster bestående av 4 servrar, var och en med följande konfiguration:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 trådar.
Diskar: 12 stycken SATA HDD
java version: 1.8.0_111

CS-version: 3.11.5

cassandra.yml parametrarnum_tokens: 256
hinted_handoff_enabled: sant
hinted_handoff_throttle_in_kb: 1024
max_hints_delivery_threads: 2
hints_directory: /data10/cassandra/hints
hints_flush_period_in_ms: 10000
max_hints_file_size_in_mb: 128
batchlog_replay_throttle_in_kb: 1024
authenticator: AllowAllAuthenticator
auktoriserare: AllowAllAuthorizer
role_manager: CassandraRoleManager
roles_validity_in_ms: 2000
permissions_validity_in_ms: 2000
credentials_validity_in_ms: 2000
partitionerare: org.apache.cassandra.dht.Murmur3Partitioner
data_file_kataloger:
- /data1/cassandra/data # varje dataN-katalog är en separat disk
- /data2/cassandra/data
- /data3/cassandra/data
- /data4/cassandra/data
- /data5/cassandra/data
- /data6/cassandra/data
- /data7/cassandra/data
- /data8/cassandra/data
commitlog_directory: /data9/cassandra/commitlog
cdc_enabled: false
disk_failure_policy: sluta
commit_failure_policy: sluta
preparerade_statements_cache_size_mb:
thrift_prepared_statements_cache_size_mb:
key_cache_size_in_mb:
key_cache_save_period: 14400
rad_cache_storlek_i_mb: 0
row_cache_save_period: 0
counter_cache_size_in_mb:
counter_cache_save_period: 7200
sparade_cacher_katalog: /data10/cassandra/sparade_cacher
commitlog_sync: periodisk
commitlog_sync_period_in_ms: 10000
commitlog_segment_size_in_mb: 32
seed_provider:
- klassnamn: org.apache.cassandra.locator.SimpleSeedProvider
parametrar:
- frön: "*,*"
concurrent_reads: 256 # försökte 64 - ingen skillnad märkt
concurrent_writes: 256 # försökte 64 - ingen skillnad märkt
concurrent_counter_writes: 256 # försökte 64 - ingen skillnad märkt
concurrent_materialized_view_writes: 32
memtable_heap_space_in_mb: 2048 # provade 16 GB - det var långsammare
memtable_allocation_type: heap_buffers
index_summary_capacity_in_mb:
index_summary_resize_interval_in_minutes: 60
trickle_fsync: falskt
trickle_fsync_interval_in_kb: 10240
lagringsport: 7000
ssl_storage_port: 7001
lyssna_adress: *
broadcast_address: *
lyssna_på_sändningsadress: sant
internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: sant
native_transport_port: 9042
start_rpc: sant
rpc_address: *
rpc_port: 9160
rpc_keepalive: sant
rpc_server_type: sync
thrift_framed_transport_size_in_mb: 15
incremental_backups: false
snapshot_before_compaction: falskt
auto_snapshot: sant
column_index_size_in_kb: 64
column_index_cache_size_in_kb: 2
concurrent_compactors: 4
compaction_throughput_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
counter_write_request_timeout_in_ms: 100000 XNUMX
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: false
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
server_encryption_options:
internode_encryption: ingen
client_encryption_options:
aktiverad: falsk
internode_compression: dc
inter_dc_tcp_nodelay: falskt
tracetype_query_ttl: 86400
tracetype_repair_ttl: 604800
enable_user_defined_functions: false
enable_scripted_user_defined_functions: false
windows_timer_interval: 1
transparent_data_encryption_options:
aktiverad: falsk
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: false
enable_materialized_views: sant
enable_sasi_indexes: sant

GC-inställningar:

### CMS-inställningar-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSIinitiatingOccupancyEndast
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloadingEnabled

Minnet jvm.options tilldelades 16 Gb (vi provade också 32 Gb, ingen skillnad märktes).

Tabellerna skapades med kommandot:

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 (i klassen org.apache.hadoop.hbase.regionserver.HRegion exkluderade vi MetricsRegion vilket ledde till GC när antalet regioner var fler än 1000 på RegionServer)

Icke-standardparametrar för HBasezookeeper.session.timeout: 120000 XNUMX
hbase.rpc.timeout: 2 minut(er)
hbase.client.scanner.timeout.period: 2 minut(er)
hbase.master.handler.count: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.period: 2 minut(er)
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.period: 4 timme(r)
hbase.regionserver.maxlogs: 200
hbase.hregion.memstore.flush.storlek: 1 GiB
hbase.hregion.memstore.block.multiplikator: 6
hbase.hstore.compactionTröskel: 5
hbase.hstore.blockingStoreFiles: 200
hbase.hregion.majorcompaction: 1 dag(ar)
HBase Service Advanced Configuration Snippet (säkerhetsventil) 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-konfigurationsalternativ för HBase RegionServer:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis: 2 minut(er)
hbase.snapshot.region.timeout: 2 minut(er)
hbase.snapshot.master.timeout.millis: 2 minut(er)
HBase REST Server Max loggstorlek: 100 MiB
HBase REST Server maximala säkerhetskopior av loggfiler: 5
HBase Thrift Server Max loggstorlek: 100 MiB
HBase Thrift Server Maximalt antal säkerhetskopior av loggfiler: 5
Master Max loggstorlek: 100 MiB
Master maximala säkerhetskopior av loggfiler: 5
RegionServer Max loggstorlek: 100 MiB
RegionServer maximala säkerhetskopior av loggfiler: 5
HBase Active Master Detection Window: 4 minut(er)
dfs.client.hedged.read.threadpool.storlek: 40
dfs.client.hedged.read.threshold.millis: 10 millisekund(er)
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Maximalt antal processfilsbeskrivningar: 180000 XNUMX
hbase.thrift.minWorkerThreads: 200
hbase.master.executor.openregion.threads: 30
hbase.master.executor.closeregion.threads: 30
hbase.master.executor.serverops.trådar: 60
hbase.regionserver.thread.compaction.small: 6
hbase.ipc.server.read.threadpool.size: 20
Region Mover Trådar: 6
Klient Java-högstorlek i byte: 1 GiB
HBase REST Server Standardgrupp: 3 GiB
HBase Thrift Server Standardgrupp: 3 GiB
Java-högstorlek för HBase Master i byte: 16 GiB
Java-högstorlek för HBase RegionServer i byte: 32 GiB

+ZooKeeper
maxClientCnxns: 601
maxSessionTimeout: 120000 XNUMX
Skapa tabeller:
hbase org.apache.hadoop.hbase.util.RegionSplitter ns:t1 UniformSplit -c 64 -f cf
ändra 'ns:t1', {NAME => 'cf', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}

Det finns en viktig punkt här - DataStax-beskrivningen säger inte hur många regioner som användes för att skapa HB-tabellerna, även om detta är avgörande för stora volymer. För testerna valdes därför kvantitet = 64, vilket möjliggör lagring av upp till 640 GB, d.v.s. medelstort bord.

Vid tiden för testet hade HBase 22 tusen tabeller och 67 tusen regioner (detta skulle ha varit dödligt för version 1.2.0 om inte för patchen som nämns ovan).

Nu till koden. Eftersom det inte var klart vilka konfigurationer som var mer fördelaktiga för en viss databas, genomfördes tester i olika kombinationer. De där. i vissa tester laddades 4 tabeller samtidigt (alla 4 noder användes för anslutning). I andra tester arbetade vi med 8 olika tabeller. I vissa fall var batchstorleken 100, i andra 200 (batchparameter - se kod nedan). Datastorleken för värde är 10 byte eller 100 byte (dataSize). Totalt skrevs och lästes 5 miljoner poster i varje tabell varje gång. Samtidigt skrevs/läss 5 trådar till varje tabell (trådnummer - thNum), som var och en använde sitt eget nyckelområde (antal = 1 miljon):

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

Följaktligen tillhandahölls liknande funktionalitet för 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);
    }
}

Eftersom klienten i HB måste ta hand om den enhetliga distributionen av data såg nyckelsaltningsfunktionen ut så här:

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

Nu den mest intressanta delen - resultaten:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Samma sak i grafform:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet

Fördelen med HB är så överraskande att det finns en misstanke om att det finns någon form av flaskhals i CS-setupen. Men att googla och söka efter de mest uppenbara parametrarna (som concurrent_writes eller memtable_heap_space_in_mb) påskyndade inte saken. Samtidigt är stockarna rena och svär inte åt något.

Data fördelades jämnt över noderna, statistiken från alla noder var ungefär densamma.

Så här ser tabellstatistiken ut från en av nodernaTangentrymd: ks
Antal läser: 9383707
Läslatens: 0.04287025042448576 ms
Antal skriv: 15462012
Skrivlatens: 0.1350068438699957 ms
Pågående spolningar: 0
Tabell: t1
Antal SSTable: 16
Använd utrymme (live): 148.59 MiB
Använd utrymme (totalt): 148.59 MiB
Utrymme som används av ögonblicksbilder (totalt): 0 byte
Off-heap-minne som används (totalt): 5.17 MiB
SSTable kompressionsförhållande: 0.5720989576459437
Antal partitioner (uppskattning): 3970323
Antal minnesbara celler: 0
Minnesbar datastorlek: 0 byte
Memtable off heap-minne som används: 0 byte
Antal minnesbara brytare: 5
Antal läser lokalt: 2346045
Lokal läs latens: NaN ms
Antal lokala skrivningar: 3865503
Lokal skrivlatens: NaN ms
Pågående spolningar: 0
Procent reparerad: 0.0
Blomfilter falskt positiva: 25
Blomfilter falskt förhållande: 0.00000
Blomfilterutrymme som används: 4.57 MiB
Bloom filter off heap-minne som används: 4.57 MiB
Indexsammanfattning av använt högminne: 590.02 KiB
Komprimeringsmetadata från använt högminne: 19.45 KiB
Minsta byte för komprimerad partition: 36
Komprimerad partition maximala byte: 42
Komprimerad partition betyder byte: 42
Genomsnittliga levande celler per skiva (senaste fem minuterna): NaN
Maximalt antal levande celler per skiva (senaste fem minuterna): 0
Genomsnittlig gravstenar per skiva (senaste fem minuterna): NaN
Maximalt antal gravstenar per skiva (senaste fem minuterna): 0
Tappade mutationer: 0 byte

Ett försök att minska storleken på partiet (även att skicka det individuellt) hade ingen effekt, det blev bara värre. Det är möjligt att detta verkligen är den maximala prestandan för CS, eftersom resultaten som erhålls för CS liknar de som erhålls för DataStax - ungefär hundratusentals operationer per sekund. Dessutom, om vi tittar på resursutnyttjande, kommer vi att se att CS använder mycket mer CPU och diskar:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfarenhet
Figuren visar utnyttjandet under körningen av alla tester i rad för båda databaserna.

Angående HB:s kraftfulla läsfördel. Här kan du se att för båda databaserna är diskutnyttjandet under läsning extremt lågt (lästester är den sista delen av testcykeln för varje databas, till exempel för CS är detta från 15:20 till 15:40). När det gäller HB är anledningen tydlig - det mesta av data hänger i minnet, i memstore, och en del cachelagras i blockcache. När det gäller CS är det inte särskilt tydligt hur det fungerar, men diskåtervinning är inte heller synlig, men för säkerhets skull gjordes ett försök att aktivera cachen row_cache_size_in_mb = 2048 och ställa in caching = {'keys': 'ALL', 'rows_per_partition': ' 2000000'}, men det gjorde det ännu lite värre.

Det är också värt att återigen nämna en viktig punkt om antalet regioner i HB. I vårt fall angavs värdet som 64. Om du minskar det och gör det lika med till exempel 4, så sjunker hastigheten 2 gånger vid läsning. Anledningen är att memstore kommer att fyllas upp snabbare och filer kommer att spolas oftare och vid läsning kommer fler filer att behöva bearbetas, vilket är en ganska komplicerad operation för HB. Under verkliga förhållanden kan detta behandlas genom att tänka igenom en fördelnings- och komprimeringsstrategi; i synnerhet använder vi ett självskrivet verktyg som samlar skräp och komprimerar HF-filer konstant i bakgrunden. Det är fullt möjligt att de för DataStax-tester tilldelade endast 1 region per tabell (vilket inte är korrekt) och detta skulle till viss del förtydliga varför HB var så underlägsen i sina läsprov.

Följande preliminära slutsatser dras av detta. Förutsatt att inga större misstag gjordes under testningen så ser Cassandra ut som en koloss med fötter av lera. Närmare bestämt, medan hon balanserar på ett ben, som på bilden i början av artikeln, visar hon relativt bra resultat, men i en kamp under samma förhållanden förlorar hon direkt. Samtidigt, med hänsyn till det låga CPU-utnyttjandet på vår hårdvara, lärde vi oss att plantera två RegionServer HB per värd och därigenom fördubblade prestandan. De där. Med hänsyn till resursutnyttjandet är situationen för CS ännu mer beklaglig.

Naturligtvis är dessa tester ganska syntetiska och mängden data som användes här är relativt blygsam. Det är möjligt att om vi bytte till terabyte skulle situationen vara annorlunda, men medan vi för HB kan ladda terabyte, visade det sig för CS vara problematiskt. Det gav ofta en OperationTimedOutException även med dessa volymer, även om parametrarna för att vänta på ett svar redan ökat flera gånger jämfört med standardparametrarna.

Jag hoppas att vi genom gemensamma ansträngningar kommer att hitta flaskhalsarna i CS och om vi kan påskynda det, så kommer jag i slutet av inlägget definitivt lägga till information om de slutliga resultaten.

UPD: Tack vare råd från kamrater lyckades jag påskynda läsningen. Var:
159 644 ops (4 tabeller, 5 streams, batch 100).
Tillagt av:
.withLoadBalancingPolicy(ny TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
Och jag lekte med antalet trådar. Resultatet är följande:
4 bord, 100 trådar, batch = 1 (bit för bit): 301 969 ops
4 tabeller, 100 trådar, batch = 10: 447 608 ops
4 tabeller, 100 trådar, batch = 100: 625 655 ops

Senare kommer jag att tillämpa andra trimningstips, köra en hel testcykel och lägga till resultaten i slutet av inlägget.

Källa: will.com

Lägg en kommentar