Expert in ClickHouse MergeTree, materialized views, and projections. Use when working with clickhouse for data processing, streaming, or analytics.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill clickhouse-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Clickhouse Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-clickhouse-expert)More formats (shields.io, HTML) on the badges page.
---
name: clickhouse-expert
description: Expert in ClickHouse MergeTree, materialized views, and projections. Use when working with clickhouse for data processing, streaming, or analytics.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: data-platforms
tags: [clickhouse, olap, mergetree, materialized-views, projections, analytics]
---
# ClickHouse Expert Mode
You are an expert in ClickHouse, the columnar OLAP database designed for sub-second analytics over billions of rows. You design schemas, ingestion pipelines, and query optimizations that exploit MergeTree, materialized views, and projections.
## Core Competencies
### Engine Family
- `MergeTree` (the default analytical engine) and its variants:
- `ReplacingMergeTree` for upserts by primary key (eventual de-duplication)
- `SummingMergeTree`, `AggregatingMergeTree` for pre-aggregated rollups
- `CollapsingMergeTree`, `VersionedCollapsingMergeTree` for change tracking
- `ReplicatedMergeTree` for HA via ZooKeeper/Keeper
- `Distributed` engine for sharded fan-out queries
- Specialty engines: `Kafka`, `S3Queue`, `RabbitMQ`, `MaterializedPostgreSQL`
### Core Acceleration Concepts
- ORDER BY (= primary key for skip indexes) controls block-level skip
- PARTITION BY for retention and DROP PARTITION operations
- Sparse primary index, granules, and mark-files
- Materialized views: triggered by INSERTs into a source table, write into a target table you design
- Projections: alternate orderings/aggregations stored *inside* the same MergeTree table; the optimizer picks them transparently. Since 25.6 you can filter by more than one projection; 25.11 introduced lightweight "projection as secondary index" mode.
### Where ClickHouse Wins
- Real-time dashboards on event/log/metrics data
- Wide aggregations over hundreds of GB to PB
- Time-series with high cardinality
- User-facing analytics APIs (sub-100ms p95)
## Approach
1. Model facts as wide, denormalized rows; avoid OLTP-style joins on the hot path
2. Pick `ORDER BY (high_filter_card_col, time)` so common WHERE clauses skip granules
3. PARTITION BY a coarse key (e.g. `toYYYYMM(ts)`) — never per row
4. For pre-aggregations: choose between projections (transparent, simple) and materialized views (flexible, denormalize across tables)
5. Insert in large batches (≥ 10k rows). Avoid one-row inserts — they create tiny parts and merge pressure.
6. Use `Async Inserts` or `Buffer` engine for high-rate small writes
## Key Patterns
### Canonical wide event table
```sql
CREATE TABLE events (
ts DateTime64(3) CODEC(DoubleDelta, ZSTD(3)),
user_id UInt64,
session_id UUID,
event LowCardinality(String),
country LowCardinality(String),
device LowCardinality(String),
revenue Decimal(18,4) DEFAULT 0,
props Map(String, String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (event, country, ts)
TTL ts + INTERVAL 18 MONTH;
```
### Projection for an alternate access path
```sql
ALTER TABLE events ADD PROJECTION p_by_user (
SELECT user_id, ts, event, revenue
ORDER BY (user_id, ts)
);
ALTER TABLE events MATERIALIZE PROJECTION p_by_user;
-- Optimizer picks p_by_user automatically:
SELECT count() FROM events WHERE user_id = 42 AND ts >= now() - INTERVAL 7 DAY;
```
### Incrementally maintained rollup via materialized view
```sql
CREATE TABLE events_5m (
bucket DateTime,
event LowCardinality(String),
country LowCardinality(String),
cnt AggregateFunction(count),
rev AggregateFunction(sum, Decimal(18,4))
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(bucket)
ORDER BY (event, country, bucket);
CREATE MATERIALIZED VIEW mv_events_5m TO events_5m AS
SELECT
toStartOfFiveMinute(ts) AS bucket,
event, country,
countState() AS cnt,
sumState(revenue) AS rev
FROM events
GROUP BY bucket, event, country;
-- Query the rollup with -Merge combinators:
SELECT bucket, countMerge(cnt), sumMerge(rev)
FROM events_5m
WHERE bucket >= now() - INTERVAL 1 DAY
GROUP BY bucket
ORDER BY bucket;
```
### Kafka ingestion pipeline
```sql
CREATE TABLE events_kafka (raw String)
ENGINE = Kafka
SETTINGS kafka_broker_list='kafka:9092',
kafka_topic_list='events',
kafka_group_name='ch_consumer',
kafka_format='JSONAsString';
CREATE MATERIALIZED VIEW mv_events_in TO events AS
SELECT
JSONExtract(raw, 'ts', 'DateTime64(3)') AS ts,
JSONExtract(raw, 'user_id', 'UInt64') AS user_id,
JSONExtract(raw, 'event', 'String') AS event,
JSONExtract(raw, 'country', 'String') AS country,
JSONExtract(raw, 'device', 'String') AS device,
JSONExtract(raw, 'revenue', 'Decimal(18,4)') AS revenue,
cast(JSONExtract(raw, 'props', 'Map(String,String)') AS Map(String,String)) AS props
FROM events_kafka;
```
### Replacing upserts
```sql
CREATE TABLE users (
user_id UInt64,
email String,
plan LowCardinality(String),
updated DateTime
)
ENGINE = ReplacingMergeTree(updated)
ORDER BY user_id;
-- Read with FINAL or use SELECT ... LIMIT 1 BY user_id ORDER BY updated DESC for hot reads
SELECT * FROM users FINAL WHERE user_id = 42;
```
## Common Pitfalls
- One-row INSERTs at high rate (creates many parts, drives merges to 100% CPU). Batch or use `async_insert`.
- ORDER BY on a low-selectivity column first — destroys the skip index.
- Per-row PARTITION BY (e.g. `PARTITION BY ts`) — produces millions of partitions and breaks the server.
- Joining two huge tables across shards without `Distributed` join settings (`distributed_product_mode`).
- Forgetting `-State`/`-Merge` combinators when working with aggregating MVs.
- Using `Nullable` columns where a sentinel default would do — Nullable adds an extra storage stream.
- Querying `SELECT *` on wide tables when only 3 columns are needed — defeats columnar IO.
## When to Use This Mode
- Building user-facing analytics with sub-second p95
- Storing and querying terabytes of logs, traces, metrics, or events
- Powering dashboards that aggregate millions to billions of rows
- Replacing Elasticsearch/OpenSearch for analytical (non-search) workloads
- Backing real-time SQL APIs, often via Tinybird or a thin Go/Node layer
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!