У SQLite странная репутация. Её охотно берут для прототипа, а перед запуском будто по ритуалу заменяют «на настоящую базу». Часто миграция оправданна. Но иногда команда получает сетевые сбои, отдельный процесс, резервное копирование и дежурства — хотя продукту всё это не было нужно.
SQLite — встраиваемая реляционная база. Она живёт в одном файле, исполняется внутри процесса приложения и при этом даёт транзакции, индексы, ограничения и большую часть привычного SQL. Отсутствие сервера здесь не недостаток, а другая архитектурная ставка.
Когда один файл — преимущество
Хороший кандидат — сервис с одним экземпляром приложения, умеренным числом записей и данными, которые естественно принадлежат этому экземпляру. Внутренние инструменты, небольшие API, очереди локальных заданий, каталоги, настольные приложения — всё это не требует сетевой базы автоматически.
- Развёртывание сводится к доставке приложения и каталога данных.
- Запрос не платит за сетевой переход к базе.
- Локальную копию просто поднять для теста или расследования.
- Транзакция остаётся настоящей транзакцией, а не соглашением в коде.
Простая эксплуатация — тоже производительность. Только измеряется она не миллисекундами, а количеством ночных инцидентов.
WAL меняет поведение под нагрузкой
В стандартном журналируемом режиме запись чаще мешает чтению. Режим write-ahead log переносит новые страницы в отдельный журнал: читатели продолжают видеть согласованный снимок, пока писатель работает. Для веб-приложения это почти всегда первая осмысленная настройка.
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
busy_timeout особенно важен. Без него короткое пересечение двух записей может сразу превратиться в ошибку блокировки. С тайм-аутом одна операция немного подождёт другую. Это не превращает SQLite в многописательную систему, но сглаживает нормальные пики.
Граница проходит по записи
Читателей может быть много, активный писатель в каждый момент один. Для блога, панели управления или сервиса с редкими изменениями это нормально. Для потока событий, общего хранилища нескольких сервисов или горизонтально масштабируемого приложения — уже нет.
Плохими признаками я считаю три вещи: несколько машин должны писать в одну базу, длительные транзакции неизбежны, а очередь записей растёт быстрее, чем диск успевает её разбирать. В этот момент PostgreSQL не «взрослее» — он просто лучше соответствует задаче.
Резервная копия — не копирование файла
Нельзя бездумно копировать открытый файл базы во время записи: можно получить несогласованный снимок, особенно рядом с WAL. Используйте встроенный backup API или команду VACUUM INTO, затем проверяйте, что копия действительно открывается. И да: копия на том же диске не является резервной.
Мой короткий чек-лист
- Оценить не размер базы, а профиль одновременных записей.
- Включить WAL, тайм-аут и внешние ключи при старте процесса.
- Ограничить размер пула соединений и длину транзакций.
- Настроить регулярную проверяемую резервную копию.
- Заранее записать сигнал для миграции: например, очередь записи или необходимость второго узла.
SQLite хороша не потому, что «выдерживает удивительно много». Она хороша, когда её ограничения совпадают с ограничениями продукта. В таком случае один файл — не компромисс, а честная и очень удобная архитектура.