Things To Avoid, Episode 1: INSERT IGNORE
Legacy code strikes back
Verrouwen op ongedocumenteerde taalfuncties is nogal riskant. En wanneer het wordt gemengd met INSERT IGNORE gaat het echt slecht.
Tijdens een van de OS upgrades begin 2016, werd Perl geupgrade van 5.14 naar 5.18. Helaas veranderde de afhandeling van hashes, de volgorde van sleutels in een vrij specifiek scenario werd niet langer gehandhaafd en de code brak. Je hebt gelijk, Perl heeft de volgorde nooit beloofd, maar onze code vertrouwde op enkele implementatiespecificaties.
Een fragment uit Perl changelog voor v5.18:
Door de standaardinstelling kunnen twee verschillende hash-variabelen met identieke sleutels en waarden nu hun inhoud in een andere volgorde leveren waar het voorheen identiek was.
Waarom was het een probleem? De code was een lijst met hashes aan het voorbereiden om in de database te worden ingevoegd. Vervolgens werd het eerste item gebruikt om de lijst met kolomnamen voor te bereiden die aan de volgende INSERT-query’s moesten worden doorgegeven. Echter, elke INSERT query die werd verstuurd gebruikte verschillende hashes om de lijst van waarden voor te bereiden om in te voegen. Dit leidde tot het volgende:
INSERT IGNORE INTO foo (id, datum) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
Basically, de volgorde van kolomnamen en respectievelijke waarden werd niet aangehouden. Natuurlijk geeft MySQL normaal gesproken een foutmelding als je een datum probeert in te voegen in een kolom met een geheel getal. Maar je herinnert je het citaat uit de MySQL documentatie, toch? Deze fouten treden niet op als INSERT IGNORE wordt gebruikt en stille dataconversie plaatsvindt:
Data consistent inconsistent
Het script draaide prima na de Perl upgrade (nou ja, in ieder geval was de stderr stil), maar na een paar dagen kregen we een bug-rapport dat er iets mis is met onze interne tools die gebruik maken van Perl-gebaseerde tabellen. Te oordelen naar het rapport zat er iets niet pluis in. En onze zorgen werden bewezen door een snelle SELECT query:
Dus wat hebben we hier. Tijdstempel en integerwaarden geconverteerd naar een string en ingevoegd als een gebruikersnaam, jaren opgeslagen in integer kolommen (is 2016 edits count of een jaar van de laatste bewerking). Data inconsistentie op zijn best.
Je hoeft het er niet mee eens te zijn, maar voor mij is geen data beter dan “data” zoals rijen hierboven.
De getroffen tabel weegt meer dan 75 GiB en slaat 460 mm rijen op. We kunnen ze regenereren, maar het is een lang proces. Het script loopt terwijl je dit verhaal leest. Nou, het draait al vijf weken… Allemaal dankzij IGNORE in een enkele query.
Lessen geleerd
- INSERT IGNORE is een stil beest dat wacht op jouw fout om de data te veranderen in een betekenisloze set waarden.
- Negeer nooit fouten. Maak ze luid. Faal vroeg.
- Verrouwen op ongedocumenteerde taal functies (Perl en hash sleutels volgorde is slechts een van de vele voorbeelden) is een riskant spel.
Je bent gewaarschuwd. Controleer nu uw code of ngrep uw verkeer voor INSERT IGNORE queries.