28. Distributed SQL, ClickHouse, Tarantool
Зачем знать на Middle 3: Senior выбирает БД под задачу. Когда нужен geo-distributed strong consistency — это CockroachDB / YugabyteDB / YDB / TiDB. Когда нужны OLAP-аналитика поверх миллиардов строк — ClickHouse. Когда нужен микросекундный latency и in-memory store — Tarantool. Каждая из них радикально отличается по архитектуре и моделям данных. Без понимания их свойств выбор сводится к «модно/слышали» — а это путь к 6-значным потерям.
Содержание
Заголовок раздела «Содержание»1. Концепция
Заголовок раздела «1. Концепция»Классификация
Заголовок раздела «Классификация»┌─────────────────────────────────────────────────────────────┐│ OLTP (row-store, ACID, точечные апдейты) ││ PostgreSQL ─── single-node / streaming repl ││ CockroachDB ──┐ ││ YugabyteDB ├── distributed SQL (NewSQL) ││ YDB │ strong consistency, multi-region ││ TiDB ┘ ││ Tarantool ─── in-memory, microsecond latency │├─────────────────────────────────────────────────────────────┤│ OLAP (column-store, batch, aggregations) ││ ClickHouse ─── распределённый, real-time ││ DuckDB ─── embedded analytics │└─────────────────────────────────────────────────────────────┘NewSQL / Distributed SQL — это попытка совместить:
- ACID-транзакции (как Postgres),
- горизонтальное масштабирование (как Cassandra),
- SQL-интерфейс,
- strong consistency между регионами.
Под капотом обычно — Raft-based consensus + MVCC + распределённый storage (KV-layer типа RocksDB/Pebble).
OLAP column-store — данные хранятся по колонкам. Это даёт колоссальное сжатие (одна колонка = однородные значения, sort + RLE), быстрое чтение нужных колонок (нет лишнего IO) и быструю агрегацию (SIMD). Но точечные апдейты — невыполнимы или медленные.
In-memory DB (Tarantool, Redis, Aerospike) — данные в RAM, диск только для durability (WAL + snapshot). Latency микросекунды, throughput миллионы RPS.
2. Production-deep dive
Заголовок раздела «2. Production-deep dive»2.1. CockroachDB (CRDB)
Заголовок раздела «2.1. CockroachDB (CRDB)»CockroachDB — Postgres-wire-compatible distributed SQL, написана на Go.
Архитектура:
┌────────────────────────────────────────────────┐ │ CockroachDB cluster (Go) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ node 1 │ │ node 2 │ │ node 3 │ │ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ │ │ │SQL │ │ │ │SQL │ │ │ │SQL │ │ │ │ │ └──┬──┘ │ │ └──┬──┘ │ │ └──┬──┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──┴──┐ │ │ ┌──┴──┐ │ │ ┌──┴──┐ │ │ │ │ │ KV │ │ │ │ KV │ │ │ │ KV │ │ │ │ │ │Raft │◄┼───┼─►│Raft │◄┼───┼─►│Raft │ │ │ │ │ └──┬──┘ │ │ └──┬──┘ │ │ └──┬──┘ │ │ │ │ ┌──┴──┐ │ │ ┌──┴──┐ │ │ ┌──┴──┐ │ │ │ │ │Pebble│ │ │ │Pebble│ │ │ │Pebble│ │ │ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └────────────────────────────────────────────────┘- Данные разбиты на ranges (~512 MiB по умолчанию).
- Каждый range реплицирован Raft group на 3+ узла.
- Lease holder обрабатывает чтения (через follower-reads — можно с follower’ов).
- Multi-version timestamps (HLC — Hybrid Logical Clock).
- Wire-protocol Postgres → drop-in replacement для большинства Go-pgx/lib-pq приложений.
Use cases:
- Финтех с глобальной аудиторией (multi-region writes).
- Сильно растущие SaaS с непредсказуемым шардингом.
- Замена Postgres, когда нужна горизонталь.
Go-driver: обычный pgx подходит. Особенности:
- Retryable errors (
40001— serialization conflict) — нужно retry в приложении. - Transactions могут идти долго — будь готов к долгим коммитам.
Локализация (multi-region):
ALTER DATABASE app PRIMARY REGION "eu-central-1";ALTER DATABASE app ADD REGION "us-east-1";ALTER TABLE users SET LOCALITY REGIONAL BY ROW AS region;Каждая строка живёт в своём регионе → reads/writes локальные.
2.2. YugabyteDB
Заголовок раздела «2.2. YugabyteDB»PG-compatible, distributed.
Архитектура: YB-TServer (data) + YB-Master (metadata). KV layer DocDB поверх RocksDB. Inspired by Google Spanner.
Layer’ы:
- YSQL — Postgres-совместимый SQL.
- YCQL — Cassandra-compatible (для key-value).
Особенности:
- Замечательная PG-совместимость (форк PG-кода).
- Transactional + распределённый.
- Tablet-based sharding (auto-split при росте).
- Колоночные индексы, materialized views.
Drawback: меньше зрелости, чем CRDB; конфликты с PG-расширениями.
2.3. YDB (Yandex Database)
Заголовок раздела «2.3. YDB (Yandex Database)»Распределённая SQL от Яндекс. Production scale (Я.Метрика, Я.Маркет, Я.Облако).
Свойства:
- Strong serializable transactions (snapshot isolation + read-write).
- Multi-DC, geo-replication.
- Распределённое хранилище (BlobStorage, like GFS).
- Coordinators + Datashards (как Spanner: TrueTime → у YDB свой clock).
- Внутренний механизм 2-phase commit между шардами.
API:
- SQL (YQL — YDB Query Language, super-set SQL).
- gRPC API.
- Topics (Kafka-like) — встроенная очередь.
- Coordination service (etcd-like).
Go SDK:
import "github.com/ydb-platform/ydb-go-sdk/v3"
db, _ := ydb.Open(ctx, "grpcs://ydb.demo:2135/?database=/local")defer db.Close(ctx)
err := db.Table().Do(ctx, func(ctx context.Context, s table.Session) error { res, err := s.Execute(ctx, table.SerializableReadWriteTxControl(table.CommitTx()), `SELECT id, name FROM users WHERE id = $id;`, table.NewQueryParameters(table.ValueParam("$id", types.Uint64Value(1)))) if err != nil { return err } defer res.Close() // ... return nil})Особенности YDB Go SDK:
- Сессия — first-class citizen.
- Retry хелперы (idempotent retry на TLI и сетевых ошибках).
- Подготовленные параметры — типизированные.
- Топики — Kafka-like, но с собственным протоколом.
Когда YDB:
- В Yandex Cloud (managed).
- Workload, где Postgres не масштабируется, но нужна SQL и strong consistency.
- Замена связки Kafka + Postgres (топики + tables в одной системе).
2.4. TiDB
Заголовок раздела «2.4. TiDB»MySQL-wire-compatible distributed SQL (PingCAP, China).
Архитектура:
- TiDB (SQL layer, stateless).
- TiKV (Raft-based KV layer, Rust).
- PD (Placement Driver, scheduler).
- TiFlash (column-store для OLAP query — HTAP).
Особенности:
- HTAP: row + column copies синхронизируются.
- MySQL compatibility — почти drop-in.
- Полностью open-source.
Use case: замена шардированного MySQL.
2.5. ClickHouse — Production deep
Заголовок раздела «2.5. ClickHouse — Production deep»ClickHouse — column-store от Яндекс / ClickHouse Inc. Сейчас флагман для real-time OLAP, observability, реклама, BI.
Архитектура
Заголовок раздела «Архитектура»┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ CH node 1 │ │ CH node 2 │ │ CH node 3 ││ │ │ │ │ ││ Shard 1 │ │ Shard 2 │ │ Shard 3 ││ Replica A │◄──►│ Replica A │◄──►│ Replica A │└─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └──────────────────┼──────────────────┘ ▼ ┌──────────────────────┐ │ Distributed table │ (router) └──────────────────────┘ ▲ │ ┌──────────────────────┐ │ ClickHouse Keeper / │ │ ZooKeeper │ (replication coord) └──────────────────────┘MergeTree family
Заголовок раздела «MergeTree family»Это сердце ClickHouse. Все Engine’ы:
| Engine | Назначение |
|---|---|
| MergeTree | Базовый: sorted, partitioned |
| ReplacingMergeTree | Дедупликация по ORDER BY ключу при мерже |
| SummingMergeTree | Авто-суммирование числовых колонок при мерже |
| AggregatingMergeTree | Хранит AggregateFunction state, мерж суммирует |
| CollapsingMergeTree | Удаление пар (Sign +1 / -1) для CDC-like |
| VersionedCollapsingMT | То же, но с версией |
| GraphiteMergeTree | Для метрик (rollup-aware) |
Replicated*MergeTree — добавляет ZK/Keeper координацию.
Создание таблицы:
CREATE TABLE events( event_date Date, event_time DateTime, user_id UInt64, event_type LowCardinality(String), payload String)ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')PARTITION BY toYYYYMM(event_date)ORDER BY (user_id, event_time)SETTINGS index_granularity = 8192;Ключевые понятия:
- Partition — физическое разделение (директории). Партиция = единица DROP / TTL.
- ORDER BY = primary key (хранится sparse index с шагом
index_granularity, обычно 8192 строки). Это главный механизм быстрого чтения. - Index granularity — каждые N строк один отметчик в primary index. Меньше = больше точность, больше overhead.
- Background merges — мелкие parts сливаются в большие фоновым process’ом. До мержа дубликаты не схлопываются (важно для
ReplacingMergeTree).
Inserts
Заголовок раздела «Inserts»⚠️ Главное правило ClickHouse: вставляй пачками 1000-100000 строк. Каждый INSERT = одна part = одна директория. Тысячи мелких INSERT’ов → миллион partов → background merge не успевает → деградация.
Batch from Go:
import ( "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver")
conn, err := clickhouse.Open(&clickhouse.Options{ Addr: []string{"clickhouse:9000"}, Auth: clickhouse.Auth{Database: "app", Username: "default", Password: "..."}, Settings: clickhouse.Settings{"async_insert": 1, "wait_for_async_insert": 0},})
batch, _ := conn.PrepareBatch(ctx, "INSERT INTO events (event_date, event_time, user_id, event_type, payload)")for _, e := range events { _ = batch.Append(e.Date, e.Time, e.UserID, e.Type, e.Payload)}_ = batch.Send()async_insert (CH 21.11+) — сервер сам буферизует мелкие inserts. Меняет правила игры для апп-серверов, которые не могут батчить.
Materialized views
Заголовок раздела «Materialized views»В CH materialized view — это trigger на INSERT. При вставке в источник, MV сам считает агрегат и сохраняет в свою таблицу.
CREATE MATERIALIZED VIEW events_hourlyENGINE = SummingMergeTreePARTITION BY toYYYYMM(hour) ORDER BY (hour, user_id)ASSELECT toStartOfHour(event_time) AS hour, user_id, count() AS cntFROM eventsGROUP BY hour, user_id;Опасности:
- Не reads источник, а только новые INSERT’ы.
- Если переименовал/дропнул source — MV сломается.
- Initial load —
POPULATE(нагрузка, не атомарно), либо ручной BACKFILL.
Skip indexes
Заголовок раздела «Skip indexes»ALTER TABLE events ADD INDEX idx_user user_id TYPE minmax GRANULARITY 4;ALTER TABLE events ADD INDEX idx_type event_type TYPE set(100) GRANULARITY 4;ALTER TABLE events ADD INDEX idx_text payload TYPE bloom_filter(0.01) GRANULARITY 1;Это skip indexes — они не указывают «где есть», а позволяют пропускать блоки.
PREWHERE
Заголовок раздела «PREWHERE»CH сначала читает условие WHERE по колонкам, потом дочитывает остальные. PREWHERE явно указывает, какое условие пускать первым (читать самую дешёвую колонку):
SELECT user_id, payload FROM eventsPREWHERE event_type = 'click'WHERE user_id > 1000;CH чаще сам ставит PREWHERE (optimize_move_to_prewhere = 1), но для сложных кейсов — руками.
Distributed engine
Заголовок раздела «Distributed engine»CREATE TABLE events_distributed AS eventsENGINE = Distributed(my_cluster, app, events, rand());- Routing INSERT’ов по shards (по hash, rand, or expression).
- Параллельные SELECT’ы на все shards с агрегацией результата.
Go clients
Заголовок раздела «Go clients»| Driver | Замечания |
|---|---|
ClickHouse/clickhouse-go v2 | Официальный, native protocol, async insert, generics |
mailru/go-clickhouse | HTTP-protocol, проще, медленнее |
uptrace/go-clickhouse | ORM-like, generators |
Observability use-case
Заголовок раздела «Observability use-case»ClickHouse — главный backend для логов/метрик/трейсов (OpenTelemetry, Grafana, Signoz, Uptrace).
Почему не Kafka / PG / ES:
- Сжатие 10-30x на структурированных логах (LZ4/ZSTD + LowCardinality columns).
- Быстрая агрегация:
count(), avg(), quantile()за миллисекунды на миллиардах строк. - Real-time: insert latency 1-2 секунды (background merge).
- Дешёвый storage: данные могут лежать на S3 (через S3 disk).
2.6. Tarantool
Заголовок раздела «2.6. Tarantool»Tarantool — in-memory DB + application server (на Lua, расширения C/Go) от Mail.ru / VK.
Свойства:
- Данные в RAM, persistent на диск (snapshot + WAL).
- Latency ~10-100 микросекунд.
- Throughput 100k-1M ops/sec на одну ноду.
- Repl. — async или sync (Raft с Tarantool 2.10+).
- Sharding — Vshard (отдельная библиотека).
Модель данных:
- Space — таблица.
- Tuple — строка (массив значений).
- Index — TREE, HASH, BITSET, RTREE.
- Schema-less или schemafull (
format).
box.cfg{ listen = 3301, wal_mode = 'write' }box.schema.space.create('users', { if_not_exists = true })box.space.users:format({ {name='id', type='unsigned'}, {name='name', type='string'}, {name='age', type='unsigned'},})box.space.users:create_index('primary', { parts = {'id'} })box.space.users:create_index('by_name', { parts = {'name'}, unique = false })box.space.users:insert{1, 'Alice', 30}Go client (tarantool/go-tarantool):
import "github.com/tarantool/go-tarantool/v2"
conn, _ := tarantool.Connect(ctx, dialer, tarantool.Opts{})defer conn.Close()resp, _ := conn.Do(tarantool.NewSelectRequest("users"). Index("primary").Iterator(tarantool.IterEq). Key([]interface{}{uint64(1)})).Get()Use cases:
- Hot cache + WAL (вместо Redis + Postgres).
- Сессии (миллионы конкурентных пользователей).
- Anti-fraud правила (микросекундный response).
- Featurestores (real-time ML features).
Where: VK, Mail.ru, Alfa-Bank, Sberbank, Russian Post, X5.
⚠️ Tarantool — RAM-based. Объём данных ограничен памятью сервера. Для холодных данных — комбинируй с другой БД.
3. Gotchas (12+)
Заголовок раздела «3. Gotchas (12+)»⚠️ 1. CockroachDB: serializable conflicts
Заголовок раздела «⚠️ 1. CockroachDB: serializable conflicts»CRDB по умолчанию SERIALIZABLE. Конкурентные транзакции часто падают 40001. Приложение обязано retry.
for retries := 0; retries < 3; retries++ { err := doTx(ctx, db) if err == nil { break } if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == "40001" { continue } return err}⚠️ 2. CRDB / Yugabyte: latency на single-region
Заголовок раздела «⚠️ 2. CRDB / Yugabyte: latency на single-region»NewSQL хорош для multi-region. Если у тебя один дата-центр — overhead Raft’а делает их медленнее одной Postgres. Не выбирай NewSQL, если distribution не нужна.
⚠️ 3. YDB: типы строгие
Заголовок раздела «⚠️ 3. YDB: типы строгие»В YDB параметры запроса нужно объявлять явно с типами. Неправильный тип = runtime ошибка. Используй типизированные параметры из SDK.
⚠️ 4. ClickHouse: один INSERT = одна part
Заголовок раздела «⚠️ 4. ClickHouse: один INSERT = одна part»Тысячи мелких INSERT’ов = катастрофа. Используй async_insert или батчинг.
⚠️ 5. ClickHouse: UPDATE и DELETE — дорогие
Заголовок раздела «⚠️ 5. ClickHouse: UPDATE и DELETE — дорогие»ALTER TABLE … UPDATE ... WHERE — это mutation, выполняется фоновой задачей, переписывает партиции. На больших таблицах — часы. Для real-time изменений — ReplacingMergeTree (последнее значение по ключу).
⚠️ 6. ClickHouse: ORDER BY = primary key
Заголовок раздела «⚠️ 6. ClickHouse: ORDER BY = primary key»Нельзя добавить второй «индекс по другому ключу» как в Postgres. Можно либо проекции (projection), либо вторичная агрегатная таблица через MV.
⚠️ 7. ClickHouse: LowCardinality важно
Заголовок раздела «⚠️ 7. ClickHouse: LowCardinality важно»LowCardinality(String) для колонок с малым числом уникальных значений (status, country) — сжатие 10-100x, фильтрация быстрее.
⚠️ 8. ClickHouse: max_insert_block_size и memory
Заголовок раздела «⚠️ 8. ClickHouse: max_insert_block_size и memory»Очень большие batch (миллион строк) могут упасть с OOM на сервере. Контролируй через max_block_size и max_insert_block_size.
⚠️ 9. ClickHouse: Distributed + INSERT
Заголовок раздела «⚠️ 9. ClickHouse: Distributed + INSERT»INSERT в Distributed таблицу — асинхронный по shards (по умолчанию). Без insert_distributed_sync=1 может потеряться при перезагрузке.
⚠️ 10. ClickHouse: Materialized view + DROP source
Заголовок раздела «⚠️ 10. ClickHouse: Materialized view + DROP source»DROP source таблицы — MV ломается. ATTACH/DETACH source — данные не доходят. Используй EXCHANGE TABLES для атомарной замены.
⚠️ 11. Tarantool: WAL + snapshot
Заголовок раздела «⚠️ 11. Tarantool: WAL + snapshot»Если упал, при старте redo WAL может занять минуты. Регулярно делай checkpoint (box.snapshot()).
⚠️ 12. Tarantool: память
Заголовок раздела «⚠️ 12. Tarantool: память»Сервер падает, если данные не влезают в RAM (или в memtx_memory). Мониторь box.slab.info().
⚠️ 13. Tarantool: GC Lua
Заголовок раздела «⚠️ 13. Tarantool: GC Lua»Lua-функции, выделяющие много объектов, могут вызвать GC паузу — это блокирует event loop. Пиши hot path на C или используй box.tuple.
⚠️ 14. Vshard: re-sharding
Заголовок раздела «⚠️ 14. Vshard: re-sharding»Vshard поддерживает онлайн-перебалансировку, но при больших объёмах — нагрузка сравнима с initial setup. Планируй заранее.
⚠️ 15. Distributed SQL: ORM compatibility
Заголовок раздела «⚠️ 15. Distributed SQL: ORM compatibility»CRDB / YugabyteDB ≈ Postgres, но не на 100%. Расширения (pg_trgm, postgis) могут не работать. ORM (GORM, Ent) обычно ок, но тесты обязательны.
4. Real cases
Заголовок раздела «4. Real cases»Case 1: миграция MySQL → TiDB
Заголовок раздела «Case 1: миграция MySQL → TiDB»Контекст: MySQL шардированный (8 шардов руками), 20TB, нагрузка растёт.
Решение: TiDB cluster (3 TiDB, 6 TiKV, 3 PD). Миграция через TiDB Data Migration tool. Минусы: latency p99 вырос с 5ms до 15ms (Raft overhead). Плюсы: SQL JOINs между бывшими шардами; авто-rebalance; analytic queries через TiFlash.
Case 2: Observability на ClickHouse
Заголовок раздела «Case 2: Observability на ClickHouse»Контекст: 200 микросервисов, 50k req/s, нужно хранить логи 30 дней.
Stack:
- OpenTelemetry collector → Kafka (буфер) → ClickHouse (через ClickHouse Sink).
- Schema:
ts DateTime64, service LowCardinality(String), level LowCardinality(String), trace_id String, message String, attrs Map(String, String). - TTL:
ttl_date + INTERVAL 30 DAY DELETE. - Cold storage: S3 disk через
tiered_storage.
Результат: 5TB/day сжимается до 250GB/day. Запросы по trace_id — < 100ms.
Case 3: Sessions на Tarantool
Заголовок раздела «Case 3: Sessions на Tarantool»Контекст: Web-app, 30M активных сессий, 500k RPS на чтение, нужна latency p99 < 1ms.
Решение: Tarantool 1.10 (memtx + WAL), 3 ноды, vshard. Каждая нода 64GB RAM, sessions ~20GB total. WAL на NVMe. Latency p99 0.4ms. Стоит в 5 раз дешевле, чем тот же объём в RDS.
Case 4: CRDB для финтеха
Заголовок раздела «Case 4: CRDB для финтеха»Контекст: Бирж-приложение, multi-region (EU + US), требование zero data loss + strong consistency для балансов.
Решение:
- CRDB cluster 9 нод (3 регионa × 3 ноды).
- Таблица
balancesлокализована по region (REGIONAL BY ROW). - Финансовые транзакции — SERIALIZABLE, ретраи в приложении.
Узкое место: cross-region transfer латентность ~80ms (2 phase commit). Решили оптимистичным двух-шаговым переводом (commit local → enqueue async transfer).
Case 5: YDB для геораспределённого SaaS
Заголовок раздела «Case 5: YDB для геораспределённого SaaS»Контекст: Yandex Cloud, multi-region SaaS, нужен ACID + 99.99% uptime.
Решение: YDB managed, схема — обычный реляционный design + топики для очередей событий. PostgreSQL — для аналитики (отдельно). Latency коммита транзакции 5-15ms, throughput 50k TPS.
5. Вопросы (25)
Заголовок раздела «5. Вопросы (25)»- Что такое NewSQL и чем он отличается от классических распределённых БД?
- Как CockroachDB обеспечивает strong consistency? Что такое HLC?
- Что такое range / tablet в распределённых SQL? Как они делятся?
- Почему CRDB в одном дата-центре медленнее Postgres?
- Что такое REGIONAL BY ROW в CRDB и зачем?
- Какие гарантии транзакций даёт YDB? Что такое YQL?
- Чем YDB topics отличаются от Kafka?
- Что такое TiDB HTAP и зачем TiFlash?
- В чём суть column-store? Почему он быстрый для аналитики?
- Какие MergeTree engines есть в ClickHouse и для чего каждый?
- Что такое
ORDER BYв CH и почему это «первичный ключ»? - Что такое
index_granularityиgranule? - Почему ClickHouse не любит мелкие INSERT’ы? Что такое async_insert?
- Что такое Materialized View в CH? Чем отличается от PG?
- Что такое PREWHERE и когда оно полезно?
- Что такое skip indexes (minmax, set, bloom_filter)?
- Что такое Distributed engine? Как идут SELECT и INSERT?
- Что такое
LowCardinality(...)и зачем? - Как ClickHouse реплицируется? Что такое Keeper?
- Почему UPDATE в CH — дорогая операция?
- Что такое Tarantool и для каких задач выбирают?
- Что такое space, tuple, index в Tarantool?
- Чем memtx отличается от vinyl?
- Что такое Vshard?
- Когда выбрать ClickHouse vs Postgres для логов?
6. Practice
Заголовок раздела «6. Practice»6.1. CRDB локально
Заголовок раздела «6.1. CRDB локально»docker run -d --name=crdb -p 26257:26257 -p 8080:8080 \ cockroachdb/cockroach:latest start-single-node --insecurepsql "postgres://root@localhost:26257/defaultdb?sslmode=disable"Создай таблицу, запусти EXPLAIN (CRDB-специфичный), посмотри на KV/Locking info.
6.2. ClickHouse + Go
Заголовок раздела «6.2. ClickHouse + Go»docker run -d --name ch -p 9000:9000 -p 8123:8123 clickhouse/clickhouse-serverCREATE TABLE events (ts DateTime, user_id UInt64, payload String) ENGINE = MergeTree ORDER BY (user_id, ts);Из Go вставляй батчами 10k строк, замерь throughput.
6.3. MaterializedView
Заголовок раздела «6.3. MaterializedView»CREATE MATERIALIZED VIEW events_per_userENGINE = SummingMergeTree ORDER BY user_idAS SELECT user_id, count() AS cnt FROM events GROUP BY user_id;Вставь 1M строк, проверь, что MV видит.
6.4. Tarantool basic
Заголовок раздела «6.4. Tarantool basic»docker run -p 3301:3301 -d --name tnt tarantool/tarantool:2.11Подключись через tarantoolctl connect ..., создай space, индекс, вставь тапл.
6.5. YDB local
Заголовок раздела «6.5. YDB local»docker run -d --rm --name ydb -p 2136:2136 -p 8765:8765 \ cr.yandex/yc/yandex-docker-local-ydb:latestПодключись через ydb -e grpc://localhost:2136 -d /local, создай таблицу, селект.
6.6. Бенч ClickHouse vs Postgres
Заголовок раздела «6.6. Бенч ClickHouse vs Postgres»Загрузи 100M строк логов в обе БД, замерь:
- Time to ingest.
SELECT count(*) WHERE date BETWEEN ....SELECT service, count() GROUP BY service.
Должна быть разница 10-100x в пользу CH.
7. Источники
Заголовок раздела «7. Источники»- CockroachDB Architecture. https://www.cockroachlabs.com/docs/stable/architecture/overview.html
- CockroachDB on Go workload. https://www.cockroachlabs.com/docs/stable/build-a-go-app-with-cockroachdb.html
- YugabyteDB Docs. https://docs.yugabyte.com/
- YDB Documentation. https://ydb.tech/docs/
- ydb-go-sdk. https://github.com/ydb-platform/ydb-go-sdk
- TiDB Docs. https://docs.pingcap.com/tidb/stable/
- ClickHouse Docs. https://clickhouse.com/docs
- ClickHouse Internals (blog series). https://clickhouse.com/blog
- ClickHouse Go Driver. https://github.com/ClickHouse/clickhouse-go
- Tarantool Docs. https://www.tarantool.io/en/doc/latest/
- go-tarantool. https://github.com/tarantool/go-tarantool
- Vshard. https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/
- Spanner paper (Google). https://research.google/pubs/spanner-googles-globally-distributed-database/
- OpenTelemetry + ClickHouse. https://signoz.io/blog/clickhouse-vs-elasticsearch/
- «Designing Data-Intensive Applications» — Martin Kleppmann. Главы о distributed transactions, replication.
Приложение A. Подробнее про ClickHouse в production
Заголовок раздела «Приложение A. Подробнее про ClickHouse в production»A.1. Projections
Заголовок раздела «A.1. Projections»Projection — это «второй порядок сортировки» поверх той же таблицы. Полезно, когда нужно эффективно фильтровать/агрегировать по разным колонкам без создания отдельных MV.
ALTER TABLE eventsADD PROJECTION p_by_type(SELECT * ORDER BY (event_type, ts));
ALTER TABLE events MATERIALIZE PROJECTION p_by_type;После — SELECT … WHERE event_type = 'click' идёт по проекции (быстро) даже если основной ORDER BY был (user_id, ts).
⚠️ Projections занимают место. Контролируй через system.projection_parts.
A.2. TTL и Tiered storage
Заголовок раздела «A.2. TTL и Tiered storage»CREATE TABLE events ( ts DateTime, payload String)ENGINE = MergeTreeORDER BY tsTTL ts + INTERVAL 7 DAY TO VOLUME 'cold', ts + INTERVAL 30 DAY TO DISK 's3_cold', ts + INTERVAL 90 DAY DELETE;Storage policy в config.xml:
<storage_configuration> <disks> <s3_cold> <type>s3</type> <endpoint>https://s3.amazonaws.com/bucket/cold/</endpoint> <access_key_id>...</access_key_id> <secret_access_key>...</secret_access_key> </s3_cold> </disks> <policies> <tiered> <volumes> <hot><disk>default</disk></hot> <cold><disk>s3_cold</disk></cold> </volumes> </tiered> </policies></storage_configuration>ClickHouse сам перекладывает old parts на дешёвый storage.
A.3. ClickHouse Keeper vs ZooKeeper
Заголовок раздела «A.3. ClickHouse Keeper vs ZooKeeper»ClickHouse 21.8+ предоставляет ClickHouse Keeper — drop-in replacement ZooKeeper с тем же ZK wire-protocol, но написан внутри CH. Преимущества:
- Один deployment process (нет отдельной JVM ZK).
- Лучше производительность (~3x на latency).
- Tested внутри CH community.
<!-- keeper_server section --><keeper_server> <tcp_port>9181</tcp_port> <server_id>1</server_id> <coordination_settings> <session_timeout_ms>30000</session_timeout_ms> </coordination_settings> <raft_configuration> <server><id>1</id><hostname>ch1</hostname><port>9234</port></server> <server><id>2</id><hostname>ch2</hostname><port>9234</port></server> <server><id>3</id><hostname>ch3</hostname><port>9234</port></server> </raft_configuration></keeper_server>A.4. Query profiling
Заголовок раздела «A.4. Query profiling»ClickHouse имеет встроенный profiler:
SET allow_introspection_functions=1, query_profiler_real_time_period_ns=10000000;SELECT … (медленный запрос);SELECT trace, count() FROM system.trace_log WHERE query_id='…' GROUP BY trace ORDER BY count() DESC LIMIT 20;system.query_log — все запросы с их временем, памятью, прочитанными rows / bytes.
A.5. Async vs Sync replication
Заголовок раздела «A.5. Async vs Sync replication»ClickHouse Replicated*MergeTree:
- Async by default — INSERT возвращается, как только запись попала в лог на ZK/Keeper.
- insert_quorum — ждать, пока N реплик подтвердят.
SET insert_quorum = 2, insert_quorum_timeout = 600000;INSERT INTO events SELECT …;Это аналог Kafka acks=all. Latency растёт, durability — выше.
A.6. Когда НЕ использовать ClickHouse
Заголовок раздела «A.6. Когда НЕ использовать ClickHouse»- Транзакции, OLTP, точечные UPDATE/DELETE → Postgres / CRDB.
- Tiny aggregates на маленьких таблицах → Postgres.
- Joins по большим таблицам без явного ключа → ClickHouse слаб на сложных JOIN’ах.
- Низкая cardinality + малый объём → overkill.
A.7. Backup / DR
Заголовок раздела «A.7. Backup / DR»clickhouse-backup — open-source tool. Делает inkremental backup на S3.
clickhouse-backup create my_backup_20260520clickhouse-backup upload my_backup_20260520clickhouse-backup download my_backup_20260520clickhouse-backup restore my_backup_20260520A.8. Tarantool detail: Vinyl engine
Заголовок раздела «A.8. Tarantool detail: Vinyl engine»Tarantool имеет vinyl engine (помимо memtx) — LSM-tree, данные не обязательно в RAM. Trade-off:
- memtx: latency 10us, ограничено RAM.
- vinyl: latency 100us-1ms, не ограничено диском.
Используется, когда объём > RAM, но нужна Tarantool API.
A.9. Tarantool sharding и Vshard
Заголовок раздела «A.9. Tarantool sharding и Vshard»Vshard — Lua-библиотека для горизонтального масштабирования:
┌────────────┐ │ Router │ (логика hash(key) → bucket → replicaset) └─────┬──────┘ │ ┌───────────┴────────────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │ Replicaset 1 │ │ Replicaset 2 │ │ (master+ │ │ (master+ │ │ replica) │ │ replica) │ └──────────┘ └──────────┘- Bucket — единица перебалансировки.
- На rebalance — буckets перемещаются между replicasets без остановки.
- Default: 3000 buckets per cluster.
A.10. Real production usage сравнение
Заголовок раздела «A.10. Real production usage сравнение»| Сценарий | Лучший выбор |
|---|---|
| Финтех с multi-region (zero data loss) | CockroachDB / YDB |
| Logs / observability на TB/день | ClickHouse |
| Real-time ad tech / analytics | ClickHouse / Pulsar+CH |
| Sessions / hot cache | Tarantool / Redis |
| Replace MySQL (sharded) | TiDB / Yugabyte |
| Embedded analytics | DuckDB |
| Postgres outgrew vertical scaling | CRDB / Citus / YDB |