ClickHouse Best Practices
Comprehensive guidance for ClickHouse covering schema design, query optimization, and data ingestion. Contains 28 rules across 3 main categories (schema, query, insert), prioritized by impact.
Official docs: ClickHouse Best Practices
IMPORTANT: How to Apply This Skill
Before answering ClickHouse questions, follow this priority order:
- Check for applicable rules in the
rules/ directory
- If rules exist: Apply them and cite them in your response using "Per
rule-name..."
- If no rule exists: Use the LLM's ClickHouse knowledge or search documentation
- If uncertain: Use web search for current best practices
- Always cite your source: rule name, "general ClickHouse guidance", or URL
Why rules take priority: ClickHouse has specific behaviors (columnar storage, sparse indexes, merge tree mechanics) where general database intuition can be misleading. The rules encode validated, ClickHouse-specific guidance.
Langfuse-Specific Rules
- Use
packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts
for queries against the events table. Do not hand-roll events SQL unless
you first confirm the query builder cannot express the query.
- Never use
FINAL on the events table; it is designed so FINAL is not
required and the keyword hurts performance.
- ClickHouse query attribution is stored in
system.query_log.log_comment as
JSON from packages/shared/src/server/clickhouse/queryTags.ts. Parse it with
JSONExtractString(log_comment, 'surface'),
JSONExtractString(log_comment, 'route'), and
JSONExtractString(log_comment, 'projectId'). Known surface values are
trpc, publicapi, worker, mcp, and unknown; ClickhouseWriter inserts
use projectId = "MULTI_PROJECT".
- Query attribution is propagated through OpenTelemetry baggage. Entry points
call
contextWithLangfuseProps(...) from
packages/shared/src/server/headerPropagation.ts, setting ClickHouse
surface, optional route, and optional projectId. The ClickHouse
repository layer then reads baggage via normalizeClickHouseQueryTags(...)
and writes it to log_comment. Prefer setting attribution at entry points
rather than passing tags through every repository call.
packages/shared/clickhouse/migrations/canonical/** is the single canonical
template tree rendered for clustered and unclustered installs. Put
{CLICKHOUSE_CLUSTER_CLAUSE} at every cluster-aware DDL position. Use
{CLICKHOUSE_REPLICATION_PREFIX} only for engines that deliberately differ
by mode; some tables intentionally stay non-replicated in both modes.
- Every metadata
ALTER (ADD/DROP/MODIFY COLUMN, ADD/DROP INDEX) in a new
canonical migration must include
{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2}, and every
mutation-creating ALTER (MATERIALIZE …, UPDATE, DELETE) must include
{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS mutations_sync = 2}.
This applies to a file holding a single ALTER too — the race is across
migration files, not within one. alter_sync defaults to 1, so the
statement returns as soon as the initiating replica has bumped the table's
metadata version in Keeper; golang-migrate then opens the next file
immediately, and its first ALTER on that table can land on a replica still
on the previous version. ClickHouse refuses to queue it and aborts the whole
run with code 517 because the replica metadata version is behind the common
metadata version. Note that mutations_sync does not substitute for alter_sync: it
governs when mutations finish, not metadata propagation. The renderer omits
these fragments for unclustered MergeTree migrations. Use
{CLICKHOUSE_UNCLUSTERED_ONLY:...} only for a deliberate mode-specific
difference. Do not retrofit synchronization settings into already-shipped
migrations merely to normalize them; the historical compatibility test
intentionally protects their existing output.
- Never use
CREATE OR REPLACE VIEW (nor CREATE OR REPLACE TABLE /
EXCHANGE TABLES) in ClickHouse migrations. The atomic replace requires
renameat2 filesystem support, which NFS-backed self-hosted deployments
(e.g. ClickHouse data on AWS EFS) lack — the migration fails and the
deployment aborts on startup (GitHub issue #14906). Redefine a plain view as
two statements in the same migration file. First use
DROP VIEW IF EXISTS <name> {CLICKHOUSE_CLUSTER_CLAUSE};, then
CREATE VIEW <name> {CLICKHOUSE_CLUSTER_CLAUSE} AS ….
The migration runner passes x-multi-statement=true and golang-migrate
splits files on ; without parsing SQL, so keep semicolons out of comments
and string literals. Keep every statement idempotent
(IF EXISTS/IF NOT EXISTS) so a dirty, half-applied migration can be
re-run after migrate force. Readers hitting the view inside the
drop→create window fail transiently — acceptable for the analytics_*
export views, so keep plain views off product hot paths.
- Never drop-and-recreate a materialized view whose source table receives live
inserts: every row inserted between
DROP and CREATE is silently and
permanently missing from the target table. Change an MV's SELECT with
ALTER TABLE <mv> {CLICKHOUSE_CLUSTER_CLAUSE} MODIFY QUERY <select>, which swaps
the transformation without interrupting ingestion. When the change adds
columns, ALTER the target table(s) first (ADD COLUMN IF NOT EXISTS …),
then MODIFY QUERY; those target-table ALTERs must carry the
clustered-only alter_sync template fragment so no host applies the new MV
query before its target replica has the new columns. MODIFY QUERY is only
viable for TO-table MVs (all Langfuse MVs use TO).
Review Procedures
For Schema Reviews (CREATE TABLE, ALTER TABLE)
Read these rule files in order:
rules/schema-pk-plan-before-creation.md - ORDER BY is immutable
rules/schema-pk-cardinality-order.md - Column ordering in keys
rules/schema-pk-prioritize-filters.md - Filter column inclusion
rules/schema-types-native-types.md - Proper type selection
rules/schema-types-minimize-bitwidth.md - Numeric type sizing
rules/schema-types-lowcardinality.md - LowCardinality usage
rules/schema-types-avoid-nullable.md - Nullable vs DEFAULT
rules/schema-partition-low-cardinality.md - Partition count limits
rules/schema-partition-lifecycle.md - Partitioning purpose
Check for:
For Query Reviews (SELECT, JOIN, aggregations)
Read these rule files:
rules/query-join-choose-algorithm.md - Algorithm selection
rules/query-join-filter-before.md - Pre-join filtering
rules/query-join-use-any.md - ANY vs regular JOIN
rules/query-index-skipping-indices.md - Secondary index usage
rules/schema-pk-filter-on-orderby.md - Filter alignment with ORDER BY
Check for:
For Insert Strategy Reviews (data ingestion, updates, deletes)
Read these rule files:
rules/insert-batch-size.md - Batch sizing requirements
rules/insert-mutation-avoid-update.md - UPDATE alternatives
rules/insert-mutation-avoid-delete.md - DELETE alternatives
rules/insert-async-small-batches.md - Async insert usage
rules/insert-optimize-avoid-final.md - OPTIMIZE TABLE risks
Check for:
Output Format
Structure your response as follows:
## Rules Checked
- `rule-name-1` - Compliant / Violation found
- `rule-name-2` - Compliant / Violation found
...
## Findings
### Violations
- **`rule-name`**: Description of the issue
- Current: [what the code does]
- Required: [what it should do]
- Fix: [specific correction]
### Compliant
- `rule-name`: Brief note on why it's correct
## Recommendations
[Prioritized list of changes, citing rules]
Rule Categories by Priority
| Priority |
Category |
Impact |
Prefix |
Rule Count |
| 1 |
Primary Key Selection |
CRITICAL |
schema-pk- |
4 |
| 2 |
Data Type Selection |
CRITICAL |
schema-types- |
5 |
| 3 |
JOIN Optimization |
CRITICAL |
query-join- |
5 |
| 4 |
Insert Batching |
CRITICAL |
insert-batch- |
1 |
| 5 |
Mutation Avoidance |
CRITICAL |
insert-mutation- |
2 |
| 6 |
Partitioning Strategy |
HIGH |
schema-partition- |
4 |
| 7 |
Skipping Indices |
HIGH |
query-index- |
1 |
| 8 |
Materialized Views |
HIGH |
query-mv- |
2 |
| 9 |
Async Inserts |
HIGH |
insert-async- |
2 |
| 10 |
OPTIMIZE Avoidance |
HIGH |
insert-optimize- |
1 |
| 11 |
JSON Usage |
MEDIUM |
schema-json- |
1 |
Quick Reference
Schema Design - Primary Key (CRITICAL)
schema-pk-plan-before-creation - Plan ORDER BY before table creation (immutable)
schema-pk-cardinality-order - Order columns low-to-high cardinality
schema-pk-prioritize-filters - Include frequently filtered columns
schema-pk-filter-on-orderby - Query filters must use ORDER BY prefix
Schema Design - Data Types (CRITICAL)
schema-types-native-types - Use native types, not String for everything
schema-types-minimize-bitwidth - Use smallest numeric type that fits
schema-types-lowcardinality - LowCardinality for <10K unique strings
schema-types-enum - Enum for finite value sets with validation
schema-types-avoid-nullable - Avoid Nullable; use DEFAULT instead
Schema Design - Partitioning (HIGH)
schema-partition-low-cardinality - Keep partition count 100-1,000
schema-partition-lifecycle - Use partitioning for data lifecycle, not queries
schema-partition-query-tradeoffs - Understand partition pruning trade-offs
schema-partition-start-without - Consider starting without partitioning
Schema Design - JSON (MEDIUM)
schema-json-when-to-use - JSON for dynamic schemas; typed columns for known
Query Optimization - JOINs (CRITICAL)
query-join-choose-algorithm - Select algorithm based on table sizes
query-join-use-any - ANY JOIN when only one match needed
query-join-filter-before - Filter tables before joining
query-join-consider-alternatives - Dictionaries/denormalization vs JOIN
query-join-null-handling - join_use_nulls=0 for default values
Query Optimization - Indices (HIGH)
query-index-skipping-indices - Skipping indices for non-ORDER BY filters
Query Optimization - Materialized Views (HIGH)
query-mv-incremental - Incremental MVs for real-time aggregations
query-mv-refreshable - Refreshable MVs for complex joins
Insert Strategy - Batching (CRITICAL)
insert-batch-size - Batch 10K-100K rows per INSERT
Insert Strategy - Async (HIGH)
insert-async-small-batches - Async inserts for high-frequency small batches
insert-format-native - Native format for best performance
Insert Strategy - Mutations (CRITICAL)
insert-mutation-avoid-update - ReplacingMergeTree instead of ALTER UPDATE
insert-mutation-avoid-delete - Lightweight DELETE or DROP PARTITION
Insert Strategy - Optimization (HIGH)
insert-optimize-avoid-final - Let background merges work
When to Apply
This skill activates when you encounter:
CREATE TABLE statements
ALTER TABLE modifications
ORDER BY or PRIMARY KEY discussions
- Data type selection questions
- Slow query troubleshooting
- JOIN optimization requests
- Data ingestion pipeline design
- Update/delete strategy questions
- ReplacingMergeTree or other specialized engine usage
- Partitioning strategy decisions
Rule File Structure
Each rule file in rules/ contains:
- YAML frontmatter: title, impact level, tags
- Brief explanation: Why this rule matters
- Incorrect example: Anti-pattern with explanation
- Correct example: Best practice with explanation
- Additional context: Trade-offs, when to apply, references
1---2name: clickhouse-best-practices3description: MUST USE when reviewing ClickHouse schemas, queries, or configurations. Contains 28 rules that MUST be checked before providing recommendations. Always read relevant rule files and cite specific rules in responses.4license: Apache-2.05---6
7# ClickHouse Best Practices
8
9Comprehensive guidance for ClickHouse covering schema design, query optimization, and data ingestion. Contains 28 rules across 3 main categories (schema, query, insert), prioritized by impact.
10
11> **Official docs:** [ClickHouse Best Practices](https://clickhouse.com/docs/best-practices)
12
13## IMPORTANT: How to Apply This Skill
14
15**Before answering ClickHouse questions, follow this priority order:**
16
171. **Check for applicable rules** in the `rules/` directory
182. **If rules exist:** Apply them and cite them in your response using "Per `rule-name`..."
193. **If no rule exists:** Use the LLM's ClickHouse knowledge or search documentation
204. **If uncertain:** Use web search for current best practices
215. **Always cite your source:** rule name, "general ClickHouse guidance", or URL
22
23**Why rules take priority:** ClickHouse has specific behaviors (columnar storage, sparse indexes, merge tree mechanics) where general database intuition can be misleading. The rules encode validated, ClickHouse-specific guidance.
24
25## Langfuse-Specific Rules
26
27- Use `packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts`
28 for queries against the `events` table. Do not hand-roll `events` SQL unless
29 you first confirm the query builder cannot express the query.
30- Never use `FINAL` on the `events` table; it is designed so `FINAL` is not
31 required and the keyword hurts performance.
32- ClickHouse query attribution is stored in `system.query_log.log_comment` as
33 JSON from `packages/shared/src/server/clickhouse/queryTags.ts`. Parse it with
34 `JSONExtractString(log_comment, 'surface')`,
35 `JSONExtractString(log_comment, 'route')`, and
36 `JSONExtractString(log_comment, 'projectId')`. Known `surface` values are
37 `trpc`, `publicapi`, `worker`, `mcp`, and `unknown`; ClickhouseWriter inserts
38 use `projectId = "MULTI_PROJECT"`.
39- Query attribution is propagated through OpenTelemetry baggage. Entry points
40 call `contextWithLangfuseProps(...)` from
41 `packages/shared/src/server/headerPropagation.ts`, setting ClickHouse
42 `surface`, optional `route`, and optional `projectId`. The ClickHouse
43 repository layer then reads baggage via `normalizeClickHouseQueryTags(...)`
44 and writes it to `log_comment`. Prefer setting attribution at entry points
45 rather than passing tags through every repository call.
46- `packages/shared/clickhouse/migrations/canonical/**` is the single canonical
47 template tree rendered for clustered and unclustered installs. Put
48 `{CLICKHOUSE_CLUSTER_CLAUSE}` at every cluster-aware DDL position. Use
49 `{CLICKHOUSE_REPLICATION_PREFIX}` only for engines that deliberately differ
50 by mode; some tables intentionally stay non-replicated in both modes.
51- Every metadata `ALTER` (`ADD/DROP/MODIFY COLUMN`, `ADD/DROP INDEX`) in a new
52 canonical migration must include
53 `{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2}`, and every
54 mutation-creating `ALTER` (`MATERIALIZE …`, `UPDATE`, `DELETE`) must include
55 `{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS mutations_sync = 2}`.
56 This applies to a file holding a single `ALTER` too — the race is across
57 migration files, not within one. `alter_sync` defaults to `1`, so the
58 statement returns as soon as the initiating replica has bumped the table's
59 metadata version in Keeper; golang-migrate then opens the next file
60 immediately, and its first `ALTER` on that table can land on a replica still
61 on the previous version. ClickHouse refuses to queue it and aborts the whole
62 run with `code 517` because the replica metadata version is behind the common
63 metadata version. Note that `mutations_sync` does not substitute for `alter_sync`: it
64 governs when mutations finish, not metadata propagation. The renderer omits
65 these fragments for unclustered `MergeTree` migrations. Use
66 `{CLICKHOUSE_UNCLUSTERED_ONLY:...}` only for a deliberate mode-specific
67 difference. Do not retrofit synchronization settings into already-shipped
68 migrations merely to normalize them; the historical compatibility test
69 intentionally protects their existing output.
70- Never use `CREATE OR REPLACE VIEW` (nor `CREATE OR REPLACE TABLE` /
71 `EXCHANGE TABLES`) in ClickHouse migrations. The atomic replace requires
72 `renameat2` filesystem support, which NFS-backed self-hosted deployments
73 (e.g. ClickHouse data on AWS EFS) lack — the migration fails and the
74 deployment aborts on startup (GitHub issue #14906). Redefine a plain view as
75 two statements in the same migration file. First use
76 `DROP VIEW IF EXISTS <name> {CLICKHOUSE_CLUSTER_CLAUSE};`, then
77 `CREATE VIEW <name> {CLICKHOUSE_CLUSTER_CLAUSE} AS …`.
78 The migration runner passes `x-multi-statement=true` and golang-migrate
79 splits files on `;` without parsing SQL, so keep semicolons out of comments
80 and string literals. Keep every statement idempotent
81 (`IF EXISTS`/`IF NOT EXISTS`) so a dirty, half-applied migration can be
82 re-run after `migrate force`. Readers hitting the view inside the
83 drop→create window fail transiently — acceptable for the `analytics_*`
84 export views, so keep plain views off product hot paths.
85- Never drop-and-recreate a materialized view whose source table receives live
86 inserts: every row inserted between `DROP` and `CREATE` is silently and
87 permanently missing from the target table. Change an MV's SELECT with
88 `ALTER TABLE <mv> {CLICKHOUSE_CLUSTER_CLAUSE} MODIFY QUERY <select>`, which swaps
89 the transformation without interrupting ingestion. When the change adds
90 columns, `ALTER` the target table(s) first (`ADD COLUMN IF NOT EXISTS …`),
91 then `MODIFY QUERY`; those target-table `ALTER`s must carry the
92 clustered-only `alter_sync` template fragment so no host applies the new MV
93 query before its target replica has the new columns. `MODIFY QUERY` is only
94 viable for TO-table MVs (all Langfuse MVs use `TO`).
95
96---
97
98## Review Procedures
99
100### For Schema Reviews (CREATE TABLE, ALTER TABLE)
101
102**Read these rule files in order:**
103
1041. `rules/schema-pk-plan-before-creation.md` - ORDER BY is immutable
1052. `rules/schema-pk-cardinality-order.md` - Column ordering in keys
1063. `rules/schema-pk-prioritize-filters.md` - Filter column inclusion
1074. `rules/schema-types-native-types.md` - Proper type selection
1085. `rules/schema-types-minimize-bitwidth.md` - Numeric type sizing
1096. `rules/schema-types-lowcardinality.md` - LowCardinality usage
1107. `rules/schema-types-avoid-nullable.md` - Nullable vs DEFAULT
1118. `rules/schema-partition-low-cardinality.md` - Partition count limits
1129. `rules/schema-partition-lifecycle.md` - Partitioning purpose
113
114**Check for:**
115
116- [ ] PRIMARY KEY / ORDER BY column order (low-to-high cardinality)
117- [ ] Data types match actual data ranges
118- [ ] LowCardinality applied to appropriate string columns
119- [ ] Partition key cardinality bounded (100-1,000 values)
120- [ ] ReplacingMergeTree has version column if used
121- [ ] Every metadata ALTER in a new canonical migration includes `{CLICKHOUSE_CLUSTERED_ONLY: SETTINGS alter_sync = 2}` — including files with a single ALTER, since the next migration file is what breaks — and every `MATERIALIZE …` / `UPDATE` / `DELETE` includes the corresponding `mutations_sync` fragment; `mutations_sync` is not a substitute for `alter_sync`; do not normalize already-shipped migration output; both rendered modes pass `prepareMigrations.test.ts`
122- [ ] No `CREATE OR REPLACE VIEW/TABLE` or `EXCHANGE TABLES` in migrations (breaks NFS/EFS self-hosting); plain views are redefined via `DROP VIEW IF EXISTS` + `CREATE VIEW` in the same file
123- [ ] Materialized views are never dropped and recreated while their source table takes inserts; SELECT changes go through `ALTER TABLE <mv> MODIFY QUERY` after the target-table `ALTER`s
124
125### For Query Reviews (SELECT, JOIN, aggregations)
126
127**Read these rule files:**
128
1291. `rules/query-join-choose-algorithm.md` - Algorithm selection
1302. `rules/query-join-filter-before.md` - Pre-join filtering
1313. `rules/query-join-use-any.md` - ANY vs regular JOIN
1324. `rules/query-index-skipping-indices.md` - Secondary index usage
1335. `rules/schema-pk-filter-on-orderby.md` - Filter alignment with ORDER BY
134
135**Check for:**
136
137- [ ] Filters use ORDER BY prefix columns
138- [ ] JOINs filter tables before joining (not after)
139- [ ] Correct JOIN algorithm for table sizes
140- [ ] Skipping indices for non-ORDER BY filter columns
141
142### For Insert Strategy Reviews (data ingestion, updates, deletes)
143
144**Read these rule files:**
145
1461. `rules/insert-batch-size.md` - Batch sizing requirements
1472. `rules/insert-mutation-avoid-update.md` - UPDATE alternatives
1483. `rules/insert-mutation-avoid-delete.md` - DELETE alternatives
1494. `rules/insert-async-small-batches.md` - Async insert usage
1505. `rules/insert-optimize-avoid-final.md` - OPTIMIZE TABLE risks
151
152**Check for:**
153
154- [ ] Batch size 10K-100K rows per INSERT
155- [ ] No ALTER TABLE UPDATE for frequent changes
156- [ ] ReplacingMergeTree or CollapsingMergeTree for update patterns
157- [ ] Async inserts enabled for high-frequency small batches
158
159---
160
161## Output Format
162
163Structure your response as follows:
164
165```
166## Rules Checked
167- `rule-name-1` - Compliant / Violation found
168- `rule-name-2` - Compliant / Violation found
169...
170
171## Findings
172
173### Violations
174- **`rule-name`**: Description of the issue
175 - Current: [what the code does]
176 - Required: [what it should do]
177 - Fix: [specific correction]
178
179### Compliant
180- `rule-name`: Brief note on why it's correct
181
182## Recommendations
183[Prioritized list of changes, citing rules]
184```
185
186---
187
188## Rule Categories by Priority
189
190| Priority | Category | Impact | Prefix | Rule Count |
191| -------- | --------------------- | -------- | ------------------- | ---------- |
192| 1 | Primary Key Selection | CRITICAL | `schema-pk-` | 4 |
193| 2 | Data Type Selection | CRITICAL | `schema-types-` | 5 |
194| 3 | JOIN Optimization | CRITICAL | `query-join-` | 5 |
195| 4 | Insert Batching | CRITICAL | `insert-batch-` | 1 |
196| 5 | Mutation Avoidance | CRITICAL | `insert-mutation-` | 2 |
197| 6 | Partitioning Strategy | HIGH | `schema-partition-` | 4 |
198| 7 | Skipping Indices | HIGH | `query-index-` | 1 |
199| 8 | Materialized Views | HIGH | `query-mv-` | 2 |
200| 9 | Async Inserts | HIGH | `insert-async-` | 2 |
201| 10 | OPTIMIZE Avoidance | HIGH | `insert-optimize-` | 1 |
202| 11 | JSON Usage | MEDIUM | `schema-json-` | 1 |
203
204---
205
206## Quick Reference
207
208### Schema Design - Primary Key (CRITICAL)
209
210- `schema-pk-plan-before-creation` - Plan ORDER BY before table creation (immutable)
211- `schema-pk-cardinality-order` - Order columns low-to-high cardinality
212- `schema-pk-prioritize-filters` - Include frequently filtered columns
213- `schema-pk-filter-on-orderby` - Query filters must use ORDER BY prefix
214
215### Schema Design - Data Types (CRITICAL)
216
217- `schema-types-native-types` - Use native types, not String for everything
218- `schema-types-minimize-bitwidth` - Use smallest numeric type that fits
219- `schema-types-lowcardinality` - LowCardinality for <10K unique strings
220- `schema-types-enum` - Enum for finite value sets with validation
221- `schema-types-avoid-nullable` - Avoid Nullable; use DEFAULT instead
222
223### Schema Design - Partitioning (HIGH)
224
225- `schema-partition-low-cardinality` - Keep partition count 100-1,000
226- `schema-partition-lifecycle` - Use partitioning for data lifecycle, not queries
227- `schema-partition-query-tradeoffs` - Understand partition pruning trade-offs
228- `schema-partition-start-without` - Consider starting without partitioning
229
230### Schema Design - JSON (MEDIUM)
231
232- `schema-json-when-to-use` - JSON for dynamic schemas; typed columns for known
233
234### Query Optimization - JOINs (CRITICAL)
235
236- `query-join-choose-algorithm` - Select algorithm based on table sizes
237- `query-join-use-any` - ANY JOIN when only one match needed
238- `query-join-filter-before` - Filter tables before joining
239- `query-join-consider-alternatives` - Dictionaries/denormalization vs JOIN
240- `query-join-null-handling` - join_use_nulls=0 for default values
241
242### Query Optimization - Indices (HIGH)
243
244- `query-index-skipping-indices` - Skipping indices for non-ORDER BY filters
245
246### Query Optimization - Materialized Views (HIGH)
247
248- `query-mv-incremental` - Incremental MVs for real-time aggregations
249- `query-mv-refreshable` - Refreshable MVs for complex joins
250
251### Insert Strategy - Batching (CRITICAL)
252
253- `insert-batch-size` - Batch 10K-100K rows per INSERT
254
255### Insert Strategy - Async (HIGH)
256
257- `insert-async-small-batches` - Async inserts for high-frequency small batches
258- `insert-format-native` - Native format for best performance
259
260### Insert Strategy - Mutations (CRITICAL)
261
262- `insert-mutation-avoid-update` - ReplacingMergeTree instead of ALTER UPDATE
263- `insert-mutation-avoid-delete` - Lightweight DELETE or DROP PARTITION
264
265### Insert Strategy - Optimization (HIGH)
266
267- `insert-optimize-avoid-final` - Let background merges work
268
269---
270
271## When to Apply
272
273This skill activates when you encounter:
274
275- `CREATE TABLE` statements
276- `ALTER TABLE` modifications
277- `ORDER BY` or `PRIMARY KEY` discussions
278- Data type selection questions
279- Slow query troubleshooting
280- JOIN optimization requests
281- Data ingestion pipeline design
282- Update/delete strategy questions
283- ReplacingMergeTree or other specialized engine usage
284- Partitioning strategy decisions
285
286---
287
288## Rule File Structure
289
290Each rule file in `rules/` contains:
291
292- **YAML frontmatter**: title, impact level, tags
293- **Brief explanation**: Why this rule matters
294- **Incorrect example**: Anti-pattern with explanation
295- **Correct example**: Best practice with explanation
296- **Additional context**: Trade-offs, when to apply, references