Reladiff: «дифф» огромных таблиц внутри СУБД для инженеров данных и DevOps
Reladiff — это бесплатный open-source инструмент (CLI и Python-библиотека) для сравнения (diff) больших наборов данных между разными базами и внутри одной базы. Он выполняет вычисления на стороне СУБД, почти не гоняет данные по сети и поэтому летит даже на миллиардах строк. Идеален для контроля репликаций (Fivetran/Striim/DMS), миграций (PostgreSQL → Snowflake/BigQuery и т.п.), регрессионных тестов ETL/ELT и проверок витрин.
Ключевая идея (в 30 сек)
- Cross-DB: делит таблицу на сегменты по ключу, для каждого сегмента в обеих базах считает контрольные суммы/агрегаты, скачивает только «подозрительные» сегменты и бьёт их дальше (divide-and-conquer). Отлично работает, когда «различий мало».
- Same-DB: если обе таблицы в одной базе — делает оптимизированный OUTER JOIN + может материализовать результат в локальную таблицу и собрать доп. статистику.
Что умеет «из коробки»
- 15+ СУБД: Postgres, MySQL, Snowflake, BigQuery, Oracle, ClickHouse, Redshift, Trino/Presto, Vertica, DuckDB, Databricks и др. (см. строки подключения ниже).
- Производительность: ~25 млн строк < 10 сек без различий; ~1 млрд строк ≈ 5 мин; «тянет» десятки миллиардов. В кросс-БД режиме близко к count(*), если изменений почти нет.
- «Умный» дифф: хеш-алгоритм качает только изменённые участки; аккуратно скругляет precision (напр., timestamp(9) → timestamp(3)), чтобы не ловить «ложные различия».
- Автоматизация: многопоточность; вывод JSON или git-стилем (+/-) для CI/CD; материализация результата в таблицу.
- Совместимость с data-diff: Reladiff — форк архивированного data-diff; код и CLI совместимы, но нет трекинга и нет интеграции с dbt. Оригинальный data-diff открыт, но с 17 мая 2024 официально архивирован.
Установка
# только библиотека и CLI pip install reladiff # сразу с драйверами под основные СУБД (можно сузить список) pip install 'reladiff[duckdb,mysql,postgresql,snowflake,presto,oracle,trino,clickhouse,vertica]' # BigQuery ставится отдельным пакетом pip install google-cloud-bigquery
Примечание для shell: экранируйте квадратные скобки кавычками в bash/PowerShell.
Быстрый старт (CLI)
Кросс-база:
reladiff \ "postgresql://user:pass@host:5432/db" orders \ "snowflake://user:pass@ACCT/DB/SCHEMA?warehouse=WH&role=ROLE" orders \ -k id \ -t updated_at \ -c amount -c status \ --min-age=5min \ --json
- -k/--key-columns — ключ (поддерживает составной);
- -t/--update-column — столбец «последнего обновления»;
- -c/--columns — дополнительные столбцы к проверке;
- --min-age — игнорировать свежие строки (учесть лаг репликации);
- --json — машинно-читаемый вывод (удобно для CI);
- -j/--threads — число потоков на БД;
- -m — материализовать результат в таблицу (в same-DB режиме).
Внутри одной БД (быстрее, JOIN):
reladiff "postgresql://user:pass@host/db" events events_backup \ -k (org_id,id) \ -w "event_time >= '2025-08-01'" \ -m diff_events_%t --materialize-all-rows --table-write-limit=10000
Быстрый старт (Python)
import logging
logging.basicConfig(level=logging.INFO)
from reladiff import connect_to_table, diff_tables
left = connect_to_table("postgresql:///", "orders", ("id",))
right = connect_to_table("mysql:///", "orders", ("id",))
for sign, row in diff_tables(left, right, update_column="updated_at"):
print(sign, row) # '-' только слева; '+' только справа
API позволяет управлять алгоритмом (AUTO|JOINDIFF|HASHDIFF), порогами биcекции, потоками, материализацией, сбором статистики и др.
Поддерживаемые СУБД и строки подключения (из документации)
- PostgreSQL postgresql://user:pass@host:5432/db
- MySQL mysql://user:pass@host:3306/db
- Snowflake snowflake://user[:pass]@account/DB/SCHEMA?warehouse=WH&role=ROLE
- BigQuery bigquery://<project>/<dataset>
- Oracle oracle://user:pass@host/service
- ClickHouse clickhouse://user:pass@host:9000/db
- Redshift / Trino / Presto / Vertica / DuckDB / Databricks — см. список в доках.
Как это работает (под капотом)
Cross-DB (hashdiff + bisection)
- Выбирается непрерывное пространство ключей (обычно PK).
- Таблица бьётся на сегменты (напр., --bisection-factor=32).
- По каждому сегменту обе БД считают агрегаты/хеши (в БД), сравнение идёт по результатам; совпало — сегмент готов.
- Для несовпавших сегментов — повторная биcекция; когда сегмент станет «маленьким» (ниже --bisection-threshold), строки качаются локально и сверяются построчно.
-
На выходе — +/- по ключам/колонкам, плюс опциональная статистика.
Такой подход даёт время близкое к count(*), если различий мало, и при этом возвращает сами «расхождения».
Same-DB (joindiff)
Строится OUTER JOIN по ключу с проверками и опциями материализации/сэмплирования «эксклюзивных» строк. Это обычно самый быстрый путь, если обе таблицы в одной СУБД.
Производительность и масштабирование на практике
- Индексы решают. Сделайте индексы по ключу и updated_at (если используете). Проверьте EXPLAIN через --interactive.
- Потоки. Увеличивайте -j/--threads (и max_threadpool_size в API) — в Postgres/MySQL это сильно ускоряет.
- Тюнинг биcекции. Увеличивайте --bisection-factor на огромных таблицах; повышайте --bisection-threshold, если изменений много.
- Сегментация по «where». Сравнивайте по дням/партициям, а не «всю историю».
- Минимизируйте колонки. Сначала проверяйте наличие/удаление по ключу, затем — критичные атрибуты (сумма/статус), и только потом «тяжёлые» JSON/BLOB.
Типовые сценарии (практика)
- Верификация CDC/репликации (Postgres → Snowflake): игнорируем последние 5–15 минут, чтобы обойти лаг, сверяем ключ+updated_at.
- reladiff PG_URI orders SNOW_URI orders -k id -t updated_at --min-age=10min --limit=1
Выходим с кодом ≠ 0 в CI, если найдена хотя бы одна разница (--limit=1).
- Проверка миграции (Oracle → BigQuery): запускаем пакетно по партициям/таблицам, сохраняем диффы в JSON артефакты pipeline.
- Регрессионные тесты витрин (ClickHouse): после пересчёта витрины сверяем amount и count с эталоном, отклонения фиксируем в материализованной таблице diff_* для разбирательства.
- Проверка «случайных» удалений: сравниваем только ключи, чтобы детектировать пропажи, без тяжёлых колонок.
- Контроль контрактов данных: в nightly-джобе считаем % расхождений, алертим при превышении порога.
Интеграция в CI/CD (пример GitHub Actions)
name: data-diff
on: [workflow_dispatch, push]
jobs:
reladiff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install 'reladiff[postgresql,snowflake]'
- run: |
reladiff "$PG_URI" events "$SNOW_URI" events \
-k event_id -t updated_at --min-age=5min --json --limit=1
JSON-вывод удобно парсить для продвинутых отчётов/комментариев к PR, а git-стиль +/- — для читабельного лога.
TOML-конфиг для повторяемых запусков
[database.pg] driver = "postgresql" user = "etl" password = "****" host = "pg.example.local" database = "dwh" [run.default] update_column = "updated_at" threads = 4 verbose = true [run.orders_last_day] 1.database = "pg" 1.table = "orders" 2.database = "snowflake://user:***@ACCT/DB/SCHEMA?warehouse=WH&role=ROLE" 2.table = "orders" reladiff --conf reladiff.toml --run orders_last_day --min-age=10min --json
Лучшие практики (чек-лист)
Перед стартом
- Есть индексы по PK и updated_at (если используете).
- Определён ключ, уникальный и NOT NULL (для same-DB это критично).
- Понимаете лаг репликации и выставили --min-age.
- Согласовали rounding/precision для дат/decimal (например, timestamp(3) в целевой).
- Отфильтровали «горячие» партиции через -w.
В процессе
- Сначала «дёшево»: --limit 1 (есть ли вообще различия?).
- Затем выборочно: ключи/критичные поля.
- Только потом полный «cell-by-cell» на спорных диапазонах.
После
- Материализовали дифф (same-DB) для разбирательства и аудита.
- Зафиксировали метрики (% diff, +/- по строкам, latency) в DQ-дашборде.
Риски и тонкости (и как их обойти)
- Непоследовательные снимки: без REPEATABLE READ/SNAPSHOT можно сравнить «разные» моменты времени. Решение: изолируйте транзакции/временные срезы, используйте --min-age.
- Большие «дыры» в ключах (разреженные PK) — лишние запросы на пустые диапазоны. Решение: фильтровать WHERE, поднять --bisection-threshold, либо выбрать другой ключ.
- Колляции/регистры и Unicode: case_sensitive и различные collations могут давать «фантомы». Решение: нормализуйте кейс/колляции по обе стороны.
- Типы и precision: timestamp(9) vs timestamp(3) и DECIMAL scale → ложные отличия. В Reladiff есть автосокругление под специфику БД, но лучше унифицировать схемы заранее.
- Дубликаты ключей: hashdiff их поддерживает, но проверка уникальности ключа в joindiff дорогая. Решение: чистить дубликаты; в крайних случаях --assume-unique-key.
- Стоимость в облаках (Snowflake/BigQuery): множество checksum-запросов = потребление кредитов/сканов. Решение: работать по партициям/окнам, --limit, минимальный набор колонок. (Инференция на основе механики алгоритма.)
- Слабая пропускная способность сети: поднимайте --bisection-threshold, чтобы чаще сравнивать локально маленькие сегменты, и повышайте --bisection-factor для меньших скачиваний.
Reladiff vs data-diff (контекст)
Reladiff — эволюция OSS-инструмента data-diff от Datafold. Datafold архивировал open-source репозиторий 17 мая 2024. Reladiff сохраняет совместимый API/CLI и добавляет улучшения, при этом убраны трекинг-модули и dbt-интеграция.
Вопрос-ответ
Q: Насколько точен дифф при разных типах и precision в разных БД?
A: В cross-DB режиме Reladiff корректно округляет (например, timestamp(9) → timestamp(3)) по спецификации СУБД. Для денег/data-time всё равно лучше выровнять схемы и согласовать scale.
Q: Что быстрее — cross-DB или same-DB?
A: Same-DB почти всегда быстрее (JOIN внутри одной БД). Cross-DB близок по времени к count(*), если различий мало; при большом числе отличий выбирайте разумный --bisection-threshold и фильтруйте WHERE.
Q: Как учесть лаг репликации?
A: Параметр --min-age=5..15min исключит «горячие» строки из сравнения. Это стандартный приём для CDC.
Q: Можно ли видеть результат в таблице?
A: Да, в same-DB режиме материализуйте -m diff_%t (можно все строки или только отличия). Укажите лимит на запись.
Q: Сложно ли встроить в CI?
A: Нет: берите --json или +/--вывод, выставляйте --limit=1 для «быстрой сигнализации», парсьте JSON для отчётов.
Q: Какие СУБД поддерживаются «наверняка»?
A: Полный и актуальный список (с пометками «протестировано/в процессе») — в документации «Supported databases».
Q: Как управлять стоимостью в Snowflake/BigQuery?
A: Сегментируйте по партициям/диапазонам, уменьшайте набор колонок, используйте --limit и --min-age. (Следствие алгоритма и практик эксплуатации.)
Примеры, близкие к «боевым»
1) Проверка ежедневной витрины (ClickHouse) на соответствие эталону (Postgres)
reladiff "$PG" sales_daily "$CH" sales_daily \ -k (org_id,day) -c gross -c net -c items \ -w "day >= current_date - 1" \ --limit=1000 --json -j 8
2) Миграция Oracle → BigQuery (батчами по месяцам)
for m in 2024-01 2024-02 2024-03; do
reladiff "$ORACLE" fact_orders "$BQ" fact_orders \
-k order_id -t updated_at -w "to_char(order_date,'YYYY-MM')='${m}'" \
--min-age=1h --json || exit 1
done
3) Кросс-проверка «только наличия строк» для ловли «жёстких удалений»
reladiff "$A" t "$B" t -k id --columns id --limit=100 --json
Когда Reladiff не первый выбор
- Нет ключа/уникальности, таблица разреженная и с огромными «дырками» по ключам — придётся тюнинговать, иначе много лишних запросов.
- Десятки процентов различий и очень «широкие» строки — проще сузить окно сравнения (партиция/дата) или сравнивать только ключевые поля на первом проходе.




