NewSQL: CockroachDB, YugabyteDB, TiDB и распределённый SQL
↑ 4.3 Поиск и специализированные БД · ← Предыдущая
Зачем это на собесе
На уровне senior спрашивают: «Что делать, когда PostgreSQL на одном узле не хватает, а отказ от SQL не вариант?» Ответ строится вокруг выбора: вертикальное масштабирование, реплики, шардирование, либо распределённый SQL со своими компромиссами.
Подтемы
- Что такое NewSQL
- Как это устроено: Raft и диапазоны данных
- CockroachDB, YugabyteDB, TiDB, Spanner
- Цена распределённости
- Когда выбирать
Объяснение
Идея
NewSQL (распределённый SQL) стремится сочетать SQL и ACID-транзакции реляционных баз с горизонтальным масштабированием и отказоустойчивостью NoSQL. Данные автоматически делятся на диапазоны (ranges, tablets), каждый диапазон реплицируется на несколько узлов алгоритмом консенсуса Raft.
flowchart LR C["Клиент (SQL)"] --> N1["Узел 1"] C --> N2["Узел 2"] C --> N3["Узел 3"] subgraph Кластер N1 --- N2 --- N3 R1["Диапазон A<br/>лидер: узел 1"] R2["Диапазон B<br/>лидер: узел 2"] R3["Диапазон C<br/>лидер: узел 3"] end
Каждый диапазон имеет лидера и реплики. Запись подтверждается, когда большинство реплик записали её. Потеря узла не останавливает кластер: лидерство переходит к другой реплике.
Представители
| Система | Совместимость | Особенности |
|---|---|---|
| CockroachDB | протокол PostgreSQL (с отличиями), собственный SQL | строгая сериализуемость по умолчанию, геораспределение, автоматическое перебалансирование |
| YugabyteDB | слой YSQL на коде PostgreSQL и YCQL в стиле Cassandra | высокая совместимость с PostgreSQL, Raft |
| TiDB | протокол MySQL | разделение вычислений и хранения (TiKV), аналитика через TiFlash |
| Google Spanner | облачный сервис | глобально согласованные транзакции (TrueTime), управляемый сервис |
Цена распределённости
- Задержка. Транзакция требует подтверждения большинством реплик, часто по сети: запись заметно медленнее, чем на одном PostgreSQL.
- Распределённые транзакции дороже: охватывают несколько диапазонов и узлов.
- Повторы транзакций. Конфликты приводят к ошибкам сериализации (SQLSTATE
40001), приложение должно повторять транзакцию. - Совместимость не стопроцентная. Расширения, типы, хранимые процедуры и планы запросов отличаются от PostgreSQL.
- Стоимость и сложность эксплуатации. Минимум три узла, мониторинг, сетевые требования.
Когда выбирать
flowchart TD A["Нужно больше одного узла PostgreSQL"] --> B{"Что именно?"} B -- "чтение" --> C["Реплики чтения"] B -- "запись упирается в один узел" --> D{"Данные делятся по ключу<br/>(тенант, пользователь)?"} D -- да --> E["Шардирование / Citus"] D -- нет --> F{"Нужны глобальные транзакции<br/>и отказоустойчивость по регионам?"} F -- да --> G["Распределённый SQL<br/>(CockroachDB, YugabyteDB, TiDB, Spanner)"] F -- нет --> H["Вертикальное масштабирование,<br/>оптимизация запросов"]
Распределённый SQL оправдан, когда нужны горизонтальное масштабирование записи, автоматическая отказоустойчивость и транзакции на разных данных, и команда готова принять задержки и эксплуатацию.
Примеры
Повтор транзакции при ошибке сериализации
async Task<T> RetryAsync<T>(Func<NpgsqlTransaction, Task<T>> work, NpgsqlDataSource ds, int attempts = 5)
{
for (var i = 1; ; i++)
{
await using var conn = await ds.OpenConnectionAsync();
await using var tx = await conn.BeginTransactionAsync();
try
{
var result = await work(tx);
await tx.CommitAsync();
return result;
}
catch (PostgresException ex) when (ex.SqlState == "40001" && i < attempts)
{
await tx.RollbackAsync();
await Task.Delay(TimeSpan.FromMilliseconds(50 * i * i)); // нарастающая пауза
}
}
}Так работают с CockroachDB и любыми системами со строгой изоляцией: ошибка 40001 это нормальный сигнал «повторите», а не сбой.
Запуск CockroachDB для экспериментов
docker run -d --name crdb -p 26257:26257 -p 8080:8080 \
cockroachdb/cockroach start-single-node --insecure
docker exec -it crdb ./cockroach sql --insecure -e "CREATE DATABASE shop;"Это одиночный узел для обучения, в продакшене нужен кластер из трёх и более узлов с TLS.
Нюансы и подводные камни
- Выбор первичного ключа. Монотонно растущие ключи создают «горячий» диапазон. Используйте UUID, составные ключи или хэш-шардированные индексы.
- Проверяйте совместимость. Перед миграцией прогоните тесты на реальных запросах и расширениях.
- Транзакции должны быть короткими и идемпотентными: они повторяются.
- Геораспределение. Данные и лидеры привязывают к регионам, иначе задержка между регионами убьёт скорость.
- Не нужен «на всякий случай». Для большинства приложений реплики, партиционирование и хороший индекс решают задачу дешевле.
- Лицензии. У продуктов разные лицензии и ограничения бесплатных версий, проверяйте актуальные условия.
Вопросы с ответами
Что такое NewSQL?
Класс баз с SQL и ACID-транзакциями, которые масштабируются горизонтально и реплицируются автоматически (обычно через Raft). Примеры: CockroachDB, YugabyteDB, TiDB, Spanner.
Чем распределённый SQL отличается от PostgreSQL с репликами?
Реплики PostgreSQL масштабируют чтение, запись идёт на один главный узел. Распределённый SQL делит данные между узлами и масштабирует запись, автоматически переживая отказ узлов, ценой задержки и сложности.
Зачем повторять транзакции при ошибке
40001?При конкурентных конфликтах в сериализуемой изоляции система откатывает одну из транзакций. Это штатная ситуация: приложение должно повторить транзакцию с нарастающей паузой.
Почему монотонный ключ плох для распределённой БД?
Все новые записи попадают в конец диапазона ключей, то есть на один узел. Возникает «горячая точка», и запись не масштабируется. Используют UUID, составные или хэш-ключи.
Когда не стоит использовать NewSQL?
Когда данных и нагрузки умеренно и хватает одного PostgreSQL с репликами и оптимизацией. Распределённый SQL добавляет задержку, стоимость и эксплуатационную сложность.
Связанные темы
- Шардирование: Шардирование
- CAP: CAP, PACELC и модели согласованности
- Уровни изоляции: Уровни изоляции
- Репликация: Репликация