Een zeldzame fout in SQLite heeft maandenlang voor storingen bij Tailscale gezorgd. De netwerkleverancier kreeg in een half jaar tijd te maken met negentien gevallen van databasecorruptie. Na maanden onderzoek bleek de oorzaak een bug die al sinds 2010 in SQLite aanwezig was.
De eerste problemen doken in augustus op, toen een datapipeline een beschadigde databaseback-up ontdekte. Databasecorruptie is bij SQLite onder normale omstandigheden zeer uitzonderlijk, maar het probleem bleef terugkeren.
Tailscale gebruikt SQLite sinds 2022 voor de databases achter zijn control plane. Die bevatten configuratiegegevens over netwerken en apparaten, maar geen privésleutels of netwerkverkeer. Bij corruptie moest het getroffen deel van de control plane tijdelijk worden stilgelegd. Bestaande verbindingen bleven werken, maar nieuwe apparaten konden bijvoorbeeld geen netwerkconfiguratie ophalen.
Geschreven data verdwijnen
Een oorzaak vinden bleek lastig. De incidenten waren niet gekoppeld aan specifieke servers, klanten, belastingniveaus of tijdstippen en konden niet worden gereproduceerd. Tailscale schakelde daarom de ontwikkelaars van SQLite in.
De doorbraak kwam uit een transactielogsysteem dat Tailscale had gebouwd om databases sneller te herstellen. Daaruit bleek dat succesvol geschreven en vastgelegde gegevens bij latere transacties soms ineens verdwenen waren.
De aandacht verschoof naar SQLite’s Write-Ahead Log (WAL). Wijzigingen worden daarbij eerst in een apart logbestand geschreven en tijdens een checkpoint naar de eigenlijke database gekopieerd. Tailscale laat SQLite dat proces niet automatisch regelen, maar stuurt checkpoints zelf aan om snel en voorspelbaar back-ups te maken.
Met nieuwe debuggingsoftware van de SQLite-ontwikkelaars werd uiteindelijk een race condition gevonden tussen een schrijftransactie en een checkpoint. Wanneer beide precies verkeerd samenvielen, kon SQLite denken dat bepaalde pagina’s uit de WAL al naar de database waren gekopieerd terwijl dat niet was gebeurd. De gegevens gingen verloren en de database kon corrupt raken.
De ontwikkelaars noemen dit de WAL-Reset bug. De fout gaat volgens hen terug tot SQLite 3.7.0 uit juli 2010. Dat hij zestien jaar onopgemerkt bleef, komt door de uitzonderlijke omstandigheden die nodig zijn om hem te activeren. WAL-modus moet actief zijn, meerdere databaseverbindingen moeten hetzelfde bestand geopend hebben en lees- en schrijfacties moeten precies verkeerd samenvallen.
Voor normaal SQLite-gebruik is de kans daarop zeer klein. Tailscale liep meer risico doordat het zelf de checkpointing regelt en dat relatief agressief doet. SQLite adviseert gebruikers wel naar een gerepareerde versie te upgraden.
Eerste oplossing zorgt voor nieuw alarm
De reparatie verscheen aanvankelijk in SQLite 3.52.0. Na de uitrol meldde Tailscale echter corruptie in dertien databases. Dat bleek loos alarm: een andere wijziging in SQLite had de afronding van bepaalde floating-pointconversies veranderd, waardoor bestaande indexes als corrupt werden aangemerkt. SQLite trok 3.52.0 terug en bracht versie 3.51.3 uit met alleen de oplossing voor de WAL-Reset bug.
Tailscale voegde vervolgens logging toe om te controleren of de race condition daadwerkelijk in productie voorkwam. Twee maanden later werd precies die situatie gedetecteerd, zonder dat de database beschadigd raakte. Daarna draaide het platform nog vier maanden zonder nieuwe database-incidenten.
Volgens Tailscale laat de kwestie zien dat ook volwassen technologie risico’s kan opleveren wanneer die op een minder gebruikelijke manier wordt ingezet. De gebruikte configuratie werd gewoon door SQLite ondersteund, maar het handmatig en frequent uitvoeren van checkpoints week af van het gangbare gebruik. Tailscale financierde ook de VFS-debuggingtool die uiteindelijk hielp de race condition bloot te leggen.