Deemwar Monads & Abstractions

A migration you can check

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.

14,000,000 rows moved in the test plan
4 buckets caught off by 70 years
14/14 release checks pass

How a move runs

  1. 1

    Plan

    chlift · --min-rows 100000

    chlift picks the big tables to move. Small tables stay in Postgres.

  2. 2

    Snapshot and change capture

    PeerDB underneath

    An initial snapshot copies each table. Live change data capture then keeps ClickHouse in step with every insert and update.

  3. 3

    Check the data

    chlift migrate check --checksums

    chlift compares per-day row counts and column checksums over primary-key ranges, on every replica.

  4. mismatch → no cutover · fix the cause, then check again
  5. 4

    Check the cluster

    chlift verify

    chlift confirms Keeper quorum and replica health, then writes a row on one replica and reads it on all.

  6. fail → no cutover until the cluster is healthy
  7. 5

    Cut over

    your team

    Reads move to ClickHouse once both checks pass.

No cutover until the checksums match on every replica.

The example plan

From our evidence run. Tables under 100,000 rows stay where they are. No timings are claimed.

TableRows copiedPlan
user_events8,000,000Move to ClickHouse
request_logs4,000,000Move to ClickHouse
system_logs2,000,000Move to ClickHouse
users20,000Stay in Postgres
accounts500Stay in Postgres

The bug a row count would miss

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.

Snapshot1970-01-01
After UPDATE1900-01-01
Row countpasses
Checksumcatches it

The end-to-end test

CI runs the full migration on a clean GitHub-hosted Ubuntu 24.04 runner, one server.

AttemptResultWhat changed
1FailedPostgres could not read the seed SQL file. Fixed the test setup.
2FailedThe checksums found the infinity-date bug above. Fixed in chlift.
3PassedRun 38212375181, 12 October 2026, 06:24 UTC.

When PeerDB says RUNNING and the mirror is broken

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.

Table droppedforced error
PeerDBRUNNING
Alertnone sent
chlift /metricserrors 0 → 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.

Hot tables under live writes

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.

15 tables505,800 rows seeded
Snapshot23 s
Live writes5 min · ~100 tx/s
Checksums15 of 15 equal

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.

Guardrails

Verified migration

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.