Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

Esto ni siquiera es una broma, parece que esta imagen refleja con mayor precisión la esencia de estas bases de datos, y al final quedará claro por qué:

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

Según DB-Engines Ranking, las dos bases de datos en columnas NoSQL más populares son Cassandra (en adelante CS) y HBase (HB).

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

Por voluntad del destino, nuestro equipo de gestión de carga de datos en Sberbank ya largo y trabaja en estrecha colaboración con HB. Durante este tiempo estudiamos bastante bien sus puntos fuertes y débiles y aprendimos a cocinarlo. Sin embargo, la presencia de una alternativa en forma de CS siempre nos obligaba a atormentarnos un poco con dudas: ¿tomamos la decisión correcta? Es más, los resultados comparaciones, realizado por DataStax, dijeron que CS vence fácilmente a HB con una puntuación casi aplastante. Por otro lado, DataStax es una parte interesada y no deberías confiar en su palabra. También estábamos confundidos por la cantidad bastante pequeña de información sobre las condiciones de prueba, por lo que decidimos descubrir por nuestra cuenta quién es el rey de BigData NoSql, y los resultados obtenidos resultaron ser muy interesantes.

Sin embargo, antes de pasar a los resultados de las pruebas realizadas, es necesario describir los aspectos significativos de las configuraciones del entorno. El hecho es que CS se puede utilizar en un modo que permita la pérdida de datos. Aquellos. esto es cuando solo un servidor (nodo) es responsable de los datos de una determinada clave, y si por alguna razón falla, entonces el valor de esta clave se perderá. Para muchas tareas esto no es crítico, pero para el sector bancario es la excepción y no la regla. En nuestro caso, es importante tener varias copias de los datos para un almacenamiento confiable.

Por lo tanto, sólo se consideró el modo de funcionamiento CS en modo de triple replicación, es decir La creación del casespace se realizó con los siguientes parámetros:

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

A continuación, hay dos formas de garantizar el nivel requerido de coherencia. Regla general:
NO + NR > RF

Lo que significa que el número de confirmaciones de nodos al escribir (NW) más el número de confirmaciones de nodos al leer (NR) debe ser mayor que el factor de replicación. En nuestro caso, RF = 3, lo que significa que son adecuadas las siguientes opciones:
2 + 2> 3
3 + 1> 3

Dado que para nosotros es de fundamental importancia almacenar los datos de la forma más fiable posible, se optó por el esquema 3+1. Además, HB funciona según un principio similar, es decir. tal comparación será más justa.

Cabe señalar que DataStax hizo lo contrario en su estudio: establecieron RF = 1 tanto para CS como para HB (para este último cambiando la configuración de HDFS). Este es un aspecto realmente importante porque el impacto en el rendimiento de CS en este caso es enorme. Por ejemplo, la siguiente imagen muestra el aumento en el tiempo necesario para cargar datos en CS:

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

Aquí vemos lo siguiente: cuantos más subprocesos de la competencia escriben datos, más tiempo lleva. Esto es natural, pero es importante que la degradación del rendimiento para RF=3 sea significativamente mayor. En otras palabras, si escribimos 4 subprocesos en 5 tablas cada uno (20 en total), entonces RF=3 pierde aproximadamente 2 veces (150 segundos para RF=3 versus 75 para RF=1). Pero si aumentamos la carga cargando datos en 8 tablas con 5 subprocesos cada una (40 en total), entonces la pérdida de RF=3 ya es 2,7 veces (375 segundos frente a 138).

Quizás este sea en parte el secreto del éxito de las pruebas de carga realizadas por DataStax para CS, porque para HB en nuestro stand cambiar el factor de replicación de 2 a 3 no tuvo ningún efecto. Aquellos. Los discos no son el cuello de botella de HB para nuestra configuración. Sin embargo, aquí hay muchos otros errores, porque cabe señalar que nuestra versión de HB fue ligeramente parcheada y modificada, los entornos son completamente diferentes, etc. También vale la pena señalar que tal vez simplemente no sé cómo preparar CS correctamente y hay algunas formas más efectivas de trabajar con él, y espero que lo averigüemos en los comentarios. Pero primero lo primero.

Todas las pruebas se realizaron en un clúster de hardware que consta de 4 servidores, cada uno con la siguiente configuración:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 subprocesos.
Discos: 12 unidades SATA HDD
versión de java: 1.8.0_111

Versión CS: 3.11.5

Parámetros de cassandra.ymlnum_tokens: 256
hinded_handoff_enabled: verdadero
hinded_handoff_throttle_in_kb: 1024
max_hints_delivery_threads: 2
directorio_sugerencias: /data10/cassandra/sugerencias
tips_flush_period_in_ms: 10000
max_hints_file_size_in_mb: 128
lotelog_replay_throttle_in_kb: 1024
autenticador: AllowAllAuthenticator
autorizador: AllowAllAuthorizer
role_manager: CassandraRoleManager
roles_validity_in_ms: 2000
permisos_validez_en_ms: 2000
credenciales_validez_en_ms: 2000
particionador: org.apache.cassandra.dht.Murmur3Partitioner
directorios_archivos_datos:
- /data1/cassandra/data # cada directorio dataN es un disco separado
- /datos2/cassandra/datos
- /datos3/cassandra/datos
- /datos4/cassandra/datos
- /datos5/cassandra/datos
- /datos6/cassandra/datos
- /datos7/cassandra/datos
- /datos8/cassandra/datos
directorio_commitlog: /data9/cassandra/commitlog
cdc_enabled: falso
disk_failure_policy: detener
commit_failure_policy: detener
declaraciones_preparadas_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
directorio_caches_guardados: /data10/cassandra/caches_guardados
commitlog_sync: periódico
commitlog_sync_period_in_ms: 10000
compromisolog_segment_size_in_mb: 32
proveedor_semilla:
- nombre_clase: org.apache.cassandra.locator.SimpleSeedProvider
parámetros:
— semillas: "*,*"
concurrent_reads: 256 # probado 64 - no se notó ninguna diferencia
concurrent_writes: 256 # probado 64 - no se notó ninguna diferencia
concurrent_counter_writes: 256 # probado 64 - no se notó ninguna diferencia
concurrent_materialized_view_writes: 32
memtable_heap_space_in_mb: 2048 # probé 16 GB - era más lento
memtable_allocation_type: montón_buffers
index_summary_capacity_in_mb:
index_summary_resize_interval_in_minutos: 60
truco_fsync: falso
trickle_fsync_interval_in_kb: 10240
puerto_almacenamiento: 7000
puerto_almacenamiento_ssl: 7001
dirección_escucha: *
dirección de Difusión: *
listening_on_broadcast_address: verdadero
internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: verdadero
puerto_transporte_nativo: 9042
start_rpc: verdadero
dirección_rpc: *
puerto_rpc: 9160
rpc_keepalive: verdadero
rpc_server_type: sincronización
ahorro_framed_transport_size_in_mb: 15
copias de seguridad incrementales: falso
snapshot_before_compaction: falso
instantánea_automática: verdadero
column_index_size_in_kb: 64
column_index_cache_size_in_kb: 2
compactadores_concurrentes: 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: falso
endpoint_snitch: GossipingPropertyFileSnitch
Dynamic_snitch_update_interval_in_ms: 100
Dynamic_snitch_reset_interval_in_ms: 600000
umbral_de_maldad_dinámico: 0.1
request_scheduler: org.apache.cassandra.scheduler.NoScheduler
opciones_encriptación_servidor:
internode_encryption: ninguno
opciones_encriptación_cliente:
habilitado: falso
compresión_internodo: dc
inter_dc_tcp_nodelay: falso
tracetype_query_ttl: 86400
tracetype_repair_ttl: 604800
enable_user_defined_functions: falso
enable_scripted_user_defined_functions: falso
windows_timer_interval: 1
opciones_de_cifrado_de_datos_transparentes:
habilitado: falso
tombstone_warn_threshold: 1000
tombstone_failure_threshold: 100000
lote_size_warn_threshold_in_kb: 200
lote_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_pression_enabled: falso
enable_materialized_views: verdadero
enable_sasi_indexes: verdadero

Configuración del GC:

### Configuración del CMS-XX: + UseParNewGC
-XX: + UseConcMarkSweepGC
-XX: + CMSParallelRemarkEnabled
-XX:Proporción de Supervivientes=8
-XX:Umbral de tenencia máx.=1
-XX: CMSInitiatingOccupancyFraction = 75
-XX: + UseCMSInitiatingOccupancyOnly
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX: + CMSClassUnloadingEnabled

A la memoria jvm.options se le asignaron 16 Gb (también probamos 32 Gb, no se notó ninguna diferencia).

Las tablas fueron creadas con el comando:

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

Versión HB: 1.2.0-cdh5.14.2 (en la clase org.apache.hadoop.hbase.regionserver.HRegion excluimos MetricsRegion, lo que conducía a GC cuando el número de regiones era superior a 1000 en RegionServer)

Parámetros de HBase no predeterminadostiempo de espera de sesión.zookeeper: 120000
hbase.rpc.timeout: 2 minuto(s)
hbase.client.scanner.timeout.period: 2 minuto(s)
hbase.master.handler.recuento: 10
hbase.regionserver.lease.period, hbase.client.scanner.timeout.period: 2 minuto(s)
hbase.regionserver.handler.count: 160
hbase.regionserver.metahandler.count: 30
hbase.regionserver.logroll.period: 4 hora(s)
hbase.regionserver.maxlogs: 200
hbase.hregion.memstore.flush.tamaño: 1 GiB
hbase.hregion.memstore.block.multiplicador: 6
hbase.hstore.compactionUmbral: 5
hbase.hstore.blockingStoreFiles: 200
hbase.hregion.majorcompaction: 1 día(s)
Fragmento de configuración avanzada del servicio HBase (válvula de seguridad) para 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.compactation.max.size1073741824
hbase.server.compactchecker.interval.multiplier200
Opciones de configuración de Java para HBase RegionServer:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis: 2 minuto(s)
hbase.snapshot.region.timeout: 2 minuto(s)
hbase.snapshot.master.timeout.millis: 2 minuto(s)
Tamaño máximo de registro del servidor HBase REST: 100 MiB
Copias de seguridad máximas de archivos de registro del servidor HBase REST: 5
Tamaño máximo de registro del servidor HBase Thrift: 100 MiB
Copias de seguridad máximas de archivos de registro de HBase Thrift Server: 5
Tamaño máximo de registro maestro: 100 MiB
Copias de seguridad máximas de archivos de registro maestros: 5
Tamaño máximo de registro de RegionServer: 100 MiB
Copias de seguridad máximas de archivos de registro de RegionServer: 5
Ventana de detección de maestro activo de HBase: 4 minuto(s)
dfs.client.hedged.read.threadpool.tamaño: 40
dfs.client.hedged.read.threshold.millis: 10 milisegundos
hbase.rest.threads.min: 8
hbase.rest.threads.max: 150
Descriptores máximos de archivos de proceso: 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.tamaño: 20
Temas de movimiento de región: 6
Tamaño del montón de Java del cliente en bytes: 1 GiB
Grupo predeterminado del servidor HBase REST: 3 GiB
Grupo predeterminado del servidor HBase Thrift: 3 GiB
Tamaño del montón de Java de HBase Master en bytes: 16 GiB
Tamaño del montón de Java de HBase RegionServer en bytes: 32 GiB

+Guardián del zoológico
maxClientCnxns: 601
tiempo de espera máximo de sesión: 120000
Creando tablas:
hbase org.apache.hadoop.hbase.util.RegionSplitter ns:t1 UniformSplit -c 64 -f cf
alterar 'ns:t1', {NOMBRE => 'cf', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESIÓN => 'GZ'}

Aquí hay un punto importante: la descripción de DataStax no dice cuántas regiones se utilizaron para crear las tablas HB, aunque esto es fundamental para grandes volúmenes. Por lo tanto, para las pruebas se eligió cantidad = 64, que permite almacenar hasta 640 GB, es decir. mesa de tamaño mediano.

En el momento de la prueba, HBase tenía 22 mil tablas y 67 mil regiones (esto habría sido letal para la versión 1.2.0 si no fuera por el parche mencionado anteriormente).

Ahora el código. Como no estaba claro qué configuraciones eran más ventajosas para una base de datos concreta, se realizaron pruebas en varias combinaciones. Aquellos. en algunas pruebas, se cargaron 4 tablas simultáneamente (se utilizaron los 4 nodos para la conexión). En otras pruebas trabajamos con 8 mesas diferentes. En algunos casos, el tamaño del lote era 100, en otros 200 (parámetro de lote; consulte el código a continuación). El tamaño de datos del valor es 10 bytes o 100 bytes (tamaño de datos). En total, se escribieron y leyeron 5 millones de registros en cada tabla cada vez. Al mismo tiempo, se escribieron/leieron 5 subprocesos en cada tabla (número de subproceso - thNum), cada uno de los cuales utilizó su propio rango de claves (recuento = 1 millón):

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

En consecuencia, se proporcionó una funcionalidad similar para 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);
    }
}

Dado que en HB el cliente debe encargarse de la distribución uniforme de los datos, la función clave de salting se veía así:

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

Ahora la parte más interesante: los resultados:

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

Lo mismo en forma gráfica:

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank

La ventaja de HB es tan sorprendente que existe la sospecha de que existe algún tipo de cuello de botella en la configuración de CS. Sin embargo, buscar en Google los parámetros más obvios (como concurrent_writes o memtable_heap_space_in_mb) no aceleró las cosas. Al mismo tiempo, los troncos están limpios y no dicen malas palabras por nada.

Los datos se distribuyeron uniformemente entre los nodos y las estadísticas de todos los nodos fueron aproximadamente las mismas.

Así se ven las estadísticas de la tabla desde uno de los nodosEspacio de claves: ks
Número de lecturas: 9383707
Latencia de lectura: 0.04287025042448576 ms
Recuento de escrituras: 15462012
Latencia de escritura: 0.1350068438699957 ms
Flujos pendientes: 0
Tabla: t1
SSRecuento de tablas: 16
Espacio utilizado (en vivo): 148.59 MiB
Espacio utilizado (total): 148.59 MiB
Espacio utilizado por las instantáneas (total): 0 bytes
Memoria fuera del montón utilizada (total): 5.17 MiB
Relación de compresión SSTable: 0.5720989576459437
Número de particiones (estimado): 3970323
Recuento de células memtables: 0
Tamaño de datos memtable: 0 bytes
Memoria memtable fuera del montón utilizada: 0 bytes
Número de interruptores memtables: 5
Recuento de lecturas locales: 2346045
Latencia de lectura local: NaN ms
Recuento de escritura local: 3865503
Latencia de escritura local: NaN ms
Flujos pendientes: 0
Porcentaje reparado: 0.0
Falsos positivos del filtro Bloom: 25
Proporción falsa del filtro Bloom: 0.00000
Espacio de filtro Bloom utilizado: 4.57 MiB
Filtro Bloom fuera de la memoria del montón utilizada: 4.57 MiB
Resumen del índice de la memoria del montón utilizada: 590.02 KiB
Metadatos de compresión de la memoria dinámica utilizada: 19.45 KiB
Bytes mínimos de partición compactada: 36
Bytes máximos de partición compactada: 42
Bytes medios de partición compactada: 42
Promedio de células vivas por corte (últimos cinco minutos): NaN
Máximo de células vivas por corte (últimos cinco minutos): 0
Promedio de lápidas por segmento (últimos cinco minutos): NaN
Lápidas máximas por segmento (últimos cinco minutos): 0
Mutaciones eliminadas: 0 bytes

Un intento de reducir el tamaño del lote (incluso enviarlo individualmente) no tuvo ningún efecto, solo empeoró. Es posible que, de hecho, este sea realmente el rendimiento máximo para CS, ya que los resultados obtenidos para CS son similares a los obtenidos para DataStax: alrededor de cientos de miles de operaciones por segundo. Además, si nos fijamos en la utilización de recursos, veremos que CS utiliza mucha más CPU y discos:

Batalla de dos yakozuna, o Cassandra vs HBase. Experiencia del equipo de Sberbank
La figura muestra la utilización durante la ejecución de todas las pruebas seguidas para ambas bases de datos.

Respecto a la poderosa ventaja de lectura de HB. Aquí puede ver que para ambas bases de datos, la utilización del disco durante la lectura es extremadamente baja (las pruebas de lectura son la parte final del ciclo de prueba para cada base de datos; por ejemplo, para CS esto es de 15:20 a 15:40). En el caso de HB, la razón es clara: la mayoría de los datos se cuelgan en la memoria, en el memstore, y algunos se almacenan en caché en blockcache. En cuanto a CS, no está muy claro cómo funciona, pero el reciclaje de disco tampoco es visible, pero por si acaso, se intentó habilitar el caché row_cache_size_in_mb = 2048 y configurar el almacenamiento en caché = {'keys': 'ALL', 'rows_per_partition': ' 2000000'}, pero eso lo empeoró un poco.

También vale la pena mencionar una vez más un punto importante sobre el número de regiones en HB. En nuestro caso, el valor se especificó como 64. Si lo reducimos y lo hacemos igual a, por ejemplo, 4, al leer, la velocidad se reduce 2 veces. La razón es que el memstore se llenará más rápido y los archivos se vaciarán con más frecuencia y, al leer, será necesario procesar más archivos, lo cual es una operación bastante complicada para HB. En condiciones reales, esto se puede tratar pensando en una estrategia de predivisión y compactación; en particular, utilizamos una utilidad autoescrita que recolecta basura y comprime HFiles constantemente en segundo plano. Es muy posible que para las pruebas de DataStax hayan asignado solo 1 región por tabla (lo cual no es correcto) y esto aclararía de alguna manera por qué HB fue tan inferior en sus pruebas de lectura.

De ello se extraen las siguientes conclusiones preliminares. Suponiendo que no se cometieron errores importantes durante las pruebas, Cassandra parece un coloso con pies de barro. Más precisamente, mientras se mantiene en equilibrio sobre una pierna, como en la imagen al principio del artículo, muestra resultados relativamente buenos, pero en una pelea en las mismas condiciones pierde rotundamente. Al mismo tiempo, teniendo en cuenta el bajo uso de CPU de nuestro hardware, aprendimos a instalar dos RegionServer HB por host y así duplicamos el rendimiento. Aquellos. Teniendo en cuenta la utilización de recursos, la situación de CS es aún más deplorable.

Por supuesto, estas pruebas son bastante sintéticas y la cantidad de datos que se utilizaron aquí es relativamente modesta. Es posible que si cambiáramos a terabytes, la situación sería diferente, pero mientras que para HB podemos cargar terabytes, para CS esto resultó problemático. A menudo arrojaba una excepción OperationTimedOutException incluso con estos volúmenes, aunque los parámetros para esperar una respuesta ya estaban aumentados varias veces en comparación con los predeterminados.

Espero que a través de esfuerzos conjuntos encontremos los cuellos de botella de CS y si podemos acelerarlo, al final de la publicación definitivamente agregaré información sobre los resultados finales.

UPD: Gracias a los consejos de compañeros logré agilizar la lectura. Era:
159 operaciones (644 mesas, 4 transmisiones, lote 5).
Agregado por:
.withLoadBalancingPolicy(nueva TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
Y jugué con la cantidad de hilos. El resultado es el siguiente:
4 mesas, 100 hilos, lote = 1 (pieza por pieza): 301 operaciones
4 tablas, 100 subprocesos, lote = 10: 447 operaciones
4 tablas, 100 subprocesos, lote = 100: 625 operaciones

Más adelante aplicaré otros consejos de ajuste, ejecutaré un ciclo de prueba completo y agregaré los resultados al final de la publicación.

Fuente: habr.com

Añadir un comentario