Self-Hosted 2026-07-28

Why Replay Vendors All Ended Up on ClickHouse

Read the architecture pages of the session replay tools you can self-host and a pattern jumps out: PostHog runs on ClickHouse, the Highlight.io lineage (including forks like LogReplay) runs on ClickHouse, and most newer entrants either started there or migrated there after an expensive detour. That is not fashion. Choosing ClickHouse for session data is one of those decisions that looks like herd behavior until you look at the shape of the data, at which point it starts to look inevitable.

Session data has three properties that matter more than anything else about it. It is append-only: events are recorded once and never updated, because you cannot revise the past — a click at 14:02:11 happened, full stop. It is wide: each event carries dozens of attributes — session ID, timestamp, event type, URL, browser, OS, viewport size, custom properties. And it is time-ordered with a strong grouping key: nearly every question anyone asks is either “give me everything for this session, in order” or “aggregate across sessions in this time range.”

Databases have shapes too, and the whole game is matching them.

What columnar storage does to event data

A row-oriented database like Postgres stores each event’s fields together: one event, then the next, interleaving timestamps with URLs with browser strings. A columnar database stores each field together: all the timestamps contiguously, then all the event types, then all the URLs.

For session events, that reorganization is close to a cheat code, because event attributes are wildly repetitive. A thousand consecutive events from the same session share one session ID. Their timestamps increase by milliseconds — stored as deltas, they are tiny. The browser column of a day’s traffic contains perhaps a few dozen distinct values repeated millions of times. Event types come from a small fixed set. Columnar compression — run-length encoding, delta encoding, dictionaries, then a general compressor over the top — feeds on exactly this redundancy. Compression ratios of 10x are unremarkable for event data; well-ordered tables do considerably better. The same repetitiveness that makes session data bulky in a row store makes it almost embarrassingly compressible in a columnar one.

Queries get the mirror-image benefit. “Count of rage-clicks per day by browser over 30 days” touches three columns out of forty. A columnar engine reads only those three; a row store drags every byte of every event off disk to use a fraction of it. When your queries scan millions of events to return twelve numbers — which is what every analytics chart on every replay dashboard does — reading 5% of the data instead of 100% is the whole ballgame.

MergeTree ordering is the quiet half of the win

Compression explains the storage bill. The query speed comes from something less discussed: ClickHouse’s MergeTree engine physically sorts data on disk by a key you choose, and session workloads have an unusually obvious right answer — something like (project_id, session_id, timestamp) for replay events, or (project_id, timestamp) for analytics tables.

Once data is sorted that way, the two dominant query patterns become almost mechanically cheap. Loading a session for playback is a contiguous read: every event for that session sits physically adjacent, in order, and the sparse primary index (see the ClickHouse docs on how MergeTree indexing works) lets the engine jump straight to the right blocks and skip everything else. Time-ranged aggregations similarly prune whole chunks of the table without touching them. Sorting also feeds back into compression — adjacent rows are similar rows, and similar rows compress better — so the ordering choice pays twice.

There is a reason this engine family emerged from web analytics at Yandex: clickstream data and session replay data are, structurally, the same animal.

Where Postgres runs out of road

I want to be fair to Postgres here, because my general position is to run Postgres until it hurts, and for plenty of things it never hurts. Every replay platform mentioned above still uses Postgres — for users, projects, settings, billing: low-volume, frequently updated, relational data, which is precisely its shape.

But push session events into it and the mismatches arrive on a schedule. Write volume comes first: a modestly busy product generates thousands of events per second, and each row inserted into a B-tree-indexed heap table does real work — index maintenance, WAL, and eventually vacuum churn on tables that never needed MVCC in the first place, because nothing ever updates an event. Storage comes next: per-row overhead plus uncompressed-by-default pages mean the same events occupy perhaps an order of magnitude more disk than they would columnar-compressed, and disk is the number one cost of self-hosting replay. Query latency arrives last but worst: analytical scans over hundreds of millions of rows are simply not what a row-store’s executor is built for, and no amount of index tuning turns a B-tree into a column scan.

None of these are Postgres bugs. They are the row-store shape meeting the event-data shape at scale. Below a few million events you will barely notice, and honestly a small install can live there for a while. The teams that started on Postgres-only architectures and later migrated did so because the growth curve of event data is steep and one-directional — sessions only accumulate.

The convergence, then, is just several teams independently doing the same arithmetic. Append-only, wide, time-ordered, queried by session and by time window: columnar storage compresses it brutally well, MergeTree ordering matches how it is read, and the operational cost of running one more database turned out to be lower than the cost of fighting the wrong one. If you are weighing whether to run this stack yourself, the practical sizing math — how much disk and memory a single-box install actually needs — is laid out in self-hosting session replay on a VPS.

It is boring technology in the best sense: a storage engine from the analytics world, applied to data shaped exactly like the data it was built for. The vendors did not all copy each other. They all hit the same wall, looked around, and found the same door.

See the bug the way your user did

LogReplay captures session replays, console output, network requests, and errors in one timeline — so you stop guessing what happened before the ticket arrived.

Try LogReplay free