Архитектура и данные

PostgreSQL или SQLite: какую базу данных выбрать для веб-проекта и когда простота становится ограничением

При старте нового веб-проекта вопрос базы данных часто звучит слишком просто:

Что выбрать — PostgreSQL или SQLite?

После этого обычно появляются два противоположных мнения.

Первое:

SQLite подходит только для тестов и маленьких приложений. Для настоящего production нужен PostgreSQL.

Второе:

Зачем поднимать отдельный сервер БД, если SQLite прекрасно хранит всё в одном файле?

Оба утверждения слишком упрощают картину.

SQLite — не «облегчённый PostgreSQL».

PostgreSQL — не «SQLite для больших проектов».

У этих систем разная архитектурная идея.

Официальная документация SQLite прямо отмечает, что SQLite не совсем корректно рассматривать как прямого конкурента PostgreSQL и другим клиент-серверным СУБД. PostgreSQL ориентирован на централизованное многопользовательское хранилище, concurrency и управление общими данными. SQLite — на локальное встроенное хранилище с минимальной эксплуатационной сложностью.

Поэтому правильный вопрос звучит иначе:

какая из этих архитектур лучше соответствует конкретному продукту?

Разберём это на примерах.


Главное различие видно ещё до первого SQL-запроса

Представим SQLite.

Приложение открывает файл:

app.db

и работает с ним напрямую:

Application
    ↓
SQLite library
    ↓
app.db

Отдельного процесса базы данных нет.

SQLite встроен непосредственно в приложение и читает или записывает database file на диске. Именно поэтому его называют serverless в классическом смысле: отдельный database server устанавливать и администрировать не требуется.

PostgreSQL устроен иначе:

Application
    ↓
TCP / Unix socket
    ↓
PostgreSQL Server
    ↓
Database files

Приложение является клиентом.

PostgreSQL — отдельным серверным процессом, который принимает соединения, выполняет запросы, управляет транзакциями и координирует параллельную работу клиентов. В актуальной документации PostgreSQL описывается именно клиент-серверная модель соединений.

Это различие определяет почти всё остальное.


Почему SQLite настолько удобен

Допустим, мы разрабатываем внутренний инструмент.

Нужно хранить:

settings
projects
notes
history

С SQLite не требуется:

устанавливать PostgreSQL;
создавать database user;
открывать порт;
настраивать service;
следить за connection pool.

Приложению достаточно иметь доступ к файлу:

data/app.sqlite

Для небольшого приложения это очень сильное преимущество.

Deployment может выглядеть так:

Application
+
app.sqlite

И всё.


Базу можно буквально перенести одним файлом

Это особенно удобно для:

desktop-приложений;
локальных утилит;
мобильных приложений;
offline-first клиентов;
внутренних инструментов;
небольших односерверных сервисов.

Формат SQLite специально рассчитан на такое локальное использование. Официальная документация приводит в качестве типовых сценариев desktop-приложения, embedded-системы, локальные кэши и даже серверные приложения с подходящей моделью нагрузки.


Поэтому фраза «SQLite не годится для production» неверна

SQLite вполне способен работать в production.

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

Но здесь есть важная оговорка.

Количество HTTP-запросов само по себе почти ничего не говорит о пригодности базы.

Намного важнее:

сколько одновременных записей;
как долго длятся транзакции;
сколько процессов работают с БД;
нужно ли несколько серверов приложения;
где физически находится database file.

Именно здесь PostgreSQL и SQLite начинают заметно расходиться.


Представим обычный небольшой сайт

Есть:

статьи;
формы обратной связи;
несколько администраторов;
пара тысяч посетителей в день.

Большинство запросов:

SELECT

Редкие операции:

INSERT contact_request
UPDATE article

Один backend.

Один VPS.

Для такого проекта SQLite может работать превосходно.

Он прост.

Быстр.

Нет отдельного сетевого round trip между приложением и БД.

Практически отсутствует административная нагрузка.

Переходить на PostgreSQL исключительно ради того, чтобы проект выглядел «серьёзнее», необязательно.


А теперь представим CRM

Одновременно работают:

50 менеджеров;
администраторы;
background worker;
интеграция с 1С;
webhook;
очередь задач.

В одну секунду происходят:

создание сделки;
обновление статуса;
добавление сообщения;
загрузка документа;
фиксация webhook;
создание notification job.

Здесь характер нагрузки уже другой.

Система активно пишет данные из нескольких процессов.

Именно в такой ситуации архитектура PostgreSQL начинает давать заметное преимущество.


Главный вопрос — конкурентная запись

SQLite отлично поддерживает параллельное чтение.

Но с параллельной записью есть фундаментальное ограничение.

Даже в WAL-режиме, который значительно улучшает concurrency, SQLite допускает одновременную работу читателей с писателем, но в каждый момент времени существует только один writer на database file.

Упрощённо:

Reader 1 ──────┐
Reader 2 ──────┼── SQLite
Reader 3 ──────┘

Writer 1 ───────── WRITE

Writer 2 ───────── WAIT

Для многих приложений это вообще не проблема.

Write transaction занимает миллисекунды.

Следующий writer быстро получает очередь.

Но если write-heavy нагрузка растёт, эта особенность начинает становиться архитектурным ограничением.


WAL сильно улучшает SQLite, но не превращает его в PostgreSQL

В Write-Ahead Logging режиме схема примерно такая:

Reader ──────→ database snapshot
Writer ──────→ WAL

Читатели могут продолжать работать, пока происходит запись.

Это очень полезно.

Но WAL остаётся одним журналом, поэтому одновременно пишет всё равно один writer. Кроме того, SQLite WAL рассчитан на процессы на одной машине и не предназначен для обычного использования через сетевую файловую систему.

Поэтому сценарий:

Server A
      \
       shared SQLite file
      /
Server B

обычно является плохой идеей.


PostgreSQL изначально проектировался под многопользовательскую работу

PostgreSQL использует MVCC — Multiversion Concurrency Control.

В упрощённом виде это означает, что транзакции работают со своими согласованными версиями данных.

Обычные чтения не должны блокировать обычные записи, а записи — чтения. PostgreSQL специально оптимизирует конкурентную работу множества сессий в одной общей БД.

Условно:

API #1 ── INSERT ──┐
API #2 ── UPDATE ──┤
Worker ── UPDATE ──┼→ PostgreSQL
Admin  ── SELECT ──┤
API #3 ── SELECT ──┘

Это именно тот класс задач, ради которого существует клиент-серверная СУБД.


Это не означает, что PostgreSQL никогда ничего не блокирует

Такое утверждение тоже было бы неверным.

В PostgreSQL существуют:

row locks;
table locks;
deadlocks;
serialization conflicts.

Два процесса, изменяющие одну строку, вполне могут конфликтовать.

Но база предоставляет значительно более развитую систему управления параллельными транзакциями.

Это особенно важно для:

платежей;
остатков;
заказов;
очередей;
ролей;
финансовых операций;
state machine.

Практический пример: последний товар на складе

Есть:

stock = 1

Два клиента одновременно покупают товар.

Нужно получить:

клиент A → success
клиент B → out of stock

а не:

stock = -1

В PostgreSQL можно использовать транзакцию и блокировку строки:

BEGIN;

SELECT stock
FROM products
WHERE id = 184
FOR UPDATE;

UPDATE products
SET stock = stock - 1
WHERE id = 184;

COMMIT;

В реальном проекте добавятся проверки, но смысл понятен:

база участвует в конкурентном бизнес-процессе.


SQLite тоже имеет транзакции

Важно не создавать ложного впечатления, что SQLite — просто файл без нормальной БД.

SQLite поддерживает ACID-транзакции.

То есть:

BEGIN
UPDATE
INSERT
COMMIT

работает совершенно серьёзно.

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

Проблема не в отсутствии транзакций.

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


Ещё одно важное различие — типизация

PostgreSQL — строго типизированная СУБД.

Если мы объявили:

price numeric(12,2)

это действительно определённый тип данных.

Если поле:

created_at timestamptz

PostgreSQL работает с timestamp.

Если:

id uuid

это настоящий UUID type. PostgreSQL также имеет специализированные типы вроде jsonb с отдельными операторами и индексированием.


SQLite исторически использует более гибкую модель

В SQLite тип относится прежде всего к самому значению, а колонка имеет type affinity.

То есть объявление:

CREATE TABLE example (
    value INTEGER
);

не означает абсолютно такую же жёсткость, как INTEGER в PostgreSQL.

SQLite способен выполнять автоматические преобразования и хранить значения гибче. Официальная документация называет это dynamic typing и type affinity.


Это одновременно преимущество и источник сюрпризов

Для небольшого приложения гибкость приятна.

Миграции проще.

Импорт данных удобнее.

Можно быстро менять модель.

Но в крупной бизнес-системе иногда хочется, чтобы база сама сказала:

Нет, здесь нельзя хранить это значение.

Например:

amount

должен быть числом.

user_id

должен соответствовать пользователю.

created_at

должен иметь предсказуемую семантику.

Чем больше domain invariants можно надёжно закрепить в схеме, тем меньше неправильных состояний проходит дальше.


Но современный SQLite умеет STRICT tables

Поэтому утверждение:

SQLite вообще не умеет строгие типы

тоже устарело.

SQLite поддерживает STRICT tables, которые позволяют включить более жёсткий контроль типов для конкретной таблицы.

Например:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email TEXT NOT NULL
) STRICT;

Это хороший пример того, почему сравнивать базы по старым мифам опасно.


Foreign Key есть в обеих системах

И PostgreSQL, и SQLite поддерживают внешние ключи.

Например:

users
  ↓
orders

Можно запретить заказ, ссылающийся на несуществующего пользователя.

В PostgreSQL referential integrity является обычной частью схемы и активно используется для поддержания связности данных.


Но у SQLite есть важный нюанс с foreign keys

В SQLite enforcement foreign key нужно явно контролировать.

В текущей документации указано, что foreign key constraints по умолчанию отключены для обратной совместимости и обычно включаются через:

PRAGMA foreign_keys = ON;

для соединения. Разработчикам рекомендуют не полагаться на default и выставлять режим явно.

Это небольшая техническая деталь, но она способна создать серьёзные сюрпризы при переносе схем между БД.


Пример различий схемы

Для PostgreSQL можно написать:

CREATE TABLE projects (
    id uuid PRIMARY KEY,
    owner_id uuid NOT NULL
        REFERENCES users(id),
    status text NOT NULL,
    budget numeric(12,2),
    created_at timestamptz NOT NULL
);

Здесь схема достаточно жёстко описывает данные.

В SQLite похожая бизнес-модель может выглядеть:

CREATE TABLE projects (
    id TEXT PRIMARY KEY,
    owner_id TEXT NOT NULL,
    status TEXT NOT NULL,
    budget NUMERIC,
    created_at TEXT NOT NULL,
    FOREIGN KEY(owner_id)
        REFERENCES users(id)
) STRICT;

Внешне SQL очень похож.

Но внутреннее поведение типов, дат, concurrency и runtime совершенно разное.


Поэтому ORM не делает базы полностью взаимозаменяемыми

Часто можно услышать:

У нас Prisma / Sequelize / SQLAlchemy / Hibernate, потом просто поменяем SQLite на PostgreSQL.

Иногда это действительно относительно просто.

Но чем взрослее проект, тем больше различий начинает просачиваться наружу.

Например:

типы дат;
JSON;
UUID;
decimal;
locking;
upsert;
индексы;
полнотекстовый поиск;
ALTER TABLE;
изоляция транзакций;
поведение concurrency.

ORM скрывает часть синтаксиса.

Он не отменяет архитектурные свойства СУБД.


Особенно опасно разрабатывать на SQLite, а production запускать на PostgreSQL без реальных тестов

Например:

development → SQLite
production → PostgreSQL

может казаться удобным.

Но часть SQL и миграций ведёт себя по-разному.

То, что успешно работает локально, может не совпадать с production по:

типам;
constraints;
case sensitivity;
locking;
DDL.

Если production использует PostgreSQL, очень часто проще и безопаснее использовать PostgreSQL и в development — например через Docker.


Где SQLite особенно хорош — локальное приложение

Представим desktop-приложение:

клиентская база;
заметки;
настройки;
локальная история.

Есть один пользователь.

Данные должны находиться на его компьютере.

Запуск отдельного PostgreSQL server выглядит странно.

SQLite здесь почти идеален:

Application
+
data.sqlite

То же относится к мобильному приложению

Телефон должен работать:

без сети;
в самолёте;
в метро;
при нестабильном интернете.

SQLite прекрасно подходит как локальный persistence layer.

Центральный сервер при этом вполне может использовать PostgreSQL.

Получается архитектура:

Mobile
 ↓
SQLite
 ↓
Sync
 ↓
API
 ↓
PostgreSQL

То есть выбирать между ними иногда вообще не требуется.

Они могут использоваться одновременно для разных задач.


Это особенно полезно в offline-first

Например:

SQLite
→ состояние на устройстве

PostgreSQL
→ центральная cloud-база

Дальше уже возникает другой класс проблем:

sync;
conflicts;
three-way merge;
offline queue.

Но сами базы отлично дополняют друг друга.


SQLite хорош и как локальный cache

Допустим основной источник истины:

PostgreSQL

Но desktop client хочет быстро искать данные без постоянного запроса к серверу.

Можно использовать:

PostgreSQL
       ↓ sync
SQLite local cache

Это официальный рекомендуемый сценарий использования SQLite: локальный cache для данных enterprise-системы.


А что насчёт backend небольшого сайта?

Здесь ответ уже интереснее.

Пример:

личный блог;
справочник;
небольшой каталог;
панель администратора.

Один Node.js process.

Практически вся нагрузка:

SELECT

SQLite вполне способен быть production-БД.

Его использование здесь не является «экономией на нормальной базе».

Это осознанное упрощение инфраструктуры.


Иногда SQLite даже быстрее

Если приложение и база находятся на одном компьютере, SQL выполняется внутри процесса без сетевого обращения к отдельному database server.

То есть нет:

application
↓
network protocol
↓
database process

Есть:

application
↓
SQLite library
↓
file

Для определённых локальных workload это может быть очень эффективно. Документация SQLite отдельно отмечает, что server-side приложение с сериализованными database requests может успешно использовать SQLite и получать очень хорошую производительность.


Поэтому вопрос «что быстрее?» почти бессмыслен

Можно провести benchmark:

SELECT 1

и получить победителя.

Но реальное приложение делает:

JOIN;
transactions;
network calls;
JSON;
sorting;
writes;
locks.

Правильнее спрашивать:

Какая архитектура лучше подходит нашей нагрузке?

SQLite может выигрывать на простой локальной работе.

PostgreSQL — на конкурентной централизованной системе.


Представим новый SaaS

План:

регистрация;
организации;
роли;
проекты;
сообщения;
оплата;
файлы;
уведомления.

На старте пользователей:

20

Кажется, SQLite достаточно.

Вероятно, технически да.

Но нужно посмотреть не только на сегодняшнюю нагрузку.


Важнее предполагаемая архитектура через год

Например, скоро появятся:

API #1
API #2
Worker
Scheduler
Webhook processor

Все они пишут в одну базу.

И далее:

1000 организаций;
несколько тысяч одновременных сессий;
фоновые jobs;
платёжные webhook.

В таком случае мы бы чаще выбирали PostgreSQL с самого начала.

Не потому, что SQLite «не выдержит 1000 пользователей».

А потому, что целевая архитектура является многопроцессной системой с конкурентной записью.


Количество пользователей — плохой критерий выбора

Допустим есть приложение:

100 000 пользователей

но каждый открывает его раз в месяц, в основном читает данные.

SQLite потенциально может чувствовать себя нормально.

Другой продукт:

100 пользователей

но каждый постоянно:

обновляет сделки;
пишет сообщения;
запускает задачи.

Здесь writer contention может быть намного выше.

Поэтому вопрос:

Сколько пользователей выдержит SQLite?

слишком примитивен.

Правильнее:

Какой concurrency profile у приложения?

Для CRM мы обычно предпочли бы PostgreSQL

Потому что CRM естественно содержит много связанных сущностей:

users;
companies;
contacts;
deals;
tasks;
messages;
documents;
audit.

Одновременно работают разные сотрудники и background процессы.

Также полезны:

row locking;
transactions;
constraints;
advanced indexes;
JSONB;
full-text;
reporting.

PostgreSQL здесь находится в своей естественной среде.


Для SaaS — тоже чаще PostgreSQL

Особенно если есть multi-tenancy:

Organization A
Organization B
Organization C

и множество одновременно работающих процессов.

Кроме concurrency появляются требования:

backup;
replication;
monitoring;
connection security;
roles;
PITR.

PostgreSQL имеет развитый набор инструментов для такого production-контура.


Для интернет-магазина мы также чаще выбрали бы PostgreSQL

Особенно если существуют:

остатки;
заказы;
платежи;
webhook;
промокоды;
резервы.

Потому что здесь важна не только скорость чтения каталога.

Есть конкурентные транзакционные операции.

Например:

два клиента
покупают последний товар.

Именно здесь server-grade concurrency становится особенно ценной.


Для небольшого CMS SQLite вполне может быть лучшим

Допустим:

1 редактор;
200 статей;
10 000 посетителей;
редкие изменения.

Если архитектура:

один backend
один VPS

PostgreSQL может добавлять административную сложность практически без реальной пользы.

SQLite позволяет сократить количество moving parts.


Чем меньше компонентов, тем меньше точек отказа

SQLite:

Application
Disk

PostgreSQL:

Application
Connection
PostgreSQL process
Configuration
Authentication
Database storage

Это не недостаток PostgreSQL.

Это цена возможностей.

Но если эти возможности проекту пока не нужны, простота SQLite имеет реальную ценность.


Backup SQLite выглядит очень привлекательно

Поскольку основная БД — файл, естественная мысль:

cp app.db backup.db

Но с работающей БД нужно быть аккуратнее.

Особенно в WAL mode нельзя бездумно копировать только основной .db, забывая о связанном WAL-состоянии.

SQLite предоставляет специальный Online Backup API и другие корректные способы создания snapshot работающей базы.

То есть:

SQLite — один файл

не означает:

backup всегда просто cp.

Зато восстановление часто очень простое

В маленьком сервисе восстановление может сводиться к:

stop app
↓
restore database file
↓
start app

Это чрезвычайно удобно.

Для локальных продуктов такая простота трудно переоценить.


PostgreSQL backup сложнее, но и возможности существенно шире

У PostgreSQL существуют несколько подходов:

SQL dump;
filesystem/base backup;
continuous archiving;
PITR.

Актуальная документация PostgreSQL отдельно описывает dump, файловые backup и непрерывное архивирование с Point-in-Time Recovery.


Что даёт PITR

Представим:

14:00
база нормальна

14:07
администратор случайно удалил данные

14:20
ошибка обнаружена

При правильно настроенном continuous archiving можно восстановить состояние, например, ближе к:

14:06:59

То есть до ошибочной операции.

Для серьёзного SaaS или CRM это очень полезная возможность.


pg_dump тоже остаётся удобным инструментом

Например:

pg_dump app > backup.sql

PostgreSQL способен создавать согласованный dump работающей базы без блокировки обычных readers/writers.

Но для больших production систем backup strategy обычно не должна ограничиваться одной командой раз в сутки.


Эксплуатация PostgreSQL требует больше внимания

Нужно следить за:

backup;
connections;
disk;
vacuum;
slow queries;
indexes;
memory;
updates.

Если использовать managed PostgreSQL, часть работы берёт провайдер.

При self-hosted варианте ответственность лежит на владельце продукта.

SQLite в этом смысле значительно проще.


Но SQLite тоже не является базой «без эксплуатации»

Например, при WAL mode важно понимать checkpoint.

Если long-running readers постоянно мешают checkpoint завершиться, WAL способен заметно расти. Официальная документация отдельно описывает checkpoint starvation как потенциальную причину увеличения WAL.

То есть даже максимально простая СУБД требует понимания её operational model.


Масштабирование — один из главных водоразделов

Сегодня:

1 VPS
1 Node.js process
SQLite

Всё работает.

Завтра нужно:

Nginx
  ↓
API #1
API #2

Где разместить database file?

Если он находится на:

API #1

API #2 его не видит.

Можно начать строить shared storage.

Но тогда мы постепенно идём против естественной архитектуры SQLite.

Официальная документация SQLite сама рекомендует рассматривать клиент-серверную БД, если приложение стало настолько нагруженным или write-intensive, что требует нескольких серверов.


PostgreSQL для этого сценария естественен

API #1 ──┐
API #2 ──┼→ PostgreSQL
API #3 ──┤
Worker ──┘

Database server является отдельным централизованным компонентом.

Масштабирование application layer не требует переноса database file между API-инстансами.


Но это не означает, что PostgreSQL автоматически решает масштабирование

При росте появятся:

connection pool;
slow queries;
indexes;
replication;
partitioning;
cache.

Любая БД имеет пределы.

Разница в том, что PostgreSQL проектировался именно под такой путь развития.


Security-модель тоже отличается

SQLite — файл.

Основная граница доступа:

кто может открыть этот файл?

Обычно безопасность обеспечивается:

правами ОС;
самим приложением;
шифрованием при необходимости.

PostgreSQL имеет собственную server-side модель:

users;
roles;
permissions;
authentication;
network rules.

Это удобно для централизованных корпоративных систем.


Но не нужно открывать PostgreSQL напрямую в интернет

Архитектура обычного веб-приложения:

Internet
   ↓
Backend API
   ↓
PostgreSQL

а не:

Internet
   ↓
PostgreSQL

Клиентский браузер не должен самостоятельно выполнять SQL.


Что насчёт JSON

Иногда SQLite выбирают потому, что:

У нас много JSON.

Или PostgreSQL:

У него есть JSONB.

Правда в том, что обе системы умеют работать с JSON.

Но PostgreSQL jsonb предоставляет зрелую систему операторов и индексирования, что особенно полезно, когда JSON становится частью сложных запросов.

При этом relational schema всё равно часто лучше для основного business domain.


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

Можно использовать SQLite очень дисциплинированно:

foreign keys;
STRICT tables;
constraints;
indexes;
migrations.

И можно использовать PostgreSQL ужасно:

одна таблица;
jsonb everything;
никаких constraints.

Качество данных определяется не только названием СУБД.


Индексы нужны обеим базам

Например:

SELECT *
FROM projects
WHERE organization_id = ?
ORDER BY created_at DESC;

Если таблица выросла до миллионов записей, отсутствие индекса будет проблемой и в SQLite, и в PostgreSQL.

Выбор «более серьёзной» базы не исправляет плохую модель запросов.


PostgreSQL особенно интересен для аналитики внутри transactional продукта

Например CRM хочет запрос:

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

или SaaS:

retention по организациям;
MRR;
churn;
cohort.

Window functions, CTE, advanced aggregates и развитый query planner делают PostgreSQL очень удобным для сложной relational аналитики.

SQLite тоже поддерживает современные SQL-возможности, но для централизованного многопользовательского аналитического workload PostgreSQL обычно даёт больше пространства для роста.


Когда миграция с SQLite на PostgreSQL становится неприятной

Типичный сценарий:

SQLite сначала

«потом легко мигрируем»

Проходит два года.

В базе:

миллионы записей;
десятки таблиц;
много миграций;
production 24/7.

Теперь нужно переносить.

Появляются различия:

типы;
date/time;
boolean;
autoincrement;
constraints;
SQL expressions;
locking expectations.

Плюс нужно выполнить data migration без долгого downtime.


Поэтому нужно заранее оценивать вероятность миграции

Если проект — локальный utility:

SQLite почти наверняка останется навсегда.

Если это:

SaaS;
CRM;
маркетплейс;
финансовый сервис,

и рост входит в бизнес-план, PostgreSQL с самого начала может быть дешевле, даже если первая версия технически могла работать на SQLite.


Но выбирать PostgreSQL «на будущее» тоже можно слишком рано

Представим маленький self-contained сервис, который всегда будет запускаться:

у одного клиента
на одном ПК

Выбор PostgreSQL ради потенциального:

А вдруг однажды миллион пользователей

только усложнит:

installation;
deployment;
backup;
support.

Архитектура должна соответствовать реальному будущему, а не максимально возможному воображаемому.


Сравним практические свойства

КритерийSQLitePostgreSQL
АрхитектураВстроеннаяClient/server
Отдельный DB serverНе нуженНужен
Основное хранениеОдин database fileУправляемое PostgreSQL storage
DeploymentОчень простойСложнее
Concurrent readsХорошоХорошо
Concurrent writesОдин writer на DB fileРассчитан на множество конкурентных транзакций
Несколько API-серверовНеестественно для общего файлаЕстественный сценарий
Локальное/offline приложениеОтличноОбычно избыточно
CRM/SaaSВозможно при ограниченной нагрузкеОбычно предпочтительнее
Strict typingЕсть через STRICT, но модель SQLite гибкаяСтрогая
BackupОчень простой, но нужно учитывать активную БД/WALБольше вариантов, включая PITR
АдминистрированиеМинимальноеТребуется
МасштабированиеВертикальное/архитектурно ограниченноеБольше вариантов роста
Network DBНе основной сценарийНормальный сценарий
Multi-process write loadМожет стать bottleneckСильная сторона
Embedded/mobileСильная сторонаНе предназначен для этого

Какую базу выбрать для конкретных типов проектов

Теперь самое практичное.

Небольшой сайт

Например:

контент;
форма обратной связи;
один администратор;
один backend.

SQLite вполне разумен.

PostgreSQL тоже будет работать, но может быть избыточен.


Корпоративный сайт с экспертным разделом

Если это в основном:

страницы;
статьи;
редкая публикация;
формы.

SQLite может быть достаточен.

Если уже появляются:

личные кабинеты;
CRM-функции;
background workers;
интеграции,

PostgreSQL становится привлекательнее.


CRM

Чаще:

PostgreSQL.

Причины:

много пользователей;
много записей;
конкурентные обновления;
связанные бизнес-сущности;
audit;
отчёты.

SaaS

Для обычного многопользовательского SaaS:

PostgreSQL.

Особенно если есть:

организации;
permissions;
billing;
jobs;
webhook;
несколько backend processes.

Интернет-магазин

Для полноценного магазина с заказами, оплатой и остатками:

обычно PostgreSQL.

Каталог можно эффективно читать и из SQLite, но transactional часть делает клиент-серверную БД более естественным выбором.


Desktop-приложение

Чаще:

SQLite.

Отдельный PostgreSQL server на компьютере пользователя обычно не нужен.


Мобильное приложение

Для локальной базы:

SQLite.

Для центрального backend:

PostgreSQL или другая server DB.

Обе могут использоваться одновременно.


Offline-first приложение

Практичная комбинация:

SQLite
→ устройство

PostgreSQL
→ сервер

Плюс слой синхронизации.


Внутренний инструмент на одном сервере

Если:

5–20 пользователей;
небольшая write load;
один process;

SQLite может быть отличным способом не усложнять инфраструктуру.


Самый полезный критерий выбора

Вместо:

Сколько будет пользователей?

мы бы задали пять вопросов.

ВопросЕсли ответ «да»
Будет много одновременно пишущих процессов?Склоняемся к PostgreSQL
Планируется несколько backend-инстансов?PostgreSQL
База является локальным хранилищем одного приложения?SQLite
Нужен минимальный deployment без DB-server?SQLite
Это долгоживущий SaaS/CRM с активным ростом?Обычно PostgreSQL

Есть ещё один простой критерий

Представьте архитектуру продукта.

Если естественная схема:

Application
   ↓
local database

скорее всего стоит серьёзно рассмотреть SQLite.

Если:

API #1
API #2
Worker
Scheduler
Webhook
      \ | /
    Database

вероятнее всего естественным выбором будет PostgreSQL.


Когда мы бы не стали мигрировать с SQLite

Если система работает:

стабильно;
быстро;
один сервер;
низкая write concurrency.

И никаких ограничений SQLite на практике нет, миграция только ради:

PostgreSQL считается серьёзнее

не имеет смысла.

Переписывание работающей инфраструктуры без измеримой причины — это технический риск и расходы.


Когда миграцию уже стоит планировать

Сигналы могут быть такими:

частые SQLITE_BUSY;
write contention;
нужно несколько серверов;
сложно координировать workers;
растёт downtime на операции;
нужна централизованная DB-security;
появляются требования к replication/PITR.

Тогда вопрос уже не:

Что моднее?

А:

Текущая архитектура перестала соответствовать workload.

SQLITE_BUSY сам по себе ещё не означает срочную миграцию

Иногда проблема находится в приложении.

Например транзакция открывается:

BEGIN

затем выполняется внешний HTTP-запрос:

10 секунд

и только потом:

COMMIT

Write lock удерживается слишком долго.

Сначала стоит исправить транзакцию.

Если writers обычно завершаются за миллисекунды, SQLite способен справляться с большей нагрузкой, чем часто предполагают. Официальная документация SQLite тоже подчёркивает, что single-writer модель не является проблемой для множества обычных workload, где записи короткие и могут быстро идти по очереди.


PostgreSQL тоже не лечит плохие транзакции

Можно перейти на PostgreSQL и продолжить держать:

transaction

открытой 30 секунд.

Появятся:

locks;
contention;
deadlocks.

Технология не отменяет необходимость правильно проектировать работу с БД.


Что выбрать для нового сложного веб-продукта

Если мы проектируем с нуля:

CRM;
SaaS;
B2B-портал;
маркетплейс;
сервис заказов;
клиентский кабинет

с ожидаемым ростом и несколькими backend-процессами, мы бы чаще начинали с PostgreSQL.

Не потому, что SQLite плох.

А потому что PostgreSQL лучше соответствует предполагаемой операционной модели.


А для небольшого и локального продукта мы бы выбрали SQLite без стеснения

Если задача:

небольшой сервис;
один процесс;
один сервер;
локальные данные;
минимум обслуживания,

SQLite способен быть более качественным архитектурным решением именно благодаря своей простоте.

Использовать PostgreSQL только ради статуса «production-grade» здесь было бы странно.

SQLite тоже является полноценной production-технологией — просто для другого класса задач.


Самая дорогая ошибка — выбирать БД по репутации

Плохая логика:

PostgreSQL мощнее
→ всегда PostgreSQL

или:

SQLite проще
→ зачем PostgreSQL

Правильнее:

Какие данные?
      ↓
Кто пишет?
      ↓
Сколько writers?
      ↓
Где работает приложение?
      ↓
Нужны ли несколько серверов?
      ↓
Какие требования к backup?
      ↓
Каким станет продукт?

И только потом:

SQLite или PostgreSQL

Вместо вывода

PostgreSQL и SQLite решают похожую задачу — хранение реляционных данных.

Но делают это из принципиально разных архитектурных предпосылок.

SQLite говорит:

Давайте встроим
надёжную SQL-базу
прямо в приложение
и уберём почти всю
инфраструктурную сложность.

PostgreSQL:

Давайте создадим
централизованный database server,
который координирует работу
множества клиентов и транзакций.

Обе идеи отличные.

Просто для разных случаев.

Если данные локальны, приложение работает на одном устройстве или одном сервере, concurrent writes невелики, а простота deployment действительно ценна — SQLite может оказаться лучшим выбором.

Если проект превращается в многопользовательскую CRM, SaaS, интернет-магазин или B2B-систему с несколькими API-процессами, workers, webhook и активной конкурентной записью — PostgreSQL обычно предоставляет более подходящий фундамент.

Поэтому мы бы не спрашивали:

«Какая база лучше?»

Мы бы спросили:

«Как будет жить и изменяться наш продукт через год после запуска?»

Если ответ на этот вопрос понятен, выбор между PostgreSQL и SQLite становится значительно проще.

И иногда самое профессиональное решение — выбрать не более мощную технологию, а ту, сложность которой действительно соответствует задаче.

Есть похожая задача?

Опишите продукт, интеграции и ограничения. До разработки зафиксируем объём, риски и критерии приёмки.

Обсудить проект →