2011-02-25 13 views
8

Sé que esta pregunta se ha formulado una y otra vez. Sin embargo, esta es una pregunta muy específica para un escenario muy específico. Espero que puedas ayudarme.Ajuste específico del rendimiento de inserción masiva de MySQL

Ejecuto una base de datos de registro, con aproximadamente 10 tablas. La tabla principal que almacena las entradas de registro reales tiene unos 30 campos, de los cuales 5 se pueden buscar. Diría que la base de datos se ha convertido recientemente en un tamaño moderado, ya que estamos alcanzando los 200 millones de entradas en esa tabla. Otras tablas almacenan datos comunes, de los cuales el más grande tiene 4 campos, todos buscables, con casi 1 millón de entradas. Todas las demás tablas contienen menos de 100 mil registros cada una.

Los insertos vienen en espigas. Obtuve los registros del día anterior en archivos csv (con un formato mediocre) todos los días a las 2 AM, y tengo hasta las 8 AM para insertarlos (alrededor de 20 archivos, 100 mil líneas cada uno) en la base de datos. Luego recibo muy pocas selecciones (tal vez unas 1000 por día) durante el día laboral. Luego enjuague y repita.

Las consultas SELECT son bastante simples, ya que consisten principalmente en una o dos uniones con una o dos instrucciones GROUP BY. Las personas que buscan en esta base de datos quieren resultados inmediatos, por lo que tengo 5 índices de varias columnas en la tabla principal, que ayudan en las búsquedas precisas que tengo, y actualmente, el rendimiento SELECCIONAR es bastante bueno. Ninguna consulta ha tomado más de 0.1 segundos hasta el momento. Hay algunos informes, pero estos demoran alrededor de 10 segundos en generarse, y eso es aceptable.

Actualmente tengo un programa C que escribí para leer los datos de los archivos CSV, limpiarlos e insertarlos en lotes de 1000 filas por consulta INSERT. Estos INSERT no son del todo tontos, porque necesito obtener los datos comunes, ver si ya están en las otras tablas, insertarlo si no es así, y guardarlo en caché, si es así. También me da datos de rendimiento en forma de cuántos registros está insertando por segundo. Este programa es bastante rápido, y sin enviar los datos a la base de datos, obtengo alrededor de 100 mil filas por segundo. Por supuesto, este programa y la base de datos se encuentran en la misma computadora física.

Ahora, los datos que obtengo todos los días están creciendo linealmente, y el rendimiento de los INSERT está disminuyendo logarítmicamente. Los datos de ayer tardaron 5 horas y media en insertarse, en alrededor de 400 inserciones de fila por segundo.

Tengo algunos datos de referencia mediante la inserción de los primeros 1 millón de filas con diferentes configuraciones en una base de datos vacía, y esto es más o menos lo que tengo:

tablas MyISAM: pone en marcha en 1500 filas por segundo, disminuye logarítmicamente abajo a aproximadamente 700 filas por segundo para el momento en que inserta la fila 1 millonésima Tablas InnoDB: igual que MyISAM, solo alrededor de 100 filas por segundo más rápido InnoDB con todos los índices desactivados en la tabla principal: comienza en 2100 filas por segundo, disminuye a 1000 filas por segundo. InnoDB Con índices, con el sistema de archivos montado con reescritura de datos (ext3): igual que InnoDB, solo ligeramente pero casi imperceptiblemente más rápido.

innodb_buffer_pool_size se establece en 1000 MB

Evitar la creación del índice no es una opción, pero es obvio que tiene un gran impacto en el rendimiento. Sin embargo, necesito insertos mucho más rápidos. Como muestran los datos, las inserciones tardarán más a medida que la base de datos crezca, así que a medida que los datos que obtengo sean más grandes cada día, necesito un gran salto en el rendimiento de las inserciones. Si pudiera obtener 10000 inserciones por segundo o más, sería realmente genial.

El monitor del sistema me dice que mi principal consumo de recursos es la E/S de disco, que casi llega al 100% al insertar. Por eso, necesito una forma ultra rápida de insertar datos. Mi límite teórico es el del autobús SATA, pero aún está bastante lejos.El uso de memoria no parece ser tan alto en un 20% (o MySQL no está utilizando la memoria correctamente)

Para lograr esto, es aceptable volver a crear la base de datos en el transcurso de varios días, y luego intercambiar en caliente desde la aplicación del lector, es aceptable cambiar cualquier configuración en el sistema operativo y MySQL, es aceptable agregar memoria si es necesario. Incluso es aceptable cambiar la estructura de la base de datos, si es necesario.

Así que estoy realmente abierto a las ideas aquí. ¿Alguien sabe de algo que pueda ayudarme?

Edición: Actualmente estoy considerando insertar las nuevas filas en una tabla de MEMORIA, y luego hacer una SELECCIÓN EN la tabla real. Con suerte, solo actualizará y purgará el índice una vez que se hayan insertado todas las filas. Voy a probar esto el lunes. ¿Alguien ha intentado algo así antes?

+0

http://stackoverflow.com/q/18033060/632951? – Pacerier

Respuesta

3

2 millones de filas en 6,5 horas?
¿Qué tan grande es el conjunto de datos que está almacenando?

utilizo el siguiente cálculo de back-of-the-sobre para llegar a una cifra algo útil:
Suponiendo 1 solo disco de mierda que se traga 35 MB por segundo, debe ser capaz de escribir (35 * 6,5 * 3600) = ~ 800 gb en ese marco de tiempo. Calculando hacia atrás (800 gb/2 mrows), da un tamaño de fila promedio de 400 kb.

Si esos números parecen correctos, necesita reforzar su hardware para aumentar la velocidad. Si están completamente desactivados, es probable que haya otro problema.

Además, eche un vistazo a comparisons of disk i/o for a dedicated MySQL server en ServerFault, para una forma de medir E/S.

Aquí están algunas sugerencias al azar (en caso de sospecha de algún otro problema)

  • Asegúrese de eliminar todos operaciones de fila por fila en su proceso de carga
  • Si la mayoría de los datos CSV Al final se almacenan, considere bulk loading en tablas intermedias y procese los datos dentro de la base de datos utilizando el procesamiento basado en conjuntos.
  • Si la mayoría de los datos se descarta, considere mover/almacenamiento en caché de las tablas de referencia fuera de la base de datos para ser capaz de filtrar los datos CSV en su código C
  • MySQL no tienen hash, pero dependen de indexado bucles. Asegúrese de que esas otras tablas tengan índices aproximados
  • Pruebe ordenar previamente los datos fuera de la base de datos para que coincidan con el índice de otra tabla utilizada en el proceso (para aumentar la probabilidad de que los datos relevantes no se eliminen de la memoria caché)
  • Lea en partitioning, y vea si puede reemplazar algunos de sus índices con un esquema de particionamiento inteligente en lugar de mantener todos esos índices.

Editado
cálculo corregido (400kb)

+0

800 gb/2m filas da un promedio de 400 Kb por fila, no 400 bytes. Como mis filas tienen una longitud aproximada de 400 bytes, con copia en bruto en el disco que describes, debería poder escribir 1000 veces más rápido que lo que estoy obteniendo ahora, así que en lugar de la velocidad del disco, mi problema es que MySQL está dando vueltas 1000 bytes de E/S por cada byte que escribo, y esto es lo que necesito resolver. Como mencioné en un comentario diferente, más que la velocidad bruta, necesito que las inserciones de mi fila no sean más lentas a medida que la base de datos crezca. –

+0

@oscar, tienes razón. Mi cálculo fue apagado. Si pudieras contarnos un poco más sobre las tablas, los datos que estás cargando y el tipo de lógica que llevas a cabo con los datos de origen, es posible que pueda ayudarte un poco más. – Ronnis

1

Se pulsa al mencionar E/S de disco. Si su disco está al máximo con insertos, no obtendrá más rápido a menos que actualice. No mencionó si era aceptable realizar actualizaciones de disco, pero sí utilizaría SCSI o discos basados ​​en flash. A pesar de que no estás alcanzando el límite de autobuses de SATA, tu disco definitivamente es un cuello de botella.

+0

Desafortunadamente, el rendimiento del disco no es el más fácil de actualizar. Sin embargo, me preocupa más que la velocidad de inserción de filas disminuya a medida que la base de datos crece. No importa qué tan rápido sea mi disco, si tengo la garantía de que las inserciones de filas son cada vez más lentas, superaré cualquier bus, independientemente de su velocidad. Prefiero tener 4000 filas/s independientes del tamaño de db, que 10000 filas/s que van por debajo de 1000 después de decir, mil millones de filas. –

1

que iba a tratar de aumentar el tamaño de la memoria innodb, y ver qué pasa. Para Innodb, también inhabilitaría las descargas permanentes con innodb_flush_log_at_trx_commit = 0 (o = 2). La configuración predeterminada es 1, que es un cuello de botella para las cargas de trabajo de escritura intensiva. 0 o 2 le darán 1 segundo de retraso entre las descargas. También puede hacer lotes más grandes usando transacciones (si no utiliza transacciones explícitamente, cada inserción es su propia transacción).

Como ya se mencionó, la entrada de ordenamiento previo (por clave principal) podría ayudar a reducir la cantidad de datos en la agrupación de almacenamiento intermedio eliminando la aleatoriedad en las cargas de página.

Todo lo anterior está relacionado con innodb.

6

Después de todo un día de hacer muchas cosas pequeñas, construí una gran cosa. La conclusión es que mejoré mi rendimiento de inserción alrededor de 8 veces, hasta casi 10000 registros por segundo.

Estas son las cosas que hice:

  1. reescribir el programa de carga. Dije que estaba en C, pero en realidad estaba en C++. Cambiar la cadena a char *, fstream con mmap y otras cosas así, casi doblé el rendimiento. (Y un montón de personas que siguen sosteniendo C++ es tan rápido o más rápido que C que ni siquiera quiera probar esto en C#/Java)

  2. encontré esta página: http://kevin.vanzonneveld.net/techblog/article/improve_mysql_insert_performance/ Este es un gran recurso (No estoy afiliado a ellos), eso explica casi todo lo que iba a intentar, con todos los diversos resultados. Más o menos, lo único que puede mejorar el rendimiento de inserción es usar LOAD DATA INFILE. ¡Pellizqué las estructuras de mi mesa para poder insertarme así casi cuadruplicado! el rendimiento de mis inserciones

  3. Reescribí las inserciones que no se pueden realizar con LOAD DATA INFILE, en inserciones masivas (varias filas por comando de inserción) utilizando expresiones complejas dentro de ACT DATE ACTUALIZADO EN LÍNEA, en lugar de SELECT/INSERT para cada fila. Esto también dio un impulso de rendimiento muy bueno. Esto también requirió algunas modificaciones a las estructuras de la tabla.

  4. Al recrear la base de datos, que ya superó los 2 mil millones de filas, cree las tablas que obtienen las inserciones LOAD DATA INFILE sin índices, y recíclelas cuando haya terminado. Todos mis puntos de referencia mostraron que el tiempo para insertar sin índices, más el tiempo para crearlos, es más corto que el tiempo para insertar en tablas con índices. La diferencia no es enorme, pero es notable (alrededor de 1,2 veces más rápido). Supongo que los B-trees también estarán mejor equilibrados de esta manera.

  5. Utilice MyISAM. Mis puntos de referencia anteriores no fueron tan concluyentes, pero al usar LOAD DATA INFILE, InnoDB pierde cada vez. Probando localmente, obtuve alrededor de 16000 registros/s en MyISAM/sin índices, 12000 registros/s en MyISAM/indexes, 9000 registros/s en InnoDB/sin índices, y alrededor de 7500 en InnoDB/indexes. La versión de MySQL es 5.1.47.

  6. Para los archivos de LOAD DATA INFILE, créelos en una partición montada en tmpfs. Esto también supone un gran impulso en el rendimiento, especialmente porque necesita escribir un archivo y vaciarlo en el disco para que MySQL pueda leerlo. Si este tmpfs no es posible, debería ser posible hacer esto usando pipes con nombre.

Lección aprendida: Cuando MySQL es lento, lo más probable es que pueda hacer más cambiando su código, que obteniendo un hardware más potente.

Buena suerte, y gracias a todos por su ayuda.

Cuestiones relacionadas