Марко Манино и Альберто Карретеро из команды dqlite в Canonical разобрали старый баг в SQLite, который приводил к повреждению базы данных. Ошибка сидела в коде с 2010 года — 16 лет. На практике она проявлялась редко, но её сложно было найти и воспроизвести. Чтобы понять механизм, инженеры смоделировали поведение SQLite на языке TLA+.
Суть бага связана с механизмом Write Ahead Log (WAL). SQLite использует его, чтобы читатели не блокировали писателей. Данные сначала попадают в WAL — специальную staging-область. Потом их переносят в основную БД — это называется checkpoint. WAL периодически сбрасывают (reset), чтобы он не рос бесконечно. Всё это управляется блокировками: CKPT_LOCK для checkpoint'ов и WRITE_LOCK для записи. Проблема возникла из-за состояния гонки. После завершения одного checkpoint'а запускался второй. Пока он стартовал, другой процесс успевал сбросить WAL и записать новые данные. Второй checkpoint не замечал сброса, из-за race condition выставлял неверный флаг в заголовке WAL-Index, а позже третий checkpoint пропускал часть транзакции. В итоге часть данных терялась, файл БД повреждался. TLA+ воспроизвел баг всего за 20 состояний.
Главный вопрос команды dqlite — касается ли это их продукта. dqlite использует SQLite, но координирует записи через Raft. Поэтому у него более жёсткие правила: пользовательские checkpoint'ы заблокированы, автоматические отключены, а во время настоящего checkpoint'а блокируется всё — dqlite просто не даёт выполняться никаким другим операциям. В модели инженеры добавили захват WRITE_LOCK перед началом checkpoint'а. Повторная проверка показала: dqlite не подвержен этому багу, потому что запись и checkpoint не могут выполняться одновременно.
SQLite опубликовал исправление 5 марта 2026 года — достаточно было добавить одну дополнительную проверку, чтобы избежать той самой гонки.