Cosas a evitar, Episodio 1: INSERTAR IGNORO
El código legado contraataca
Confiar en las características no documentadas del lenguaje es bastante arriesgado. Y cuando se mezcla con INSERT IGNORE las cosas van realmente mal.
Durante una de las actualizaciones del sistema operativo allá por principios de 2016, Perl se actualizó de 5.14 a 5.18. Por desgracia, el manejo de los hashes cambió, el orden de las llaves en un escenario bastante específico ya no se mantuvo y el código se rompió. Tienes razón, Perl nunca prometió el orden, pero nuestro código se basaba en algunas especificidades de implementación.
Un extracto del registro de cambios de Perl para la v5.18:
Por defecto, dos variables hash distintas con claves y valores idénticos pueden ahora proporcionar su contenido en un orden diferente donde antes era idéntico.
¿Por qué era un problema? El código estaba preparando una lista de hashes para ser insertados en la base de datos. A continuación, utilizaba el primer elemento para preparar la lista de nombres de columnas que se pasarían a las consultas INSERT que vendrían a continuación. Sin embargo, cada consulta INSERT enviada estaba utilizando diferentes hashes para preparar la lista de valores a insertar. Esto llevaba a lo siguiente:
INSERT IGNORE INTO foo (id, date) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
Básicamente, no se mantenía el orden de los nombres de las columnas y sus respectivos valores. Por supuesto, MySQL normalmente genera un error cuando se intenta insertar una fecha en una columna entera. Pero recuerda la cita de la documentación de MySQL, ¿verdad? Estos errores no se producen cuando se utiliza INSERT IGNORE y se realiza una conversión de datos silenciosa:
Datos consistentemente inconsistentes
El script funcionaba bien después de la actualización de Perl (bueno, al menos su stderr era silencioso), pero después de unos días recibimos un informe de error que indicaba que algo iba mal en nuestras herramientas internas que utilizan tablas alimentadas por Perl. A juzgar por el informe, algo era bastante sospechoso. Y nuestras preocupaciones fueron probadas por una rápida consulta SELECT:
Así que lo que tenemos aquí. Timestamp y los valores enteros convertidos a la cadena y se inserta como un nombre de usuario, los años almacenados en columnas de números enteros (es 2016 cuenta de ediciones o un año de la última edición). Incoherencia de datos en su máxima expresión.
No hace falta estar de acuerdo, pero para mí no hay datos mejores que «datos» como las filas de arriba.
La tabla afectada pesa más de 75 GiB y almacena 460 mm de filas. Podemos regenerarlas, pero es un proceso largo. El script se está ejecutando mientras lees esta historia. Bueno, ya lleva cinco semanas funcionando… Todo gracias a IGNORE en una sola consulta.
Lecciones aprendidas
- INSERT IGNORE es una bestia silenciosa que sólo espera su error para convertir los datos en un conjunto de valores sin sentido.
- Nunca ignores los errores. Hazlos sonar. Falla pronto.
- Confiar en características no documentadas del lenguaje (Perl y el orden de las claves hash es sólo uno de los numerosos ejemplos) es un juego arriesgado.
Has sido advertido. Ahora revise su código o ngrep su tráfico para las consultas INSERT IGNORE.