Construyendo una tabla MySQL de 1,000M de filas

18

Esta pregunta se vuelve a publicar desde Stack Overflow en función de una sugerencia en los comentarios, disculpas por la duplicación.

Preguntas

Pregunta 1: a medida que aumenta el tamaño de la tabla de la base de datos, ¿cómo puedo ajustar MySQL para aumentar la velocidad de la llamada LOAD DATA INFILE?

Pregunta 2: ¿usaría un grupo de computadoras para cargar diferentes archivos csv, mejorar el rendimiento o matarlo? (esta es mi tarea de referencia para mañana usando los datos de carga y las inserciones masivas)

Objetivo

Estamos probando diferentes combinaciones de detectores de características y parámetros de agrupación para la búsqueda de imágenes, como resultado, necesitamos poder construir y grandes bases de datos de manera oportuna.

Información de la máquina

La máquina tiene 256 gig de ram y hay otras 2 máquinas disponibles con la misma cantidad de ram si hay una manera de mejorar el tiempo de creación mediante la distribución de la base de datos.

Esquema de tabla

el esquema de la tabla se parece

+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| match_index   | int(10) unsigned | NO   | PRI | NULL    |                |
| cluster_index | int(10) unsigned | NO   | PRI | NULL    |                |
| id            | int(11)          | NO   | PRI | NULL    | auto_increment |
| tfidf         | float            | NO   |     | 0       |                |
+---------------+------------------+------+-----+---------+----------------+

creado con

CREATE TABLE test 
(
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL AUTO_INCREMENT,
  tfidf FLOAT NOT NULL DEFAULT 0,
  UNIQUE KEY (id),
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

Benchmarking hasta ahora

El primer paso fue comparar las inserciones en masa frente a la carga de un archivo binario en una tabla vacía.

It took:  0:09:12.394571  to do  4,000  inserts with 5,000 rows per insert
It took: 0:03:11.368320 seconds to load 20,000,000 rows from a csv file

Dada la diferencia en el rendimiento que utilicé al cargar los datos de un archivo csv binario, primero cargué archivos binarios que contenían filas de 100K, 1M, 20M, 200M usando la siguiente llamada.

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test;

Eliminé la carga del archivo binario de 200M filas (archivo csv ~ 3GB) después de 2 horas.

Entonces ejecuté un script para crear la tabla, e inserto diferentes números de filas de un archivo binario y luego dejo caer la tabla, vea el gráfico a continuación.

ingrese la descripción de la imagen aquí

Tomó aproximadamente 7 segundos insertar 1M filas del archivo binario. A continuación, decidí hacer un benchmark insertando filas de 1M a la vez para ver si habría un cuello de botella en un tamaño de base de datos particular. Una vez que la base de datos alcanzó aproximadamente 59 millones de filas, el tiempo promedio de inserción se redujo a aproximadamente 5,000 / segundo

ingrese la descripción de la imagen aquí

Establecer global key_buffer_size = 4294967296 mejoró ligeramente las velocidades para insertar archivos binarios más pequeños. El siguiente gráfico muestra las velocidades para diferentes números de filas

ingrese la descripción de la imagen aquí

Sin embargo, para insertar filas de 1M no mejoró el rendimiento.

filas: 1,000,000 de tiempo: 0: 04: 13.761428 insertos / seg: 3,940

vs para una base de datos vacía

filas: 1,000,000 de tiempo: 0: 00: 6.339295 insertos / seg: 315,492

Actualizar

Hacer los datos de carga usando la siguiente secuencia versus simplemente usando el comando cargar datos

SET autocommit=0;
SET foreign_key_checks=0;
SET unique_checks=0;
LOAD DATA INFILE '/mnt/imagesearch/tests/eggs.csv' INTO TABLE test_ClusterMatches;
SET foreign_key_checks=1;
SET unique_checks=1;
COMMIT;
ingrese la descripción de la imagen aquí

Por lo tanto, esto parece bastante prometedor en términos del tamaño de la base de datos que se está generando, pero las otras configuraciones no parecen afectar el rendimiento de la llamada de archivo de datos de carga.

Luego intenté cargar varios archivos desde diferentes máquinas, pero el comando de carga de archivos de datos bloquea la tabla, debido al gran tamaño de los archivos que hace que las otras máquinas agoten el tiempo de espera.

ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction

Aumentar el número de filas en archivo binario

rows:  10,000,000  seconds rows:  0:01:36.545094  inserts/sec:  103578.541236
rows:  20,000,000  seconds rows:  0:03:14.230782  inserts/sec:  102970.29026
rows:  30,000,000  seconds rows:  0:05:07.792266  inserts/sec:  97468.3359978
rows:  40,000,000  seconds rows:  0:06:53.465898  inserts/sec:  96743.1659866
rows:  50,000,000  seconds rows:  0:08:48.721011  inserts/sec:  94567.8324859
rows:  60,000,000  seconds rows:  0:10:32.888930  inserts/sec:  94803.3646283

Solución: precomputar la identificación fuera de MySQL en lugar de usar el incremento automático

Construyendo la mesa con

CREATE TABLE test (
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL ,
  tfidf FLOAT NOT NULL DEFAULT 0,
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

con el SQL

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';"

ingrese la descripción de la imagen aquí

Hacer que el script precalcule los índices parece haber eliminado el impacto en el rendimiento a medida que la base de datos crece en tamaño.

Actualización 2 - usando tablas de memoria

Aproximadamente 3 veces más rápido, sin tener en cuenta el costo de mover una tabla en memoria a una tabla basada en disco.

rows:  0  seconds rows:  0:00:26.661321  inserts/sec:  375075.18851
rows:  10000000  time:  0:00:32.765095  inserts/sec:  305202.83857
rows:  20000000  time:  0:00:38.937946  inserts/sec:  256818.888187
rows:  30000000  time:  0:00:35.170084  inserts/sec:  284332.559456
rows:  40000000  time:  0:00:33.371274  inserts/sec:  299658.922222
rows:  50000000  time:  0:00:39.396904  inserts/sec:  253827.051994
rows:  60000000  time:  0:00:37.719409  inserts/sec:  265115.500617
rows:  70000000  time:  0:00:32.993904  inserts/sec:  303086.291334
rows:  80000000  time:  0:00:33.818471  inserts/sec:  295696.396209
rows:  90000000  time:  0:00:33.534934  inserts/sec:  298196.501594

cargando los datos en una tabla basada en memoria y luego copiándolos en una tabla basada en disco en fragmentos tuvo una sobrecarga de 10 min 59.71 segundos para copiar 107,356,741 filas con la consulta

insert into test Select * from test2;

lo que hace que sea aproximadamente 15 minutos cargar filas de 100M, que es aproximadamente lo mismo que insertarlo directamente en una tabla basada en disco.

Ben
fuente
1
Creo que cambiar la clave principal a solo iddebería ser más rápido. (Aunque creo que no estás buscando esto)
DavidEG
Hola David, gracias por el comentario, desafortunadamente sin la clave, las consultas que necesitamos hacer no son lo suficientemente rápidas (la lógica detrás de la selección de la clave principal se describe en esta publicación stackoverflow.com/questions/4282526/mysql-group-by- optimización )
Ben
1
¿Esto es solo para probar? Es posible que desee ver el motor de MEMORIA MySQL: dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html. Si planea implementar esto como una arquitectura, tengo curiosidad por cómo planea recuperarse de fallas, parece algo que MapReduce / Hadoop manejaría mejor.
polinomio
Hola polinomio, gracias por la sugerencia, en este momento solo estamos probando diferentes detectores de características a diferentes escalas, una vez que se genera la base de datos, no cambiará mucho (de todos modos, en la especificación actual)
Ben

Respuestas:

4

Buena pregunta, bien explicada.

¿Cómo puedo ajustar MySQL para aumentar la velocidad de la llamada LOAD DATA INFILE?

Ya tiene una configuración alta (ish) para el búfer de claves, pero ¿es suficiente? Supongo que se trata de una instalación de 64 bits (si no, lo primero que debe hacer es actualizar) y no ejecutarse en MSNT. Eche un vistazo a la salida de mysqltuner.pl después de ejecutar algunas pruebas.

Para utilizar el caché para obtener el mejor efecto, puede encontrar beneficios al agrupar / ordenar previamente los datos de entrada (las versiones más recientes del comando 'ordenar' tienen mucha funcionalidad para ordenar grandes conjuntos de datos). Además, si genera los números de identificación fuera de MySQL, entonces puede ser más eficiente.

usaría un grupo de computadoras para cargar diferentes archivos csv

Suponiendo (nuevamente) que desea que el conjunto de salida se comporte como una sola tabla, entonces los únicos beneficios que obtendrá serán al distribuir el trabajo de ordenar y generar identificadores, para lo cual no necesita más bases de datos. OTOH usando un clúster de base de datos, obtendrá problemas con la contención (que no debería ver más que como problemas de rendimiento).

Si puede fragmentar los datos y manejar los conjuntos de datos resultantes de forma independiente, entonces sí, obtendrá beneficios de rendimiento, pero esto no elimina la necesidad de ajustar cada nodo.

Comprueba que tienes al menos 4 Gb para sort_buffer_size.

Más allá de eso, el factor limitante en el rendimiento se trata de E / S de disco. Hay muchas maneras de abordar esto, pero probablemente debería considerar un conjunto reflejado de conjuntos de datos seccionados en SSD para un rendimiento óptimo.

symcbean
fuente
1
  • Considera tu factor limitante. Es casi seguro que es un procesamiento de CPU de un solo subproceso.
  • Ya has determinado que load data...es más rápido que insertar, así que úsalo.
  • Ya ha determinado que los archivos realmente grandes (por número de fila) ralentizan mucho las cosas; quieres romperlos en pedazos.
  • Usando claves primarias no superpuestas, ponga en cola al menos N * conjuntos de CPU, utilizando no más de un millón de filas ... probablemente menos (punto de referencia).
  • Use bloques secuenciales de claves primarias en cada archivo.

Si desea ser realmente elegante, puede crear un programa multiproceso para alimentar un solo archivo a una colección de canalizaciones con nombre y administrar las instancias de inserción.

En resumen, no ajusta MySQL para esto tanto como ajusta su carga de trabajo a MySQL.

Jeff Ferland
fuente
-1

No recuerdo exactamente la sintaxis, pero si es inob, puede desactivar la verificación de clave externa.

También puede crear el índice después de la importación, puede ser realmente una ganancia de rendimiento.

Julien Duponchelle
fuente
Aplazar la reconstrucción del índice solo mejora el rendimiento cuando el número de filas que ya están en la tabla es significativamente menor que el número de filas que está agregando.
symcbean