Transakcja została zapisana i potwierdzona, a chwilę później następne zapytanie jej nie widziało. Bez błędu, bez ostrzeżenia, bez śladu w dzienniku. Zespół Tailscale’a zobaczył to dwa razy przy odtwarzaniu własnych kopii i dopiero wtedy sprawa ruszyła z miejsca. Wcześniej przez pół roku bazy psuły się bez wyjaśnienia dziewiętnaście razy, a każda taka awaria oznaczała postój dla części klientów. Winowajcą okazał się wyścig ukryty w SQLite od co najmniej szesnastu lat. Twórcy biblioteki oszacowali jego wiek dopiero wtedy, gdy udało się go zobaczyć.
Dziewiętnaście uszkodzonych baz w sześć miesięcy
Pierwsze uszkodzenie wykrył w sierpniu 2025 roku potok danych czytający kopie zapasowe z magazynu w chmurze. Sprawdzenie spójności bazy potwierdziło, że plik faktycznie jest zepsuty. To się zdarza, ale nie w normalnej pracy i nie regularnie. Potem zdarzyło się jeszcze osiemnaście razy w ciągu pół roku.
Warto od razu powiedzieć, czego w tych bazach nie było. Tailscale trzyma w nich dane o konfiguracji sieci i o urządzeniach. Nie ma tam kluczy prywatnych ani ruchu sieciowego użytkowników. Firma pisze to wprost w swoim rozliczeniu. Nie było też tak, że problem dotknął wszystkich. Większość fragmentów usługi i większość sieci klientów nigdy nie miała żadnego incydentu.
Skutki i tak były dotkliwe. Naprawa wymagała zatrzymania procesu obsługującego dany fragment usługi. Urządzenia już połączone działały dalej, ale przestawały dowiadywać się o zmianach w sieci. Nowe nie mogły dołączyć. Znikała też konsola administracyjna i dostęp przez interfejs programistyczny. W pierwszych incydentach taki postój trwał ponad godzinę.
Dlaczego pół roku nie wystarczyło, żeby to znaleźć
Ten błąd opierał się wszystkim standardowym metodom i to jest w tej historii najciekawsze.
Podejrzanych zmian w kodzie nie było, bo warstwa obsługująca bazę powstała lata wcześniej i nikt jej ostatnio nie ruszał. Zabrakło też wspólnego mianownika między awariami. Nie łączył ich ani konkretny fragment usługi, ani klient, ani funkcja, ani pora dnia, ani obciążenie. Bez powtarzalnych warunków nie da się zbudować testu, więc odpadła najprostsza droga. Zostało zbieranie danych na żywym systemie i czekanie.
Czekanie też było kiepskie. Awarie przychodziły raz co kilka godzin, raz co kilka tygodni. Między październikiem a grudniem nastała sześciotygodniowa cisza, po której problem wrócił. Firma wykupiła płatne wsparcie u twórców SQLite i razem odrzucili kolejne hipotezy. Podejrzewano między innymi błędne zwalnianie blokad plików, złe gospodarowanie pamięcią i korzystanie z biblioteki z wielu wątków przy wyłączonych zabezpieczeniach. Żadna z tych teorii się nie potwierdziła.
Zapis, który zniknął bez błędu
Przełom przyszedł z narzędzia zbudowanego do czegoś innego. Zespół chciał odtwarzać bazy bez cofania się do starej kopii, więc zaczął zapisywać każdą operację zmieniającą dane do osobnego dziennika. Przy jednym pisarzu i uporządkowanych transakcjach taka historia jest liniowa, więc powinna dać się odtworzyć zawsze.
Dwa razy nie dała. Przy dokładniejszym oglądzie okazało się, że dane zapisane i zatwierdzone przez jedną transakcję były niewidoczne dla następnych. Żaden błąd przy tym nie wystąpił. Drugi trop przyszedł z metryk: biblioteka raportowała, że przepisała z dziennika więcej stron, niż ten dziennik w ogóle zawierał.
Na czym polegał błąd resetu WAL
Żeby to zrozumieć, wystarczy jedno pojęcie. SQLite może zapisywać zmiany najpierw do osobnego pliku, zwanego dziennikiem zapisu wyprzedzającego. Skrót WAL pochodzi od angielskiej nazwy tego mechanizmu. Co jakiś czas zawartość tego pliku trzeba przepisać do właściwej bazy, i ta operacja nazywa się punktem kontrolnym.
Błąd polegał na wyścigu między punktem kontrolnym a trwającym zapisem. Jeśli zapis trafił w konkretny moment, procedura przepisywania uznawała, że część stron została już przeniesiona do bazy. W rzeczywistości nie została. Te strony nie trafiały tam nigdy, a ich zawartość przepadała bezpowrotnie. Sama baza stawała się przy tym niespójna, bo inne strony odwołujące się do tych brakujących, na przykład indeks, zapisywały się normalnie.
Twórcy SQLite nazwali to błędem resetu WAL i oszacowali, że siedział w kodzie co najmniej szesnaście lat. Mógł tam siedzieć tak długo, bo był skrajnie rzadki. Najlepszym dowodem jest to, że do własnych testów musieli dopisać kod wywołujący go sztucznie. Poprawka dokłada w procedurze punktu kontrolnego sprawdzenie, czy dziennik nie został w międzyczasie zresetowany przez inny wątek.
Pierwsza Misja AI · Kodożercy
Rozumiesz zagrożenia AI, gdy rozumiesz jak naprawdę działa.
Kurs Pierwsza Misja AI ma dedykowaną lekcję o ciemnej stronie AI: halucynacje, deepfakes, manipulacja. Zanim zaczniesz się bać, zacznij rozumieć.
Poznaj pełny program →

Poprawka, która zrobiła własne zamieszanie
Historia mogłaby się tu skończyć, ale ma jeszcze jeden zakręt wart opowiedzenia.
Poprawkę wypuszczono najpierw jako wersję 3.52.0, na początku marca 2026 roku. Tailscale wdrożył ją ostrożnie, najpierw na kilku fragmentach próbnych, potem wszędzie. Monitor kopii natychmiast zgłosił uszkodzenie trzynastu baz. Alarm okazał się fałszywy, ale przyczyna była prawdziwa i leżała w tej samej wersji. Przy okazji zmieniono w niej sposób zaokrąglania przy przeliczaniu tekstu na liczbę zmiennoprzecinkową. Firma trzymała precyzyjne znaczniki czasu jako tekst i przeliczała je w kolumnie wyliczanej, więc indeks przestał się zgadzać z danymi.
Twórcy SQLite wycofali wtedy całe wydanie 3.52.0. Tydzień później opublikowali 3.51.3, zawierające wyłącznie poprawkę błędu resetu WAL. Reszta zmian trafiła dopiero do 3.53.0, razem z mechanizmem naprawiającym nieaktualne indeksy. To dobra ilustracja tego, jak kosztowne bywa łączenie pilnej poprawki z optymalizacjami w jednym wydaniu.
Piętnaście minut kontra pół roku
Tego samego dnia co Tailscale swój opis opublikowała firma Antithesis, zajmująca się testowaniem oprogramowania. Kolejność jest tu istotna, bo łatwo ją przekręcić. Antithesis nie znalazł tego błędu i nie brał udziału w śledztwie. Inżynier tej firmy wziął podatną wersję 3.51.2 już po tym, jak poprawka była publiczna. Obłożył kod asercjami i puścił obciążenie z równoległymi zapisami i punktami kontrolnymi. Ich platforma znalazła błąd w pierwszym uruchomieniu, w piętnaście minut.
Nie jest to powód do złośliwości wobec kogokolwiek, bo szukanie znanej igły w znanym stogu to inne zadanie. Warto natomiast zapamiętać różnicę w metodzie. Testowanie deterministyczne, w którym środowisko celowo przestawia kolejność zdarzeń, znajduje wyścigi, których zwykłe testy nie ruszą. Sami twórcy SQLite przyznają, że nigdy nie udało im się wywołać tego błędu w naturalny sposób.
Co z tego ma firma, która nie pisze bazy danych
Najłatwiejszy wniosek byłby taki, że oprogramowaniu nie można ufać. Byłby też bezużyteczny, więc proponujemy inny.
Tailscale nie użył SQLite wbrew instrukcji. Konfiguracja była publiczna, udokumentowana i wspierana. Nietypowe było jedno: firma przejęła ręczne sterowanie punktami kontrolnymi i wykonywała je bardzo często, żeby robić częste kopie. To wystarczyło, żeby zejść z wydeptanej ścieżki. Zdanie, które sami z tego wyciągają, warto powiesić nad biurkiem. Uruchamianie nudnej, sprawdzonej technologii w nietypowy sposób jest ryzykiem samo w sobie.
Praktycznie znaczy to tyle, że własne odstępstwa od domyślnych ustawień trzeba spisać i traktować jak dług. Nie chodzi o to, żeby ich nie robić, tylko żeby wiedzieć, gdzie się jest samemu. Druga rzecz to kopie i procedura odtwarzania. Tailscale przeżył ten rok dlatego, że miał częste kopie, monitor sprawdzający ich spójność i przećwiczone odtwarzanie. Pisaliśmy wcześniej, co się dzieje, gdy firma opiera ciągłość działania na jednym elemencie, i to jest ta sama lekcja od innej strony. Trzecia rzecz dotyczy zależności. Jeśli najlepiej przetestowana biblioteka świata miała taki błąd przez szesnaście lat, to warto choćby wiedzieć, co dokładnie ciągniesz do projektu z zewnątrz.
Podsumowanie
Tailscale opisał 12 sierpnia 2026 roku, jak przez sześć miesięcy szukał przyczyny dziewiętnastu uszkodzeń bazy danych. Winny okazał się wyścig między punktem kontrolnym a zapisem w SQLite, obecny w kodzie od co najmniej szesnastu lat i nazwany błędem resetu WAL. Powodował, że zatwierdzone dane znikały bez żadnego komunikatu, a baza stawała się niespójna. Poprawka trafiła najpierw do wycofanego wydania 3.52.0, a ostatecznie do 3.51.3 z marca 2026 roku. Firma trafiła na ten błąd, bo ręcznie sterowała punktami kontrolnymi i robiła je bardzo często, choć była to konfiguracja udokumentowana i wspierana. Dla większości użytkowników SQLite ryzyko było i pozostaje znikome, na co najlepszym dowodem jest to, że twórcy musieli wywoływać ten wyścig sztucznie.
Newsletter · DevstockAcademy & Kodożercy
Bądź na bieżąco ze światem IT, AI i automatyzacji
Co wtorek: newsy z branży, praktyczne tipy i narzędzia które warto znać. Zero spamu.




