Merge — and the 'too many parts' crash
A background worker continuously fuses small adjacent parts into bigger sorted parts; if the write rate exceeds the merge rate, the part count crosses parts_to_throw_insert and the engine rejects new INSERTs.
Many small parts pile up fast. If the count grows unbounded, every query has to interrogate every part — there must be a background process that fuses them, or the engine collapses.
Scene 07
Merge — and the 'too many parts' crash
- Watch
- Try it
- Predict
- Capture
Watch the steady-state baseline. Batched INSERTs land as new parts on the stack; the background merge worker picks 3-5 adjacent parts and fuses them into one bigger part at the next level. The active-parts counter hovers around 40 — well below the two thresholds.
Highlighted lines are the ones running in the diagram right now.
def merge_worker():while True:for partition in active_partitions():# candidates = adjacent parts at the SAME levelpicks = pick_adjacent_same_level(partition)if len(picks) < 2:continuemerged = sort_merge(picks) # in sorted-key orderemit_larger_part(merged) # level += 1delete(picks) # originals dim and go
def insert_path(block, partition):active_parts = count_active_parts(partition)if active_parts > parts_to_throw_insert: # default 3000raise "Too many parts (" + str(active_parts) + ")"if active_parts > parts_to_delay_insert: # default 1000sleep(backoff(active_parts)) # artificial throttlenew_part = write_part(block, partition) # 1 INSERT = 1 partregister(new_part)
def pick_adjacent_same_level(partition):parts = list_parts(partition) # ONE partition only# merges NEVER cross partition boundaries —# a high-cardinality partition key (e.g. hourly)# leaves each partition with too few adjacent# same-level parts to fuse, so merge starves.return longest_adjacent_run_at_same_level(parts)
Where this sits in Build a columnar OLAP store (ClickHouse / Druid style)
Scene 07 of 13, in the Write side act — Bulk inserts → immutable parts → background merge → too-many-parts cliff.. Background worker fuses small parts into bigger ones; when write rate exceeds merge rate, parts_to_throw_insert fires and inserts get rejected.
Up next. Merge keeps the part count bounded, but a query still has to descend into surviving parts. What it does inside each part — scan everything, or jump to a range — depends on how the rows were sorted when the part was written.
All 13 scenes in Build a columnar OLAP store (ClickHouse / Druid style) · Every curriculum