Ting du skal undgå, episode 1: INSERT IGNORE
Legacy code strikes back
Det er ret risikabelt at stole på udokumenterede sprogfunktioner. Og når det er blandet med INSERT IGNORE går det rigtig galt.
Under en af OS-opgraderingerne tilbage i begyndelsen af 2016 blev Perl opgraderet fra 5.14 til 5.18. Desværre blev hash-håndteringen ændret, nøglerækkefølgen i et helt specifikt scenarie blev ikke længere opretholdt, og koden gik i stykker. Du har ret, Perl har aldrig lovet rækkefølgen, men vores kode var afhængig af nogle implementeringsspecifikationer.
Et uddrag fra Perl changelog for v5.18:
Som standard kan to forskellige hash-variabler med identiske nøgler og værdier nu levere deres indhold i en anden rækkefølge, hvor det tidligere var identisk.
Hvorfor var det et problem? Koden forberedte en liste over hashes, der skulle indsættes i databasen. Derefter brugte den det første element til at forberede listen over kolonnnavne, der skal videregives til INSERT-forespørgsler, der følger efter. Hver INSERT-forespørgsel, der blev sendt, brugte imidlertid forskellige hashes til at forberede listen over værdier, der skulle indsættes. Dette førte til følgende:
INSERT IGNORE INTO foo (id, date) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
Grundlæggende blev rækkefølgen af kolonnenavne og de respektive værdier ikke overholdt. Selvfølgelig giver MySQL normalt en fejl, når man forsøger at indsætte en dato i en heltalskolonne. Men du husker vel citatet fra MySQL-dokumentationen, ikke sandt? Disse fejl bliver ikke rejst, når INSERT IGNORE bruges, og der sker en tavs datakonvertering:
Data konsekvent inkonsistente
Scriptet kørte fint efter Perl-opgraderingen (ja, i det mindste var dets stderr tavs), men efter få dage fik vi en fejlrapport om, at noget er galt med vores interne værktøjer, der bruger Perl-drevne tabeller. At dømme ud fra rapporten var der noget helt galt. Og vores bekymringer blev bekræftet af en hurtig SELECT forespørgsel:
Så hvad har vi her. Tidsstempel og heltalsværdier konverteret til streng og indsat som brugernavn, årstal gemt i heltals kolonner (er 2016 redigeringer tæller eller et årstal for den sidste redigering). Datainkonsistens når den er bedst.
Du behøver ikke at være enig, men for mig er ingen data bedre end “data” som ovenstående rækker.
Den berørte tabel vejer over 75 GiB og gemmer 460 mm rækker. Vi kan regenerere dem, men det er en lang proces. Scriptet kører, mens du læser denne historie. Tja, det har allerede kørt i fem uger… Alt sammen takket være IGNORE i en enkelt forespørgsel.
Lærdom lært
- INSERT IGNORE er et tavst bæst, der bare venter på din fejltagelse for at forvandle dataene til et meningsløst sæt værdier.
- Ignorér aldrig fejl. Gør dem højlydt. Fail early.
- Det er et risikabelt spil at stole på udokumenterede sprogfunktioner (Perl og hash keys order er blot et af mange eksempler).
Du er blevet advaret. Tjek nu din kode eller ngrep din trafik for INSERT IGNORE-forespørgsler.