Что это
SQL-БД (реляционные): PostgreSQL, MySQL, Oracle, SQL Server, CockroachDB, Spanner. Модель: таблицы + строгая схема + SQL + ACID-транзакции + JOIN.
NoSQL -- зонтичный термин. Реально это четыре разных семейства:
| Семейство | Примеры | Модель |
|---|---|---|
| Document | MongoDB, Couchbase, Firestore | Вложенные JSON-документы |
| Key-Value | Redis, DynamoDB, Riak | Хэш-мапа |
| Wide-column | Cassandra, ScyllaDB, HBase | Sparse таблицы, partition key + clustering |
| Graph | Neo4j, JanusGraph, Neptune | Узлы и рёбра с атрибутами |
Главный ментальный сдвиг: "SQL vs NoSQL" -- не один выбор, а пять разных выборов, каждый со своими компромиссами.
ACID vs BASE
| Свойство | ACID (SQL) | BASE (многие NoSQL) |
|---|---|---|
| A | Atomicity: всё или ничего | Basically Available |
| C | Consistency: invariants | Soft state |
| I | Isolation: параллельные транзакции | Eventual consistency |
| D | Durability: после commit данные сохранены | (Durability обычно есть) |
ACID -- контракт "после COMMIT всё правильно". BASE -- "данные в итоге сойдутся, а пока работай с тем что есть". Выбор ACID vs BASE -- это по сути CAP: CP vs AP. См. 06.distributed-systems/2.cap-theorem.
Важно: BASE не означает "беспорядок". Eventual consistency имеет модели (read-your-writes, monotonic reads, causal), и хорошо спроектированные BASE-системы соблюдают их.
Когда SQL лучше
Преимущества реляционной модели
- JOIN и произвольные запросы. Не знаешь заранее, какие отчёты понадобятся -- SQL позволяет делать любые. NoSQL обычно оптимизирован под конкретный access pattern.
- Целостность через foreign keys. БД сама не даст создать заказ без существующего пользователя.
- ACID-транзакции через несколько таблиц. Списать с одного счёта, зачислить на другой -- атомарно.
- Декларативные индексы и query planner. Оптимизатор сам выбирает план.
- Зрелость ops. Бэкапы, репликация, миграции, монитор -- отлаженные 40 лет.
- Constraints: UNIQUE, CHECK, NOT NULL -- схема-сторож.
Типичные use-cases SQL
- Финансы и учёт (ledger, bills, invoices).
- E-commerce: заказы, корзины, инвентарь.
- CRM, ERP.
- Любая система записей (source of truth).
- Отчётность и BI поверх OLTP.
Postgres как "супер-SQL"
PostgreSQL сейчас -- not "просто SQL". Он покрывает много NoSQL кейсов:
- JSONB с GIN-индексами -- document store внутри реляционки.
- Arrays, hstore -- полуструктурированные данные.
- PostGIS -- геоданные.
- pg_trgm, tsvector -- full-text search на уровне MVP.
- pgvector -- векторный поиск для RAG/ML.
- LISTEN/NOTIFY -- pub/sub.
- Partitioning -- горизонтальное разбиение.
Часто выбор "берём Mongo, потому что нужны гибкие документы" -- это просто незнание JSONB.
Когда NoSQL лучше
Document (MongoDB и друзья)
- Гибкая схема, аггрегат как единица. Продуктовый каталог с разными атрибутами у товаров.
- Embedded data без JOIN. Заказ + позиции одним документом -- быстрое чтение.
- Быстрая эволюция. Меняй поля без ALTER TABLE.
- Write-heavy для независимых документов.
Минусы: JOIN -- боль, транзакции на несколько документов появились поздно и дороги, агрегации сложнее SQL.
Key-Value (Redis, DynamoDB)
- Простейший access pattern: get/set по ключу.
- Кеш, сессии, rate limit, счётчики.
- Очень высокий QPS: Redis -- сотни тысяч ops/sec на узле, DynamoDB -- миллионы.
- Low latency: миллисекунда и меньше.
Минусы: невозможно искать по значению, нет сложных запросов, нет JOIN.
Wide-column (Cassandra, ScyllaDB)
- Масштаб в PB.
- Write-heavy: логи, метрики, события, time-series.
- Tunable consistency (LOCAL_QUORUM, ONE и т.д.).
- Linear scalability: добавь узел -- получи линейный рост.
Минусы: думать надо партициями и clustering keys заранее; JOIN нет; транзакций нет; плохие запросы = drop кластера.
Graph (Neo4j)
- Сильно связанные данные: соцграф, рекомендации, fraud detection, knowledge graph.
- N-hop запросы дешевле, чем рекурсивные CTE в SQL.
Минусы: мало инженеров, экосистема скромнее, дорогое железо.
Таблица: семейства vs критерии
| Критерий | SQL | Document | Key-Value | Wide-column | Graph |
|---|---|---|---|---|---|
| Схема | Строгая | Гибкая | Нет | Гибкая | Гибкая |
| JOIN | Да | Нет / слабо | Нет | Нет | Нативно (n-hop) |
| Транзакции | ACID multi-row | Multi-doc (поздно) | Обычно single-key | Lightweight / single-row | Зависит |
| Query language | SQL | Query API / Aggregation | GET/PUT | CQL | Cypher / Gremlin |
| Consistency default | Strong | Tunable / eventual | Strong (single key) | Tunable | Strong |
| Масштаб записи | До десятков TB | Средний (шардинг) | Огромный | Огромный | Средний |
| Latency p50 | 1-10 ms | 1-10 ms | < 1 ms | 1-5 ms | 5-50 ms |
| Use cases | OLTP, finance | Каталоги, CMS | Кеш, сессии | Логи, телеметрия | Соцграф, реки |
Реальные стеки (смесь)
Никто в проде не ограничивается одной БД. Типичный стек:
PostgreSQL — source of truth, заказы, пользователи, деньги
Redis — сессии, кеш, rate limit, очереди
Elasticsearch — full-text поиск, логи
ClickHouse — аналитика OLAP
S3 — большие blob
Kafka — event bus
Это называется polyglot persistence. Вопрос: как синхронизировать? -- через CDC (Debezium), outbox pattern, ETL.
Pattern-driven decision
Правильный вопрос не "SQL или NoSQL", а "какие у меня access patterns":
Для каждого запроса опиши:
- ключ доступа
- размер результата
- частота
- latency budget
- consistency требования
Потом подбери БД, которая естественно обслуживает эти паттерны.
Пример:
- "Получить пользователя по id" -- любой движок.
- "Получить все заказы пользователя" -- SQL (FK + index) или embedded doc в Mongo.
- "Лента событий за 7 дней с 500M событий" -- Cassandra / ClickHouse.
- "Рекомендации друзей друзей" -- Graph.
- "Кто купил X и Y" -- SQL или OLAP.
Sizing и scaling
| Аспект | SQL | NoSQL |
|---|---|---|
| Вертикальный scale | До лимитов железа (~64 vCPU, TB RAM) | Нормально, но не акцент |
| Горизонтальный scale read | Read replicas -- просто | Нативно |
| Горизонтальный scale write | Sharding -- сложно, ручной | Нативно (consistent hashing) |
| Re-sharding | Болезненно | Обычно автоматически |
| Cross-shard transactions | Распределённая БД (Spanner, CockroachDB) | Обычно невозможно |
Обычный путь:
- Одна SQL-БД → до 10k QPS / 1 TB.
- Read replicas → до 50k read QPS.
- Partitioning / sharding + Redis кеш → до 100k QPS.
- Переезд hot-path в NoSQL (feeds, metrics) → миллионы QPS.
Большинство продуктов никогда не выходят из пункта 1-2.
Код: одна модель, два подхода
<?php
declare(strict_types=1);
/**
* Relational model: users, orders, order_items — three normalized tables.
* Pros: constraints enforce integrity; rich queries; multi-row ACID.
* Cons: reading the whole order = 3 JOINs.
*/
final class OrderRepositorySql
{
public function __construct(private readonly \PDO $db) {}
public function create(int $userId, array $items): int
{
$this->db->beginTransaction();
try {
$this->db->prepare(
'INSERT INTO orders(user_id, status, created_at) VALUES (:u, :s, NOW())'
)->execute(['u' => $userId, 's' => 'new']);
$orderId = (int) $this->db->lastInsertId();
$stmt = $this->db->prepare(
'INSERT INTO order_items(order_id, sku, qty, price) VALUES (:o, :s, :q, :p)'
);
foreach ($items as $item) {
$stmt->execute([
'o' => $orderId,
's' => $item['sku'],
'q' => $item['qty'],
'p' => $item['price'],
]);
}
$this->db->commit();
return $orderId;
} catch (\Throwable $e) {
$this->db->rollBack();
throw $e;
}
}
public function getFull(int $orderId): array
{
$sql = 'SELECT o.id, o.status, u.email,
i.sku, i.qty, i.price
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN order_items i ON i.order_id = o.id
WHERE o.id = :id';
$stmt = $this->db->prepare($sql);
$stmt->execute(['id' => $orderId]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}
Матрица выбора
| Требование | Выбор |
|---|---|
| Финансы, инвойсы | SQL |
| Каталог с разнотипными атрибутами | SQL + JSONB / Document |
| Кеш, сессии | Key-Value |
| Логи, метрики, события | Wide-column / timeseries DB |
| Соцграф, рекомендации | Graph (или SQL с CTE на малых объёмах) |
| Поиск полнотекстовый | Elasticsearch / Postgres tsvector |
| OLAP отчёты | ClickHouse / Redshift / BigQuery |
| PB-масштаб write | Cassandra / Scylla |
| MVP, не знаешь нагрузки | PostgreSQL |
Правило: начинайте с PostgreSQL, добавляйте специальную БД только когда профилировано узкое место. Преждевременный выбор NoSQL -- классическая ошибка.
Выводы
- SQL vs NoSQL -- это не один выбор, а пять разных: реляционная, документ, KV, wide-column, graph.
- SQL: JOIN, ACID, зрелость, констрейнты; минус -- труднее масштабировать на запись.
- Postgres с JSONB, pgvector, partitioning закрывает 80% NoSQL-кейсов.
- NoSQL берут под конкретный паттерн доступа и масштаб, а не "на всякий случай".
- Polyglot persistence -- норма: SQL + Redis + Elasticsearch + Kafka в одном проекте.
- Начинать с PostgreSQL, мигрировать часть хот-пути в специализированные БД при необходимости.