Les choses à éviter, épisode 1 : INSERT IGNORE
Legacy code strikes back
S’appuyer sur des fonctionnalités non documentées du langage est assez risqué. Et quand c’est mélangé avec INSERT IGNORE, les choses vont vraiment mal.
Pendant l’une des mises à jour du système d’exploitation au début de 2016, Perl a été mis à niveau de 5.14 à 5.18. Malheureusement, la gestion des hachages a changé, l’ordre des clés dans un scénario assez spécifique n’était plus maintenu et le code s’est cassé. Vous avez raison, Perl n’a jamais promis l’ordre, mais notre code s’appuyait sur certaines spécificités de mise en œuvre.
Un extrait du changelog de Perl pour la v5.18:
Par défaut, deux variables de hachage distinctes avec des clés et des valeurs identiques peuvent maintenant fournir leur contenu dans un ordre différent où il était précédemment identique.
Pourquoi était-ce un problème ? Le code préparait une liste de hachages à insérer dans la base de données. Puis il utilisait le premier élément pour préparer la liste des noms de colonnes à transmettre aux requêtes INSERT qui suivront. Cependant, chaque requête INSERT envoyée utilisait des hashs différents pour préparer la liste des valeurs à insérer. Cela conduisait à ce qui suit:
INSERT IGNORE INTO foo (id, date) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
En gros, l’ordre des noms de colonnes et des valeurs respectives n’était pas respecté. Bien sûr, MySQL lève normalement une erreur lorsqu’on essaie d’insérer une date dans une colonne entière. Mais vous vous souvenez de la citation de la documentation MySQL, n’est-ce pas ? Ces erreurs ne sont pas soulevées lorsque INSERT IGNORE est utilisé et qu’une conversion silencieuse des données a lieu:
Data consistent consistent inconsistent
Le script fonctionnait bien après la mise à jour de Perl (enfin, au moins son stderr était silencieux), mais après quelques jours, nous avons reçu un rapport de bogue indiquant que quelque chose ne va pas avec nos outils internes qui utilisent des tables alimentées par Perl. A en juger par le rapport, il y avait quelque chose de louche. Et nos inquiétudes ont été prouvées par une rapide requête SELECT:
Alors qu’avons-nous ici. Timestamp et valeurs entières converties en chaîne de caractères et insérées comme un nom d’utilisateur, années stockées dans des colonnes entières (est 2016 edits compte ou une année de la dernière modification). L’incohérence des données à son meilleur.
Vous n’avez pas besoin d’être d’accord, mais pour moi, aucune donnée n’est meilleure que les « données » comme les lignes ci-dessus.
La table affectée pèse plus de 75 GiB et stocke 460 mm de lignes. Nous pouvons les régénérer, mais c’est un long processus. Le script est en cours d’exécution au moment où vous lisez cette histoire. Eh bien, il tourne depuis cinq semaines déjà… Tout cela grâce à IGNORE dans une seule requête.
Les leçons apprises
- INSERT IGNORE est une bête silencieuse qui n’attend que votre erreur pour transformer les données en un ensemble de valeurs sans signification.
- N’ignorez jamais les erreurs. Rendez-les bruyantes. Échouez tôt.
- S’appuyer sur des caractéristiques de langage non documentées (Perl et l’ordre des clés de hachage n’est qu’un des nombreux exemples) est un jeu risqué.
Vous avez été prévenu. Maintenant, vérifiez votre code ou ngrep votre trafic pour les requêtes INSERT IGNORE.