SQLite ha portato un bug di corruzione per 16 anni. Ecco perché probabilmente il tuo è a posto, e cosa controllare comunque.

Questa settimana Tailscale ha pubblicato un post-mortem che ogni team che utilizza SQLite dovrebbe leggere. Per sei mesi, a partire dallo scorso agosto, il loro control plane continuava a corrompere i database. Diciannove incidenti separati, nessun trigger evidente, nessuna modifica recente al codice che spiegasse il problema. La causa si è rivelata essere una race condition presente in SQLite dalla versione 3.7.0, rilasciata nel luglio 2010. È rimasta nascosta lì per circa sedici anni.

Il team di SQLite la chiama il WAL-reset bug. Colpisce solo i database in modalità WAL (write-ahead logging) quando due o più connessioni hanno lo stesso file aperto su thread o processi separati e cercano di scrivere o fare un checkpoint nello stesso istante. Quando il tempismo coincide, un checkpoint può lasciare un flag nell'indice WAL che dichiara che parte del log è già stata copiata nel file principale del database quando in realtà non lo era. Un checkpoint successivo salta quindi quei dati e il file si corrompe. Il bug è stato corretto in SQLite 3.51.3 il 13 marzo 2026, con backport nelle versioni 3.44.6 e 3.50.7.

Ecco la parte su cui vale la pena soffermarsi. Gli sviluppatori di SQLite non sono riusciti a riprodurre questo bug intenzionalmente. Hanno dovuto modificare il proprio codice sorgente per attivare un callback esattamente nel momento sbagliato durante un checkpoint. La loro telemetria indica che il tasso di occorrenza nel mondo reale è pari o inferiore al tasso di guasti degli SSD e dei bit-flip causati dai raggi cosmici. Quindi, se utilizzi SQLite in una configurazione normale, quasi certamente non sei mai stato colpito, e probabilmente non lo sei ancora. Tailscale è stata colpita perché ha preso il controllo manuale del checkpointing e lo ha eseguito in modo aggressivo per backup veloci. Ha abbandonato il percorso battuto, e una race condition da una su un miliardo è diventata un evento mensile.

Cosa faremmo concretamente

Aggiorna, ma senza farti prendere dal panico. Passa a SQLite 3.51.3 o successivo, o a uno dei backport, con i tuoi tempi. Il problema è che «la tua versione di SQLite» è raramente ovvia. SQLite è incorporato quasi ovunque, e non lo installi direttamente. Viene distribuito all'interno del runtime del linguaggio, dell'ORM, del sistema operativo mobile e della piattaforma edge.

Nel nostro lavoro nativo per iOS e Android, questo è quello che si tende a dimenticare più facilmente. Il SQLite di sistema è legato alla versione del sistema operativo che usano i tuoi utenti, non alla versione con cui hai compilato, e non puoi scegliere quando si aggiornano. Se la tua app fa qualcosa di insolito con le connessioni o i checkpoint — ad esempio un thread di sincronizzazione in background che scrive mentre l'interfaccia legge — includi la tua versione di SQLite nel bundle così da controllarne la versione invece di ereditare qualunque cosa installi il dispositivo. Lo abbiamo fatto in app sanitarie e IoT offline-first, dove un archivio locale corrotto significa dati che l'utente non potrà mai recuperare.

Sul lato backend ed edge, verifica contro cosa si collega effettivamente il tuo driver. Dal nostro lavoro con PHP e Docker, la versione di SQLite inclusa in un'immagine base può essere in ritardo di mesi rispetto all'upstream, e «siamo sull'ultimo framework» non ti dice nulla sulla libreria C sottostante. SQLite serverless ed edge — ovvero D1, Turso, LiteFS e simili — merita un'analisi specifica, perché queste piattaforme si affidano pesantemente alla modalità WAL e all'accesso concorrente. È esattamente la condizione che questo bug richiede.

La lezione più importante riguarda i backup

Il dettaglio che mi è rimasto impresso non è la race condition. È che Tailscale se n'è accorta solo perché una pipeline separata leggeva i backup su S3 ed eseguiva PRAGMA integrity_check su di essi. I database in produzione sembravano sani. La corruzione era già presente nei file che stavano conservando per dopo.

Durante i nostri impegni di sicurezza e penetration testing, i backup sono il punto in cui troviamo le lacune più preoccupanti. I team li effettuano religiosamente e non li ripristinano mai. Un backup che non hai mai ripristinato è un'ipotesi, non una rete di sicurezza. Se conservi file SQLite, esegui PRAGMA integrity_check su una copia periodicamente, e ripristina effettivamente in un ambiente di prova di tanto in tanto. La corruzione che emerge solo il giorno in cui hai bisogno del backup è il modo peggiore per imparare questa lezione.

Un'altra cosa dal post che è facile non notare. Dopo che Tailscale ha distribuito la correzione, un cambiamento di arrotondamento nella release ha generato falsi avvisi di corruzione su database che utilizzavano indici di espressione. L'hanno scoperto perché hanno distribuito prima su alcuni canary shard, poi al resto. I rollout graduali non sono burocrazia. Sono il modo in cui trovi il secondo bug che la correzione del primo ha introdotto.

Cosa puoi fare questa settimana

Scopri quale versione di SQLite stanno effettivamente eseguendo i tuoi sistemi in produzione e le app che hai distribuito. Non la versione del framework, ma la libreria SQLite effettiva. Poi prendi un backup e dimostra di poterlo ripristinare correttamente. È un pomeriggio di lavoro, e per la maggior parte dei team vale più dell'aggiornamento stesso.

Se rintracciare quale versione di SQLite stiano effettivamente eseguendo le tue app e i tuoi servizi, e se i tuoi backup sopravviverebbero a un ripristino, ti suona familiare, parliamone.

databasesexpert-analysissoftware-developmentsqlitetech-news