π§ ClickHouse Fundamentals
π― What is ClickHouse?
ClickHouse is an open-source, column-oriented OLAP database management system designed for real-time analytics. Originally developed by Yandex in 2016 to power Yandex.Metrica (the world's second-largest web analytics platform), it processes billions of rows per second with sub-second query latency and full SQL support. It's known for being one of the fastest analytical databases in the world.
Why ClickHouse Matters
Speed: Billions of rows/second. Columnar: Optimized for analytics. SQL: Full SQL support. Scale: Petabytes. Simple: Single binary. Real-time: Sub-second queries.
# ClickHouse β the big picture
# The fastest open-source columnar OLAP database.
# CONCEPTUAL OVERVIEW
print("""
=== ClickHouse ===
What it is:
- Column-oriented OLAP database
- Real-time analytics
- Full SQL support
- Vectorized execution
- Single binary
- Open source (Apache 2.0)
Created by:
- Yandex (2016)
- Yandex.Metrica
- Open source (2016)
- ClickHouse Inc. (2021)
Key features:
- MergeTree engine
- Vectorized execution
- Columnar storage
- Compression
- Materialized views
- Distributed tables
- Replication
- Real-time ingestion
What it's NOT:
- Not OLTP
- Not for high-frequency updates
- Not a general-purpose RDBMS
- Not for small data
What it IS:
- Real-time analytics
- Log analysis
- Observability
- Clickstream
- Business intelligence
- Time-series
""")
# WHY IT MATTERS
print("=== Why It Matters ===")
print("""
Challenges:
- Billions of rows
- Real-time analytics
- Sub-second queries
- Full SQL needed
- Simple operations
- Cost-effective
ClickHouse solves:
- Blazing speed
- Full SQL
- Simple ops
- Compression
- Scalability
- Open source
""")
# REAL-WORLD
print("=== Real-World Scale ===")
print("""
Companies using ClickHouse:
- Yandex (original)
- Uber
- Cloudflare
- eBay
- Cisco
- Spotify
- Netflix
- Alibaba
Scale examples:
- Yandex.Metrica: trillions of rows
- Cloudflare: 20M+ requests/sec
- Uber: petabyte-scale logs
- eBay: 100B+ rows
""")ποΈ Architecture
π― Architecture
ClickHouse uses a shared-nothing, MPP (Massively Parallel Processing) architecture. Each node is independent; data is distributed across shards, and queries are parallelized across nodes.
Architecture
Single binary: No dependencies. Shared-nothing: Independent nodes. MPP: Parallel queries. Sharding: Data distributed. Replication: Replica per shard. ZooKeeper: Coordination (optional).
# ClickHouse Architecture
print("=== Architecture Diagram ===")
print("""
βββββββββββββββββββββββββββββββββββββββββββ
β Clients β
β (SQL, HTTP, JDBC, ClickHouse CLI) β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Distributed Table β
β (Logical view over shards) β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββΌββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
β Shard 1 β β Shard 2 β β Shard 3 β
β β β β β β
β βββββββ β β βββββββ β β βββββββ β
β βRep 1β β β βRep 1β β β βRep 1β β
β βRep 2β β β βRep 2β β β βRep 2β β
β βββββββ β β βββββββ β β βββββββ β
βββββββββββ βββββββββββ βββββββββββ
β β β
βββββββββββββΌββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β ZooKeeper / ClickHouse Keeper β
β (Coordination, optional) β
βββββββββββββββββββββββββββββββββββββββββββ
""")
print("\n=== Components ===")
print("""
1. SERVER
- Single binary
- No dependencies
- Multi-threaded
- Vectorized execution
2. SHARD
- Horizontal partition
- Subset of data
- Independent
- Scaled out
3. REPLICA
- Copy of shard
- High availability
- Read scaling
- ReplicatedMergeTree
4. DISTRIBUTED TABLE
- Logical view
- Spans shards
- Query fan-out
- Result merge
5. ZOOKEEPER / KEEPER
- Optional
- Coordination
- Replication metadata
- ClickHouse Keeper (Raft)
6. CLIENT
- clickhouse-client
- HTTP interface
- JDBC/ODBC
- Native protocol
""")
print("\n=== Single Node vs Cluster ===")
print("""
Single Node:
- Dev, small workloads
- Simple setup
- Single binary
- No coordination
Cluster:
- Production
- Sharding + replication
- MPP parallelism
- ZooKeeper/Keeper
- Distributed tables
""")
print("\n=== Configuration ===")
print("""
# config.xml
information
/var/log/clickhouse-server/clickhouse-server.log
0.0.0.0
8123
9000
/var/lib/clickhouse/
users.xml
""")
print("\n=== Cluster Config ===")
print("""
ch1 9000
ch2 9000
ch3 9000
ch4 9000
""")π² MergeTree
π― The Core Engine
MergeTree is ClickHouse's flagship table engine. Data is stored in sorted parts that are asynchronously merged in the background. It's the foundation for most ClickHouse tables.
MergeTree
Sorted: By primary key. Parts: Immutable chunks. Merges: Background. Index: Sparse primary. Compression: Columnar. TTL: Data lifecycle.
# MergeTree
print("=== What is MergeTree? ===")
print("""
MergeTree:
- Primary table engine
- Data stored in parts
- Parts sorted by primary key
- Async background merges
- Sparse primary index
- Columnar storage
- Compression
Key concepts:
- Part: Immutable file
- Granule: 8192 rows (default)
- Primary key: Sort order
- ORDER BY: Required
- PARTITION BY: Optional
- TTL: Data expiration
Properties:
- Fast range scans
- Fast aggregations
- High insert throughput
- Background merges
- Data immutability
""")
print("\n=== Basic Example ===")
print("""
CREATE TABLE events (
timestamp DateTime,
user_id UInt64,
event_type String,
amount Float64
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (timestamp, user_id)
TTL timestamp + INTERVAL 90 DAY
SETTINGS index_granularity = 8192;
""")
print("\n=== Part Structure ===")
print("""
Part:
- Immutable directory
- Data columns (bin)
- Primary index (idx)
- Marks (mrk)
- Metadata (txt)
- Checksums
Naming:
partition_min_block_max_block_level
Example:
202401_1_1_0/
βββ timestamp.bin
βββ timestamp.mrk
βββ user_id.bin
βββ user_id.mrk
βββ event_type.bin
βββ event_type.mrk
βββ amount.bin
βββ amount.mrk
βββ primary.idx
βββ count.txt
βββ checksums.txt
""")
print("\n=== Primary Key ===")
print("""
Primary key:
- Sort order of data
- Sparse index (per granule)
- Not unique (unlike RDBMS)
- Prefix of ORDER BY
Example:
ORDER BY (timestamp, user_id)
Primary key: (timestamp, user_id)
Query: WHERE timestamp = '2024-01-01'
β Uses primary index
β Fast
Query: WHERE user_id = 123
β No primary index
β Full scan
Rule:
- Put most-filtered columns first
- Low cardinality? Maybe partition
- High cardinality? Consider
""")
print("\n=== Granules ===")
print("""
Granule:
- 8192 rows (default)
- Unit of index
- Unit of read
- Configurable
index_granularity:
- Default: 8192
- Rows per index entry
- Trade-off
Smaller granules:
- More index
- More memory
- Faster lookup
Larger granules:
- Less index
- Less memory
- Slower lookup
""")
print("\n=== Merges ===")
print("""
Background merges:
- Combine small parts
- Maintain sort order
- Merge indexes
- Free resources
Merge:
- Automatic
- Background
- Configurable
- Non-blocking
Benefits:
- Fewer parts
- Faster queries
- Compressed
Trigger:
- Parts count
- Time interval
- Manual OPTIMIZE
""")
print("\n=== TTL ===")
print("""
TTL:
- Time-to-live
- Auto-expire data
- Column-level or table-level
- Efficient
Example:
TTL timestamp + INTERVAL 90 DAY
Column-level:
TTL timestamp + INTERVAL 30 DAY DELETE,
timestamp + INTERVAL 7 DAY TO DISK 'cold'
Actions:
- DELETE
- TO DISK
- TO VOLUME
- GROUP BY (aggregate)
""")π§ Table Engines
π― Engines for Every Use Case
ClickHouse has many table engines. MergeTree family is the core; others serve specific purposes like aggregation, replication, or distributed queries.
Engines
MergeTree: Core. Replacing: Dedup. Summing: Pre-agg. Aggregating: Merge states. Distributed: Cluster. Kafka: Streaming.
# Table Engines
print("=== MergeTree Family ===")
print("""
1. MergeTree
- Base engine
- No dedup
- Raw data
2. ReplacingMergeTree
- Dedup by ORDER BY key
- Keeps latest
- Eventual (on merge)
ENGINE = ReplacingMergeTree(version)
3. SummingMergeTree
- Sum numeric columns
- By ORDER BY key
- Eventual (on merge)
ENGINE = SummingMergeTree((amount))
4. AggregatingMergeTree
- Merge aggregate states
- For materialized views
ENGINE = AggregatingMergeTree()
5. CollapsingMergeTree
- Collapse sign (+1/-1)
- For updates
ENGINE = CollapsingMergeTree(sign)
6. VersionedCollapsingMergeTree
- Order-independent
- Uses version column
""")
print("\n=== Other Engines ===")
print("""
Distributed:
- Distributed table
- Spans shards
- Query fan-out
Kafka:
- Streaming ingestion
- From Kafka topics
- No storage itself
Buffer:
- In-memory buffer
- Batch inserts
Memory:
- In-memory storage
- Fast, volatile
Log:
- Simple append-only
- For small tables
Null:
- Discard data
- For testing
File:
- Read external files
- CSV, Parquet
URL:
- Read from URL
- HTTP
Join:
- Pre-joined data
- Fast lookups
Dictionary:
- External dictionaries
- Fast key-value
View:
- Virtual table
- No storage
MaterializedView:
- Pre-computed
- Triggered on insert
""")
print("\n=== Example: ReplacingMergeTree ===")
print("""
-- Create
CREATE TABLE users (
user_id UInt64,
name String,
email String,
updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;
-- Insert
INSERT INTO users VALUES (1, 'Alice', 'a@x.com', now());
INSERT INTO users VALUES (1, 'Alice Smith', 'a@x.com', now());
-- Query (may show duplicates until merge)
SELECT * FROM users;
-- Force merge
OPTIMIZE TABLE users FINAL;
-- Query (deduplicated)
SELECT * FROM users;
-- Query with FINAL (dedup on read)
SELECT * FROM users FINAL;
""")
print("\n=== Example: SummingMergeTree ===")
print("""
-- Create
CREATE TABLE daily_stats (
date Date,
user_id UInt64,
pageviews UInt64,
revenue Float64
)
ENGINE = SummingMergeTree((pageviews, revenue))
ORDER BY (date, user_id);
-- Insert
INSERT INTO daily_stats VALUES ('2024-01-01', 1, 10, 100.5);
INSERT INTO daily_stats VALUES ('2024-01-01', 1, 5, 50.25);
-- Force merge
OPTIMIZE TABLE daily_stats FINAL;
-- Query (summed)
SELECT * FROM daily_stats;
-- 2024-01-01, 1, 15, 150.75
""")
print("\n=== Engine Comparison ===")
print(f"{'Engine':<30} {'Purpose':<35}")
print("-" * 70)
print(f"{'MergeTree':<30} {'Raw data, sorted':<35}")
print(f"{'ReplacingMergeTree':<30} {'Dedup by key':<35}")
print(f"{'SummingMergeTree':<30} {'Sum by key':<35}")
print(f"{'AggregatingMergeTree':<30} {'Aggregate states':<35}")
print(f"{'CollapsingMergeTree':<30} {'Update patterns':<35}")
print(f"{'Distributed':<30} {'Span shards':<35}")
print(f"{'Kafka':<30} {'Streaming ingestion':<35}")
print(f"{'Buffer':<30} {'Batch inserts':<35}")
print(f"{'Memory':<30} {'Fast in-memory':<35}")
print(f"{'Join':<30} {'Pre-joined lookups':<35}")π Data Types
π― Rich Type System
ClickHouse has a rich type system optimized for analytics: numeric types, strings, dates, arrays, maps, tuples, and specialized types like AggregateFunction for materialized views.
Data Types
Numeric: UInt8-256, Int8-256, Float32/64. String: Variable length. FixedString: Fixed. Date/Time: Date, DateTime. Array: Lists. Map: Key-value.
# Data Types
print("=== Numeric Types ===")
print("""
Unsigned integers:
UInt8, UInt16, UInt32, UInt64, UInt128, UInt256
Signed integers:
Int8, Int16, Int32, Int64, Int128, Int256
Floats:
Float32, Float64
Decimals:
Decimal(P, S) β precision, scale
Special:
Bool (alias for UInt8)
""")
print("\n=== String Types ===")
print("""
String:
- Variable length
- UTF-8
- Any bytes
FixedString(N):
- Fixed length N
- Padded with null bytes
Example:
name String,
uuid FixedString(16)
""")
print("\n=== Date/Time Types ===")
print("""
Date:
- 2 bytes
- Days since 1970
- Range: 1970-2149
Date32:
- 4 bytes
- Wider range
DateTime:
- 4 bytes
- Seconds since 1970
- Timezone configurable
DateTime64:
- 8 bytes
- Sub-second precision
- Configurable precision
Example:
timestamp DateTime('UTC'),
precise DateTime64(3, 'UTC')
""")
print("\n=== Complex Types ===")
print("""
Array(T):
- Lists of type T
- Any type
tags Array(String)
Map(K, V):
- Key-value pairs
attributes Map(String, String)
Tuple(T1, T2, ...):
- Fixed-size tuples
coordinates Tuple(Float64, Float64)
Nested:
- Special array of structs
Nested(
name String,
value Float64
)
""")
print("\n=== Special Types ===")
print("""
Nullable(T):
- Adds NULL support
- Performance cost
- Use sparingly
LowCardinality(T):
- Dictionary encoding
- For low cardinality strings
- Big performance gain
AggregateFunction:
- Aggregate states
- For materialized views
- Type-specific
SimpleAggregateFunction:
- Simple aggregates
- Merged on read
Enum:
- Enumerated values
Enum('a' = 1, 'b' = 2)
UUID:
- 16 bytes
- Unique identifiers
""")
print("\n=== Example ===")
print("""
CREATE TABLE events (
timestamp DateTime('UTC'),
user_id UInt64,
session_id UUID,
event_type LowCardinality(String),
country LowCardinality(String),
tags Array(String),
metadata Map(String, String),
amount Decimal(18, 2),
is_mobile Bool,
score Nullable(Float64)
)
ENGINE = MergeTree()
ORDER BY (timestamp, user_id);
""")
print("\n=== Type Comparison ===")
print(f"{'Type':<25} {'Size':<15} {'Use Case':<30}")
print("-" * 70)
print(f"{'UInt64':<25} {'8 bytes':<15} {'IDs, counters':<30}")
print(f"{'Int32':<25} {'4 bytes':<15} {'Signed numbers':<30}")
print(f"{'Float64':<25} {'8 bytes':<15} {'Measurements':<30}")
print(f"{'Decimal':<25} {'varies':<15} {'Money':<30}")
print(f"{'String':<25} {'varies':<15} {'Text':<30}")
print(f"{'Date':<25} {'2 bytes':<15} {'Dates':<30}")
print(f"{'DateTime':<25} {'4 bytes':<15} {'Timestamps':<30}")
print(f"{'Array':<25} {'varies':<15} {'Lists':<30}")
print(f"{'Map':<25} {'varies':<15} {'Key-value':<30}")
print(f"{'LowCardinality':<25} {'varies':<15} {'Enums, categories':<30}")π SQL Querying
π― Full SQL, Extended
ClickHouse supports full SQL with extensions: array functions, higher-order functions, window functions, and specialized aggregations. It's ANSI SQL-compatible but adds powerful analytic extensions.
SQL Querying
SELECT: Full. JOIN: All types. GROUP BY: Extended. Window: Full. Arrays: Powerful. Approximate: Fast.
# SQL Querying
print("=== Basic SELECT ===")
print("""
SELECT * FROM events LIMIT 10;
SELECT
event_type,
COUNT(*) as count,
SUM(amount) as revenue
FROM events
WHERE timestamp >= '2024-01-01'
GROUP BY event_type
ORDER BY count DESC
LIMIT 10;
""")
print("\n=== Advanced Aggregations ===")
print("""
-- Approximate distinct
SELECT uniq(user_id) FROM events;
-- Approximate quantile
SELECT quantile(0.95)(latency) FROM events;
-- Quantiles
SELECT quantiles(0.5, 0.9, 0.99)(latency) FROM events;
-- Top K
SELECT topK(10)(page) FROM events;
-- Histogram
SELECT histogram(10)(latency) FROM events;
-- Group by with filters
SELECT
event_type,
countIf(amount > 100) as high_value,
sumIf(amount, country = 'US') as us_revenue
FROM events
GROUP BY event_type;
""")
print("\n=== Array Functions ===")
print("""
-- Array column
SELECT tags FROM events;
-- Array length
SELECT length(tags) FROM events;
-- Array contains
SELECT has(tags, 'important') FROM events;
-- Array filter
SELECT arrayFilter(x -> x > 10, values) FROM events;
-- Array map
SELECT arrayMap(x -> x * 2, values) FROM events;
-- Array join (explode)
SELECT
event_id,
tag
FROM events
ARRAY JOIN tags AS tag;
-- Group array
SELECT
user_id,
groupArray(event_type) as events
FROM events
GROUP BY user_id;
""")
print("\n=== Higher-Order Functions ===")
print("""
-- Lambda functions
SELECT arrayMap(x -> x * 2, [1, 2, 3]); -- [2, 4, 6]
SELECT arrayFilter(x -> x > 2, [1, 2, 3, 4]); -- [3, 4]
SELECT arrayReduce('sum', [1, 2, 3]); -- 6
-- Complex
SELECT
user_id,
arraySum(arrayMap(x -> x * price, items)) as total
FROM orders;
""")
print("\n=== Window Functions ===")
print("""
SELECT
user_id,
timestamp,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY timestamp
) as running_total,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY timestamp
) as row_num
FROM events;
""")
print("\n=== JOINs ===")
print("""
-- All standard JOINs
SELECT * FROM a JOIN b ON a.id = b.id;
SELECT * FROM a LEFT JOIN b ON a.id = b.id;
SELECT * FROM a RIGHT JOIN b ON a.id = b.id;
SELECT * FROM a FULL JOIN b ON a.id = b.id;
-- ASOF JOIN (time-series)
SELECT *
FROM trades t
ASOF JOIN quotes q
ON t.symbol = q.symbol
AND t.time >= q.time;
-- Cross join
SELECT * FROM a CROSS JOIN b;
""")
print("\n=== EXPLAIN ===")
print("""
EXPLAIN SELECT * FROM events WHERE user_id = 123;
-- Types:
EXPLAIN AST ...
EXPLAIN SYNTAX ...
EXPLAIN PLAN ...
EXPLAIN PIPELINE ...
EXPLAIN ESTIMATE ...
-- Example output:
Expression
Aggregating
Expression
Filter
ReadFromMergeTree
""")
print("\n=== Query Optimization ===")
print("""
1. Use PREWHERE for filter
2. Use PARTITION BY for time
3. ORDER BY for primary key
4. Approximate algorithms
5. Use FINAL sparingly
6. Avoid SELECT *
7. Use LowCardinality
8. Sample data
""")π¦ Materialized Views
π― Pre-Computed Aggregations
ClickHouse materialized views are insert triggers that transform data as it's written. Unlike traditional MVs, they only process new data β they're incremental by design.
Materialized Views
Insert trigger: Fires on insert. Incremental: New data only. Target table: Stores results. Aggregating: Use AggregatingMergeTree. Fast: Pre-computed.
# Materialized Views
print("=== What is a Materialized View? ===")
print("""
Materialized View (ClickHouse):
- Insert trigger
- Transforms data on insert
- Stores in target table
- Incremental (new data only)
- NOT a snapshot
Unlike traditional MVs:
- Not refreshed
- Not a cache
- Just an insert pipeline
- Data flows through
""")
print("\n=== Basic Example ===")
print("""
-- Source table
CREATE TABLE events (
timestamp DateTime,
user_id UInt64,
event_type String,
amount Float64
)
ENGINE = MergeTree()
ORDER BY timestamp;
-- Target table
CREATE TABLE events_by_type (
event_type String,
count UInt64,
revenue Float64
)
ENGINE = SummingMergeTree((count, revenue))
ORDER BY event_type;
-- Materialized view
CREATE MATERIALIZED VIEW mv_events_by_type
TO events_by_type
AS
SELECT
event_type,
count() as count,
sum(amount) as revenue
FROM events
GROUP BY event_type;
-- Insert into source
INSERT INTO events VALUES
(now(), 1, 'click', 10),
(now(), 2, 'view', 0),
(now(), 3, 'click', 20);
-- Query target
SELECT * FROM events_by_type;
""")
print("\n=== AggregatingMergeTree Pattern ===")
print("""
-- Source
CREATE TABLE raw_events (...)
ENGINE = MergeTree()
ORDER BY timestamp;
-- Aggregating target
CREATE TABLE agg_events (
date Date,
event_type String,
unique_users AggregateFunction(uniq, UInt64),
total_amount AggregateFunction(sum, Float64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, event_type);
-- Materialized view
CREATE MATERIALIZED VIEW mv_agg
TO agg_events
AS
SELECT
toDate(timestamp) as date,
event_type,
uniqState(user_id) as unique_users,
sumState(amount) as total_amount
FROM raw_events
GROUP BY date, event_type;
-- Query (merge states)
SELECT
date,
event_type,
uniqMerge(unique_users) as users,
sumMerge(total_amount) as revenue
FROM agg_events
GROUP BY date, event_type;
""")
print("\n=== Live Updates ===")
print("""
Materialized views are incremental:
- Only process NEW inserts
- Don't reprocess old data
- Fast and cheap
For real-time dashboards:
- Query MV + raw
- MV: historical
- Raw: recent
- UNION
Example:
SELECT * FROM agg_events
UNION ALL
SELECT
toDate(timestamp),
event_type,
uniq(user_id),
sum(amount)
FROM raw_events
WHERE timestamp > (SELECT max(date) FROM agg_events)
GROUP BY 1, 2;
""")
print("\n=== Refreshable MVs ===")
print("""
New feature (2024):
- REFRESH EVERY interval
- Full refresh
- Or APPEND
Example:
CREATE MATERIALIZED VIEW mv
REFRESH EVERY 1 HOUR
AS SELECT ...;
CREATE MATERIALIZED VIEW mv
REFRESH EVERY 1 HOUR APPEND
AS SELECT ...;
""")
print("\n=== Best Practices ===")
print("""
1. Use AggregatingMergeTree
- For aggregations
- State functions
2. Use LowCardinality
- Dimension columns
3. Partition target
- By date usually
4. Order by group keys
- Query performance
5. Don't reprocess
- Insert-only
- Plan for backfill
6. Monitor
- MV lag
- Target size
7. Test
- Insert data
- Verify target
""")π₯ Ingestion
π― Getting Data In
ClickHouse supports many ingestion methods: SQL inserts, HTTP, Kafka, S3, batch files, and streaming. It's optimized for high-throughput inserts in large batches.
Ingestion
INSERT: SQL. HTTP: API. Kafka: Streaming. S3: Batch. File: CSV, Parquet. Buffer: Batching.
# Ingestion
print("=== INSERT (SQL) ===")
print("""
-- Values
INSERT INTO events VALUES
(now(), 1, 'click', 10),
(now(), 2, 'view', 0);
-- From SELECT
INSERT INTO events
SELECT * FROM source_table;
-- From file
INSERT INTO events
SELECT * FROM file('data.csv', 'CSV');
-- From S3
INSERT INTO events
SELECT *
FROM s3('s3://bucket/data/*.parquet', 'Parquet');
-- From URL
INSERT INTO events
SELECT * FROM url('http://example.com/data.csv');
""")
print("\n=== HTTP Interface ===")
print("""
# Insert via HTTP
curl -X POST 'http://localhost:8123/' \\
--data-binary 'INSERT INTO events FORMAT JSONEachRow
{"timestamp": "2024-01-01 00:00:00", "user_id": 1, "amount": 10}'
# Query via HTTP
curl 'http://localhost:8123/?query=SELECT%20count()%20FROM%20events'
# With auth
curl -u user:pass 'http://localhost:8123/?query=...'
""")
print("\n=== Kafka Engine ===")
print("""
-- Kafka table
CREATE TABLE events_queue (
timestamp DateTime,
user_id UInt64,
amount Float64
)
ENGINE = Kafka
SETTINGS
kafka_broker_list = 'localhost:9092',
kafka_topic_list = 'events',
kafka_group_name = 'clickhouse',
kafka_format = 'JSONEachRow';
-- Materialized view to destination
CREATE TABLE events (...)
ENGINE = MergeTree()
ORDER BY timestamp;
CREATE MATERIALIZED VIEW events_mv
TO events
AS SELECT * FROM events_queue;
""")
print("\n=== S3 Ingestion ===")
print("""
-- Read from S3
SELECT *
FROM s3(
's3://bucket/data/*.parquet',
'access_key',
'secret_key',
'Parquet'
);
-- Insert into table
INSERT INTO events
SELECT * FROM s3('s3://bucket/data/*.parquet', 'Parquet');
-- With schema
SELECT *
FROM s3(
's3://bucket/data/*.csv',
'CSV',
'timestamp DateTime, user_id UInt64, amount Float64'
);
""")
print("\n=== File Ingestion ===")
print("""
-- CSV
INSERT INTO events
SELECT * FROM file('data.csv', 'CSV');
-- Parquet
INSERT INTO events
SELECT * FROM file('data.parquet', 'Parquet');
-- JSON
INSERT INTO events
SELECT * FROM file('data.json', 'JSONEachRow');
-- TSV
INSERT INTO events
SELECT * FROM file('data.tsv', 'TSV');
""")
print("\n=== Bulk Insert Best Practices ===")
print("""
1. Batch inserts
- 10K-100K rows per batch
- Fewer parts
- Faster
2. Avoid small inserts
- Creates many parts
- Background merges
- Overhead
3. Use async_insert
- ClickHouse batches
- Configurable
4. Use Buffer engine
- In-memory buffer
- Flush to target
5. Parallel inserts
- Multiple clients
- Higher throughput
6. Monitor parts count
- Alert on high
- Prevent problems
""")
print("\n=== Async Inserts ===")
print("""
-- Enable
SET async_insert = 1;
-- Configure
SET async_insert_max_data_size = 10000000; -- 10MB
SET async_insert_busy_timeout_ms = 200;
-- Or per query
INSERT INTO events
SETTINGS async_insert = 1
VALUES (...);
Benefits:
- Auto batching
- Fewer parts
- Higher throughput
""")
print("\n=== Ingestion Comparison ===")
print(f"{'Method':<20} {'Best For':<35}")
print("-" * 55)
print(f"{'INSERT':<20} {'Simple, ad-hoc':<35}")
print(f"{'HTTP':<20} {'Apps, APIs':<35}")
print(f"{'Kafka':<20} {'Streaming':<35}")
print(f"{'S3':<20} {'Batch, data lake':<35}")
print(f"{'File':<20} {'Local, small':<35}")
print(f"{'Buffer':<20} {'High frequency':<35}")π Performance
π― Blazing Fast
ClickHouse's speed comes from vectorized execution, columnar storage, compression, and parallel processing. It processes billions of rows per second.
Performance
Vectorized: SIMD. Columnar: Less I/O. Compressed: Faster reads. Parallel: All cores. Sparse index: Fast lookup. Approximate: Faster.
# Performance
print("=== Vectorized Execution ===")
print("""
Vectorized:
- Process data in batches
- SIMD instructions
- CPU cache friendly
- 100x faster than row-by-row
Example:
Traditional:
for row in rows:
result = row.a + row.b
Vectorized:
result = vector_a + vector_b
Modern CPU processes 4-8 values per instruction
""")
print("\n=== Columnar Storage ===")
print("""
Columnar:
- Column stored together
- Only read needed columns
- Better compression
- Less I/O
Query: SELECT avg(price) FROM sales;
Row: read all columns
Columnar: read only price
I/O: 10x less
""")
print("\n=== Compression ===")
print("""
Codecs:
- LZ4 (default)
- ZSTD (better)
- Delta
- DoubleDelta
- Gorilla
- T64
Example:
timestamp DateTime CODEC(DoubleDelta)
price Float64 CODEC(Gorilla)
name String CODEC(ZSTD(3))
Savings:
- 5-20x compression
- Faster reads
- Less storage
""")
print("\n=== Index Granules ===")
print("""
index_granularity:
- 8192 rows per granule
- Sparse index
- Fast lookups
Smaller:
- More index
- Faster lookup
- More memory
Larger:
- Less index
- Slower lookup
- Less memory
Default: 8192
""")
print("\n=== Query Optimization ===")
print("""
1. Use PREWHERE
SELECT * FROM events
PREWHERE user_id = 123
WHERE timestamp > '2024-01-01'
PREWHERE reads fewer columns first
β Faster
2. Use partition pruning
WHERE timestamp >= '2024-01-01'
β Skip old partitions
3. Use primary key
ORDER BY (timestamp, user_id)
WHERE timestamp = '2024-01-01'
β Fast
4. Use approximate
uniq() instead of count(distinct)
quantile() instead of exact
β 10x faster
5. Avoid FINAL
SELECT * FROM table FINAL
β Slower
Use ReplacingMergeTree carefully
6. Use materialized views
β Pre-computed
""")
print("\n=== Benchmark ===")
print("""
ClickHouse benchmark:
- 100M rows
- Simple aggregation
Results:
- ClickHouse: 0.05s
- PostgreSQL: 10s
- MySQL: 30s
- MongoDB: 100s
Speedup: 200-2000x
""")
print("\n=== Scaling ===")
print("""
Vertical:
- More CPU
- More RAM
- Faster disk
Horizontal:
- Add shards
- Add replicas
- Distributed tables
Example:
1 node: 1M rows/sec
10 nodes: 10M rows/sec
100 nodes: 100M rows/sec
Linear scaling
""")
print("\n=== Resource Usage ===")
print(f"{'Resource':<20} {'Usage':<35}")
print("-" * 55)
print(f"{'CPU':<20} {'All cores':<35}")
print(f"{'RAM':<20} {'Query + cache':<35}")
print(f"{'Disk':<20} {'Compressed data':<35}")
print(f"{'Network':<20} {'Shuffle for joins':<35}")π’ Deployment
π― Running ClickHouse
ClickHouse can be deployed on a single node, a cluster, or managed services. It's simple to install (single binary) but needs planning for production.
Deployment
Single binary: Easy. Docker: Container. Cluster: Sharded. Cloud: Managed. Kubernetes: Operator. ClickHouse Cloud: Official.
# Deployment
print("=== Single Node ===")
print("""
Install:
# Ubuntu/Debian
sudo apt-get install clickhouse-server clickhouse-client
# Or download binary
curl https://clickhouse.com/ | sh
./clickhouse server
# Docker
docker run -d --name clickhouse \\
-p 8123:8123 -p 9000:9000 \\
clickhouse/clickhouse-server
Config:
/etc/clickhouse-server/config.xml
/etc/clickhouse-server/users.xml
Start:
sudo systemctl start clickhouse-server
clickhouse-client
""")
print("\n=== Docker Compose ===")
print("""
version: '3.8'
services:
clickhouse:
image: clickhouse/clickhouse-server:latest
ports:
- "8123:8123"
- "9000:9000"
volumes:
- ./data:/var/lib/clickhouse
- ./config:/etc/clickhouse-server/config.d
ulimits:
nofile:
soft: 262144
hard: 262144
environment:
CLICKHOUSE_USER: admin
CLICKHOUSE_PASSWORD: secret
""")
print("\n=== Cluster Setup ===")
print("""
3-node cluster:
- 2 shards
- 2 replicas per shard
- ZooKeeper/Keeper for coordination
Config:
ch1 9000
ch2 9000
ch3 9000
ch4 9000
01
ch1
zk1 2181
zk2 2181
zk3 2181
""")
print("\n=== Distributed Tables ===")
print("""
-- Local tables on each shard
CREATE TABLE events_local ON CLUSTER prod_cluster (
timestamp DateTime,
user_id UInt64,
amount Float64
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY timestamp;
-- Distributed table
CREATE TABLE events ON CLUSTER prod_cluster
AS events_local
ENGINE = Distributed('prod_cluster', 'default', 'events_local', rand());
-- Query distributed table
SELECT count() FROM events;
-- Insert distributed
INSERT INTO events VALUES (...);
""")
print("\n=== ClickHouse Cloud ===")
print("""
ClickHouse Cloud:
- Managed service
- Auto-scaling
- Serverless
- Pay per use
- AWS, GCP, Azure
Features:
- Automatic backups
- Multi-AZ
- Encryption
- Monitoring
- IAM integration
Console: console.clickhouse.cloud
""")
print("\n=== Kubernetes ===")
print("""
ClickHouse Operator:
- Kubernetes-native
- CRDs
- Automated cluster
Example:
apiVersion: clickhouse.altinity.com/v1
kind: ClickHouseInstallation
metadata:
name: ch-cluster
spec:
configuration:
clusters:
- name: cluster
layout:
shardsCount: 2
replicasCount: 2
""")
print("\n=== Sizing ===")
print(f"{'Workload':<20} {'CPU':<10} {'RAM':<10} {'Disk':<15}")
print("-" * 55)
print(f"{'Dev':<20} {'2':<10} {'8 GB':<10} {'100 GB':<15}")
print(f"{'Small':<20} {'8':<10} {'32 GB':<10} {'1 TB':<15}")
print(f"{'Medium':<20} {'32':<10} {'128 GB':<10} {'10 TB':<15}")
print(f"{'Large':<20} {'64+':<10} {'256+ GB':<10} {'100+ TB':<15}")
print("\n=== Best Practices ===")
print("""
1. Use SSD for data
2. Separate data and log
3. Ample RAM
4. 10G+ network
5. Replication
6. Backup
7. Monitoring
8. Test failover
""")πΌ Use Cases
π― Where ClickHouse Excels
ClickHouse is ideal for real-time analytical workloads: log analytics, observability, clickstream, IoT, BI, and time-series data.
Use Cases
Logs: Log analytics. Observability: Metrics, traces. Clickstream: User behavior. IoT: Sensor data. BI: Analytics. Real-time: Dashboards.
# Use Cases
print("=== Log Analytics ===")
print("""
Use case:
- Application logs
- Server logs
- Security logs
- Audit logs
Why ClickHouse:
- High ingestion rate
- Fast queries
- Full-text search (via tokens)
- Compression
Example:
- Cloudflare: 20M+ req/sec
- Uber: PB-scale logs
- Netflix: streaming logs
""")
print("\n=== Observability ===")
print("""
Use case:
- Metrics
- Traces
- Logs
- APM
Why ClickHouse:
- Time-series
- Real-time
- Aggregations
- Fast queries
Tools:
- Grafana
- Uber M3
- Signoz
- Uptrace
""")
print("\n=== Clickstream Analytics ===")
print("""
Use case:
- User behavior
- Page views
- Funnels
- Sessions
Why ClickHouse:
- High cardinality
- Session analysis
- Funnel queries
- Fast
Example:
- Yandex.Metrica
- eBay
- Spotify
""")
print("\n=== IoT and Time-Series ===")
print("""
Use case:
- Sensor data
- Device telemetry
- Monitoring
Why ClickHouse:
- Time-series
- Compression
- Fast aggregations
- TTL
Example:
- Smart meters
- Industrial IoT
- Fleet tracking
""")
print("\n=== Business Intelligence ===")
print("""
Use case:
- Dashboards
- Reports
- Analytics
Why ClickHouse:
- SQL
- Fast
- Connects to BI tools
Tools:
- Grafana
- Metabase
- Superset
- Tableau
""")
print("\n=== Real-World Examples ===")
print("""
1. Yandex.Metrica
- Trillions of rows
- Millions of queries/day
- Sub-second
2. Cloudflare
- 20M+ requests/sec
- Real-time dashboards
- DDoS detection
3. Uber
- PB-scale logs
- Observability
- Real-time analytics
4. eBay
- 100B+ rows
- Real-time monitoring
- Business analytics
5. Netflix
- Streaming logs
- Observability
- Analytics
6. Spotify
- User analytics
- Real-time metrics
""")
print("\n=== When to Use ClickHouse ===")
print("""
USE CLICKHOUSE:
β Real-time analytics
β Log analysis
β Observability
β Clickstream
β IoT / time-series
β Business intelligence
β Sub-second queries
β Full SQL
β Simple operations
AVOID:
β OLTP (use Postgres)
β High-frequency updates (use ReplacingMergeTree)
β Full-text search (use Elasticsearch)
β Complex joins (limit)
β Small data (use SQLite)
β Key-value (use Redis)
""")βοΈ vs Alternatives
π― ClickHouse vs the Competition
ClickHouse competes with Druid, Pinot, Snowflake, BigQuery, and traditional warehouses. Each has trade-offs.
Comparison
ClickHouse: Batch speed, SQL. Druid: Streaming, concurrency. Pinot: User-facing. Snowflake: Managed. BigQuery: Serverless. Redshift: AWS.
| Aspect | ClickHouse | Druid | Pinot |
|---|---|---|---|
| Origin | Yandex | Metamarkets | |
| Architecture | Monolithic | Microservices | Microservices |
| Primary Use | Batch analytics | Real-time analytics | User-facing |
| SQL | Full | Partial | Partial |
| Joins | Full | Limited | Limited |
| Streaming | Kafka (table engine) | Kafka, Kinesis (native) | Kafka (native) |
| Rollup | Materialized views | Built-in (default) | Pre-aggregation |
| Concurrency | High | Very high | Very high |
| Updates | Async mutations | No | Upserts (CDC) |
| Ops Complexity | Low | High | High |
| Storage | Local + S3 | Deep storage | Deep storage |
| Best For | Batch speed, SQL | Real-time, concurrency | User-facing analytics |
# ClickHouse vs Snowflake/BigQuery
print("=== ClickHouse vs Snowflake ===")
print(f"{'Aspect':<25} {'ClickHouse':<25} {'Snowflake':<25}")
print("-" * 75)
print(f"{'Type':<25} {'Self-managed':<25} {'Managed cloud':<25}")
print(f"{'Cost':<25} {'Low (self-host)':<25} {'Pay-per-use':<25}")
print(f"{'Setup':<25} {'Simple':<25} {'Easy':<25}")
print(f"{'Performance':<25} {'Very fast':<25} {'Fast':<25}")
print(f"{'SQL':<25} {'Full + extensions':<25} {'Full':<25}")
print(f"{'Concurrency':<25} {'High':<25} {'High':<25}")
print(f"{'Open source':<25} {'Yes':<25} {'No':<25}")
print(f"{'Lock-in':<25} {'None':<25} {'High':<25}")
print("\n=== ClickHouse vs BigQuery ===")
print(f"{'Aspect':<25} {'ClickHouse':<25} {'BigQuery':<25}")
print("-" * 75)
print(f"{'Type':<25} {'Self-managed':<25} {'Serverless':<25}")
print(f"{'Cost':<25} {'Fixed':<25} {'Per query':<25}")
print(f"{'Setup':<25} {'You install':<25} {'No setup':<25}")
print(f"{'Performance':<25} {'Very fast':<25} {'Very fast':<25}")
print(f"{'SQL':<25} {'Full':<25} {'Full':<25}")
print(f"{'Concurrency':<25} {'High':<25} {'Very high':<25}")
print(f"{'Open source':<25} {'Yes':<25} {'No':<25}")
print(f"{'Lock-in':<25} {'None':<25} {'High':<25}")
print("\n=== When to Use Which ===")
print("""
USE CLICKHOUSE:
β Self-hosted
β Cost-sensitive
β Real-time analytics
β Batch + streaming
β Full SQL
β No lock-in
USE DRUID:
β Real-time streaming
β High concurrency
β Rollup pre-aggregation
β User-facing
USE PINOT:
β User-facing analytics
β High concurrency
β LinkedIn scale
USE SNOWFLAKE/BIGQUERY:
β Managed
β Serverless
β Enterprise
β Multi-cloud
""")
print("\n=== Decision Flow ===")
print("""
Start
β
ββ Managed? ββYesβββ Snowflake/BigQuery
β β
β No
β β
ββ Streaming? ββYesβββ Druid
β β
β No
β β
ββ User-facing? ββYesβββ Pinot
β β
β No
β β
ββ Batch analytics? ββYesβββ ClickHouse
""")β FAQ
π― Frequently Asked Questions
Answers to common questions about ClickHouse.
What is ClickHouse?
ClickHouse is an open-source, column-oriented OLAP database management system designed for real-time analytics. Originally developed by Yandex in 2016, it processes billions of rows per second with sub-second query latency and full SQL support.
What is MergeTree in ClickHouse?
MergeTree is ClickHouse's primary table engine family. Data is stored in sorted parts that are asynchronously merged in the background. MergeTree variants (ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree) provide different merge behaviors for various use cases.
What is the difference between ClickHouse and Druid?
ClickHouse is a monolithic, full-SQL analytical database optimized for raw batch query speed. Druid is a microservices-based real-time analytics database optimized for streaming ingestion, high concurrency, and pre-aggregation (rollup). ClickHouse is simpler to operate and has broader SQL support; Druid excels at user-facing analytics with streaming data.
Does ClickHouse support updates and deletes?
Yes, but they are asynchronous and heavyweight. ClickHouse supports UPDATE and DELETE via mutations (ALTER TABLE ... UPDATE/DELETE), plus lightweight deletes (DELETE FROM). For high-frequency updates, use ReplacingMergeTree or CollapsingMergeTree patterns.
When should I use ClickHouse?
Use ClickHouse for real-time analytical workloads: log analytics, observability, clickstream analysis, IoT, business intelligence, and time-series data. It excels when you need sub-second query latency on billions of rows with full SQL support and simple operations.
Is ClickHouse free?
Yes. ClickHouse is open source under Apache 2.0. There's also ClickHouse Cloud (managed service) with a free trial, and enterprise support from ClickHouse Inc.
How does ClickHouse achieve such high performance?
ClickHouse uses vectorized execution (SIMD), columnar storage, compression (LZ4, ZSTD), sparse primary indexes, and parallel processing across all CPU cores. It's designed from scratch for analytical workloads.
What is the difference between ClickHouse and PostgreSQL?
PostgreSQL is a general-purpose relational database optimized for OLTP (transactions). ClickHouse is a specialized OLAP database optimized for analytics. PostgreSQL is better for transactions, updates, and complex joins; ClickHouse is 100-1000x faster for analytical queries on large datasets.
Does ClickHouse support JOINs?
Yes. ClickHouse supports all standard JOIN types (INNER, LEFT, RIGHT, FULL, CROSS) plus ASOF JOIN for time-series. For best performance, use smaller tables on the right side (broadcast) and consider dictionary or denormalization for high-cardinality joins.
What are ClickHouse materialized views?
ClickHouse materialized views are insert triggers that transform data as it's written. Unlike traditional MVs, they only process new data β they're incremental by design. Use AggregatingMergeTree for aggregations and combine MV with raw data for real-time dashboards.
# Quick Reference
print("=== Quick Answers ===")
print("""
Q: What is ClickHouse?
A: Fastest open-source columnar OLAP database.
Q: Free?
A: Yes β Apache 2.0.
Q: Sub-second queries?
A: Yes β designed for it.
Q: Streaming?
A: Kafka, Kinesis (table engines).
Q: Updates?
A: Async mutations, ReplacingMergeTree.
Q: Joins?
A: Full SQL support.
Q: SQL?
A: Full β ANSI + extensions.
Q: Alternative?
A: Druid, Pinot, Snowflake, BigQuery.
Q: Best for?
A: Logs, observability, clickstream, BI.
Q: Managed?
A: ClickHouse Cloud.
""")π Future
π― Where ClickHouse Is Going
The future is cloud-native, AI-augmented, serverless, and more integrated with the modern data stack.
Future Directions
Cloud: Managed. AI: Auto-optimization. Serverless: ClickHouse Cloud. Lakehouse: Iceberg. Real-time: Streaming. Open: Standards.
# Future of ClickHouse
print("=== 1. Cloud-First ===")
print("""
Trend: ClickHouse Cloud
Features:
- Managed service
- Auto-scaling
- Serverless
- Pay per use
- Multi-cloud
Benefits:
- No ops
- Elastic
- Cost-effective
Future:
- Serverless default
- Managed standard
""")
print("\n=== 2. AI-Augmented ===")
print("""
Trend: AI in ClickHouse
Features:
- Query optimization
- Auto-indexing
- Anomaly detection
- Natural language queries
Future:
- Self-tuning
- Predictive
- AI-native
""")
print("\n=== 3. Lakehouse Integration ===")
print("""
Trend: ClickHouse + Lakehouse
Features:
- Iceberg tables
- Delta Lake
- Hudi
- S3 native
Future:
- Read from lake
- Write to lake
- Unified analytics
""")
print("\n=== 4. Real-Time ===")
print("""
Trend: Streaming-first
Features:
- Kafka
- Kinesis
- Pulsar
- Async inserts
- Real-time MVs
Future:
- Streaming-native
- Real-time default
""")
print("\n=== 5. Performance ===")
print("""
Trend: Even faster
Features:
- New codecs
- Vectorization
- SIMD
- GPU (experimental)
- Better compression
Future:
- 10x faster?
- Lower cost
- More efficient
""")
print("\n=== 6. Open Standards ===")
print("""
Trend: Open wins
Standards:
- SQL
- Iceberg
- OpenTelemetry
- OpenLineage
Future:
- Interoperability
- No lock-in
- Open ecosystem
""")
# TIMELINE
print("\n=== Timeline ===")
print("""
Past:
2016: ClickHouse open sourced
2017: Yandex.Metrica
2019: Growing adoption
2021: ClickHouse Inc.
2022: Cloud launch
2024: Mature
Present:
2025: Rapid growth
2026: AI features
Future:
2030: Serverless?
2035: AI-native?
2040: New paradigms?
""")
print("\n=== Final Thoughts ===")
print("""
ClickHouse is:
- Fastest OLAP database
- Full SQL
- Columnar
- Open source
- Easy to operate
The future is:
- Cloud-first
- AI-augmented
- Serverless
- Lakehouse
- Real-time
"Blazing fast analytics at scale."
Keep exploring. Keep building.
""")