13. NewSQL. CockroachDB
Архитектура CockroachDB, распределенный Raft и Serializable изоляция в Go
Мир баз данных долгое время был расколот на два лагеря: SQL (строгая консистентность, ACID, но плохая горизонтальная масштабируемость) и NoSQL (1. Что такое NoSQL — бесконечный масштаб, но слабая консистентность и отсутствие JOIN).
NewSQL — это современная попытка взять лучшее от обоих миров. CockroachDB (CRDB) — пожалуй, самый яркий представитель этого класса. Это распределенная SQL-база данных, которая из коробки дает вам честный Serializable ACID, но при этом масштабируется так же легко, как Cassandra, просто добавлением новых узлов (нод).
Для бэкенд-разработчика CockroachDB интересен тем, что он «прикидывается» обычным PostgreSQL (совместим по протоколу), но внутри работает как гигантское распределенное Key-Value хранилище.
Архитектура CockroachDB: Слоеный пирог
В отличие от монолитного Postgres, архитектура CRDB разделена на 5 четких слоев. Понимание этого разделения — ключ к проектированию надежных систем и прохождению интервью на позицию Senior/Lead:
- SQL Layer: Принимает запрос от вашего Go-приложения, парсит его и строит план.
- Transactional Layer: Гарантирует атомарность и изоляцию.
- Distribution Layer: Разбивает данные на куски (Ranges) и знает, на каких нодах они лежат.
- Replication Layer: Синхронизирует данные между нодами через алгоритм консенсуса Raft.
- Storage Layer: Физическая запись байтов на диск (используется Pebble — форк RocksDB, написанный на Go).
flowchart TD
App["Go Application<br>(pgx driver)"] --> SQL["SQL Layer<br>Парсинг и оптимизация"]
SQL --> Trans["Transactional Layer<br>Контроль консистентности"]
Trans --> Dist["Distribution Layer<br>Поиск Range-а в кластере"]
Dist --> Repl["Replication Layer<br>Raft-консенсус между нодами"]
Repl --> Store["Storage Layer<br>Запись в Pebble (LSM-Tree)"]
subgraph Node1["Node 1 (Coordinator)"]
SQL
Trans
end
subgraph Node2["Node 2 (Follower)"]
Repl2["Replica Log"]
end
Repl <-->|"Raft Log Sync"| Repl2
classDef default fill:#1e293b,stroke:#334155,color:#f8fafc;
classDef highlight fill:#312e81,stroke:#6366f1,color:#ffffff;
class SQL,Trans,Repl highlight;
Mechanical Sympathy: SQL на стероидах Key-Value
Самое изящное решение в CockroachDB — это то, как реляционные таблицы превращаются в ключи и значения. Под капотом CRDB нет никаких «таблиц» — там только один огромный, отсортированный массив Key-Value пар.
Когда вы создаете таблицу, CRDB мапит её строки в KV следующим образом:
- Key:
/TableID/IndexID/PrimaryKeys... - Value:
ColumnFamily/ColumnValues...
Распределение и Raft: Как она выживает?
CockroachDB получила свое название («Таракан») за невероятную живучесть. Данные разбиваются на Ranges (обычно по 512 МБ). Каждый Range реплицируется минимум на 3 узла.
Для управления репликацией используется Raft. Любая запись считается успешной только тогда, когда большинство реплик (Quorum) подтвердили запись в свой лог.
- Если одна нода падает — кластер продолжает работать без пауз (если реплик было 3+).
- Если данные на одной ноде становятся слишком «горячими», CRDB автоматически переносит Range на менее нагруженную ноду или разделяет его (Split).
Консистентность: Serializable по умолчанию
Это «киллер-фича» CRDB. В то время как в PostgreSQL по умолчанию стоит Read Committed, и вам нужно вручную следить за аномалиями, CockroachDB всегда работает в режиме Serializable — самом строгом уровне изоляции.
Это достигается за счет использования HLC (Hybrid Logical Clocks) — комбинации физического времени сервера и логических счетчиков. Это позволяет CRDB упорядочивать транзакции в распределенной системе без использования дорогостоящих атомных часов (как в Google Spanner).
Использование с Go
Поскольку CockroachDB поддерживает протокол PostgreSQL, вы можете использовать идиоматичный драйвер pgx (или официальный пакет github.com/cockroachdb/cockroach-go/v2/crdb/crdbpgx для автоматических ретраев).
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
// Строка подключения почти такая же, как для Postgres
// Единственное отличие - порт 26257 по умолчанию
dsn := "postgresql://root@localhost:26257/defaultdb?sslmode=disable"
pool, err := pgxpool.New(context.Background(), dsn)
if err != nil {
log.Fatalf("Ошибка подключения: %v", err)
}
defer pool.Close()
// Важно: CockroachDB рекомендует использовать логику ретраев
// pgx не делает это автоматически для Serializable конфликтов!
ctx := context.Background()
err = runWithRetry(ctx, pool, func(tx pgx.Tx) error {
_, err := tx.Exec(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
return err
})
if err != nil {
log.Printf("Транзакция не удалась после ретраев: %v", err)
}
}
func runWithRetry(ctx context.Context, pool *pgxpool.Pool, fn func(tx pgx.Tx) error) error {
const maxRetries = 5
for attempt := 0; attempt < maxRetries; attempt++ {
tx, err := pool.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.Serializable})
if err != nil {
return err
}
if err := fn(tx); err != nil {
tx.Rollback(ctx)
// При ошибке Serializable (SQLSTATE 40001) повторяем
continue
}
if err := tx.Commit(ctx); err == nil {
return nil
}
}
return fmt.Errorf("превышен лимит ретраев")
}
Когда выбирать CockroachDB?
- Гео-распределенность: Вам нужно, чтобы данные пользователей из Европы лежали в Европе, а из США — в США, но при этом база выглядела как единое целое.
- Нулевой простой: Вам нужна база, которая не падает, когда вы выключаете целый дата-центр.
- Горизонтальный масштаб: Когда вы понимаете, что один инстанс Postgres уже не тянет, а 4. Sharding вручную на уровне приложения — это архитектурный ад.
Итог
- CockroachDB — это NewSQL, дающий масштаб NoSQL и гарантии SQL.
- Внутри это Распределенное Key-Value хранилище, где SQL-строки мапятся в байтовые ключи.
- Для обеспечения консистентности используются Raft и Hybrid Logical Clocks.
- Ваш Go-код должен быть готов к Serializable Retries — это цена за строгую согласованность.
Мир баз данных движется в сторону специализации. Мы разобрали реляционные, колоночные и NewSQL системы. Но с развитием ИИ и поиска по сходству появился совершенно новый класс баз, которые оперируют не числами и строками, а многомерными векторами. Об этом в следующей статье: 14. Векторные базы данных.