В SQLite нашли баг, который 16 лет мог повреждать базы данных

Бэкап, который ломает то, что должен спасать.


rsovacibimxeq1dwpjzvh7rrkgltlpvi.jpg

Несколько потерянных страниц базы данных нарушили работу целой VPN-инфраструктуры. Специалисты Tailscale потратили полгода на поиски причины неожиданных сбоев и обнаружили редкую ошибку SQLite, остававшуюся незамеченной с 2010 года.

Tailscale рассказала о завершении расследования после серии повреждений баз данных, начавшейся в августе 2025 года. Для поиска неисправности разработчикам потребовалась помощь создателей SQLite и новый диагностический инструмент, подробно записывающий операции с файлами.

Сервис Tailscale создает частные одноранговые сети на базе протокола WireGuard и напрямую соединяет компьютеры, серверы, сетевые хранилища и другие устройства. Каждая частная сеть, которую компания называет tailnet, работает на одном из нескольких серверов. Информацию о подключениях хранит база SQLite, используемая Tailscale с 2022 года.

Система резервного копирования каждые несколько минут создавала полный снимок базы и загружала файл SQLite в хранилище S3. В августе 2025 года во время подготовки копий начали регулярно появляться сообщения о повреждении данных. Общего условия для возникновения сбоя найти не удавалось, а низкоуровневый код к тому моменту не менялся уже несколько месяцев.

Инженеры проверили все компоненты, обращавшиеся к SQLite, но первоначальный аудит не выявил причину. Команда последовательно исключила нарушение блокировок POSIX после вызовов close(), ошибки управления памятью и работу с базой из нескольких потоков при отключенной потокобезопасности. После каждого нового случая специалисты расширяли диагностику и собирали дополнительные сведения.

Подозрение постепенно перешло на механизм контрольных точек SQLite. Для ускорения работы база сначала записывает изменения в отдельный журнал упреждающей записи, известный как Write-Ahead Log или WAL. Во время создания контрольной точки накопленные страницы переносятся из журнала в основной файл базы данных.

При обычной работе SQLite самостоятельно выбирает время для запуска контрольной точки. Tailscale управляла процессом вручную, чтобы быстрее создавать согласованные резервные копии. Нестандартный и довольно интенсивный режим повысил вероятность крайне редкого совпадения операций.

При финансовой поддержке Tailscale разработчики SQLite создали специальную прослойку виртуальной файловой системы , которая подробно регистрировала работу контрольных точек. После очередного повреждения журнал помог обнаружить ошибку, получившую название WAL-Reset .

Причиной оказалась гонка между созданием контрольной точки и транзакцией записи. Если новая запись появлялась в строго определенный момент, SQLite ошибочно считала, что часть страниц уже перенесена из WAL в основной файл. Фактически страницы оставались неперенесенными, а затем безвозвратно терялись. Одновременно база сохраняла другие страницы со ссылками на утраченные данные, что приводило к повреждению всей структуры.

Ошибка возникает только при одновременном выполнении нескольких условий: активном режиме WAL, нескольких открытых подключениях к одному файлу и точном совпадении операций чтения и записи. В стандартных конфигурациях вероятность сбоя остается крайне низкой, однако ручное и частое создание контрольных точек в инфраструктуре Tailscale заметно повышало риск.

По оценке разработчиков SQLite, дефект присутствовал в коде начиная с версии 3.7.0, выпущенной в июле 2010 года. Исправление уже подготовлено, а пользователям рекомендовали перейти на обновленную версию. Вероятность столкновения с WAL-Reset невелика, но последствия включают необратимую потерю страниц и повреждение базы данных.