chlift moves big Postgres tables, such as events and logs, to ClickHouse for self-managed teams. It takes a snapshot, keeps up with live changes, and then proves both sides agree. Version 0.2.1 shipped on 12 October 2026.
chlift picks the big tables to move. Small tables stay in Postgres.
An initial snapshot copies each table. Live change data capture then keeps ClickHouse in step with every insert and update.
chlift compares per-day row counts and column checksums over primary-key ranges, on every replica.
chlift confirms Keeper quorum and replica health, then writes a row on one replica and reads it on all.
Reads move to ClickHouse once both checks pass.
No cutover until the checksums match on every replica.
From our evidence run. Tables under 100,000 rows stay where they are. No timings are claimed.
| Table | Rows copied | Plan |
|---|---|---|
user_events | 8,000,000 | Move to ClickHouse |
request_logs | 4,000,000 | Move to ClickHouse |
system_logs | 2,000,000 | Move to ClickHouse |
users | 20,000 | Stay in Postgres |
accounts | 500 | Stay in Postgres |
Postgres dates can be infinity or -infinity. PeerDB writes them as 1970-01-01 in the initial snapshot. When change capture rewrites an updated row, it writes 1900-01-01.
So every infinity row touched by an UPDATE showed up as a checksum mismatch in one date column: 4 buckets, each off by exactly 2,208,988,800 seconds, which is 70 years. The row counts matched. Only the checksum caught it.
The fix (commit 485985a): migrate check treats those two sentinel instants as equal on both sides.
CI runs the full migration on a clean GitHub-hosted Ubuntu 24.04 runner, one server.
| Attempt | Result | What changed |
|---|---|---|
| 1 | Failed | Postgres could not read the seed SQL file. Fixed the test setup. |
| 2 | Failed | The checksums found the infinity-date bug above. Fixed in chlift. |
| 3 | Passed | Run 38212375181, 12 October 2026, 06:24 UTC. |
PeerDB says RUNNING while the mirror is broken; chlift's exporter catches it.
In CI on a throwaway runner (run 38248428552), we broke a mirror on purpose: we dropped its ClickHouse table, then inserted rows in Postgres. PeerDB kept reporting the mirror as RUNNING (chlift_mirror_up 1), and its v0.37.10 source sends no Slack or email alert for this. chlift's exporter counted the error about 11 seconds after the Postgres insert (10.6 s in all three runs; the test polls every 10 seconds, so that bounds it): chlift_mirror_errors went from 0 to 1.
Shipped in chlift v0.2.2; exact values since v0.2.3, whose release gate ran this same forced-error proof (run 38254323676). Proven on one forced error type so far: a missing target table.
In CI on a throwaway runner (run 38305405655), we seeded 15 Postgres tables (5 hot) with 505,800 rows. The snapshot to ClickHouse took 23 s. Then we ran 5 minutes of live writes at about 100 transactions/s (7 row changes each) on the hot tables.
All 15 tables were equal by checksum (64 key ranges per table, 0 differ) 2 s after the writes stopped. Then we dropped one ClickHouse table: PeerDB still reported the mirror RUNNING and sent no alert; chlift's error metric fired within 11 s and its check failed, naming the table.
We run chlift with your team, and the checksums prove nothing was lost.
chlift.deemwar.com · io@deemwar.com
Sources: chlift v0.2.1 release verification and CI end-to-end run 38212375181 (12 October 2026); the evidence-run migration plan; fix commit 485985a.