В конце прошлого года аптайм Tailscale сильно просел, и причина оказалась в редком баге глубоко внутри SQLite. Несколько месяцев ушло на его поиски, но сейчас компания уверена, что нашла и исправила проблему. Клиенты несколько месяцев страдали от перебоев, и Tailscale приносит извинения в блоге, подробно объясняя, что пошло не так.
Контрольная плоскость Tailscale разделена на координационные серверы-шарды, и у каждого шарда своя база SQLite, к которой обращается один Go-процесс. Это штатный сценарий — одиночный писатель. SQLite выбрали в 2022 году как надёжную «скучную технологию». Бэкапы делались снапшотом каждые несколько минут и загружались в S3. В августе один из пайплайнов, читающий эти бэкапы, обнаружил повреждение базы. PRAGMA integrity_check подтвердила коррупцию. За полгода случилось 19 таких инцидентов.
Повреждения приводили к потере метаданных — новых устройств или изменений конфигурации. Часть данных приходилось вводить заново. Пока шёл ремонт, контрольная плоскость шарда была недоступна: устройства не могли подключиться, а онлайн-устройства теряли связь с админкой и Tailscale API. При этом данные ключей и трафик не пострадали.
Найти баг было сложно. Никаких общих факторов между инцидентами не нашлось, не было и триггера для воспроизведения. Пришлось разворачивать пассивную диагностику в проде. Затем Tailscale заключила платный контракт поддержки с разработчиками SQLite. Строили и проверяли теории: сломанные POSIX-локи, неверное управление памятью, потоки. Параллельно автоматизировали восстановление: shard жёстко останавливался при коррупции, бэкапы постоянно мониторились, время реакции сократили до часа.
Неожиданный ключ дал собственный пайплайн журналирования транзакций. В двух случаях лог не воспроизводился: записанные и закоммиченные данные становились невидимы для более поздних транзакций. Заподозрили checkpoint. Разработчики SQLite написали отладочную обёртку над виртуальной файловой системой — tmstmpvfs shim. Развернув её, компания поймала следующий инцидент, и баг нашёлся. Это редкая гонка между checkpoint и записью: при определённом тайминге checkpoint думал, что страницы скопированы из WAL в основную базу, хотя на самом деле нет. Данные терялись, файл портился. Баг назвали «WAL-Reset bug», он жил в SQLite как минимум 16 лет. Фикс вышел в SQLite 3.52.0, но прикатился с другим багом: из-за изменения округления чисел с плавающей точкой битые индексы на вычисляемых значениях стали выглядеть как коррупция. Релиз отозвали и заменили на 3.51.3 с исправлением WAL-Reset. Tailscale понизила точность меток времени до целых секунд, а в SQLite 3.53.0 добавили самочинящиеся индексы.
Чтобы убедиться, что гонка действительно была причиной, компания добавила в свой драйвер предупреждение о пересечении записи и сброса WAL. Оно сработало через два месяца, а затем ещё четыре месяца прошло без инцидентов. В итоге: стандартные конфигурации SQLite надёжны, но нестандартное использование даже скучной технологии — риск. Tailscale забрала в финал десятки исправлений, улучшенный бэкап и бесплатный вклад в open-source SQLite.