-
OLAP, column-oriented DBMS
-
Started as a clickstream data warehouse at Yandex
-
”Real-time data warehouse”
-
Parallel processing on multiple cores
-
Distributed processing on multiple servers
-
SQL syntax
-
At least 100x faster than row-oriented DBMS when used in OLAP scenarios
Pros
- Simple installation
- Strongly-typed schema
- Hot / cold storage with automatic migration
- Designed for scalability
- Very high ingestion rate
- Simple queries as fast as Timescale
- Complex queries are faster than Timescale
- Lower HW requirements than Druid
- Engine configurable for each table
Cons
- Query language is SQL-like, not true SQL
- Engine configurable for each table (difficult tuning ?? )
- JOINS not as straightforward - use dictionaries ???
- Backup / restore has limitations
- No strong atomicity guarantees
https://fr.slideshare.net/Altinity/migration-to-clickhouse-practical-guide-by-alexander-zaitsev
- Comprehensive list of caveats
Setup
https://clickhouse.com/docs/en/getting-started
Local instance
https://clickhouse.com/docs/en/install#quick-install
- Install:
curl https://clickhouse.com/ | sh
- Start server:
./clickhouse server
- Start client:
./clickhouse
Courses
https://learn.clickhouse.com/user_catalog_class/show/1309771?title=March-27---28-2024
- CH fundamentals, 6 hours
https://learn.clickhouse.com/class_catalog/category/116050
- CH in depth, 12 hours
Fundamentals
Table engines
MergeTree (MergeTree family of engines)
- most univeral and functional engine for high-load tasks
CREATE TABLE my_table
( ... )
ENGINE = MergeTree
Sort order and primary keys
- sort order defines the primary keys
CREATE TABLE my_table
(
column1 ...
column2 ...
column3 ...
)
PRIMARY KEY (column1, column2)
same as
ORDER BY column1, column2
- primary key is per-row, some rows might not have that key
Parts
Each insert creates a part
- A part is “just a folder” and inside that folder there are immutable files
- A part typically contains multiple rows
Best to load 1000’s or 10,000s of rows with one insert
Over time, parts can be merged in the background. Up to a configurable size limit.
Pro tip: async insert
If you insert one row at a time, enable configuration for async_insert
Primary index
The primary index is sparse and stored in primary.idx
.
Built by taking first row every 8192 rows (this is a granule)
- Granule is the smallest data set that is read when searching rows
- Size of granule determines the sparseness of the index
Granules
When searching with multiple primary keys ⇒ Stripes of granules Each stripe of granules is processed by an individual thread on a CPU core.
Primary keys ⇒ granules
Database
”It’s just a folder”
Predefined DBs:
-
system
database is read-only, contains info about what’s happening with the DBMS -
default
database is where we create tables if we don’t specify a database -
INFORMATION_SCHEMA
Partitions
-
Unlike other DBs, partitions are mostly for data management, not for performance
-
DON’T partition by high cardinality data
-
A good partition would be, for example, by month
- Easy to delete data from a few months ago
- If you have A LOT of data, might need to partition by day
Views and materialized views
By default, in CH views are not materialized.
By default, materialized views are based on INSERT trigger
- when a row is inserted in the origin table, the materialized view query re-runs
- the query runs only on the new rows
- CAVEAT: deletes or updates don’t trigger a materialized view
In general, if you go back and change data, materialized view are not the best way for now but they are being improved
- EXPERIMENTAL: Refreshable materialized views (at regular intervals)
Best approach to create a materialized view:
- Define destination table
- Define MV using the
TO
clause to the destination table - Populate the destination table with historic data
Projections
It’s like a materialized view for a part or partition
You don’t define a separate materialized table, a table is built for you behind the scenes
CH chooses at query time which projection to use, if any
ALTER TABLE uk_price_paid
ADD PROJECTION town_sort_projection (
SELECT
town, price, date, street, locality
ORDER BY town
)
Applies only to new parts (due to inserts or merge of other parts)
Cons:
- New feature, less reliable than materialized views
Pros:
- Great for re-sorted data
Deduplication / updating and deleting data
Mutations
Reminder: parts are immutable files Updates and deletes will result in creation of new parts This is called a mutation
See active mutations with:
SELECT * FROM system.mutations
CAVEAT: you cannot update a primary key column
Lightweight deletes
Available only in Cloud version, a row is marked as deleted
Engines to handle deduplication
- ReplacingMergeTree
- CollapsingMergeTree
Best practices
-
Only SELECT the columns you need
-
Joins work but need to understand how they work
-
Choose primary keys that are used most often in queries
- Lower cardinality key first
- Add columns to primary key in order to skip granules
-
Don’t use Nullable on every single column Nullables are represented with an additional bool column
-
For low cardinality data (up to 10000 unique values), use
LowCardinality(type)
Creates a dictionary behind the scenes, don’t need to know the values in advance -
Use dictionaries with JOINs
-
Avoid floats when possible, use Decimal
-
Understand how projections work
-
Use data types for UUID and IP addresses, not String
-
Prefer LowCardinality data types to Enums
-
Flatten your data at insertion time
-
Use materialized views
-
Don’t overuse regular views
-
View query details with
EXPLAIN
Anti-patterns - things to avoid
https://clickhouse.com/blog/common-getting-started-issues-with-clickhouse
- Too many parts a. bad choice of partition key b. Many small inserts c. Excessive materialized views
- Going horizontal too early
- Mutation pain a. Deduplication
- Unnecessary use of complex types
- Deduplication at insert time a. replicated_deduplication_window
- Poor primary key selection
- Overuse of data skipping indices
- LIMIT doesn’t always short circuit + point lookups
- IP Filtering in Cloud
- Readonly tables
- Memory Limit Exceeded for Query a. Aggregations b. JOINs c. Rogue queries
- Issues relating to Materialized Views LOTS OF CAVEATS, RE-READ
- Experimental features in production
Other issues
- Not understanding the limitations of materialized views
How to
Inexpensive aggregates that are recomputed frequently
ENGINE = AggregatingMergeTree
CREATE TABLE uk_aggregated_prices (
district String,
avg_price AggregateFunction(avg, UInt32),
max_price SimpleAggregateFunction(max, UInt32),
quant90 AggregateFunction(quantiles(0.90), UInt32)
)
ENGINE = AggregatingMergeTree
PRIMARY KEY district;
See here for difference between Aggregate and SimpleAggregate
Custom codecs
https://clickhouse.com/docs/en/sql-reference/statements/create/table#specialized-codecs
- DeltaDelta
- Gorilla
- T64 …
Hot and cold storage, TTL
Load large datasets from S3
https://altinity.com/blog/tips-for-high-performance-clickhouse-clusters-with-s3-object-storage
Pro tip: S3Queue table engine
https://clickhouse.com/docs/en/engines/table-engines/integrations/s3queue
- Watches a S3 bucket and ingest every time a file is added
Pro tip: ingest as JSON to detect schema
https://clickhouse.com/blog/getting-data-into-clickhouse-part-2-json
- for data with slowly changing schema (example: logs from apps), ingest as JSON
JSONEachRow
BEWARE: CH is working on new way to store JSON
Pro tip: For joins, smaller table on the right-hand side
Reason: in table joins, right table is turned into a map
Primary keys with multiple columns
primary keys by ascending cardinality
https://altinity.com/blog/2017/11/27/clickhouse-primary-keys
- faster insertion
- faster queries
- better compression ratios https://clickhouse.com/docs/en/guides/improving-query-performance/sparse-primary-indexes/sparse-primary-indexes-cardinality/
PRIMARY KEY
and ORDER BY
can be different
- Set the keys on columns that are smaller
- Order by larger data
PRIMARY KEY(user_id, timestamp)
ORDER BY(timestamp, user_id, message)
Updating and deleting rows
Mutations : heavyweight
Reminder: parts are immutable files
ALTER TABLE random UPDATE y = 'hello' WHERE x > 10
will result in creation of new parts This is called mutation
See active mutations with:
SELECT * FROM system.mutations
CAVEAT: you cannot update a primary key column
Lightweight deletes
CAVEAT: only available in Cloud version, not Open Source one
DELETE FROM my_table WHERE y != 'hello'
Each deleted row is marked using a special hidden column
FINAL to combine multiple parts and don’t show deleted rows
SELECT * FROM duplicate_demo FINAL;
https://clickhouse.com/docs/en/operations/settings/settings#final or configure it for all queries:
SET final = 1;
CAVEAT: Avoid OPTIMIZE TABLE ... FINAL
https://clickhouse.com/docs/en/optimize/avoidoptimizefinal
Deleting records
Experience reports / use cases
Small inexpensive setup for log analysis
https://www.hybridlogic.co.uk/2023/02/clog/
Time series
https://clickhouse.com/docs/en/faq/use-cases/time-series
https://www.percona.com/sites/default/files/ple19-slides/day1-pm/clickhouse-for-timeseries.pdf
Analytics at scale
https://clickhouse.com/blog/denic-improves-query-times-by-10x-with-clickhouse
- Moving on from RDBMS
- Clustered arch
- Data privacy requirements
- Query optimization
- Clickhouse support
Customers’ use cases
Log ingestion
- Disney+: 2 billion rows / s
Cloudflare
- …
- 8M / s
Didi
- Logs
- Moved from Elasticsearch to ClickHouse
DeepL
- Metrics, logging
With Pandas for data science
https://altinity.com/blog/2019/2/25/clickhouse-and-python-jupyter-notebooks
Performance
Vs Redshift
https://altinity.com/blog/2017/6/20/clickhouse-vs-redshift
https://news.ycombinator.com/item?id=19019943
Vs Timescale
https://www.pradeepchhetri.xyz/clickhousevstimescaledb/#conclusion
Vs BigQuery
- with set up scripts for Postgres, Clickhouse, BigQuery
https://tech.marksblogg.com/faster-geospatial-enrichment.html
Vs Druid and Pinot
https://link.medium.com/yAP7PKsFHsb
Vs other columnar store
https://pixeljets.com/blog/clickhouse-as-a-replacement-for-elk-big-query-and-timescaledb/
Ecosystem
Cloud platform
- ClickHouse Cloud
- ClickPipes
- To import and process
Integrations
https://clickhouse.com/docs/en/integrations
Schema migration
https://github.com/golang-migrate/migrate
https://kb.altinity.com/altinity-kb-setup-and-maintenance/schema-migration-tools/
BI and data visualization
Grafana
https://clickhouse.com/blog/visualizing-data-with-grafana
Superset
https://clickhouse.com/blog/visualizing-data-with-superset
Metabase
https://clickhouse.com/blog/visualizing-data-with-metabase
Courses
ClickHouse class
This course you’re taking the next two days is here, which is where all the course content and assets will be: slides, labs, and eventually the chat/Q&A logs recording once the course is complete. Each day’s recording is generally uploaded here in about 1 day: https://learn.clickhouse.com/visitor_catalog_class/show/1328622
SQL
https://clickhouse.com/docs/en/sql-reference/ansi
Data types from ANSI to ClickHouse:
SELECT * FROM system.data_type_families;
Functions
https://clickhouse.com/docs/en/sql-reference/functions
SELECT * FROM system.functions;
ORDER BY name
Specific to ClickHouse:
Regular
lower(town)
position(...)
multiFuzzyMatchAny(...)
multiSearchAllPositionsCaseInsensitive(...)
Aggregate
count
min/max
sum
avg
median
quantile/quantiles/quantilesExact
any
uniq
uniqExact/uniqTheta/uniqHLL12/uniqCombined
varPop/stddevPop/covarPop
simpleLinearRegression
stochasticLinearRegression/stochasticLogisticRegression
corr
topK/topKWeight
studentTTest/welchTTest/meanZTest/mannWhitneyUTest
Table
Functions for creating tables
SELECT ...
FROM url(...)
Nice to know
If
variant of aggregate functions
https://clickhouse.com/docs/en/sql-reference/aggregate-functions/combinators
All aggregate functions have an If
variant:
SELECT topKIf(10)(street, street != '') FROM uk_price_paid;
vs
SELECT topK(10)(street)
More readable numbers
formatReadableQuantity()
as in
select formatReadableQuantity(count()) from table;
bar()
to visualize values as bars
Change output format
SELECT ...
FORMAT Vertical
other common formats: CSV CVSNames JSONEachRow Parquet
argMin
/argMax
To return the value for another column associated with a min or max:
SELECT town,max(price),argMax(street,price) FROM uk_price_paid;
Split into words
SELECT arrayJoin(splitByChar(' ', street)) FROM uk_price_paid;
Views
CREATE VIEW name AS ...
CREATE MATERIALIZED VIEW {name}
ENGINE = MergeTree
ORDER BY {columns}
POPULATE AS
{query}
Open questions
- Configuration change requires restart ?
- What is the difference between the primary key defined in as an argument of the storage engine, ie MergeTree(EventDate, (CounterID, EventDate), 8192) and the Order By clause?
Performance optimizations
https://www.highlight.io/blog/lw5-clickhouse-performance-optimization
- Reducing Merges - Batch Inserts
- Keeping Data in Wide Parts
- Optimizing Order By Granularity
- Checking Merge Levels
- Avoiding Use of Projections
- TTL Optimization / Clearing Old Parts