Engineering story

Heavy queries, 10–100× faster

We moved event analytics to ClickHouse. Funnels and breakdowns that couldn't finish before now come back in about a second. Here's the honest version of how.

Where Postgres hit its ceiling

Mostly Good Metrics started on PostgreSQL, and Postgres carried us a long way. Date-partitioned event tables, nightly aggregates, and HyperLogLog sketches kept dashboards quick for most projects.

Then our largest customer — a project sending 50M+ events — started asking harder questions. Simple charts stayed fast, but the heavy ones didn't: multi-step funnels over months of data, breakdowns across high-cardinality properties, retention cohorts over long ranges. Some of those queries didn't come back slow. They didn't come back at all.

You can only pre-aggregate the questions you predicted. The whole point of analytics is asking the ones you didn't.

What changed

We moved analytical reads to ClickHouse, a columnar database built for exactly this workload. Real numbers, not benchmarks on synthetic data:

10–100×

Faster heavy queries

The queries that strained Postgres — long ranges, big projects, high-cardinality breakdowns — got faster by one to two orders of magnitude.

~1s

Funnels that used to time out

Funnels and breakdowns that previously couldn't complete at 50M+ events now finish in about a second.

Exact

Unique user counts

Scanning raw events is now cheap enough that approximate unique counts became exact ones. The number on your dashboard is the number.

52M

Events migrated, zero lost

We backfilled 52 million historical events into ClickHouse and verified counts against Postgres before serving a single query from it.

The architecture, in one paragraph

Each database does the job it's good at. No exotic infrastructure, no data pipeline to babysit.

Postgres for your account

Users, projects, API keys, saved funnels, billing — everything transactional stays in PostgreSQL, where relational integrity matters.

ClickHouse for your events

Raw events land in a columnar store built for scanning billions of rows. Funnels, retention, breakdowns, and the event explorer query it directly.

Verified nightly

A nightly job compares event counts between the two stores, per project, per day. If they ever disagree, we know before you do.

Built to not lose your data

Fast queries don't matter if the data behind them is shaky. The boring parts got as much attention as the fast parts:

  • Buffered ingestion — Events are accepted, buffered, and written in durable batches. A slow database never means a dropped event.
  • Automatic retries — If a ClickHouse write fails, a background worker retries it until it lands. Writes are idempotent, so retries can't double-count.
  • Automatic fallback — If a ClickHouse read errors for any reason, the query transparently falls back to the Postgres path. You get an answer either way.
  • Nightly parity checks — The same verification we ran during the migration still runs every night, comparing the two stores so drift can't creep in silently.

None of this required a data team, a Kafka cluster, or a six-month replatform. It's the same simple analytics — it just stopped having a ceiling.

Analytics that keep up with your growth

Drop in an SDK, track events, and ask heavy questions without worrying about the database behind them.