โ† Previous: Module 5 - Cluster Deployment ๐Ÿ  Home Next: Module 7 - Backup & Recovery โ†’

โšก MODULE 6

Query Optimization & Performance
Duration: 4-6 hours | Week 4
๐Ÿ“– What is Query Optimization & Performance?

Query Optimization in a Nutshell

Query optimization is the art and science of making ClickHouse queries run faster, use less memory, and consume fewer CPU cycles. In ClickHouse, a well-optimized query can be 100-1000x faster than a poorly written one. Optimization involves understanding the query execution pipeline, leveraging indexes effectively, and writing queries that align with ClickHouse's architecture.

๐Ÿ“Š

Query Analysis

Use EXPLAIN to understand how ClickHouse executes your queries and identify bottlenecks.

๐Ÿ—‚๏ธ

Index Strategy

Leverage primary keys, skipping indexes, and bloom filters to drastically reduce data scanned.

โš™๏ธ

Execution Pipeline

Understand how ClickHouse processes queries: parsing, optimization, compilation, execution.

๐Ÿ’พ

Resource Management

Optimize memory usage, CPU utilization, and I/O operations for maximum performance.

Architecture Diagrams

1. Query Execution Pipeline (Parse โ†’ Optimize โ†’ Execute โ†’ Merge)

1๏ธโƒฃ
LEXER

Tokenize SQL

โ†’
2๏ธโƒฃ
PARSER

Build AST

โ†’
3๏ธโƒฃ
SEMANTIC

Validate

โ†’
4๏ธโƒฃ
OPTIMIZE

Rewrite

5๏ธโƒฃ
COMPILATION

Generate code

โ†’
6๏ธโƒฃ
EXECUTION

Run query

โ†’
โœ“
RESULT

Return data

Key Optimization Areas

Primary Key Optimization

The primary key determines sort order and enables range queries. Use low-cardinality columns first to maximize effectiveness.

  • Reduces data to scan
  • Enables binary search
  • Improves cache locality

Index Utilization

ClickHouse has multiple index types: primary key, skipping indexes, bloom filters.

  • Skipping indexes skip blocks
  • Bloom filters check membership
  • Reduce I/O significantly

Memory Optimization

Control memory usage with settings and query tuning.

  • max_memory_usage limits RAM
  • Aggregation state compression
  • Streaming aggregation

JOIN Optimization

Joins are expensive; minimize or eliminate them.

  • Denormalize data when possible
  • Use dictionary tables
  • Filter before joining
๐Ÿš€ Quick Start: Basic Query Optimization

1. Understand Your Query Performance

2. Add an Index to Speed Up Queries

3. Materialized Views for Pre-Aggregation

๐Ÿ’ป Commands & Tools Reference

EXPLAIN - Understanding Query Plans

Index Management

Query Profiling & System Tables

โœจ Best Practices

1. Primary Key Design is Critical

โŒ Bad

Problem: Poor compression, range queries don't benefit

โœ… Good

Benefit: 10-100x better compression, faster queries

2. Use Indexes Strategically

Index Type Selection

Index Type Use Case Query Type Speedup
set(n) Low cardinality columns WHERE country = 'US' 5-50x
bloom_filter IN and EXISTS queries WHERE event_type IN (...) 10-100x
minmax Range queries WHERE timestamp > date 2-10x
tokenbf_v1 String substring search WHERE message LIKE '%error%' 5-20x

3. Optimize JOINs or Eliminate Them

โŒ Bad: Expensive JOIN

Problem: JOINs are slow and memory-intensive

โœ… Good: Denormalized Data

Benefit: 100-1000x faster, no memory overhead

4. Common Anti-Patterns to Avoid

Anti-Pattern 1: Full Table Scans

Anti-Pattern 2: Functions on WHERE Clause Columns

-- BAD: Function prevents index use
SELECT * FROM events WHERE toDate(timestamp) = '2026-01-22';

-- GOOD: Filter on indexed column
SELECT * FROM events WHERE timestamp >= '2026-01-22' AND timestamp < '2026-01-23';

5. Memory Management

Control Memory Usage

  • max_memory_usage - Limit per-query memory (default 10GB)
  • max_memory_usage_for_user - Total for all user queries
  • memory_overcommit_ratio - Allow temporary overage for final results
  • max_bytes_before_external_sort - Use disk for sorting
  • max_bytes_before_external_group_by - Use disk for GROUP BY

6. Query Rewriting Techniques

Technique 1: Use PREWHERE for Early Filtering

Benefit: Reduces bytes read by 50-90%

Technique 2: Approximate Queries with SAMPLE

-- Query 10% of data for faster approximate results
SELECT country, COUNT(*) * 10 as estimated_count
FROM events SAMPLE 0.1
WHERE event_date >= today() - 30
GROUP BY country;

Benefit: 10x faster for exploratory queries

๐ŸŽฏ When to Use Query Optimization

โœ… Optimize When

  • Query takes > 100ms
  • Full table scans happening
  • Memory usage is high
  • JOINs are slow
  • Dashboards are lagging
  • Reports take too long
  • Alerts trigger late
  • Ad-hoc queries are slow

โŒ When Optimization Matters Less

  • One-time batch jobs (correct > fast)
  • Query already < 50ms
  • Data is tiny (< 1M rows)
  • Running on fast SSD with plenty of RAM
  • Queries run rarely
  • Not on critical path
๐Ÿ† Real-World Results
100x

Faster with Index

Adding proper indexes can speed up queries 100x on large tables

50-90%

I/O Reduction

Using PREWHERE and proper filtering reduces bytes read by 50-90%

10x

Memory Savings

Denormalizing data eliminates JOINs and reduces memory 10x

1000x

Combined Optimization

Combining multiple techniques: indexes + denormalization + materialized views

Case Study 1: Analytics Dashboard

Challenge

Dashboard queries taking 5-10 seconds, users frustrated with slow loads

Solutions Applied

  • Added skipping indexes on country, event_type
  • Created materialized views for hourly/daily aggregations
  • Denormalized user data into events table
  • Used PREWHERE for event filtering

Results

Query Time: 10s โ†’ 200ms (50x faster)

  • Dashboard refresh time: 30s โ†’ 2s
  • User satisfaction: 2/5 โ†’ 5/5
  • Dashboard CPU usage: 80% โ†’ 15%

Case Study 2: Real-Time Alerting System

Challenge

Alert queries take 15 seconds, alerts fire late and miss anomalies

Solutions Applied

  • Added bloom filter indexes on alert_type
  • Created streaming aggregation views
  • Optimized memory usage with streaming aggregation
  • Used sampling for approximate anomaly detection

Results

Alert Latency: 15s โ†’ 500ms (30x faster)

  • Anomalies detected: 70% โ†’ 98%
  • Mean time to alert: 15s โ†’ 0.5s
  • False positives reduced with faster queries
๐Ÿ“‹ Ready-to-Use Templates

Template 1: Optimized Events Table

Template 2: Performance Monitoring Query

Template 3: Index Analysis

๐Ÿ”ฅ Advanced Patterns

1. Query Execution Pipeline Deep Dive

2. Bloom Filter Indexes for Complex Conditions

3. Materialized Views for Complex Aggregations

4. Optimizing JOIN Queries

5. Memory-Aware Query Design

๐Ÿ“š Resources & Further Learning

๐Ÿ“˜ View in Notion

Access this module in your Notion workspace for note-taking and tracking your progress.

Open in Notion โ†’

๐ŸŽฏ Key Takeaways

  • โœ… Use EXPLAIN to understand query execution
  • โœ… Design primary keys for your access patterns
  • โœ… Add skipping indexes for common filter columns
  • โœ… Denormalize data to eliminate JOINs
  • โœ… Use materialized views for pre-aggregation
  • โœ… Monitor system tables to identify bottlenecks
  • โœ… Rewrite queries using PREWHERE and SAMPLE
  • โœ… Control memory usage with settings

๐ŸŽฏ Next Steps

After mastering query optimization, you should be able to:

  • โœ… Analyze query execution plans
  • โœ… Design optimal primary and secondary indexes
  • โœ… Create materialized views for pre-aggregation
  • โœ… Optimize JOIN queries or eliminate them
  • โœ… Manage memory and CPU resources effectively
  • โœ… Implement performance monitoring
  • โœ… Achieve 100-1000x query speedups

Ready for Module 7? Next we'll explore Backup & Recovery!

๐Ÿณ Hands-on Docker Demo for this Module

A self-contained ClickHouse stack you can run locally. Every step is reproducible; the README below walks through each concept with diagrams and measured outcomes.

cd code-examples/demos/module-6-query-opt
./up.sh        # bring stack up, wait for health
./run.sh       # run the demo (idempotent โ€” re-runnable)
./down.sh      # tear down + drop volumes

๐Ÿ“บ Live demo capture

Recorded with vhs: the actual terminal output from ./run.sh running on this very stack.

Module demo GIF
Animated GIF, ~477 KB. Right-click โ†’ open in new tab to view full-size.

โœจ Animated concept diagrams

Inline SVG animations โ€” no plugins, native browser playback. Each visualises a key flow this module covers.

Read rows
Read rows

๐Ÿ—‚๏ธ Architecture diagrams

Rendered from the README's Mermaid sources. Click any to open the source SVG full-size.

Q query text p parser
Q query text p parser
Source 20m synthetic events
Source 20m synthetic events

๐Ÿ“š ClickHouse reference visuals

Curated from the official ClickHouse documentation and engineering blog (and Altinity's docs/blog) โ€” the same diagrams the broader CH community uses to explain these concepts. Click any image to read the source.

Animated: query splits into parallel processing lanes per CPU core
ClickHouse Docs Animated: query splits into parallel processing lanes per CPU core
Animated: vectorised pipeline pushing chunks through transforms
ClickHouse Docs Animated: vectorised pipeline pushing chunks through transforms
Parallel replicas: query fans out across replicas of a shard
ClickHouse Docs Parallel replicas: query fans out across replicas of a shard
Processors-and-ports query pipeline (sources, transforms, sinks)
ClickHouse Blog Processors-and-ports query pipeline (sources, transforms, sinks)
Index-based pruning: skip granules that cannot match the predicate
ClickHouse Blog Index-based pruning: skip granules that cannot match the predicate
Parallel replicas coordinator distributing work across nodes
ClickHouse Blog Parallel replicas coordinator distributing work across nodes
Animated GIF: parallel-replicas GROUP BY finishing in 33 ms
ClickHouse Blog Animated GIF: parallel-replicas GROUP BY finishing in 33 ms

๐Ÿ“š Full module reference

Module 6 โ€” Query Optimisation & Performance

Audience: anyone making CH queries fast โ€” or wondering why theirs aren't. Prerequisites: Modules 1โ€“2. Time: ~75 min reading + 30 min hands-on with measurable timings on 60M rows.

By the end you will be able to:


1. The query lifecycle

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "The query lifecycle".

Here is a brief excerpt from the material I'm reading:

"""
What you actually tune at each step: | Stage          | Knobs you control                                                                  | | Reader         | `ORDER BY`, `PARTITION BY`, projections, skip-indexes, `PREWHERE`, `SAMPLE`         | | Optimizer      | settings (`optimize_read_in_order`, `optimize_use_projections`, JIT, `compile_aggregate_expressions`) | | Pipeline       | `max_threads`, `max_memory_usage`, parallel replicas                                | | Result         | `max_block_size`, format choice                                                     |
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

What you actually tune at each step:

Stage Knobs you control
Reader ORDER BY, PARTITION BY, projections, skip-indexes, PREWHERE, SAMPLE
Optimizer settings (optimize_read_in_order, optimize_use_projections, JIT, compile_aggregate_expressions)
Pipeline max_threads, max_memory_usage, parallel replicas
Result max_block_size, format choice

2. The first thing to check: did the engine read what you think it did?

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "The first thing to check: did the engine read what you think it did?".

Here is a brief excerpt from the material I'm reading:

"""
**`read_rows` is the truth.** If your `WHERE` filter looks selective but `read_rows โ‰ˆ total table size`, the index didn't help. `SYSTEM FLUSH LOGS;` first if the table is empty โ€” `query_log` is flushed periodically (~7.5 s default), not synchronously.
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.
SELECT
    query_duration_ms,
    read_rows,
    formatReadableSize(read_bytes) AS read_bytes,
    formatReadableSize(memory_usage) AS mem,
    result_rows
FROM system.query_log
WHERE event_time > now() - INTERVAL 5 MINUTE
  AND type = 'QueryFinish'
ORDER BY event_time DESC LIMIT 10;

read_rows is the truth. If your WHERE filter looks selective but read_rows โ‰ˆ total table size, the index didn't help.

SYSTEM FLUSH LOGS; first if the table is empty โ€” query_log is flushed periodically (~7.5 s default), not synchronously.


3. ORDER BY shape โ€” where almost every win comes from

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "ORDER BY shape โ€” where almost every win comes from".

Here is a brief excerpt from the material I'm reading:

"""
The same 20M rows, three table layouts (this is what the demo tests): | Layout                                    | Filter `WHERE event_time BETWEEN 'a' AND 'b'`    | Filter `WHERE country = 'US'` | | `ORDER BY (event_type, country)`          | full scan (no time prefix in PK)                 | partial via secondary scan    | | `ORDER BY (event_time, user_id)`          | granule pruning โ†’ reads ~1/30th                  | full scan, scans by date      | | `ORDER BY (event_time, user_id)` + projection on `(country, day)` | granule pruning            | reads from projection (tiny)  | ### How to c...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

The same 20M rows, three table layouts (this is what the demo tests):

Layout Filter WHERE event_time BETWEEN 'a' AND 'b' Filter WHERE country = 'US'
ORDER BY (event_type, country) full scan (no time prefix in PK) partial via secondary scan
ORDER BY (event_time, user_id) granule pruning โ†’ reads ~1/30th full scan, scans by date
ORDER BY (event_time, user_id) + projection on (country, day) granule pruning reads from projection (tiny)

How to choose

  1. What's the most common WHERE filter? That column should be the first in ORDER BY. Time-series workloads almost always lead with event_time or its truncation (toStartOfHour(event_time)).
  2. What's the most common GROUP BY? Putting that column second enables optimize_aggregation_in_order.
  3. High-cardinality columns later in the key. user_id after event_time is fine; event_time after user_id would scatter adjacent rows in time across the file.

4. Projections โ€” second copies optimised for a different shape

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Projections โ€” second copies optimised for a different shape".

Here is a brief excerpt from the material I'm reading:

"""
Projections are *managed-by-the-engine* materialised aggregations / sort orders that live alongside the main part. What happens: - Every part now also stores a projection part (a tiny aggregated sub-table). - A query of the right shape reads from the projection, not the base table. - Triggered automatically when `optimize_use_projections = 1` (default) *and* the query matches. | Win                                                          | Cost                              | | Aggregation queries 10โ€“100ร— faster                           | Projection storage โ‰ˆ size of the aggregate result ร— nu...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

Projections are managed-by-the-engine materialised aggregations / sort orders that live alongside the main part.

CREATE TABLE events_proj (
    event_time DateTime,
    user_id    UInt64,
    country    LowCardinality(String),
    amount     Float64,

    PROJECTION pv_country_day (
        SELECT country, toDate(event_time) AS day,
               count(), sum(amount), avg(amount)
        GROUP BY country, day
    )
)
ENGINE = MergeTree
ORDER BY (event_time, user_id);

What happens: - Every part now also stores a projection part (a tiny aggregated sub-table). - A query of the right shape reads from the projection, not the base table. - Triggered automatically when optimize_use_projections = 1 (default) and the query matches.

EXPLAIN PROJECTION = 1
SELECT country, toDate(event_time) AS day, count()
FROM events_proj
WHERE event_time BETWEEN '2026-02-01' AND '2026-02-28'
GROUP BY country, day;
-- Plan should mention "Projection: pv_country_day"
Win Cost
Aggregation queries 10โ€“100ร— faster Projection storage โ‰ˆ size of the aggregate result ร— number of parts
No application changes Mutations rewrite the projection too
Multiple projections per table Slower writes (each projection is computed)

5. Skip indexes โ€” granule pruning for the second column

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Skip indexes โ€” granule pruning for the second column".

Here is a brief excerpt from the material I'm reading:

"""
Skip indexes prune **granules**, not rows. A skip index on column X is useful when X *isn't* in the PK prefix but you filter on it. | Type                    | Stores                              | Best for                                  | | `minmax`                | min, max per N granules             | Range filters on numeric/date columns     | | `set(K)`                | up to K distinct values per N granules | `IN` / `=` on low-cardinality columns  | | `bloom_filter`          | bloom filter per N granules         | High-cardinality `=` filters (like `user_id`) | | `tokenbf_v1(L, K, S)`...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

Skip indexes prune granules, not rows. A skip index on column X is useful when X isn't in the PK prefix but you filter on it.

Type Stores Best for
minmax min, max per N granules Range filters on numeric/date columns
set(K) up to K distinct values per N granules IN / = on low-cardinality columns
bloom_filter bloom filter per N granules High-cardinality = filters (like user_id)
tokenbf_v1(L, K, S) per-token bloom filter Substring search via hasToken()
ngrambf_v1(N, L, K, S) per-ngram bloom filter LIKE searches with %abc%
CREATE TABLE events_idx (
    event_time DateTime,
    user_id    UInt64,
    country    LowCardinality(String),
    amount     Float64,
    url        String,

    INDEX idx_amount  amount  TYPE minmax        GRANULARITY 4,
    INDEX idx_country country TYPE set(100)      GRANULARITY 4,
    INDEX idx_user    user_id TYPE bloom_filter() GRANULARITY 4,
    INDEX idx_url_tok url     TYPE tokenbf_v1(8192, 3, 0) GRANULARITY 4
)
ENGINE = MergeTree ORDER BY (event_time, user_id);

GRANULARITY = 4 means "one index entry per 4 marks" โ€” i.e. one entry per ~32k rows. Higher granularity = smaller index, less precise; lower = larger.

A skip index never produces wrong results. It just might fail to prune (false positives in bloom filters โ†’ still read the granule). If your queries don't filter on the indexed column, the index is dead weight on writes โ€” drop it.


6. PREWHERE โ€” read less

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "PREWHERE โ€” read less".

Here is a brief excerpt from the material I'm reading:

"""
CH automatically rewrites cheap predicates into `PREWHERE`, which reads the predicate columns first, prunes granules, and only then reads the remaining columns. You'd write `PREWHERE` explicitly when: - The optimiser picks the wrong predicate to push down (rare on modern CH). - You want to *guarantee* the order: cheap filter first, expensive later. `EXPLAIN SYNTAX` shows what the optimiser actually did:
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

CH automatically rewrites cheap predicates into PREWHERE, which reads the predicate columns first, prunes granules, and only then reads the remaining columns.

-- These are equivalent on CH 21+:
SELECT count() FROM events WHERE country = 'US' AND amount > 100;
SELECT count() FROM events PREWHERE country = 'US' WHERE amount > 100;

You'd write PREWHERE explicitly when:

EXPLAIN SYNTAX shows what the optimiser actually did:

EXPLAIN SYNTAX
SELECT count() FROM events WHERE country = 'US' AND amount > 100;

7. SAMPLE โ€” orders-of-magnitude faster, with caveats

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "SAMPLE โ€” orders-of-magnitude faster, with caveats".

Here is a brief excerpt from the material I'm reading:

"""
`SAMPLE` reads a *consistent fraction* of rows. Requires `SAMPLE BY` in the table definition. | Use SAMPLE for                     | Don't use SAMPLE for                                       | | Real-time dashboards over fresh data | Exact-count metrics                                       | | `quantile`, `avg`, `count` approximations | Reports auditors will scrutinise                      | | Iterative exploration              | Joins (sampling on both sides is hard to reason about)     |
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

SAMPLE reads a consistent fraction of rows. Requires SAMPLE BY in the table definition.

CREATE TABLE events_sampled (
    ...
)
ENGINE = MergeTree
ORDER BY (event_time, intHash32(user_id), user_id)
SAMPLE BY intHash32(user_id);

SELECT count() * 10, avg(amount) FROM events_sampled SAMPLE 0.1;
-- โ‰ˆ 10ร— faster, ~10ร— variance
Use SAMPLE for Don't use SAMPLE for
Real-time dashboards over fresh data Exact-count metrics
quantile, avg, count approximations Reports auditors will scrutinise
Iterative exploration Joins (sampling on both sides is hard to reason about)

8. JOIN strategies

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "JOIN strategies".

Here is a brief excerpt from the material I'm reading:

"""
CH joins are *generally* slower than its scans. Pick wisely. | Join type         | Right-side cardinality | Left-side cardinality | Behaviour                                          | | `INNER JOIN`      | small                  | any                   | Hash-build right, stream left.                     | | `LEFT/RIGHT JOIN` | small                  | any                   | Same; null-fill non-matches.                       | | `ANY LEFT JOIN`   | any                    | any                   | First match per left row. Faster than ALL.         | | `ALL LEFT JOIN`   | any...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

CH joins are generally slower than its scans. Pick wisely.

Join type Right-side cardinality Left-side cardinality Behaviour
INNER JOIN small any Hash-build right, stream left.
LEFT/RIGHT JOIN small any Same; null-fill non-matches.
ANY LEFT JOIN any any First match per left row. Faster than ALL.
ALL LEFT JOIN any any Cartesian on duplicates (default behaviour).
ASOF LEFT JOIN any any Match nearest-but-not-after on a continuous column.
Dictionary lookup tiny (< 100M rows) huge Best-of-class for small dim tables.

Dictionary as a JOIN replacement

CREATE DICTIONARY users_dict (
    user_id UInt64,
    cohort  String,
    signup_date Date
)
PRIMARY KEY user_id
SOURCE(CLICKHOUSE(host 'localhost' port 9000 db 'm6' table 'users_dim'))
LIFETIME(MIN 60 MAX 300)
LAYOUT(HASHED());

SYSTEM RELOAD DICTIONARY m6.users_dict;

-- Now lookups are RAM-resident hashmap reads:
SELECT count(),
       avgIf(amount, dictGetString('m6.users_dict', 'cohort', user_id) = 'alpha')
FROM events_good;
Layout RAM Lookup speed Use for
HASHED high fastest < 50M small rows
SPARSE_HASHED medium fast < 200M rows
RANGE_HASHED medium fast versioned dim with date ranges
IP_TRIE low fast IPv4/IPv6 ranges
CACHE configurable medium huge dims, slower miss path
DIRECT none slow always queries the source

9. Materialized Views for query optimisation

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Materialized Views for query optimisation".

Here is a brief excerpt from the material I'm reading:

"""
A MV pre-computes aggregates **at INSERT time**. Read latency is then a function of the MV's row count, not the source's. Reads from `country_daily` are millions of times smaller than from `events_good`. Module 2 covers MV semantics in depth.
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

A MV pre-computes aggregates at INSERT time. Read latency is then a function of the MV's row count, not the source's.

CREATE TABLE country_daily (
    day Date, country LowCardinality(String),
    events UInt64, revenue Float64
) ENGINE = SummingMergeTree ORDER BY (day, country);

CREATE MATERIALIZED VIEW country_daily_mv TO country_daily AS
SELECT toDate(event_time) AS day, country,
       count() AS events, sum(amount) AS revenue
FROM events_good
GROUP BY day, country;

-- Backfill once
INSERT INTO country_daily
SELECT toDate(event_time), country, count(), sum(amount)
FROM events_good GROUP BY toDate(event_time), country;

Reads from country_daily are millions of times smaller than from events_good. Module 2 covers MV semantics in depth.


10. The EXPLAIN family

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "The EXPLAIN family".

Here is a brief excerpt from the material I'm reading:

"""
(no excerpt โ€” section heading was "The EXPLAIN family")
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.
Form Shows
EXPLAIN Plan tree (the default).
EXPLAIN AST Raw parser AST.
EXPLAIN SYNTAX Query as the optimiser rewrote it (PREWHERE pushdown, alias inlining).
EXPLAIN PIPELINE The actual pipeline (operators, threads, branches).
EXPLAIN PIPELINE graph = 1 DOT format; pipe to graphviz for a picture.
EXPLAIN PLAN actions = 1 Per-operator action list โ€” every projection/filter/aggregate.
EXPLAIN indexes = 1 Which indexes (PK, partition, skip) the planner used.
EXPLAIN PROJECTION = 1 Whether a projection got picked.
EXPLAIN ESTIMATE Estimated rows/parts/granules to read.
EXPLAIN indexes = 1
SELECT count() FROM events_proj WHERE user_id = 12345;
-- Looks for: "Skip index: idx_user (bloom_filter)"

11. The hands-on demo

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "The hands-on demo".

Here is a brief excerpt from the material I'm reading:

"""
### What you get ### Three layouts of the same 20M rows ### Execution flow โ€” what runs, in order | #  | Step          | What happens                                                                                                                                                                                                            | | 0  | self-bootstrap | If `m6-clickhouse` isn't healthy, `up.sh` brings it up. The container is given a 6 GB memory limit because the dataset is large.                                                                                       | | 1  | `setup.sql`...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

What you get

docker-compose.yml          m6-clickhouse ยท 6 GB memory
configs/clickhouse-config.xml
setup.sql ยท data.sql ยท queries.sql ยท extras.sql
up.sh ยท run.sh ยท down.sh

Three layouts of the same 20M rows

Execution flow โ€” what runs, in order

# Step What happens
0 self-bootstrap If m6-clickhouse isn't healthy, up.sh brings it up. The container is given a 6 GB memory limit because the dataset is large.
1 setup.sql Creates database m6 and three identically-shaped tables: events_bad (wrong PK), events_good (right PK), events_proj (right PK + bloom-filter on user_id + projection on (country, day)).
2 data.sql Inserts 20M rows into events_good, then INSERT INTO bad/proj SELECT * FROM events_good so all three tables hold identical data. OPTIMIZE FINAL on each so timings aren't muddied by background merges. ~30โ€“60s.
3 queries.sql Five comparisons: time-range count on each layout (Q1), country aggregation on each (Q2 โ€” projection should crush), point lookup by user_id (Q3 โ€” skip index helps), EXPLAIN indexes=1 and EXPLAIN PROJECTION=1 (Q4), then SYSTEM FLUSH LOGS + system.query_log summary (Q5).
4 extras.sql More tools: PREWHERE (auto + explicit, with EXPLAIN SYNTAX), SAMPLE BY intHash32(user_id) table copy and SAMPLE 0.1 query, three more skip-index types (minmax, set, tokenbf_v1), JOIN strategies (ANY vs ALL vs Dictionary lookup) against a 500k-row users_dim, and a Materialized View (country_daily_mv โ†’ country_daily SummingMergeTree) with a backfill.

What you should observe

The exact numbers depend on hardware; the ratios are stable. On an M-series MacBook the demo typically prints:

Query BAD ordering GOOD ordering PROJ + skip idx
Q1 โ€” time range scans most parts reads few granules reads few granules
Q2 โ€” country aggregation per month ~1.8 s, ~20M read ~0.25 s, ~5M read ~0.02 s, ~3k read
Q3 โ€” point lookup by user_id scans all rows scans all rows bloom filter prunes most marks

12. Settings worth knowing for query work

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Settings worth knowing for query work".

Here is a brief excerpt from the material I'm reading:

"""
| Setting                                | Default | Effect                                                              | | `max_threads`                          | cores  | Per-query parallelism.                                              | | `max_memory_usage`                     | 10 GB  | Per-query memory cap. Aggregations OOM here.                        | | `max_bytes_before_external_group_by`   | 0      | If > 0, spill GROUP BY to disk above this size.                     | | `optimize_read_in_order`               | 1      | Skip sort if PK already orders the data....
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.
Setting Default Effect
max_threads cores Per-query parallelism.
max_memory_usage 10 GB Per-query memory cap. Aggregations OOM here.
max_bytes_before_external_group_by 0 If > 0, spill GROUP BY to disk above this size.
optimize_read_in_order 1 Skip sort if PK already orders the data.
optimize_aggregation_in_order 0 Stream aggregation when PK matches GROUP BY prefix.
optimize_use_projections 1 Try to match a projection.
compile_aggregate_expressions 1 JIT-compile aggregate functions.
parallel_replicas_for_non_replicated_merge_tree 0 Allow parallel replicas mode even on non-replicated tables.
max_block_size 65536 Rows per pipeline block. Bigger = more memory, fewer overhead bumps.

13. Common pitfalls

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Common pitfalls".

Here is a brief excerpt from the material I'm reading:

"""
| Symptom                                                                | Cause                                                                                | Fix                                                                                              | | `WHERE date_col = '2026-05-01'` reads everything                       | `date_col` not in PK prefix; or filter on `toDate(date_col)` while PK is `DateTime`. | Match the type/expression exactly. Use a function-index or partition key.                        | | Projection isn't picked                                                | Que...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.
Symptom Cause Fix
WHERE date_col = '2026-05-01' reads everything date_col not in PK prefix; or filter on toDate(date_col) while PK is DateTime. Match the type/expression exactly. Use a function-index or partition key.
Projection isn't picked Query shape doesn't match projection. EXPLAIN PROJECTION = 1 to see why. Reshape the query or add another projection.
Bloom-filter index doesn't help Granularity too coarse; or column has high collision rate. Lower GRANULARITY; verify with EXPLAIN indexes = 1.
Memory limit (for query) exceeded Aggregation cardinality > max_memory_usage. Set max_bytes_before_external_group_by to spill to disk.
Same query is fast once, slow on second run Filesystem page cache cold the first time, hot the second. Benchmark with SYSTEM DROP MARK CACHE and SYSTEM DROP UNCOMPRESSED CACHE between runs.
JOIN is slow Right-hand side too big. Use a Dictionary; pre-aggregate; or use IN (SELECT ...) with SETTINGS distributed_product_mode='global'.
SAMPLE returns wildly different counts on re-runs Forgot SAMPLE BY in the table definition; SAMPLE became a no-op or random. SHOW CREATE TABLE and add SAMPLE BY ....

14. Talking points for the live session

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Talking points for the live session".

Here is a brief excerpt from the material I'm reading:

"""
1. **`read_rows` is your X-ray machine.** Show before/after on the same query. 2. **`ORDER BY` is the highest-leverage knob.** Run Q1 on the `BAD` and `GOOD` tables side-by-side and watch the ratio. 3. **Projections are not free.** They add write-time cost but query-time wins are 10โ€“100ร—. 4. **Skip indexes prune granules, not rows.** Show `idx_user` on a point lookup; `EXPLAIN indexes = 1` shows the prune. 5. **PREWHERE is auto.** `EXPLAIN SYNTAX` reveals it. Don't write it unless the optimiser picks wrong. 6. **Dictionary > JOIN** for small dim tables. Demo with the 500k-row `users_dim`. 7. *...
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.
  1. read_rows is your X-ray machine. Show before/after on the same query.
  2. ORDER BY is the highest-leverage knob. Run Q1 on the BAD and GOOD tables side-by-side and watch the ratio.
  3. Projections are not free. They add write-time cost but query-time wins are 10โ€“100ร—.
  4. Skip indexes prune granules, not rows. Show idx_user on a point lookup; EXPLAIN indexes = 1 shows the prune.
  5. PREWHERE is auto. EXPLAIN SYNTAX reveals it. Don't write it unless the optimiser picks wrong.
  6. Dictionary > JOIN for small dim tables. Demo with the 500k-row users_dim.
  7. Materialized Views finish the job. Once a query is materialised, it's microseconds regardless of source size.

15. Going deeper

๐Ÿ’ฌ Discuss with AI โ€” click to view prompt
Paste into ChatGPT, Claude, Gemini, or any LLM chat.
I'm studying ClickHouse and working through the module "ClickHouse Query Optimization". I want to deeply understand the section: "Going deeper".

Here is a brief excerpt from the material I'm reading:

"""
- **Module 2** โ€” Materialized Views in detail. - **Module 3** โ€” distributed query plans. - **CH docs**: <https://clickhouse.com/docs/en/optimize/query-optimization> - **Profile-guided optimisation:** load `system.query_log` into a separate table, then aggregate by query pattern to find your top offenders.
"""

Please explain this concept in depth. Cover:

1. **Core mechanics** โ€” how does it actually work under the hood? What data structures, algorithms, or engine internals are involved? Where is the implementation in the ClickHouse source tree (file/folder names) if you know?

2. **When to use vs avoid** โ€” what concrete workloads does this help, and when would it hurt or be wrong? Give specific scale/latency trade-offs.

3. **Comparable features in other systems** โ€” how does this compare with similar features in PostgreSQL, MySQL, BigQuery, Snowflake, Redshift, or DuckDB? Build my intuition by analogy.

4. **Common pitfalls and gotchas** โ€” what trips engineers up in production? Subtle bugs, performance traps, surprising semantics?

5. **Worked example** โ€” show me a small but realistic SQL or config example that demonstrates the concept end-to-end. Include the expected output where useful.

6. **Where to read more** โ€” a short list of authoritative pointers (CH docs, blog posts, talks, source-code files).

Assume I have solid SQL fundamentals but I'm new to ClickHouse internals. Be technical but pragmatic; avoid marketing language.

๐Ÿ“Š ClickHouse Knowledge Transfer

Module 6 of 10 | Duration: 6-8 weeks total

Created for comprehensive ClickHouse training | 2026

โ† Previous: Module 5 - Cluster Deployment ๐Ÿ  Home Next: Module 7 - Backup & Recovery โ†’