Skip to main content

A canary for your CDC pipeline โ€” catches silent replication drift before your analysts do.

Project description

๐Ÿค CDCanary

CI License: MIT Python

A canary for your CDC pipeline โ€” catches silent replication drift before your analysts do.

CDCanary demo โ€” one scan catches four kinds of silent drift

CDC pipelines fail loudly when they crash โ€” and silently when they don't. A column added mid-stream lands as NULL in your warehouse. A backfill quietly skips rows. Replication lag creeps from minutes to days. No error, no alert. Someone finds out three months later, in a business meeting, with a wrong number on the slide.

CDCanary is a lightweight monitor that runs aggregate consistency checks between your source and replica on a schedule, and pings Slack when they disagree.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   CDC (Datastream / Debezium / Fivetran / ...)   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  MySQL   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ BigQuery  โ”‚
โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜                                                  โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
     โ”‚                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                             โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€ aggregate โ”€โ”€โ”€โ–ถโ”‚ ๐Ÿค CDCanaryโ”‚โ—€โ”€โ”€โ”€โ”€ aggregate queries โ”€โ”€โ”€โ”€โ”˜
            queries       โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
                                โ”‚  row delta ยท freshness ยท null-rate ยท schema drift
                                โ–ผ
                            Slack alert

Approach

CDCanary deliberately does not do row-level diffing. Aggregate signals (counts, timestamps, null fractions, schemas) catch the failure modes that actually happen in CDC pipelines, run in seconds on billion-row tables, and keep the adapter surface small enough to maintain. It monitors continuously โ€” this is a canary you run from cron, not a one-off migration validator.

The known blind spot: rows whose values are wrong while counts and null rates still match (e.g. lost UPDATE events). A sampled-checksum check is on the roadmap to cover that probabilistically without paying full-diff costs.

Checks

Check Signal Catches
row_delta source vs target row count (windowed) dropped rows, stalled backfill
freshness source vs target max(updated_at) lag replication lag, dead connector
null_rate source vs target null fraction per column schema-drift NULL corruption
schema_drift column set + coarse types added/missing columns, type changes

All checks are symmetric aggregate queries โ€” any supported connector can be a source or a target. 3 connectors = 9 directions, including reverse ETL (BigQuery โ†’ MySQL) and same-engine replicas (PostgreSQL โ†’ PostgreSQL).

Connectors (v0.1)

MySQL ยท PostgreSQL ยท BigQuery

Quickstart

Zero config โ€” point it at both ends and see what disagrees:

pip install cdcanary[mysql,bigquery]

cdcanary scan \
  --source mysql://readonly:$PW@prod-db/shop \
  --target bigquery://my-project/raw_shop

# โœ… ok    shop.orders    row_delta      rows match: 1,204,331 vs 1,204,318 (ฮ”0.00%)
# โœ… ok    shop.orders    freshness      replication lag 4m (within limit)
# ๐Ÿ”ด FAIL  shop.products  null_rate      null-rate drift: sale_status (src 0.0% vs tgt 5.9%)
# ๐Ÿ”ด FAIL  shop.coupons   table_presence table exists in source but not in target

Then make it permanent โ€” generate a config and put check on a schedule:

cdcanary init --discover --source mysql://... --target bigquery://...
cdcanary check -c cdcanary.yml        # cron / GitHub Actions / Airflow

Try the demo locally

The GIF above is a real run. examples/demo spins up a MySQL "source" and a PostgreSQL "replica" with four kinds of drift deliberately baked in โ€” a missing table, stalled replication, schema drift, and the NULL-corruption case this tool exists for:

cd examples/demo && docker compose up -d --wait
cdcanary scan \
  --source mysql://root:demo@127.0.0.1:13306/shop \
  --target postgres://demo:demo@127.0.0.1:15432/shop/public
docker compose down -v   # cleanup

Configuration

Config describes rules, not snapshots. A schema pair re-discovers tables on every run, so tables added or dropped at the source are picked up without config edits โ€” and a new source table that hasn't reached the target yet is itself a finding (table_presence).

connections:
  mysql_prod:
    type: mysql
    host: prod-db.internal
    database: shop
    user: readonly
    password: env:MYSQL_PASSWORD      # secrets always come from the environment

  bq_raw:
    type: bigquery
    project: my-project
    credentials: env:GOOGLE_APPLICATION_CREDENTIALS

pairs:
  - name: shop
    source: { connection: mysql_prod, schema: shop }
    target: { connection: bq_raw, schema: raw_shop }
    tables: ["*", "!tmp_*"]
    # target_table: "shop_{table}"   # uncomment if the CDC tool renames tables           # globs; "!" excludes
    defaults:                         # applied to every discovered table
      row_delta:    { tolerance_pct: 0.5 }
      freshness:    { column: auto, max_lag_minutes: 60 }
      null_rate:    { columns: auto, max_diff_pp: 1.0 }
      schema_drift: { ignore_columns: [datastream_metadata] }
    overrides:                        # applied top-to-bottom; later rules win
      - match: "log_*"                # glob, exact name, or a list of either
        checks:
          null_rate: false            # too big โ€” skip the expensive check
      - match: orders
        checks:
          freshness: { column: paid_at, max_lag_minutes: 30 }
      - match: users
        target_table: customers       # irregular rename, right next to its exceptions

  # Single-table pair โ€” for tables that deserve hand-tuned checks.
  # Can point across schemas/names and coexist with schema pairs above.
  - name: payments
    source: { connection: mysql_prod, table: shop.payments }
    target: { connection: bq_raw, table: raw_shop.payments_v2 }
    checks:
      row_delta: { tolerance_pct: 0.1, where: "created_at < CURRENT_DATE" }
      freshness: { column: paid_at, max_lag_minutes: 15 }
      null_rate: { columns: [amount, status], max_diff_pp: 0.5 }

alerts:
  slack_webhook: env:CDCANARY_SLACK_WEBHOOK

column: auto picks the first timestamp column by convention (updated_at, modified_at, created_at, ...); columns: auto compares every column present on both sides. Schema pairs and table pairs can be mixed freely โ€” broad coverage from discovery, precise thresholds where it counts.

Table names that differ between source and target are handled in two layers: a pair-level target_table: "shop_{table}" template for systematic renames (CDC tools usually rename uniformly), and per-table target_table in overrides for the irregular few. If the mapping has no pattern at all, single-table pairs are the honest answer.

Exit codes are cron-friendly: 0 all green ยท 1 warnings ยท 2 failures.

Non-goals

  • Row-level diffing โ€” full row comparison fits one-off migration validation better than scheduled monitoring. CDCanary sticks to aggregate queries so checks stay fast and cheap enough to run every hour, and connectors stay small. A sampled-checksum check is planned for value-level verification (see roadmap).
  • Auto-remediation โ€” CDCanary detects and alerts; deciding how to fix a pipeline is left to a human.
  • Web dashboard โ€” results are available as terminal output, --json, and Slack alerts, which integrate with whatever dashboarding you already run.

Roadmap

  • v0.1 โ€” 4 checks, 3 connectors, Slack alerts, CLI
  • v0.2 โ€” sampled_checksum check: hash-compare a sampled PK range on both sides โ€” catches value-level corruption (e.g. lost UPDATE events) that aggregate signals can't see, without the cost of a full row diff
  • v0.2 โ€” baseline state (trend-based anomaly instead of fixed thresholds)
  • v0.3 โ€” Snowflake connector, Prometheus exporter

Releasing

See RELEASING.md โ€” tags publish to PyPI via trusted publishing.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cdcanary-0.1.0.tar.gz (213.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cdcanary-0.1.0-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

File details

Details for the file cdcanary-0.1.0.tar.gz.

File metadata

  • Download URL: cdcanary-0.1.0.tar.gz
  • Upload date:
  • Size: 213.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cdcanary-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ac0062cdd4e40b45b52a136ece80ccdf206cfce018ae9e16354a4e2c9c501909
MD5 b2b14951e1c95de5ab535b0733d91457
BLAKE2b-256 413d3bd4df2ff8d47dede4ad3d473fe15408e26e3cf87b003f7eacf36080969c

See more details on using hashes here.

Provenance

The following attestation bundles were made for cdcanary-0.1.0.tar.gz:

Publisher: release.yml on thomas783/cdcanary

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cdcanary-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: cdcanary-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cdcanary-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6ff84cefc9698b88076f4c72b9074513515b3f6b2c240fd2971276e4c29a359f
MD5 6bb14124c0679883862891b16d6292e8
BLAKE2b-256 f8bfb683b3c0b6a49e294b2e158ff648ac1525f974ffa68c2a17c86fea672559

See more details on using hashes here.

Provenance

The following attestation bundles were made for cdcanary-0.1.0-py3-none-any.whl:

Publisher: release.yml on thomas783/cdcanary

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page