¿Por qué MySQL haría E / S sincrónicas en serie?

8

Al mirar una consulta particularmente molesta sobre tablas MyISAM que lleva mucho tiempo ejecutar en varias ocasiones, noté que MySQL parece exponer un patrón de E / S bastante extraño: al ejecutar una sola consulta y tener que hacer una tarea significativa cantidad de E / S (por ejemplo, para un escaneo de tabla o cuando las memorias caché están vacías como resultado de echo 3 > /proc/sys/vm/drop_cacheslo cual los índices deben cargarse primero del disco), el tamaño de la cola para el dispositivo de bloque subyacente está cerca del valor 1, con un rendimiento abismal de solo 4-5 MB / s:

root@mysql-test:~# iostat -xdm 5 /dev/sda
Linux 3.2.0-40-generic (mysql-test)  04/30/2014      _x86_64_        (4 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.14    24.82   18.26   88.79     0.75     4.61   102.56     2.83   26.39   19.29   27.85   2.46  26.31

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    69.29  151.52   72.73     5.31     0.59    53.95     1.21    5.39    7.84    0.29   4.39  98.51

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   153.06  144.29  174.69     4.96     1.36    40.54     1.39    4.36    8.91    0.60   3.15 100.49

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   105.75  150.92  109.03     4.53     0.85    42.41     1.29    4.96    8.15    0.54   3.90 101.36

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    48.89  156.36   51.72     5.28     0.76    59.38     1.28    6.16    8.02    0.55   4.77  99.23

Si bien los 150 IOPS simplemente son lo que un solo disco en la configuración dada es capaz de entregar en términos de E / S aleatorias, el resultado todavía me sorprende, ya que esperaría que MySQL pueda ejecutar E / S asíncronas para lecturas y obtener un gran cantidad de bloques simultáneamente en lugar de leerlos y evaluarlos uno por uno, descuidando efectivamente las ganancias de paralelización disponibles en las configuraciones RAID. ¿Qué decisión de diseño u opción de configuración es responsable de esto? ¿Es este un problema específico de la plataforma?

Si bien he probado esto con tablas MyISAM de gran tamaño, veo efectos similares con las mismas tablas convertidas a InnoDB (aunque no es tan malo, la consulta de muestra todavía tarda 20-30 segundos y la mayor parte del tiempo se dedica a leer el disco con una longitud de cola de 1) después de reiniciar el demonio mysql y, por lo tanto, los grupos de búferes están vacíos. También verifiqué que el mismo problema persiste en 5.6 GA y el hito actual 5.7 5.7 - siempre que esté usando un solo hilo de consulta, MySQL parece incapaz de paralelizar las operaciones de E / S necesarias para el procesamiento de consultas.


Según la solicitud, algunos detalles adicionales sobre el escenario. El comportamiento se puede observar con una multitud de tipos de consulta. Elegí arbitrariamente uno para pruebas adicionales que dice algo así:

SELECT herp.id, herp.firstname, herp.lastname, derp.label, herp.email, 
(SELECT CONCAT(label, " (", zip_code, " ", city,")" ) FROM subsidiaries WHERE subsidiaries.id=herp.subsidiary_id ) AS subsidiary, 
(SELECT COUNT(fk_herp) from herp_missing_data WHERE fk_herp=herp.id) AS missing_data
FROM herp LEFT JOIN derp ON derp.id=herp.fk_derp
WHERE (herp.fk_pools='123456')  AND herp.city LIKE '%Some City%' AND herp.active='yes' 
ORDER BY herp.id desc LIMIT 0,10;

Sé que tiene algo de espacio para la optimización, pero decidí dejarlo así por varias razones y concentrarme en encontrar una explicación general del inesperado patrón de E / S que estoy viendo.

Las tablas usadas tienen un montón de datos en ellas:

mysql> select table_name, engine, table_rows, data_length, index_length from information_schema.tables WHERE tables.TABLE_SCHEMA = 'mydb' and tables.table_name in ( 'herp', 'derp', 'missing_data', 'subsidiaries');
+-------------------------+--------+------------+-------------+--------------+
| table_name              | engine | table_rows | data_length | index_length |
+-------------------------+--------+------------+-------------+--------------+
| derp                    | MyISAM |      14085 |     1118676 |       165888 |
| herp                    | MyISAM |     821747 |   828106512 |    568057856 |
| missing_data            | MyISAM |    1220186 |    15862418 |     29238272 |
| subsidiaries            | MyISAM |       1499 |     6490308 |       103424 |
+-------------------------+--------+------------+-------------+--------------+
4 rows in set (0.00 sec)

Ahora, cuando ejecuto la consulta anterior sobre estas tablas, obtengo tiempos de ejecución de más de 1 minuto mientras el sistema aparentemente está continuamente ocupado leyendo datos del disco con un solo hilo.

El perfil para una ejecución de consulta de muestra (que tomó 1 min 9.17 segundos en este ejemplo) se ve así:

mysql> show profile for query 1;
+--------------------------------+-----------+
| Status                         | Duration  |
+--------------------------------+-----------+
| starting                       |  0.000118 |
| Waiting for query cache lock   |  0.000035 |
| init                           |  0.000033 |
| checking query cache for query |  0.000399 |
| checking permissions           |  0.000077 |
| checking permissions           |  0.000030 |
| checking permissions           |  0.000031 |
| checking permissions           |  0.000035 |
| Opening tables                 |  0.000158 |
| init                           |  0.000294 |
| System lock                    |  0.000056 |
| Waiting for query cache lock   |  0.000032 |
| System lock                    |  0.000116 |
| optimizing                     |  0.000063 |
| statistics                     |  0.001964 |
| preparing                      |  0.000104 |
| Sorting result                 |  0.000033 |
| executing                      |  0.000030 |
| Sending data                   |  2.031349 |
| optimizing                     |  0.000054 |
| statistics                     |  0.000039 |
| preparing                      |  0.000024 |
| executing                      |  0.000013 |
| Sending data                   |  0.000044 |
| optimizing                     |  0.000017 |
| statistics                     |  0.000021 |
| preparing                      |  0.000019 |
| executing                      |  0.000013 |
| Sending data                   | 21.477528 |
| executing                      |  0.000070 |
| Sending data                   |  0.000075 |
| executing                      |  0.000027 |
| Sending data                   | 45.692623 |
| end                            |  0.000076 |
| query end                      |  0.000036 |
| closing tables                 |  0.000109 |
| freeing items                  |  0.000067 |
| Waiting for query cache lock   |  0.000038 |
| freeing items                  |  0.000080 |
| Waiting for query cache lock   |  0.000044 |
| freeing items                  |  0.000037 |
| storing result in query cache  |  0.000033 |
| logging slow query             |  0.000103 |
| cleaning up                    |  0.000073 |
+--------------------------------+-----------+
44 rows in set, 1 warning (0.00 sec)
syneticon-dj
fuente
¿Tiene un caso de prueba repetible (idealmente simple) que podría explicar con más detalle? Por ejemplo, una consulta que genera este comportamiento? ¿Bajo que circunstancias? Comenzaste por ese camino con "echo 3> ..." y "reiniciar el demonio mysql" pero no entró en detalles.
Scott Leadley
@ScottLeadley gracias por investigar esto. No creo que sea capaz de hacerlo "simple": el problema solo sería observable si fuera necesario leer una gran cantidad de datos para una sola consulta y sería principalmente E / S aleatoria. Las tablas y consultas son razonablemente sencillas y, aunque podría publicar los textos DDL y Consulta, dudo que alguien pueda reproducirlo de inmediato a menos que los datos de la tabla / índice hayan crecido a cientos de Megabytes.
syneticon-dj
Como aludiste, los 5 ms de espera para lecturas son consistentes con una latencia rotacional promedio de un disco de 5400 RPM. Busque contención al leer "una gran cantidad de datos ... principalmente E / S aleatorias" explicaría eso. En cuanto a RAID, lo ha mencionado, pero no ha dado ningún detalle de esta configuración específica.
Scott Leadley
No estoy seguro de poder ayudarte directamente, porque no ejecuto tu configuración. Pero la regla general de StackExchange es que una pregunta realmente buena recibe más atención que una recompensa. Escribiendo la pregunta perfecta
Scott Leadley
@ScottLeadley los 5 ms de espera se deben principalmente a la latencia del sistema de almacenamiento empleado. He probado esto en diferentes escenarios, desde un simple RAID10 de 4 discos hasta un archivador de almacenamiento en niveles con un estante de 16 discos y respaldo SSD, los resultados muestran consistentemente que la carga de E / S no está paralela y, por lo tanto, está unida a la latencia. Lo que siento es fundamentalmente incorrecto. He agregado los detalles de la consulta a la pregunta, pero todavía no estoy convencido de que serían de gran ayuda.
syneticon-dj

Respuestas:

8

Primero déjenme aclarar confirmando que MyISAM no hace E / S asíncrona, pero que InnoDB lo hace y lo hará de manera predeterminada desde MySQL 5.5. Antes de 5.5 usaba "AIO simulado" mediante hilos de trabajo.

Creo que también es importante distinguir entre tres situaciones:

  1. Múltiples consultas ejecutándose a la vez
  2. Una sola consulta ejecutándose en paralelo
  3. Algún tipo de lectura lógica anticipada para escaneos de tablas / casos claros donde las páginas siguientes son bien conocidas.

Para (1) I / O podrá ejecutarse en paralelo para esto. Existen algunos límites con MyISAM: bloqueo de tabla y un bloqueo global que protege el key_buffer(caché de índice). InnoDB en MySQL 5.5+ realmente brilla aquí.

Para (2) esto actualmente no es compatible. Un buen caso de uso sería con la partición, donde podría buscar cada tabla particionada en paralelo.

Para (3) InnoDB tiene lectura lineal anticipada para leer en toda su extensión (grupo de 64 páginas) si se leen> 56 páginas (esto es configurable), pero hay espacio para mejoras adicionales. Facebook ha escrito sobre la implementación de cabeza de lectura lógica en su rama (con una ganancia de rendimiento de 10x en los cuadros).

Morgan Tocker
fuente
Gracias, esto me da una comprensión adicional de lo que estoy viendo. ¿Eso generalmente significa que MyISAM no puede utilizar más de un disco de IOPS para una carga de subproceso único? No puedo encontrar ninguna referencia a esto en los documentos: ¿tiene algo a mano?
syneticon-dj
Si. No puedo pensar en un lugar en los documentos donde esto sería.
Morgan Tocker
2

Espero missing_dataque no sea MyISAM porque una tabla MyISAM vacía generalmente tiene un byte de 1024 .MYI. Se espera un tamaño de byte distinto de cero de un MyISAM. Un byte cero me .MYIsuena un poco espeluznante.

Si ejecuta esta consulta de metadatos

select table_name, table_rows, data_length, index_length, engine
from information_schema.tables
WHERE tables.TABLE_SCHEMA = 'mydb'
and tables.table_name = 'missing_data';

y el motor de esa mesa es MyISAM, debes repararlo.

NOTA LATERAL: Si enginees así NULL, es una vista. Si es una vista o no es MyISAM, ignore el resto de mi publicación y agregue esa información a la pregunta. Si la tabla es MyISAM, sigue leyendo ...

Según su consulta de metadatos, missing_data.MYDes de aproximadamente 46M.

Primero, ejecuta esto

SHOW CREATE TABLE mydb.missing_data\G

Obtendrá la descripción de la tabla o un mensaje de error que dice algo como

ERROR 126 (HY000): Incorrect key file for table ...

Si obtiene la descripción de la tabla y es MyISAM, ejecute

OPTIMIZE TABLE mydb.missing_data;

Volverá a crear la tabla sin fragmentación y calculará estadísticas de índice nuevas. Si eso no funciona, intente:

REPAIR TABLE mydb.missing_data;

Eso debería regenerar las páginas de índice para MyISAM.

Solo para estar seguro (si usa MySQL 5.6), ejecute esto después de la reparación

FLUSH TABLES mydb.missing_data;

Tu pregunta

Es posible que los índices de su tabla no se carguen en la memoria si MySQL Query Optimizer decide no usarlo. Si su cláusula WHERE dicta que se debe leer una cantidad significativa de filas de los índices, MySQL Query Optimizer lo verá al construir el plan EXPLAIN y decidirá usar un escaneo completo de la tabla.

Las operaciones de E / S paralelas en una tabla MyISAM son inalcanzables porque no son configurables.

InnoDB se puede ajustar para aumentar el rendimiento de esa manera.

RolandoMySQLDBA
fuente
Debo empatizarlo de nuevo: si mydb.missing_data es MyISAM y tiene un índice de cero bytes, algo definitivamente está mal.
RolandoMySQLDBA
He actualizado los datos para que sean más coherentes: ahora muestra resultados solo de MyISAM de un solo host para que la gente no se confunda.
syneticon-dj