Rendimiento de importación de InnoDB

10

Estoy luchando con la importación masiva de una tabla InnoDB bastante grande que consta de aproximadamente 10 millones de filas (o 7 GB) (que para mí es la tabla más grande con la que he trabajado hasta ahora).

Investigué un poco sobre cómo mejorar la velocidad de importación de Inno y por el momento mi configuración se ve así:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

Los datos se proporcionan en un CSVarchivo.
Actualmente pruebo mi configuración con 'volcados de prueba' más pequeños con 2 millones, 3 millones, ... filas cada uno y uso time import_script.shpara comparar el rendimiento.

El inconveniente es que solo obtengo un tiempo de ejecución general, así que tengo que esperar a que termine la importación completa para obtener un resultado.

Mis resultados hasta ahora:

  • 10 000 filas: <1 segundo
  • 100 000 filas: 10 segundos
  • 300 000 filas: 40 segundos
  • 2 millones de filas: 18 minutos
  • 3 millones de filas: 26 minutos
  • 4 millones de filas: (cancelado después de 2 horas)

Parece que no hay una solución de 'libro de cocina' y uno tiene que descubrir la combinación óptima de configuraciones por su cuenta.
Además de las sugerencias sobre qué cambiar en mi configuración, también agradecería más información sobre cómo podría comparar mejor el proceso de importación / obtener más información sobre lo que está sucediendo y dónde podría estar el cuello de botella.
Traté de leer la documentación de la configuración que estoy cambiando, pero, una vez más, no estoy al tanto de ningún efecto secundario y si incluso podría disminuir el rendimiento con un valor mal elegido.

Por el momento, me gustaría probar una sugerencia de chat para usar MyISAMdurante la importación y cambiar el motor de la tabla después.
Me gustaría probar esto, pero por el momento mi DROP TABLEconsulta también tarda horas en terminar. (Lo que parece otro indicador de que mi configuración es menos que óptima).

Información adicional:
La máquina que estoy usando actualmente tiene 8 GB de RAM y un disco duro híbrido de estado sólido con 5400 RPM.
Si bien también apuntamos a eliminar datos obsoletos de la tabla en cuestión, todavía necesito una importación algo rápida para
a) probar automatic data cleanup featuredurante el desarrollo
yb) en caso de que nuestro servidor se bloquee, nos gustaría usar nuestro segundo servidor como reemplazo (que necesita datos actualizados, la última importación tardó más de 24 horas)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8
nuala
fuente
2
¿Intentó con importaciones menos grandes, como filas de 10K o 100K?
ypercubeᵀᴹ
1
Ejecute SHOW CREATE TABLE yourtable\Gpara mostrarnos la estructura de la tabla de esta tabla de 10 millones de filas.
RolandoMySQLDBA
@RolandoMySQLDBA, así que lo hice (con nombres de campo oscurecidos)
nuala
Al deshabilitar el búfer de doble escritura ( innodb_doublewrite = 0), su instalación de MySQL no es segura: si tiene una falla de energía (no un bloqueo de MySQL), sus datos podrían corromperse silenciosamente.
jfg956

Respuestas:

13

Primero, necesita saber qué le está haciendo a InnoDB cuando coloca millones de filas en una tabla de InnoDB. Echemos un vistazo a la arquitectura InnoDB.

Arquitectura InnoDB

En la esquina superior izquierda, hay una ilustración del InnoDB Buffer Pool. Observe que hay una sección dedicada al búfer de inserción. Que hace eso Está diseñado para migrar los cambios a los índices secundarios del grupo de búferes al búfer de inserción dentro del espacio de tabla del sistema (también conocido como ibdata1). De forma predeterminada, innodb_change_buffer_max_size se establece en 25. Esto significa que se puede utilizar hasta el 25% del grupo de búferes para procesar índices secundarios.

En su caso, tiene 6.935 GB para el InnoDB Buffer Pool. Se utilizará un máximo de 1.734 GB para procesar sus índices secundarios.

Ahora mira tu mesa. Tienes 13 índices secundarios. Cada fila que procese debe generar una entrada de índice secundaria, acoplarla con la clave primaria de la fila y enviarla como un par desde Insert Buffer en el Buffer Pool al Insert Buffer en ibdata1. Eso sucede 13 veces con cada fila. Multiplique esto por 10 millones y casi puede sentir un cuello de botella.

No olvide que la importación de 10 millones de filas en una sola transacción agrupará todo en un segmento de reversión y llenará el espacio UNDO en ibdata1.

SUGERENCIAS

SUGERENCIA # 1

Mi primera sugerencia para importar esta tabla bastante grande sería

  • Descarte todos los índices no únicos
  • Importar los datos
  • Crea todos los índices no únicos

SUGERENCIA # 2

Deshágase de los índices duplicados. En tu caso, tienes

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

Ambos índices comienzan con party_id, puede aumentar el procesamiento del índice secundario en al menos un 7,6%, eliminando un índice de 13. Es necesario ejecutar eventualmente

ALTER TABLE monster DROP INDEX party_id;

SUGERENCIA # 3

Deshágase de los índices que no usa. Revise el código de su aplicación y vea si sus consultas usan todos los índices. Es posible que desee ver el uso de índice-pt para permitirle sugerir qué índices no se están utilizando.

SUGERENCIA # 4

Debe aumentar innodb_log_buffer_size a 64M ya que el valor predeterminado es 8M. Un búfer de registro más grande puede aumentar el rendimiento de E / S de escritura de InnoDB.

EPÍLOGO

Poniendo las dos primeras sugerencias en su lugar, haga lo siguiente:

  • Descarte los 13 índices no únicos
  • Importar los datos
  • Crear todos los índices no únicos, excepto el party_idíndice.

Quizás lo siguiente pueda ayudar

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

Importar los datos a monster. Entonces, ejecuta esto

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

DARLE UNA OPORTUNIDAD !!!

ALTERNATIVA

Puede crear una tabla llamada monster_csvcomo tabla MyISAM sin índices y hacer esto:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

Importa tus datos a monster_csv. Luego, use mysqldump para crear otra importación

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

El archivo mysqldump data.sqlextenderá los comandos INSERT para importar 10,000-20,000 filas a la vez.

Ahora, solo carga el mysqldump

mysql -uroot -p mydb < data.sql

Finalmente, deshazte de la tabla MyISAM

DROP TABLE monster_csv;
RolandoMySQLDBA
fuente
Ni siquiera estaba al tanto de todas esas claves (no es mi diseño), pero su explicación parece muy convincente. Por hoy es tarde para comenzar otro intento, pero veo algunos buenos consejos sobre qué probar mañana. ¡Te mantendremos informado! <3
nuala
1
Logré importar la base de datos completa (no solo la monstertabla) en menos de 20 minutos cuando no tenía claves en las tablas de InnoDB. Agregar llaves tomó aprox. otros 20 min. Yo diría que esto resuelve mi problema en este caso. ¡Muchas gracias!
nuala
8

Quería escribir un comentario (ya que esta no es una respuesta definitiva), pero se hizo demasiado largo:

Voy a darle varios consejos generales, y podemos entrar en detalles para cada uno, si lo desea:

  • Reduce la durabilidad (ya has hecho algo de eso). Las últimas versiones permiten incluso hacerlo más. Puede ir tan lejos como deshabilitar el búfer de doble escritura, ya que la corrupción no es un problema para las importaciones.
  • Aumente el almacenamiento en búfer: aumente el tamaño del registro de transacciones y aumente el tamaño de la agrupación de almacenamientos intermedios disponible. Monitoree el uso del archivo de registro de transacciones y los puntos de control. No temas a los registros enormes por una importación.
  • Evite grandes transacciones: su reversión estará llena de datos innecesarios. Este es probablemente tu mayor problema.
  • SQL será un cuello de botella, evite la sobrecarga de SQL (handlersocket, memcached) y / o cárguelo en concurrencia con varios hilos al mismo tiempo. La concurrencia tiene que llegar a un punto óptimo, ni demasiado ni muy poco.
  • Cargar datos en la fragmentación de orden de clave primaria puede ser una isse
  • Pruebe la compresión InnoDB si IO es su cuello de botella y la CPU y la memoria no lo hacen más lento
  • Intente crear sus claves secundarias después (más rápido en algunos casos), no cargue datos indexados: DISABLE KEYS no afecta a InnoDB . De lo contrario, controle su búfer de inserción (quizás superando la mitad de su grupo de búferes).
  • Cambiar o deshabilitar el algoritmo de suma de comprobación: probablemente no sea su problema, pero se convierte en un cuello de botella en las tarjetas flash de gama alta.
  • Último recurso: supervise su servidor para encontrar su cuello de botella actual e intente mitigarlo (InnoDB es muy flexible al respecto).

Recuerde que algunos de estos no son seguros o recomendables para las no importaciones (operación normal).

jynus
fuente
¡Muchas gracias! Me gusta probar primero la idea de Rolando con respecto a los índices, pero supongo que este tema de "reversión de transacciones" seguirá siendo un problema. ¿Podrías dar más detalles sobre esto? Creo que quiero deshabilitar la mayor cantidad de esta funcionalidad posible durante la importación y volver a habilitarla al entrar en producción ~ Creo ...
nuala
1
La sugerencia de Rolando es mi punto # 7. Evitar la retrotracción general es tan fácil como una combinación de SET SESSION tx_isolation='READ-UNCOMMITTED';(solo útil si importa con varios hilos en paralelo) y el comentario de @ypercube sobre la inserción en lotes. Tiene un ejemplo completo aquí: mysqlperformanceblog.com/2008/07/03/… Asegúrese de aprovechar todas las funciones de las últimas versiones de InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus
1
Tenía la impresión general de que uno evitaría importar en platos más pequeños, sino que optaría por una operación "todo incluido", pero veo que el subprocesamiento múltiple podría abrir algunas posibilidades. Supongo que es muy específico para cada caso. Sin embargo, acepté la respuesta de Rolando ya que este ajuste (su # 7) solo me ayudó a obtener la importación completa en <1 hora, pero su lista definitivamente no tiene ningún valor y supongo que la usará como referencia muy pronto, ya que la tasa de nuestro DB está creciendo un poco me asusta :)
nuala
Estoy de acuerdo con @yoshi. Su respuesta es más completa en términos de solución de problemas y mejoras de rendimiento. +1
RolandoMySQLDBA
3

La mayoría de los buenos consejos se han dado hasta ahora, pero sin muchas explicaciones para los mejores. Daré más detalles.

Primero, retrasar la creación del índice es bueno, con suficientes detalles en otras respuestas. No volveré sobre eso.

Un archivo de registro InnoDB más grande lo ayudará mucho (si está usando MySQL 5.6, ya que no es posible aumentarlo en MySQL 5.5). Está insertando 7 GB de datos, recomendaría un tamaño de registro total de al menos 8 GB (mantenga innodb_log_files_in_groupsu valor predeterminado (2) y aumente innodb_log_file_sizea 4 GB). Estos 8 GB no son exactos: debe tener al menos el tamaño de importación en el registro REDO y probablemente duplicar o cuadruplicar ese tamaño. El razonamiento detrás del tamaño del registro de InnoDB aumenta que cuando el registro esté casi lleno, InnoDB comenzará a vaciar agresivamente su grupo de búferes al disco para evitar que el registro se llene (cuando el registro está lleno, InnoDB no puede escribir ninguna base de datos hasta que algunos las páginas del grupo de búferes se escriben en el disco).

Un archivo de registro InnoDB más grande lo ayudará, pero también debe insertarlo en el orden de las claves principales (ordene el archivo antes de insertarlo). Si inserta en orden de clave principal, InnoDB llenará una página, y luego otra, y así sucesivamente. Si no inserta en el orden de la clave principal, su próxima inserción podría terminar en una página que está llena e incurrirá en una "división de página". Esta división de página será costosa para InnoDB y ralentizará su importación.

Ya tiene un grupo de búferes tan grande como su RAM le permite y si su tabla no cabe, no hay mucho que pueda hacer, excepto comprar más RAM. Pero si su tabla cabe en el grupo de búferes, pero es mayor que el 75% de su grupo de búferes, puede intentar aumentar innodb_max_dirty_pages_pcta 85 o 95 durante la importación (el valor predeterminado es 75). Este parámetro de configuración le dice a InnoDB que comience a vaciar agresivamente el grupo de búferes cuando el porcentaje de páginas sucias alcanza este límite. Al aumentar este parámetro (y si tiene suerte con el tamaño de los datos), puede evitar las E / S agresivas durante la importación y retrasar esas E / S para más adelante.

Tal vez (y esto es una suposición) importar sus datos en muchas transacciones pequeñas lo ayudará. No sé exactamente cómo se construye el registro REDO, pero si está almacenado en la memoria RAM (y en el disco cuando se necesitaría demasiada RAM) mientras la transacción está progresando, podría terminar con IO innecesarias. Puede intentar esto: una vez que su archivo esté ordenado, divídalo en muchos fragmentos (intente con 16 MB y otros tamaños) e impórtelos uno por uno. Esto también le permitiría controlar el progreso de su importación. Si no desea que sus datos sean parcialmente visibles para otro lector mientras realiza la importación, puede importar utilizando un nombre de tabla diferente, crear los índices más tarde y luego cambiar el nombre de la tabla.

Acerca de su disco híbrido SSD / 5400RPM, no sé acerca de ellos y cómo optimizar esto. 5400RPM parece lento para una base de datos, pero tal vez el SSD lo está evitando. Tal vez esté llenando la parte SSD de su disco con escrituras secuenciales en el registro REDO y la SSD está afectando el rendimiento. No lo sé.

Un mal consejo que no debe probar (o tener cuidado con) es el siguiente: no use multiproceso: será muy difícil de optimizar para evitar divisiones de página en InnoDB. Si desea utilizar varios subprocesos, inserte en diferentes tablas (o en diferentes particiones de la misma tabla).

Si está considerando varios subprocesos, tal vez tenga una computadora con varios zócalos (NUMA). En este caso, asegúrese de evitar el problema de locura de intercambio de MySQL .

Si está utilizando MySQL 5.5, actualice a MySQL 5.6: tiene la opción de aumentar el tamaño del registro REDO y tiene mejores algoritmos de descarga de agrupación de almacenamiento intermedio.

Buena suerte con tu importación.

jfg956
fuente