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:

  1. SQL Layer: Принимает запрос от вашего Go-приложения, парсит его и строит план.
  2. Transactional Layer: Гарантирует атомарность и изоляцию.
  3. Distribution Layer: Разбивает данные на куски (Ranges) и знает, на каких нодах они лежат.
  4. Replication Layer: Синхронизирует данные между нодами через алгоритм консенсуса Raft.
  5. 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 для автоматических ретраев).

Go
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?

  1. Гео-распределенность: Вам нужно, чтобы данные пользователей из Европы лежали в Европе, а из США — в США, но при этом база выглядела как единое целое.
  2. Нулевой простой: Вам нужна база, которая не падает, когда вы выключаете целый дата-центр.
  3. Горизонтальный масштаб: Когда вы понимаете, что один инстанс Postgres уже не тянет, а 4. Sharding вручную на уровне приложения — это архитектурный ад.

Итог

  1. CockroachDB — это NewSQL, дающий масштаб NoSQL и гарантии SQL.
  2. Внутри это Распределенное Key-Value хранилище, где SQL-строки мапятся в байтовые ключи.
  3. Для обеспечения консистентности используются Raft и Hybrid Logical Clocks.
  4. Ваш Go-код должен быть готов к Serializable Retries — это цена за строгую согласованность.

Мир баз данных движется в сторону специализации. Мы разобрали реляционные, колоночные и NewSQL системы. Но с развитием ИИ и поиска по сходству появился совершенно новый класс баз, которые оперируют не числами и строками, а многомерными векторами. Об этом в следующей статье: 14. Векторные базы данных.