LINUX.ORG.RU

SQLite 3.51.3 с исправлением возможного повреждения базы данных в режиме WAL

 , ,

SQLite 3.51.3 с исправлением возможного повреждения базы данных в режиме WAL

0

2

13-го марта состоялся корректирующий выпуск 3.51.3 компактной встраиваемой СУБД SQLite. Код проекта написан на языке C и распространяется как общественное достояние (public domain).

В связи с проблемами обратной совместимости, связанными с некоторыми новыми функциями, версия 3.52.0 (вышедшая 6-го марта) была отозвана.

Список изменений версии 3.51.3:

  • 3-го марта один из разработчиков SQLite (Dan) обнаружил и устранил ошибку, которая в редких случаях могла приводить к повреждению базы данных и названная «WAL-reset bug»:
    • Эта ошибка возникает только в базах данных, работающих в режиме WAL, если к одному и тому же файлу открыто два и более подключения в разных потоках или процессах, и если эти два подключения пытаются выполнить запись или создать контрольную точку одновременно.
    • Ошибка связана с конфликтом доступа к данным при жёстких временных ограничениях. В обычных условиях эксплуатации она возникает крайне редко. Разработчикам так и не удалось воспроизвести эту ошибку в естественных условиях, и им пришлось добавить в SQLite специальную логику тестирования, которая намеренно создает условия для возникновения ошибки, чтобы убедиться в том, что проблема устранена.
    • Ошибка возникает, когда:
      1. Одно соединение выполняет проверку контрольной точки. Эта первая проверка должна быть завершена. Другими словами, в ходе проверки необходимо успешно скопировать всё содержимое файла WAL обратно в базу данных и привести файл WAL в состояние, при котором его можно будет сбросить.
      2. Сразу после завершения работы первой контрольной точки запускается вторая.
      3. Пока запускается вторая контрольная точка из шага 2, другое подключение к базе данных фиксирует транзакцию, которая сбрасывает файл WAL и записывает новое содержимое в начало файла WAL.
      4. Из-за конфликта доступа к данным вторая контрольная точка из шага 2 не учитывает, что файл WAL был сброшен в результате фиксации транзакции на шаге 3. Вторая контрольная точка устанавливает неверное значение для одного из полей в заголовке индекса WAL. Это поле указывает, что часть файла WAL уже была зафиксирована в контрольной точке, хотя на самом деле это не так.
      5. Фиксация дополнительных транзакций приводит к увеличению количества страниц в файле WAL, которое превышает количество страниц, существовавшее на момент первой контрольной точки из шага 1.
      6. Позже, когда возникает третья контрольная точка, она пропускает всю или часть транзакции, записанной на этапе 3. Таким образом, части транзакции с этапа 3 так и не попадают в файл базы данных, в результате чего файл базы данных повреждается.
    • Ошибка, вероятно, присутствует во всех версиях SQLite, начиная с 3.7.0 (21.07.2010) и заканчивая 3.51.2. Также выпущены исправления для некоторых более ранних версий: 3.44.6 и 3.50.7.
  • Другие незначительные исправления.

>>> Страница загрузки

>>> Подробности на sqlite.org

★★★★★

Проверено: shell-script ()
Последнее исправление: dataman (всего исправлений: 2)

Я просто ещё раз напоминаю, почему ошибки в многопоточных-многопроцессовых-распределённых системах невозможно отследить тестированием. 15 лет софтина крутилась в проде с багом, и наверняка все происходящие ошибки списывались на «повреждение накопителя».

Варианта два: либо вычитывать сорцы и формально доказывать корректность логики; либо ставить палки во все потенциальные гонки, чтобы логика проводила в них 20% времени, как не 0.001% как обычно.

byko3y ★★★★
()

если к одному и тому же файлу открыто два и более подключения в разных потоках или процессах

…то не надо пытаться из хорошей работающей локальной СУБД делать убогое и калечное подобие клиент-серверной! Одного Access-a должно было хватить, чтобы это понять, зачем ещё SQLite во второй Access превращать?

Нужно несколько клиентов – берите хоть Марию, хоть Постгрю, для эстетов :) есть Firebird. Если хочется масштабируемости – есть разные рабочие способы обеспечить работу с разными СУБД в одном проекте. Из открытых примеров назову, например, OpenSimulator, там есть поддержка SQLite и трёх клиент-серверных продуктов (два из них, правда, родственники, но тем не менее).

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

делать убогое и калечное подобие клиент-серверной!

Не удивлюсь, если очень многие используют многотопоточную запись в базы SQLite, когда хватило бы и KVDB, которых достаточно много.

dataman ★★★★★
() автор топика
Ответ на: комментарий от hobbit

Бывает нужно, чтобы БД была в том же процессе.
Что касается многопоточного доступа, то это решается внешними RWLock`ами. Полагатся на встроеные блокировки SQLite не стоит.

MirandaUser2 ★★
()
Ответ на: комментарий от hobbit

…то не надо пытаться из хорошей работающей локальной СУБД делать убогое и калечное подобие клиент-серверной! Одного Access-a должно было хватить, чтобы это понять, зачем ещё SQLite во второй Access превращать?

Ну всё-таки MS Access работает по совершенно иной архитектуре многопользователя. SQLite чисто теоретически полностью блокирует БД при записи, но есть ньюанс...

Нужно несколько клиентов – берите хоть Марию, хоть Постгрю, для эстетов :) есть Firebird.

Мы брали. Я не особо впечатлён был. Довольно часто возникают задачи full scan, которые через сетевой слой плохо пропихиваются, нужно так или иначе расширять серверную часть, чтобы меньше гонять по сети.

Как ни странно, модель эксклюзивной блокировки работает намного быстрее в однопользовательском режиме. MVCC СУБД с транзакциями нужны для длительно выполняющихся пишущих транзакций — если у вас таких нет, то зачем вам мария?

WAL в SQLite нужен хотя бы для того, чтобы можно было писать, не блокируя читателя (даже одного), и при этом иметь консистентный снимок в этом самом читателе. Иначе просто нельзя читать базу, если, боже упаси, какая-то транзакция окажется медленной.

byko3y ★★★★
()

тут просто отсутствие понимания как в мультитредах работать или реально разрабов ткнули в их говно. или single process - single database не взлетело, что сомнительно.

etwrq ★★★★★
()
Ответ на: комментарий от etwrq

тут просто отсутствие понимания как в мультитредах работать или реально разрабов ткнули в их говно. или single process - single database не взлетело, что сомнительно.

Оно-то круто звучит — держать блокировку несколько секунд, пока вся запись на диск не закончится, но в реальной жизни такой профиль очень неприятен в эксплуатации. Попытались оптимизировать — получили неожиданные гонки.

Для справки, я напоминаю, что хоть в WAL, хоть в Rollback log режиме запись в БД — это не просто один write, это большой и сложный процесс.

byko3y ★★★★
()

версия 3.52.0 (вышедшая 6-го марта) была отозвана.

Скачавшие её были вычислены по IP и расстреляны.

BydymTydym ★★
()
Ответ на: комментарий от byko3y

15 лет софтина крутилась в проде с багом, и наверняка все происходящие ошибки списывались на «повреждение накопителя».

Конкурентная запись в sqlite отродясь хреново работает, хоть с wal хоть без него. Поэтому вероятность дойти до прода у такого решения на несколько порядков ниже, чем наткнуться на этот конкретный баг.

Lucky ★★
()
Последнее исправление: Lucky (всего исправлений: 1)

Яркий пример, как некорректные системы тем не менее генерят прибыль.

seiken ★★★★★
()

По крайней мере эта ошибка не проявилась при нагрузочном тестировании. А в реальных условиях ожидается:

However, this is not an emergency. Based on available telemetry, the occurrence rate of this problem in the wild appears to be less than or equal to the expect occurrence rate of SSD malfunctions and/or cosmic-ray hits.

gag ★★★★★
()
Ответ на: комментарий от Lucky

Конкурентная запись в sqlite отродясь хреново работает, хоть с wal хоть без него. Поэтому вероятность дойти до прода у такого решения на несколько порядков ниже, чем наткнуться на этот конкретный баг.

На каких нагрузках конкурентная запись в SQLite работает плохо?

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Это чего там надо писать несколько секунд? Если запись занимает дольше доли секунды, значит sqlite используется не по назначению. А долю секунды юзер может и подождать.

vbr ★★★★★
()
Ответ на: комментарий от byko3y

15 лет софтина крутилась в проде с багом, и наверняка все происходящие ошибки списывались на «повреждение накопителя».

Ты читал источник?

Based on available telemetry, the occurrence rate of this problem in the wild appears to be less than or equal to the expect occurrence rate of SSD malfunctions and/or cosmic-ray hits. So even if you are running an unpatched version of SQLite, and unless you are doing something really unusual, you are unlikely to ever encounter this problem.

shdown
()
Ответ на: комментарий от byko3y

На каких нагрузках конкурентная запись в SQLite работает плохо?

но ведь там вообще нет конкурентной (в смысле одновременной) записи. И что хуже одновременного чтения-записи, даже если оно производится на разных таблицах :(
Достаточно одного «тяжелого» SELECT, и запись встала.

Да, есть WAL, но он сам по себе просаживает производительность запросов на чтение. Т.е. если важна производительность запросов на чтение, то толку от записи в WAL немного. Кроме того, WAL не работает на сетевой ФС.

MirandaUser2 ★★
()
Ответ на: комментарий от MirandaUser2

Эта история звучит как «не машину, а квартиру, и не в лотерею, а в покер; и не выиграл, а проиграл».

Так всё-таки есть одновременная запись-чтение или нет? Но как это нет, если есть WAL? Просаживает чтение? Опять же, на каких нагрузках? При отсутствии записей после PRAGMA wal_checkpoint(TRUNCATE) в режимах WAL и Rollback основной файл БД полностью идентичен - как WAL может что-то просаживать?

byko3y ★★★★
()
Ответ на: комментарий от byko3y

При отсутствии записей после PRAGMA wal_checkpoint(TRUNCATE) в режимах WAL и Rollback основной файл БД полностью идентичен - как WAL может что-то просаживать?

после того, как произведен checkpoint никакого WAL уже нет, и очевидно он ничего не «просаживает». До чекпойнта SELECT оперирует как с данными основного файла, так и с данными из WAL. Работа с данными из WAL - медленнее. Замечу также, что сама операция чекпойнта тяжелая (пропорционально размеру WAL).

Просаживает чтение? Опять же, на каких нагрузках?

Тут наверное лучше говорить о размерах БД: в диапазоне 1-10GB + сопоставимого размера WAL.

Так всё-таки есть одновременная запись-чтение или нет?

Есть, но на практике оно ограничено применимо.
Если у тебя операции чтение/записи короткие, то конкурентный доступ с записью (и WAL) тебе особо и не нужен. Даже сериализованные (последовательно выполняемые) запросы покажут приемлимую производительность.
Если же у тебя в сценариях длительный SELECT в сочетании с интенсивным INSERT (т.е. требуется конкурентный доступ с записью), то просто положится на автоматику WAL не выйдет - нужно следить за его размером, мерять просадку производительности SELECT, подбирать параметры чекпойнтов. См. https://sqlite.org/wal.html#avoiding_excessively_large_wal_files

MirandaUser2 ★★
()
Ответ на: комментарий от MirandaUser2

Тут наверное лучше говорить о размерах БД: в диапазоне 1-10GB + сопоставимого размера WAL.

За пределами процессов миграции это нездоровая конфигурация. Если не чекпоинтить SQLite, то он, действительно, начинает работать отвратительно.

Неочевидный факт: серверные СУБД решают эту проблему при помощи огромных кэшей, из-за чего на самом деле не могут работать эффективно на системе с маленькой оперативкой.

Если у тебя операции чтение/записи короткие, то конкурентный доступ с записью (и WAL) тебе особо и не нужен.

Нужен. Если у тебя много операций чтения, то они могут сильно мешать записи. Если в режиме WAL у тебя параллельные чтения блокирую только чекпоинт, то в режиме Rollback Log они блокируют записи вообще.

Даже сериализованные (последовательно выполняемые) запросы покажут приемлимую производительность.

Если у тебя много вставок, то их намного эффективнее делать одной транзакцией. Это актуально и для Rollback, и для WAL, потому что индексы перестраиваются раз в транзакцию.

Если же у тебя в сценариях длительный SELECT в сочетании с интенсивным INSERT

«Длительный SELECT» уже настораживает. Вы там не джоины по десяти таблицам делаете? Глубокая нормализация уже давно стала моветоном.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

За пределами процессов миграции это нездоровая конфигурация.

ну я примерно о том же - нужно следить за размерами WAL.

Неочевидный факт: серверные СУБД решают эту проблему при помощи огромных кэшей, из-за чего на самом деле не могут работать эффективно на системе с маленькой оперативкой.

Действительно неочевидный. Зачем кэш в оператике, если обычно достаточно TABLE_LOCK или ROW_LOCK? И разработчику гораздо проще проектировать схему БД и запросы, зная что БД умеет хотя бы TABLE_LOCK.

Нужен. Если у тебя много операций чтения, то они могут сильно мешать записи. Если в режиме WAL у тебя параллельные чтения блокирую только чекпоинт, то в режиме Rollback Log они блокируют записи вообще.

ну так блокировка чекпойнта reader`ами это тоже плохо. WAL будет расти. Это просто откладывание проблемы на потом. Нужно разбираться с источниками проблемы, т.е. применять такой внешний RWLock, который не позволит reader`ам постоянно мешать записи/чекпойнту.
(из ссылки, которую давал ранее)

However, if a database has many concurrent overlapping readers and there is always at least one active reader, then no checkpoints will be able to complete and hence the WAL file will grow without bound. 

Если у тебя много вставок, то их намного эффективнее делать одной транзакцией. Это актуально и для Rollback, и для WAL, потому что индексы перестраиваются раз в транзакцию.

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

«Длительный SELECT» уже настораживает. Вы там не джоины по десяти таблицам делаете?

угу JOIN. Таблиц не 10, но большие, и размер результата сопоставимый.

Глубокая нормализация уже давно стала моветоном.

денормализовать не выйдет. Данных много. Собственно для той задачи PoC на базе SQLite не взлетел.

MirandaUser2 ★★
()
Ответ на: комментарий от MirandaUser2

Действительно неочевидный. Зачем кэш в оператике, если обычно достаточно TABLE_LOCK или ROW_LOCK? И разработчику гораздо проще проектировать схему БД и запросы, зная что БД умеет хотя бы TABLE_LOCK.

Да ещё проще ничего не проектировать и писать данные JSON-ом в один файл. Без шуток, какой-нибудь LokiJS именно так и делает.

ну так блокировка чекпойнта reader`ами это тоже плохо. WAL будет расти. Это просто откладывание проблемы на потом.

Достаточно раз в десять минут стопнуть всех читателей на десять миллисекунд. Авось никто не умрёт.

применять такой внешний RWLock, который не позволит reader`ам постоянно мешать записи/чекпойнту.

Достаточно остановить весь доступ к БД самыми простыми механизмами. Здесь, опять же, смотри на мои комментарии про длительных читателей.

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

sqlite3_interrupt()
Всё остальное — полумеры.

угу JOIN. Таблиц не 10, но большие, и размер результата сопоставимый.

Ну такими приколами можно и постргю положить, и оракл. Причём, в них обоих если ты вывалился из кеша, то восстановление может занимать кучу времени.

Вон, SAP предлагает решать проблему применением сервера с 2000 Гб оперативы.

byko3y ★★★★
()

«Пацан сказал - пацан сделал!» :))

Спасибо! :)

Somebody ★★★★
()
Ответ на: комментарий от byko3y

«Длительный SELECT» уже настораживает. Вы там не джоины по десяти таблицам делаете? Глубокая нормализация уже давно стала моветоном.

А что принято делать сейчас? Дублировать данные в специальных таблицах под запросы?

BruteForce ★★★★
()
Ответ на: комментарий от BruteForce

А что принято делать сейчас? Дублировать данные в специальных таблицах под запросы?

Да хоть бы и дублировать данные. Естественно, зависит от того, насколько большие данные и насколько проблематично это дублирование в целом.

Далее, MVCC никто не запрещает делать явно, то есть, буквально писать разные версии одной и той же записи в базу.

Потом, крайне редко действительно нужен большой цельный согласованный список всех сущностей. Есть аргумент «а вот бухгалтерия» — а вот не нужно бухгалтерию делать на основе счетов, её нужно делать на основе перемещений, при этом итоги высчитывать как сумму перемещений.

byko3y ★★★★
()
Ответ на: комментарий от hobbit

если к одному и тому же файлу открыто два и более подключения в разных потоках или процессах

то не надо пытаться из хорошей работающей локальной СУБД делать убогое и калечное подобие клиент-серверной!

Ну многопоток в локальном ПО никто не отменял. К тому же возможен запуск нескольких экземпляров одной и той же программы.

Rodegast ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.