ClickHouse partition granularity: day vs month
How to size a ClickHouse PARTITION BY expression — the trade-offs between daily and monthly partitions, and the diagnostic SQL to tell if yours is too fine or too coarse.
Part of the ClickHouse query optimization guide. Read partition key best practices first if you haven't chosen the partition expression itself yet — this page is about how fine to make it.
The trade-off
Daily (toDate(...)) | Monthly (toYYYYMM(...)) | |
|---|---|---|
| TTL / retention granularity | Drop exactly one day instantly (DROP PARTITION) | Coarsest unit you can drop is a month |
| Backfill / reprocess granularity | Reprocess a single bad day cheaply | Reprocessing touches a whole month |
| Merge efficiency | More partitions to track; fine if daily volume is large | Fewer, larger partitions merge more efficiently |
| Part count risk | High risk of many small parts if daily volume is small | Lower risk — more data lands in the same partition |
| Best for | High-volume tables (multi-GB+ compressed per day) | Low-to-medium volume tables |
Neither is universally "correct" — the right choice depends on how much data lands in a single partition, not on a fixed calendar unit.
Rule of thumb
Size the partition expression so each partition ends up in the hundreds of MB to low single-digit GB compressed, and so the table has at most a few thousand active partitions across its whole retention window. Below that, you're paying partition/merge overhead for no benefit; above it, you get the too-many-parts symptoms described in partition key best practices.
- Small or bursty daily volume (< ~500 MB/day compressed) → partition
monthly (
toYYYYMM(event_date)), or even weekly for a middle ground (toStartOfWeek(event_date)). - High, steady daily volume (multi-GB+/day) → partition daily
(
toDate(event_date)); you get fast per-day TTL/reprocessing without starving individual partitions of merge candidates. - Very high volume tables (tens of GB+/day) → daily is usually still
right; going finer than a day rarely pays off since ClickHouse already
prunes efficiently within a partition via
ORDER BYand skip indices.
Diagnose an existing table
This ranks partitions by part count within one table — a large number of partitions each holding only 1-2 tiny parts means the granularity is too fine for the actual insert volume:
SELECT
partition,
count() AS parts_in_partition,
formatReadableSize(sum(data_compressed_bytes)) AS partition_size,
formatReadableSize(avg(data_compressed_bytes)) AS avg_part_size
FROM system.parts
WHERE active
AND database = 'default'
AND table = 'events'
GROUP BY partition
ORDER BY parts_in_partition DESC
LIMIT 20;If most partitions show 1-3 parts at a few MB each, the granularity is
oversized for the volume — move to a coarser expression (day → week/month). If
a handful of partitions carry hundreds of parts each and merges can't keep up,
the granularity may actually be too coarse for the insert rate, or (more
likely) the underlying merge pool is undersized — check background_pool_size
and the merge backlog on the Health page before repartitioning.
Changing granularity
Like the partition key itself, granularity can't be altered in place — it's
part of the same immutable PARTITION BY expression. Create a new table with
the new expression, backfill with INSERT INTO new SELECT * FROM old, then
swap. Test the new granularity on a copy of representative data before
committing on a large table.
Related
ClickHouse query optimization (pillar)
The full map of six optimization areas.
Partition key best practices
Choosing the expression itself — the too-many-parts trap.
External GROUP BY
What to do when a genuinely large scan still runs out of memory.
Tables
Per-table part and partition counts, no SQL required.
ClickHouse partition key best practices
How to choose a PARTITION BY key that keeps merges fast — why a high-cardinality partition key causes a too-many-parts spiral, and the diagnostic SQL to catch it early.
ClickHouse PREWHERE vs WHERE
How PREWHERE cuts I/O by filtering on cheap columns before reading the rest of the row, when ClickHouse already does this automatically, and how to verify it with EXPLAIN.