Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

Това дори не е шега, изглежда, че тази конкретна снимка най-точно отразява същността на тези бази данни и накрая ще стане ясно защо:

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

Според DB-Engines Ranking двете най-популярни NoSQL колонни бази данни са Cassandra (наричани по-долу CS) и HBase (HB).

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

По волята на съдбата нашият екип за управление на зареждането на данни в Сбербанк вече го направи дълго и работи в тясно сътрудничество с HB. През това време проучихме доста добре силните и слабите му страни и се научихме да го приготвяме. Но наличието на алтернатива под формата на CS винаги ни караше да се измъчваме малко със съмнения: направихме ли правилния избор? Освен това резултатите сравнения, извършено от DataStax, те казаха, че CS лесно бие HB с почти смазващ резултат. От друга страна, DataStax е заинтересована страна и не бива да вярвате на думата им. Бяхме объркани и от доста малкото количество информация за условията на тестване, така че решихме сами да разберем кой е кралят на BigData NoSql и получените резултати се оказаха много интересни.

Въпреки това, преди да преминем към резултатите от проведените тестове, е необходимо да опишем значимите аспекти на конфигурациите на средата. Факт е, че CS може да се използва в режим, който позволява загуба на данни. Тези. това е, когато само един сървър (възел) е отговорен за данните на определен ключ и ако по някаква причина той се провали, тогава стойността на този ключ ще бъде загубена. За много задачи това не е критично, но за банковия сектор това е по-скоро изключение, отколкото правило. В нашия случай е важно да имате няколко копия на данни за надеждно съхранение.

Следователно беше разгледан само режимът на работа на CS в режим на тройна репликация, т.е. Създаването на casespace беше извършено със следните параметри:

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

След това има два начина за осигуряване на необходимото ниво на последователност. Общо правило:
NW + NR > RF

Което означава, че броят на потвържденията от възли при запис (NW) плюс броя на потвържденията от възли при четене (NR) трябва да бъде по-голям от коефициента на репликация. В нашия случай RF = 3, което означава, че следните опции са подходящи:
2 + 2 > 3
3 + 1 > 3

Тъй като за нас е фундаментално важно да съхраняваме данните възможно най-надеждно, беше избрана схемата 3+1. Освен това HB работи на подобен принцип, т.е. такова сравнение ще бъде по-справедливо.

Трябва да се отбележи, че DataStax направи обратното в своето проучване, те зададоха RF = 1 както за CS, така и за HB (за последното чрез промяна на настройките на HDFS). Това е наистина важен аспект, защото въздействието върху производителността на CS в този случай е огромно. Например, снимката по-долу показва увеличението на времето, необходимо за зареждане на данни в CS:

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

Тук виждаме следното: колкото повече конкуриращи се нишки пишат данни, толкова повече време отнема. Това е естествено, но е важно влошаването на производителността за RF=3 да е значително по-високо. С други думи, ако напишем 4 нишки в 5 таблици всяка (20 общо), тогава RF=3 губи около 2 пъти (150 секунди за RF=3 срещу 75 за RF=1). Но ако увеличим натоварването, като заредим данни в 8 таблици с 5 нишки всяка (40 общо), тогава загубата на RF=3 вече е 2,7 пъти (375 секунди срещу 138).

Може би това отчасти е тайната на успешните тестове за натоварване, извършени от DataStax за CS, тъй като за HB на нашия щанд промяната на коефициента на репликация от 2 на 3 не даде никакъв ефект. Тези. дисковете не са тясното място на HB за нашата конфигурация. Тук обаче има много други клопки, защото трябва да се отбележи, че нашата версия на HB беше леко закърпена и променена, средите са напълно различни и т.н. Също така си струва да се отбележи, че може би просто не знам как да подготвя правилно CS и има някои по-ефективни начини за работа с него и се надявам да разберем в коментарите. Но на първо място.

Всички тестове бяха извършени на хардуерен клъстер, състоящ се от 4 сървъра, всеки със следната конфигурация:

Процесор: Xeon E5-2680 v4 @ 2.40GHz 64 нишки.
Дискове: 12 броя SATA HDD
версия на java: 1.8.0_111

CS версия: 3.11.5

параметри на cassandra.ymlброй_токени: 256
hinted_handoff_enabled: вярно
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
удостоверител: AllowAllAuthenticator
авторизатор: AllowAllAuthorizer
role_manager: CassandraRoleManager
roles_validity_in_ms: 2000
permissions_validity_in_ms: 2000
credentials_validity_in_ms: 2000
програма за разделяне: org.apache.cassandra.dht.Murmur3Partitioner
директории_файл_данни:
- /data1/cassandra/data # всяка директория dataN е отделен диск
- /data2/cassandra/data
- /data3/cassandra/data
- /data4/cassandra/data
- /data5/cassandra/data
- /data6/cassandra/data
- /data7/cassandra/data
- /data8/cassandra/data
директория_commitlog: /data9/cassandra/commitlog
cdc_enabled: невярно
disk_failure_policy: стоп
commit_failure_policy: стоп
подготвили_изявления_cache_size_mb:
thrift_prepared_statements_cache_size_mb:
key_cache_size_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
saved_caches_directory: /data10/cassandra/saved_caches
commitlog_sync: периодично
commitlog_sync_period_in_ms: 10000
commitlog_segment_size_in_mb: 32
seed_provider:
- име на клас: org.apache.cassandra.locator.SimpleSeedProvider
параметри:
— семена: "*,*"
concurrent_reads: 256 # опитах 64 - не се забелязва разлика
concurrent_writes: 256 # опитах 64 - не се забелязва разлика
concurrent_counter_writes: 256 # опитах 64 - не се забелязва разлика
concurrent_materialized_view_writes: 32
memtable_heap_space_in_mb: 2048 # опитах 16 GB - беше по-бавно
memtable_allocation_type: heap_buffers
index_summary_capacity_in_mb:
index_summary_resize_interval_in_minutes: 60
trickle_fsync: невярно
trickle_fsync_interval_in_kb: 10240
порт за съхранение: 7000
ssl_storage_port: 7001
слушай_адрес: *
излъчван_адрес: *
listen_on_broadcast_address: вярно
internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: вярно
native_transport_port: 9042
start_rpc: вярно
rpc_адрес: *
rpc_port: 9160
rpc_keepalive: вярно
rpc_server_type: синхронизиране
thrift_framed_transport_size_in_mb: 15
incremental_backups: невярно
snapshot_before_compaction: невярно
auto_snapshot: вярно
размер_индекс_на_колона_в_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 XNUMX
range_request_timeout_in_ms: 200000 XNUMX
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 XNUMX
request_timeout_in_ms: 200000 XNUMX
slow_query_log_timeout_in_ms: 500
cross_node_timeout: невярно
endpoint_snitch: GossipingPropertyFileSnitch
dynamic_snitch_update_interval_in_ms: 100
dynamic_snitch_reset_interval_in_ms: 600000
dynamic_snitch_badness_threshold: 0.1
заявка_планировчик: org.apache.cassandra.scheduler.NoScheduler
сървър_опции_шифроване:
internode_encryption: няма
client_encryption_options:
активиран: false
компресия_на_вътрешност: dc
inter_dc_tcp_nodelay: невярно
tracetype_query_ttl: 86400
tracetype_repair_ttl: 604800
enable_user_defined_functions: невярно
enable_scripted_user_defined_functions: невярно
windows_timer_interval: 1
transparent_data_encryption_options:
активиран: false
tombstone_warn_threshold: 1000
tombstone_failure_threshold: 100000 XNUMX
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: невярно
enable_materialized_views: вярно
enable_sasi_indexes: вярно

Настройки на GC:

### Настройки на CMS-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloadingEnabled

Бяха разпределени 16 Gb jvm.options памет (пробвахме и 32 Gb, не се забеляза разлика).

Таблиците са създадени с командата:

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

HB версия: 1.2.0-cdh5.14.2 (в класа org.apache.hadoop.hbase.regionserver.HRegion изключихме MetricsRegion, което доведе до GC, когато броят на регионите беше повече от 1000 на RegionServer)

HBase параметри, които не са по подразбиранеzookeeper.session.timeout: 120000
hbase.rpc.timeout: 2 минути
hbase.client.scanner.timeout.period: 2 минути
hbase.master.handler.count: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.period: 2 минути
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.period: 4 час(а)
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 дни
Фрагмент за разширена конфигурация на услугата HBase (предпазен клапан) за 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 за HBase RegionServer:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis: 2 минути
hbase.snapshot.region.timeout: 2 минути
hbase.snapshot.master.timeout.millis: 2 минути
HBase REST сървър Макс. размер на журнала: 100 MiB
HBase REST Server Максимално архивиране на регистрационни файлове: 5
HBase Thrift Server Максимален размер на журнала: 100 MiB
HBase Thrift Server Максимално архивиране на регистрационни файлове: 5
Главен максимален размер на регистрационния файл: 100 MiB
Основни максимални резервни копия на лог файлове: 5
Максимален размер на регистрационния файл на RegionServer: 100 MiB
RegionServer Максимално архивиране на регистрационни файлове: 5
Прозорец за откриване на активен главен HBase: 4 минути
dfs.client.hedged.read.threadpool.size: 40
dfs.client.hedged.read.threshold.millis: 10 милисекунди(и)
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Максимален дескриптор на процесния файл: 180000 XNUMX
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
Нишки за преместване на регион: 6
Размер на клиентска Java Heap в байтове: 1 GiB
Група по подразбиране на HBase REST сървър: 3 GiB
Група по подразбиране на HBase Thrift Server: 3 GiB
Размер на Java Heap на HBase Master в байтове: 16 GiB
Размер на Java Heap на HBase RegionServer в байтове: 32 GiB

+ ZooKeeper
maxClientCnxns: 601
maxSessionTimeout: 120000
Създаване на таблици:
hbase org.apache.hadoop.hbase.util.RegionSplitter ns:t1 UniformSplit -c 64 -f cf
променя 'ns:t1', {NAME => 'cf', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}

Тук има един важен момент - описанието на DataStax не казва колко региона са използвани за създаване на HB таблиците, въпреки че това е критично за големи обеми. Затова за тестовете е избрано количество = 64, което позволява съхраняване на до 640 GB, т.е. среден размер маса.

По време на теста HBase имаше 22 хиляди таблици и 67 хиляди региона (това щеше да е смъртоносно за версия 1.2.0, ако не беше споменатата по-горе корекция).

Сега за кода. Тъй като не беше ясно кои конфигурации са по-изгодни за конкретна база данни, бяха проведени тестове в различни комбинации. Тези. в някои тестове 4 таблици бяха заредени едновременно (всичките 4 възела бяха използвани за връзка). В други тестове работихме с 8 различни таблици. В някои случаи размерът на партидата беше 100, в други 200 (параметър на партидата - вижте кода по-долу). Размерът на данните за стойност е 10 байта или 100 байта (dataSize). Общо 5 милиона записа бяха записани и прочетени във всяка таблица всеки път. В същото време 5 нишки бяха записани/прочетени във всяка таблица (номер на нишка - thNum), всяка от които използваше собствен диапазон от ключове (брой = 1 милион):

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

Съответно, подобна функционалност беше предоставена за 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);
    }
}

Тъй като в HB клиентът трябва да се грижи за равномерното разпределение на данните, функцията за осоляване на ключа изглеждаше така:

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

Сега най-интересната част - резултатите:

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

Същото нещо под формата на графика:

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк

Предимството на HB е толкова изненадващо, че има подозрение, че има някакво тясно място в настройката на CS. Търсенето в Google и най-очевидните параметри (като concurrent_writes или memtable_heap_space_in_mb) обаче не ускори нещата. В същото време трупите са чисти и не се кълнат в нищо.

Данните бяха разпределени равномерно между възлите, статистиките от всички възли бяха приблизително еднакви.

Ето как изглежда статистиката на таблицата от един от възлитеКлючово пространство: ks
Брой четения: 9383707
Латентност при четене: 0.04287025042448576 ms
Брой на записите: 15462012
Латентност при запис: 0.1350068438699957 ms
Чакащи флъшове: 0
Таблица: t1
Брой SSTable: 16
Използвано пространство (на живо): 148.59 MiB
Използвано пространство (общо): 148.59 MiB
Пространство, използвано от моментни снимки (общо): 0 байта
Използвана свободна памет (общо): 5.17 MiB
SSTable Коефициент на компресия: 0.5720989576459437
Брой дялове (оценка): 3970323
Брой мемируеми клетки: 0
Размер на паметните данни: 0 байта
Memtable off heap използвана памет: 0 байта
Брой превключватели на Memtable: 5
Локален брой четения: 2346045
Локално забавяне при четене: NaN ms
Брой локални записи: 3865503
Локална латентност при запис: NaN ms
Чакащи флъшове: 0
Процент ремонтиран: 0.0
Фалшиви положителни резултати на филтъра Bloom: 25
Фалшиво съотношение на филтъра на Bloom: 0.00000
Използвано място за филтър Bloom: 4.57 MiB
Използвана памет за филтриране на Bloom: 4.57 MiB
Резюме на индекса извън използваната памет на стека: 590.02 KiB
Използвани метаданни за компресиране извън паметта на купчината: 19.45 KiB
Минимален байт на компактния дял: 36
Максимален байт на компактния дял: 42
Средни байтове на компактен дял: 42
Среден брой живи клетки на срез (последните пет минути): NaN
Максимален брой живи клетки на срез (последните пет минути): 0
Среден брой надгробни плочи на парче (последните пет минути): NaN
Максимален брой надгробни камъни на парче (последните пет минути): 0
Премахнати мутации: 0 байта

Опитът за намаляване на размера на партидата (дори изпращането й поотделно) нямаше ефект, само се влоши. Възможно е всъщност това наистина да е максималната производителност за CS, тъй като резултатите, получени за CS, са подобни на тези, получени за DataStax - около стотици хиляди операции в секунда. Освен това, ако разгледаме използването на ресурсите, ще видим, че CS използва много повече CPU и дискове:

Битката на два якодзуна, или Касандра срещу HBase. Опитът на екипа на Сбербанк
Фигурата показва използването по време на изпълнението на всички тестове подред за двете бази данни.

Относно мощното предимство на HB при четене. Тук можете да видите, че и за двете бази данни използването на диска по време на четене е изключително ниско (тестовете за четене са последната част от цикъла на тестване за всяка база данни, например за CS това е от 15:20 до 15:40). В случая с HB причината е ясна - повечето от данните висят в паметта, в memstore, а някои са кеширани в blockcache. Що се отнася до CS, не е много ясно как работи, но рециклирането на диска също не се вижда, но за всеки случай беше направен опит да се активира кеша row_cache_size_in_mb = 2048 и да се зададе caching = {'keys': 'ALL', 'rows_per_partition': ' 2000000'}, но това го направи дори малко по-лошо.

Също така си струва да споменем още веднъж важен момент относно броя на регионите в HB. В нашия случай стойността беше посочена като 64. Ако я намалите и я направите равна например на 4, тогава при четене скоростта пада 2 пъти. Причината е, че memstore ще се пълни по-бързо и файловете ще се флъшват по-често и при четене ще трябва да се обработват повече файлове, което е доста сложна операция за HB. В реални условия това може да се третира чрез обмисляне на стратегия за предварително разделяне и уплътняване; по-специално ние използваме самостоятелно написана помощна програма, която събира боклука и компресира HFiles постоянно във фонов режим. Напълно възможно е за тестовете на DataStax да са разпределили само 1 регион на таблица (което не е правилно) и това би изяснило донякъде защо HB е толкова по-нисък в техните тестове за четене.

От това се правят следните предварителни заключения. Ако приемем, че не са направени големи грешки по време на тестването, тогава Касандра изглежда като колос с глинени крака. По-точно, докато балансира на един крак, както е на снимката в началото на статията, тя показва сравнително добри резултати, но в битка при същите условия губи направо. В същото време, като вземем предвид ниското използване на процесора на нашия хардуер, се научихме да поставяме два RegionServer HB на хост и по този начин удвоихме производителността. Тези. Като се има предвид използването на ресурсите, положението на CS е още по-плачевно.

Разбира се, тези тестове са доста синтетични и количеството данни, използвани тук, е относително скромно. Възможно е, ако минахме на терабайти, ситуацията да е различна, но докато за HB можем да заредим терабайти, за CS това се оказа проблемно. Често хвърляше OperationTimedOutException дори при тези обеми, въпреки че параметрите за изчакване на отговор вече бяха увеличени няколко пъти в сравнение с тези по подразбиране.

Надявам се, че с общи усилия ще намерим тесните места на CS и ако успеем да го ускорим, тогава в края на публикацията определено ще добавя информация за крайните резултати.

UPD: Благодарение на съветите на другарите, успях да ускоря четенето. Беше:
159 644 операции (4 маси, 5 потока, партида 100).
Пуснато на:
.withLoadBalancingPolicy(нова TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
И си поиграх с броя на нишките. Резултатът е следният:
4 таблици, 100 нишки, партида = 1 (част по част): 301 969 операции
4 таблици, 100 нишки, партида = 10: 447 608 операции
4 таблици, 100 нишки, партида = 100: 625 655 операции

По-късно ще приложа други съвети за настройка, ще проведа пълен тестов цикъл и ще добавя резултатите в края на публикацията.

Източник: www.habr.com

Добавяне на нов коментар