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

Dette er ikke engang en joke, det ser ud til, at dette særlige billede mest præcist afspejler essensen af ​​disse databaser, og i sidste ende vil det være klart hvorfor:

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

Ifølge DB-Engines Ranking er de to mest populære NoSQL søjledatabaser Cassandra (herefter CS) og HBase (HB).

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

Efter skæbnens vilje har vores dataindlæsningsteam hos Sberbank allerede gjort det давно og arbejder tæt sammen med HB. I løbet af denne tid studerede vi dens styrker og svagheder ganske godt og lærte, hvordan man tilbereder den. Tilstedeværelsen af ​​et alternativ i form af CS tvang os dog altid til at plage os selv lidt med tvivl: traf vi det rigtige valg? Desuden resultaterne sammenligning, udført af DataStax, sagde de, at CS nemt slår HB med næsten en knusende score. På den anden side er DataStax en interesseret part, og du skal ikke tage deres ord for det. Vi var også forvirrede over den ret lille mængde information om testbetingelserne, så vi besluttede at finde ud af på egen hånd, hvem der er kongen af ​​BigData NoSql, og de opnåede resultater viste sig at være meget interessante.

Før man går videre til resultaterne af de udførte tests, er det dog nødvendigt at beskrive de væsentlige aspekter af miljøkonfigurationerne. Faktum er, at CS kan bruges i en tilstand, der tillader tab af data. De der. dette er, når kun én server (node) er ansvarlig for dataene for en bestemt nøgle, og hvis den af ​​en eller anden grund fejler, vil værdien af ​​denne nøgle gå tabt. For mange opgaver er dette ikke kritisk, men for banksektoren er dette undtagelsen snarere end reglen. I vores tilfælde er det vigtigt at have flere kopier af data til pålidelig opbevaring.

Derfor blev kun CS-driftstilstanden i tredobbelt replikeringstilstand taget i betragtning, dvs. Oprettelsen af ​​casespace blev udført med følgende parametre:

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

Dernæst er der to måder at sikre det nødvendige niveau af konsistens på. Generel regel:
NW + NR > RF

Hvilket betyder, at antallet af bekræftelser fra noder ved skrivning (NW) plus antallet af bekræftelser fra noder ved læsning (NR) skal være større end replikationsfaktoren. I vores tilfælde er RF = 3, hvilket betyder, at følgende muligheder er egnede:
2 + 2 > 3
3 + 1 > 3

Da det er grundlæggende vigtigt for os at opbevare dataene så pålideligt som muligt, blev 3+1-ordningen valgt. Derudover arbejder HB efter et lignende princip, dvs. en sådan sammenligning vil være mere retfærdig.

Det skal bemærkes, at DataStax gjorde det modsatte i deres undersøgelse, de satte RF = 1 for både CS og HB (for sidstnævnte ved at ændre HDFS-indstillingerne). Dette er et virkelig vigtigt aspekt, fordi indvirkningen på CS-ydeevne i dette tilfælde er enorm. For eksempel viser billedet nedenfor stigningen i den tid, der kræves for at indlæse data i CS:

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

Her ser vi følgende: Jo flere konkurrerende tråde skriver data, jo længere tid tager det. Dette er naturligt, men det er vigtigt, at ydeevneforringelsen for RF=3 er væsentligt højere. Med andre ord, hvis vi skriver 4 tråde i 5 tabeller hver (20 i alt), så taber RF=3 ca. 2 gange (150 sekunder for RF=3 versus 75 for RF=1). Men hvis vi øger belastningen ved at indlæse data i 8 tabeller med 5 tråde hver (40 i alt), så er tabet af RF=3 allerede 2,7 gange (375 sekunder mod 138).

Måske er dette til dels hemmeligheden bag den vellykkede belastningstest udført af DataStax for CS, for for HB på vores stand havde det ingen effekt at ændre replikationsfaktoren fra 2 til 3. De der. diske er ikke HB-flaskehalsen for vores konfiguration. Der er dog mange andre faldgruber her, for det skal bemærkes, at vores version af HB var lidt lappet og tweaket, miljøerne er helt anderledes osv. Det er også værd at bemærke, at jeg måske bare ikke ved, hvordan man forbereder CS korrekt, og der er nogle mere effektive måder at arbejde med det på, og jeg håber, vi finder ud af det i kommentarerne. Men først ting først.

Alle test blev udført på en hardwareklynge bestående af 4 servere, hver med følgende konfiguration:

CPU: Xeon E5-2680 v4 @ 2.40GHz 64 tråde.
Diske: 12 stk SATA HDD
java version: 1.8.0_111

CS-version: 3.11.5

cassandra.yml parametreantal_tokens: 256
hinted_handoff_enabled: sandt
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
autorisator: AllowAllAuthorizer
rolle_manager: CassandraRoleManager
roles_validity_in_ms: 2000
permissions_validity_in_ms: 2000
credentials_validity_in_ms: 2000
partitioner: org.apache.cassandra.dht.Murmur3Partitioner
data_fil_mapper:
- /data1/cassandra/data # hver dataN-mappe er 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: falsk
disk_failure_policy: stop
commit_failure_policy: stop
forberedte_udsagn_cache_størrelse_mb:
thrift_prepared_statements_cache_size_mb:
key_cache_size_in_mb:
key_cache_save_periode: 14400
række_cache_størrelse_i_mb: 0
row_cache_save_period: 0
counter_cache_size_in_mb:
counter_cache_save_periode: 7200
gemte_caches_mappe: /data10/cassandra/gemte_caches
commitlog_sync: periodisk
commitlog_sync_period_in_ms: 10000
commitlog_segment_size_in_mb: 32
frøleverandør:
- klassenavn: org.apache.cassandra.locator.SimpleSeedProvider
parametre:
— frø: "*,*"
concurrent_reads: 256 # prøvet 64 - ingen forskel bemærket
concurrent_writes: 256 # prøvet 64 - ingen forskel bemærket
concurrent_counter_writes: 256 # prøvet 64 - ingen forskel bemærket
concurrent_materialized_view_writes: 32
memtable_heap_space_in_mb: 2048 # prøvet 16 GB - det var langsommere
memtable_allocation_type: heap_buffers
index_summary_capacity_in_mb:
index_summary_resize_interval_in_minutes: 60
trickle_fsync: falsk
trickle_fsync_interval_in_kb: 10240
storage_port: 7000
ssl_storage_port: 7001
lytte_adresse: *
udsendelsesadresse: *
lytte_på_udsendelsesadresse: sandt
internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: sandt
native_transport_port: 9042
start_rpc: sandt
rpc_adresse: *
rpc_port: 9160
rpc_keepalive: sandt
rpc_server_type: sync
thrift_framed_transport_size_in_mb: 15
incremental_backups: falsk
snapshot_before_compaction: falsk
auto_snapshot: sandt
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
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: falsk
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:
aktiveret: falsk
internode_compression: dc
inter_dc_tcp_nodelay: falsk
tracetype_query_ttl: 86400
tracetype_repair_ttl: 604800
enable_user_defined_functions: falsk
enable_scripted_user_defined_functions: falsk
windows_timer_interval: 1
transparent_data_encryption_options:
aktiveret: falsk
gravstensadvarselstærskel: 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: falsk
enable_materialized_views: sand
enable_sasi_indexes: sand

GC-indstillinger:

### CMS-indstillinger-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=1
-XX:CMSIinitiatingOccupancyFraction=75
-XX:+Brug CMSIinitierende BelægningKun
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloadingEnabled

jvm.options-hukommelsen blev tildelt 16 Gb (vi prøvede også 32 Gb, ingen forskel blev bemærket).

Tabellerne blev oprettet med kommandoen:

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 ekskluderede vi MetricsRegion, hvilket førte til GC, da antallet af regioner var mere end 1000 på RegionServer)

Ikke-standard HBase-parametrezookeeper.session.timeout: 120000
hbase.rpc.timeout: 2 minut(er)
hbase.client.scanner.timeout.periode: 2 minut(er)
hbase.master.handler.count: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.periode: 2 minut(er)
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.periode: 4 time(r)
hbase.regionserver.maxlogs: 200
hbase.hregion.memstore.flush.størrelse: 1 GiB
hbase.hregion.memstore.blok.multiplikator: 6
hbase.hstore.compactionTærskel: 5
hbase.hstore.blockingStoreFiles: 200
hbase.hregion.majorcompaction: 1 dag(e)
HBase Service Advanced Configuration Snippet (sikkerhedsventil) til 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-konfigurationsmuligheder for 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 Maks. logstørrelse: 100 MiB
HBase REST Server Maksimal sikkerhedskopiering af logfiler: 5
HBase Thrift Server Maks. logstørrelse: 100 MiB
HBase Thrift Server Maksimal sikkerhedskopiering af logfiler: 5
Master Max Log Størrelse: 100 MiB
Master maksimale logfil-sikkerhedskopier: 5
RegionServer Max Log Størrelse: 100 MiB
RegionServer Maximum Log File Backups: 5
HBase Active Master Detection Window: 4 minutter
dfs.client.hedged.read.threadpool.størrelse: 40
dfs.client.hedged.read.threshold.millis: 10 millisekund(er)
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Maksimal procesfilbeskrivelser: 180000
hbase.thrift.minWorkerThreads: 200
hbase.master.executor.openregion.threads: 30
hbase.master.executor.closeregion.threads: 30
hbase.master.executor.serverops.tråde: 60
hbase.regionserver.thread.compaction.small: 6
hbase.ipc.server.read.threadpool.size: 20
Region Mover Tråde: 6
Klient Java-heapstørrelse i bytes: 1 GiB
HBase REST Server Standardgruppe: 3 GiB
HBase Thrift Server Standardgruppe: 3 GiB
Java Heap Størrelse af HBase Master i bytes: 16 GiB
Java-heapstørrelse på HBase RegionServer i bytes: 32 GiB

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

Der er en vigtig pointe her - DataStax-beskrivelsen siger ikke, hvor mange regioner der blev brugt til at oprette HB-tabellerne, selvom dette er kritisk for store mængder. Derfor blev der til testene valgt kvantitet = 64, som tillader lagring af op til 640 GB, dvs. medium størrelse bord.

På tidspunktet for testen havde HBase 22 tusinde tabeller og 67 tusinde regioner (dette ville have været dødeligt for version 1.2.0, hvis ikke for patchen nævnt ovenfor).

Nu til koden. Da det ikke var klart, hvilke konfigurationer der var mest fordelagtige for en bestemt database, blev der udført test i forskellige kombinationer. De der. i nogle tests blev 4 tabeller indlæst samtidigt (alle 4 noder blev brugt til forbindelse). I andre test arbejdede vi med 8 forskellige tabeller. I nogle tilfælde var batchstørrelsen 100, i andre 200 (batchparameter - se kode nedenfor). Datastørrelsen for værdi er 10 bytes eller 100 bytes (dataSize). I alt blev 5 millioner poster skrevet og læst ind i hver tabel hver gang. Samtidig blev der skrevet/læst 5 tråde til hver tabel (trådnummer - thNum), som hver brugte sit eget nøgleområde (antal = 1 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);
    }
}

Følgelig blev lignende funktionalitet tilvejebragt for 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);
    }
}

Da klienten i HB skal sørge for ensartet fordeling af data, så nøglesaltningsfunktionen således ud:

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 interessante del - resultaterne:

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

Det samme i grafform:

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

Fordelen ved HB er så overraskende, at der er en mistanke om, at der er en slags flaskehals i CS-opsætningen. Googling og søgning efter de mest åbenlyse parametre (såsom concurrent_writes eller memtable_heap_space_in_mb) fremskyndede dog ikke tingene. Samtidig er træstammerne rene og bander ikke til noget.

Dataene var fordelt jævnt på tværs af noderne, statistikken fra alle noder var nogenlunde ens.

Sådan ser tabelstatistikken ud fra en af ​​knudepunkterneTasterum: ks
Læseantal: 9383707
Læseforsinkelse: 0.04287025042448576 ms
Skriveantal: 15462012
Skriveforsinkelse: 0.1350068438699957 ms
Afventende skylninger: 0
Tabel: t1
SSTable antal: 16
Brugt plads (live): 148.59 MiB
Brugt plads (i alt): 148.59 MiB
Plads brugt af snapshots (i alt): 0 bytes
Off heap-hukommelse brugt (i alt): 5.17 MiB
SSTable kompressionsforhold: 0.5720989576459437
Antal partitioner (estimat): 3970323
Antal huskelige celler: 0
Husk datastørrelse: 0 bytes
Memtable off heap-hukommelse brugt: 0 bytes
Antal kontakter, der kan huskes: 5
Lokalt læst antal: 2346045
Lokal læselatens: NaN ms
Antal lokale skrivelser: 3865503
Lokal skriveforsinkelse: NaN ms
Afventende skylninger: 0
Procent repareret: 0.0
Bloom filter falske positiver: 25
Bloom filter falsk forhold: 0.00000
Brugt blomstringsfilterplads: 4.57 MiB
Bloom-filter fra heap-hukommelse brugt: 4.57 MiB
Indeksoversigt fra brugt heap-hukommelse: 590.02 KiB
Komprimeringsmetadata fra brugt heap-hukommelse: 19.45 KiB
Komprimeret partition minimum bytes: 36
Komprimeret partition maksimale bytes: 42
Komprimeret partition betyder bytes: 42
Gennemsnitlige levende celler pr. skive (sidste fem minutter): NaN
Maksimalt antal levende celler pr. skive (sidste fem minutter): 0
Gennemsnitlige gravsten pr. skive (sidste fem minutter): NaN
Maksimalt antal gravsten pr. skive (sidste fem minutter): 0
Droppede mutationer: 0 bytes

Et forsøg på at reducere størrelsen af ​​partiet (selv at sende det enkeltvis) havde ingen effekt, det blev kun værre. Det er muligt, at dette faktisk er den maksimale ydeevne for CS, da resultaterne opnået for CS svarer til dem, der opnås for DataStax - omkring hundredtusindvis af operationer pr. sekund. Derudover, hvis vi ser på ressourceudnyttelsen, vil vi se, at CS bruger meget mere CPU og diske:

Battle of two yakozuna, eller Cassandra vs HBase. Sberbank team erfaring
Figuren viser udnyttelsen under kørslen af ​​alle test i træk for begge databaser.

Med hensyn til HB's stærke læsefordel. Her kan du se, at for begge databaser er diskudnyttelsen under læsning ekstremt lav (læsetests er den sidste del af testcyklussen for hver database, f.eks. for CS er dette fra 15:20 til 15:40). I tilfælde af HB er årsagen klar - det meste af dataene hænger i hukommelsen, i memstore, og nogle er cachelagret i blockcache. Med hensyn til CS er det ikke særlig klart, hvordan det virker, men diskgenbrug er heller ikke synligt, men for en sikkerheds skyld blev der forsøgt at aktivere cachen row_cache_size_in_mb = 2048 og indstille caching = {'keys': 'ALL', 'rows_per_partition': ' 2000000'}, men det gjorde det endnu lidt værre.

Det er også værd at nævne endnu en gang en vigtig pointe om antallet af regioner i HB. I vores tilfælde blev værdien angivet som 64. Hvis vi reducerer den og gør den lig med for eksempel 4, så falder hastigheden 2 gange ved læsning. Årsagen er, at memstore bliver hurtigere fyldt op, og filer vil blive skyllet oftere, og ved læsning skal flere filer behandles, hvilket er en ret kompliceret operation for HB. Under virkelige forhold kan dette behandles ved at gennemtænke en forudopdelings- og komprimeringsstrategi; især bruger vi et selvskrevet værktøj, der samler skrald og komprimerer HFiles konstant i baggrunden. Det er meget muligt, at de til DataStax-tests kun tildelte 1 region pr. tabel (hvilket ikke er korrekt), og dette ville lidt afklare, hvorfor HB var så underlegen i deres læsetest.

Heraf drages følgende foreløbige konklusioner. Hvis vi antager, at der ikke blev lavet større fejl under testen, så ligner Cassandra en kolos med fødder af ler. Mere præcist, mens hun balancerer på et ben, som på billedet i begyndelsen af ​​artiklen, viser hun relativt gode resultater, men i en kamp under samme forhold taber hun direkte. Samtidig, under hensyntagen til den lave CPU-udnyttelse på vores hardware, lærte vi at plante to RegionServer HB'er pr. vært og fordoblede derved ydeevnen. De der. Når man tager ressourceudnyttelsen i betragtning, er situationen for CS endnu mere beklagelig.

Selvfølgelig er disse tests ret syntetiske, og mængden af ​​data, der blev brugt her, er relativt beskeden. Det er muligt, at hvis vi skiftede til terabyte, ville situationen være anderledes, men mens vi for HB kan indlæse terabyte, viste det sig for CS at være problematisk. Det kastede ofte en OperationTimedOutException selv med disse volumener, selvom parametrene for at vente på et svar allerede var øget flere gange sammenlignet med standardparametrene.

Jeg håber, at vi gennem fælles indsats vil finde flaskehalsene ved CS, og hvis vi kan fremskynde det, så vil jeg i slutningen af ​​indlægget helt sikkert tilføje information om de endelige resultater.

UPD: Takket være råd fra kammerater lykkedes det mig at fremskynde læsningen. Var:
159 operationer (644 borde, 4 streams, batch 5).
Tilføjet af:
.withLoadBalancingPolicy(ny TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
Og jeg legede med antallet af tråde. Resultatet er følgende:
4 borde, 100 tråde, batch = 1 (stykke for stykke): 301 ops
4 tabeller, 100 tråde, batch = 10: 447 ops
4 tabeller, 100 tråde, batch = 100: 625 ops

Senere vil jeg anvende andre tuning tips, køre en fuld testcyklus og tilføje resultaterne i slutningen af ​​indlægget.

Kilde: www.habr.com

Tilføj en kommentar