У 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, затем проверяйте, что копия действительно открывается. И да: копия на том же диске не является резервной.

Мой короткий чек-лист

  1. Оценить не размер базы, а профиль одновременных записей.
  2. Включить WAL, тайм-аут и внешние ключи при старте процесса.
  3. Ограничить размер пула соединений и длину транзакций.
  4. Настроить регулярную проверяемую резервную копию.
  5. Заранее записать сигнал для миграции: например, очередь записи или необходимость второго узла.

SQLite хороша не потому, что «выдерживает удивительно много». Она хороша, когда её ограничения совпадают с ограничениями продукта. В таком случае один файл — не компромисс, а честная и очень удобная архитектура.