Cose da evitare, Episodio 1: INSERT IGNORE
Il codice legacy colpisce ancora
Affidarsi a caratteristiche del linguaggio non documentate è piuttosto rischioso. E quando è mescolato con INSERT IGNORE le cose vanno davvero male.
Durante uno degli aggiornamenti del sistema operativo all’inizio del 2016, Perl è stato aggiornato dalla 5.14 alla 5.18. Sfortunatamente, la gestione degli hash cambiò, l’ordine delle chiavi in uno scenario abbastanza specifico non fu più mantenuto e il codice si ruppe. Hai ragione, Perl non ha mai promesso l’ordine, ma il nostro codice faceva affidamento su alcune specifiche implementazioni.
Un estratto dal changelog di Perl per la v5.18:
Per impostazione predefinita, due variabili hash distinte con chiavi e valori identici possono ora fornire i loro contenuti in un ordine diverso dove prima era identico.
Perché era un problema? Il codice stava preparando una lista di hash da inserire nel database. Poi usava il primo elemento per preparare l’elenco dei nomi delle colonne da passare alle query INSERT che seguiranno. Tuttavia, ogni query INSERT inviata utilizzava hash diversi per preparare l’elenco dei valori da inserire. Questo portava al seguente:
INSERT IGNORE INTO foo (id, date) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
Fondamentalmente, l’ordine dei nomi delle colonne e dei rispettivi valori non era mantenuto. Naturalmente, MySQL normalmente solleva un errore quando si cerca di inserire una data in una colonna intera. Ma vi ricordate la citazione dalla documentazione di MySQL, vero? Questi errori non vengono sollevati quando si usa INSERT IGNORE e la conversione silenziosa dei dati ha luogo:
Dati costantemente inconsistenti
Lo script funzionava bene dopo l’aggiornamento di Perl (beh, almeno il suo stderr era silenzioso), ma dopo pochi giorni abbiamo ricevuto una segnalazione di bug che qualcosa non va con i nostri strumenti interni che usano tabelle alimentate da Perl. A giudicare dal rapporto qualcosa era abbastanza sospetto. E le nostre preoccupazioni sono state dimostrate da una rapida query SELECT:
Quindi cosa abbiamo qui. Timestamp e valori interi convertiti in stringa e inseriti come nome utente, anni memorizzati in colonne intere (è il conteggio delle modifiche del 2016 o un anno dell’ultima modifica). Incoerenza dei dati al suo meglio.
Non è necessario che siate d’accordo, ma per me nessun dato è meglio di “dati” come le righe di cui sopra.
La tabella interessata pesa oltre 75 GiB e memorizza 460 mm di righe. Possiamo rigenerarle, ma è un processo lungo. Lo script è in esecuzione mentre leggete questa storia. Beh, è in esecuzione già da cinque settimane… Tutto grazie a IGNORE in una singola query.
Lezioni imparate
- INSERT IGNORE è una bestia silenziosa che aspetta solo il tuo errore per trasformare i dati in un insieme di valori senza senso.
- Mai ignorare gli errori. Rendeteli rumorosi. Fallite presto.
- Affidarsi a caratteristiche del linguaggio non documentate (Perl e l’ordine delle chiavi hash è solo uno dei numerosi esempi) è un gioco rischioso.
Siete stati avvertiti. Ora controllate il vostro codice o ngrep il vostro traffico per le query INSERT IGNORE.
Siete avvisati.