Performance Tuning and Optimization
This domain tests whether you can find where a query spends time and fix the cause rather than raise a limit. Questions give a symptom - huge read_rows, memory spikes, slow joins, too many parts - and ask for the highest-leverage change and the evidence that it worked.
The read path
Partition pruning → primary index → skip indexes / projections → decompression → filtering. Verify each stage with EXPLAIN indexes = 1, and confirm real numbers in system.query_log (read_rows, read_bytes, memory_usage, ProfileEvents).
Design levers
- Sorting key first: put the most selective equality column first, then time.
- Projections store an alternative sort order and are chosen automatically by the optimizer - the standard fix for a second query shape.
- Skip indexes (
minmax,set,bloom_filter,tokenbf_vs_ngrambf) only help when values are physically clustered; on a random high-cardinality column they add overhead and prune nothing. LowCardinality, narrow integer types, and per-column codecs reduce bytes read, which is the dominant cost.
Query levers
max_threadscontrols parallelism;max_memory_usagecaps a query, andmax_bytes_before_external_group_by/max_bytes_before_external_sortspill to disk.optimize_aggregation_in_order = 1helps when GROUP BY matches a sorting-key prefix.uniq()instead ofuniqExact()for high-cardinality distinct counts.- Joins: smaller table on the right;
join_algorithm = 'grace_hash'for large-large joins,'partial_merge'when memory is tight. - Cheaper than
FINAL:argMax(col, ver) ... GROUP BY key.
Worked example
A dashboard query reads 2e9 rows to return 500, with 30 GB peak memory. system.query_log shows read_rows = 2e9; the table is ORDER BY (ts, tenant_id) but the query filters on tenant_id and groups by tenant_id. Diagnosis: no index pruning on the second key column, and the aggregation cannot run in sort order. Fix: add a projection ordered by (tenant_id, ts). EXPLAIN indexes = 1 then shows the projection selected and read_rows drops to the tenant's range.
Operational performance
- Watch active parts per partition in
system.partsand in-flight work insystem.merges;OPTIMIZE TABLE ... FINALis a heavy manual tool, not routine maintenance. - Mutations rewrite whole parts and compete with merges for I/O.
max_concurrent_queriesbounds server-side concurrency; per-query limits belong in settings profiles.
Traps that cost marks
- Raising
max_threadsto cure a memory problem - it usually worsens it. - Adding a skip index to a random UUID column.
- Confusing the query's
ORDER BYwith the table's physical order. - Forgetting that
ALTER ... MODIFY COLUMNto LowCardinality rewrites data.