Why move
Dashboards aggregate: events per page, per topic, per segment, per day, across whatever range the user picks. On a row store those scans grew with traffic. Tuning the Postgres queries bought 20–30%; the aggregate-heavy screens needed a columnar engine.
The goal wasn't to replace Postgres. Segments, experiences, topics and billing stayed there. ClickHouse took the data that is written once, read in aggregate, and never edited by hand.
Schema decisions
Segment engine
A segment is a rule tree: conditions joined by AND, where any condition can be an OR group. Evaluating one takes four steps.
Trade-offs
- Set logic lives in Node. Every leaf is a small, testable query and the combination is easy to debug, at the cost of holding ID sets in memory. At a much larger scale I'd push the whole tree into a single ClickHouse query with bitmap functions.
- ReplacingMergeTree deduplicates eventually, not immediately. FINAL makes a read exact but costs, so it's used where a duplicate would be wrong: membership, profiles and scores.
- The same pipeline predicts a segment's size over the last N days before it's saved — which is what the agent shows while it builds one.