SQLite concurrency and why you should care about it 🔥 Горячее 💬 Длинная дискуссия
—
Комментарии (153)
- SQLite не поддерживает параллельную запись; WAL-режим лишь разрешает параллельное чтение, но не пишущие транзакции.
- Проблема SQLITE_BUSY чаще всего возникает из-за длительных транзакций, которые не закрывают файл, и не из-за реальной конкуренции за доступ.
- Не стоит забывать, что SQLite — это встроенная СУБД, а не серверная СУБД, и что она не предназначена для высоконагруженных веб-приложений.
- В отличие от PostgreSQL, MySQL и других серверных СУБД, SQLite не поддерживает параллельную запись, и поэтому не может быть использована в ситуациях, где требуется высокая параллельность.
Is Postgres read heavy or write heavy?
PostgreSQL может быть как чтением, так и записью интенсивной в зависимости от бизнес-логики приложения. Для социальных сетей характерно чтение интенсивное, а для IoT логгеров — запись интенсивная. Определение профиля нагрузки критично для эффективной настройки: чтение интенсивные БД выигрывают от индексации, кэширования запросов и реплик, тогда как запись интенсивные требуют оптимизации хранилищ, управления WAL и дизайна таблиц.
Чтения и записи в PostgreSQL не равны по стоимости: чтение происходит 8kb блоками, часто из памяти, в то время как запись включает WAL, индексы, TOAST таблицы и требует больше ресурсов. Автор предлагает запрос для оценки соотношения чтения/записи на основе внутренних метаданных PostgreSQL, где по умолчанию используется соотношение 5:1 (чтение:запись).
Комментарии (46)
- Обсуждение критикует статью за то, что она не сравнивает PostgreSQL с другими СУБД и не дает практических советов по тюнингу под конкретную нагрузку.
- Участники обсуждают, что статья не учитывает, что большинство приложений имеют смешанную нагрузку на чтение и запись, а не чисто чтение или запись.
- Некоторые комментаторы отмечают, что статья не упоминает OLTP и OLAP, что делает ее менее полезной для практического использования.
- Также обсуждается, что статья не дает ясного определения, что считается "read-heavy" или "write-heavy" в контексте PostgreSQL.
- Наконец, участники обсуждают, что статья не предоставляет конкретных советов по тюнингу PostgreSQL под конкретную нагрузку.
Rails on SQLite: new ways to cause outages
Rails + SQLite: новые способы уронить прод
SQLite встроен в процесс веб-сервера — нет отдельного демона, портов, сокетов; всё хранится в одном файле. Плюс: пропали ошибки подключения к БД. Минус: файл живёт в контейнере, а контейнеры пересоздают, и данные исчезают.
Правило 1: клади БД в персистентное хранилище (EBS, Fly Volumes, …) и включи снапшоты.
Правило 2: веб, кеш, очередь и джобы по умолчанию пишут в тот же файл. Удобно, но воркеры теперь должны видеть этот файл. Запускай воркеры в том же VM, либо разнеси данные по разным БД и настрой database.yml.
Правило 3: SQLite блокирует всю БД на время записи. Параллельные длинные запросы = таймауты. Держи транзакции короткими, используй PRAGMA journal_mode=WAL, synchronous=NORMAL, busy_timeout=5000.
Правило 4: бекапы. sqlite3 db.sqlite3 ".backup backup.sqlite3" — атомарно, без остановки сервиса. Крути каждый час и перед деплоем.
Плюсы:
- FTS5-индекс из коробки
- Мегабайты вместо гигабайтов RAM
- $14/мес на Fly.io при 1 млн запросов
- Нет Redis, Postgres, S3 — только Rails-контейнер
Итог: SQLite позволяет поднять pet-project за вечер, но требует новых привычек: персистентные диски, WAL, короткие транзакции, общий доступ к файлу. Соблюдай правила — и база не уйдёт в /dev/null.
Комментарии (55)
- Автор статьи утверждает, что его сервис «Feed Your Email» возможен только благодаря SQLite, но не объясняет, почему именно SQLite, а не PostgreSQL/MySQL.
- Многие участники считают SQLite удобным для малонагруженных и внутренних приложений из-за простоты развёртывания и отсутствия отдельного процесса БД.
- Критики отмечают: при росте нагрузки появляются проблемы с бэкапами, масштабированием, единственным писателем и отказоустойчивостью, смывая преимущества.
- Часть разработчиков использует обёртки вроде Litestream, Turso или Cloudflare D1, чтобы добавить репликацию и горизонтальное масштабирование к SQLite.
- В сообществе Rails новый тренд — «по-умолчанию SQLite» для быстрого старта MVP, но опытные пользователи предупреждают о риске «выстрелить себе в ногу» при росте проекта.
SQLite's File Format
Формат файла SQLite
SQLite хранит всё состояние БД в одном файле.
Размер страницы — степень двойки 512–65 536 байт; номера страниц начинаются с 1, максимум 2³²-2.
Первая страница (512–100 байт) — заголовок:
- 16 байт: «SQLite format 3\000»
- 2 байт: размер страницы
- 1 байт: версия формата, 1 байт: версия для записи
- 1 байт: резервировано под расширения
- 1 байт: макс. доля переполнения
- 4 байт: счётчик изменений
- 4 байт: размер БД (страниц)
- 4 байт: свободные страницы
- 4 байт: схема-cookie
- 4 байт: формат схемы
- 4 байт: рекомендованный кэш
- 4 байт: vacuum-настройки
- 4 байт: кодировка текста
- 4 байт: пользовательская версия
- 4 байт: ID приложения
- 4 байт: версия библиотеки, 4 байт: «valid-for»
- 20 байт: резерв
Страницы бывают:
- b-tree (таблица/индекс, внутр./лист)
- freelist (trunk/leaf)
- переполнение payload
- pointer-map (ptrmap)
Схема
SQL-таблицы → b-tree, строки — последовательность записей (varint длина + payload).
WITHOUT ROWID → ключ в b-tree, дубли в PK убираются.
Индексы → отдельный b-tree, ключи с rowid.
Системные таблицы: sqlite_master (DDL), sqlite_sequence, sqlite_stat1-4.
Журналы
Rollback-journal: старая страница + заголовок.
WAL: кадры с новыми страницами, контрольная сумма, индекс в памяти.
Комментарии (77)
- SQLite хвалят за идею «один файл = база» — она перекрывает недостатки форматов и SQL.
- Формат задокументирован до байта, работает по HTTP-диапазонам и читается с NFS, если только чтение.
- Предел 281 ТБ пока недостижим; реальные ограничения — файловая система и сбои железа.
- Повреждение страницы убивает весь файл, починить нельзя — нужна обратная совместимость.
- Для «пишем один раз» советуют Parquet + DuckDB: колонковый, неизменяемый, читается любым движком.
SQLite's documentation about its durability properties is unclear
SQLite и надёжность: бардак в настройках
Надёжность (durability) — гарантия, что после COMMIT изменения сохранятся даже при падении ОС или отключении питания. На Linux это обеспечивается вызовом fsync. Цена — производительность, поэтому СУБД дают «ручки» для настройки. Главное, чтобы документация чётко объясняла, что включено по умолчанию.
У SQLite всё запутано. Два ключевых параметра:
journal_mode(DELETE | WAL …)synchronous(EXTRA, FULL, NORMAL, OFF)
По документации:
- По умолчанию
journal_mode=DELETE,synchronous=FULL. - В режиме DELETE
FULLне гарантирует надёжность; нуженEXTRA. - В режиме WAL
FULLуже достаточно.
Вывод: «из коробки» SQLite не надёжна; переключившись на WAL — становится.
Однако Ричард Хипп (автор SQLite) в комментарии на HN утверждает прямо противоположное:
- «В конфигурации по умолчанию SQLite надёжна».
- «Если включить WAL, по умолчанию надёжность теряется».
Документация и автор расходятся.
Дополнительные ловушки:
- Обертки могут переопределять
synchronous. Популярный драйвер Go для SQLite ставитNORMALв WAL-режиме — надёжности нет. - На macOS
fsyncработает не как на Linux, что тоже снижает гарантии.
Итого: чтобы быть уверенным, явно задайте
PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;
Комментарии (51)
- Участники спорят, что «durability» в SQLite зависит от комбинации journal_mode и synchronous, а не от единого определения.
- По-умолчанию (DELETE + FULL) SQLite не гарантирует durability при сбое ОС/питания, потому что fsync не всегда успевает закрепить журнал.
- WAL по умолчанию защищает от краха приложения, но не от краха ОС; для полной durability нужно synchronous=FULL.
- Некоторые считают документацию ясной, другие — запутанной и просят примеров выбора режимов.
- Поднимаются вопросы надёжности fsync, файловых систем и дисков, а также желание «SQLite 4» с новыми умолчаниями.
SQLite (with WAL) doesn't do `fsync` on each commit under default settings
SQLite в режиме WAL по умолчанию не вызывает fsync после каждого COMMIT.
- Параметр
PRAGMA synchronous=NORMAL(значение по умолчанию) не гарантирует сохранность транзакции при внезапном отключении питания. - В этом режиме
fsyncвыполняется лишь:
– перед контрольной точкой WAL;
– после завершения контрольной точки;
– при повторном использовании WAL-файла. - Для жёсткой гарантии сохранности нужно:
Тогда после каждогоPRAGMA synchronous = FULL;COMMITбудет вызыватьсяfsyncWAL-файла.
Комментарии (70)
- По умолчанию SQLite компилируется с
synchronous=FULL, но дистрибутивы или обёртки могут изменить это. - Не стоит полагаться на умолчания — явно задавайте параметры, особенно если нужна надёжность.
- WAL-режим ускоряет работу, но требует общей памяти и нарушает ACID для attached БД.
- На macOS для гарантированной надёжности нужен
F_FULLFSYNC, но Apple использует собственную реализацию. - Litestream рекомендует
synchronous=NORMAL, так как и так делает регулярные бэкапы.