Storage layer: concurrent inserts are isolated from each other
Storage layer: concurrent inserts and selects are isolated
Storage layer: merge-time computation
- Replacing merges which retain only the most recent version of a row in the input parts and discard all other row versions. Replacing merges can be thought of as a merge-time cleanup operation.
- Aggregating merges which combine intermediate aggregation states in the input part to a new aggregation state. While this seems difficult to understand, it really actually only implements an incremental aggregation.
- TTL (time-to-live) merges compress, move, or delete rows based on certain time-based rules.
Storage layer: data pruning
- Primary key indexes which define the sort order of the table data. A well-chosen primary key allows to evaluate filters (like the WHERE clauses in the above query) using fast binary searches instead of full-column scans. In more technical terms, the runtime of scans becomes logarithmic instead of linear in the data size.
- Table projections as alternative, internal versions of a table, storing the same data but sorted by a different primary key. Projections can be useful when there is more than one frequent filter condition.
- Skipping indexes that embed additional data statistics into columns, e.g. the minimum and maximum column value, the set of unique values, etc. Skipping indexes are orthogonal to primary keys and table projections, and depending on the data distribution in the column, they can greatly speed up the evaluation of filters.
Storage layer: data compression
State-of-the-art query processing layer
Meticulous attention to detail
“ClickHouse is a freak system - you guys have 20 versions of a hash table. You guys have all these amazing things where most systems will have one hash table … ClickHouse has this amazing performance because it has all these specialized components” Andy Pavlo, Database Professor at CMUWhat sets ClickHouse apart is its meticulous attention to low-level optimization. Building a database that simply works is one thing, but engineering it to deliver speed across diverse query types, data structures, distributions, and index configurations is where the “freak system” artistry shines. Hash Tables. Let’s take a hash table as an example. Hash tables are central data structures used by joins and aggregations. As a programmer, one needs to consider these design decisions:
- The hash function to choose,
- The collision resolution: open addressing or chaining,
- The memory layout: one array for keys and values or separate arrays?
- The fill factor: When and how to resize? How to move values during resize?
- Deletions: Should the hash table allow evicting entries?
- What will be sorted: numbers, tuples, strings, or structures?
- Is the data in RAM?
- Is the sort required to be stable?
- Should all data be sorted or will a partial sort suffice?