Template variables
A variable is resolved once per run and interpolated into both sides, so the two queries are guaranteed to use the same boundary.where:, so you do not have to switch to raw SQL to use them.
What you can write inside {{ }}
Offsets subtract from
now, today or yesterday, in units of d (days), h (hours), m (minutes) or w (weeks). Only subtraction is supported — a comparison window that reaches into the future is not a thing you want.
Override any variable for a single run:
Windows
Awindow: restricts a reconciliation to a recent period without you writing the predicate:
lookbackis required:"14d","2h","1w".strategy: sliding(the default) measures back from the run’s reference time, so each run compares a moving fortnight.fixedanchors the window to a period boundary, so consecutive runs inside the same period compare exactly the same rows.
fixed when you want two runs to be comparable to each other, not just each correct in isolation.
Cutoffs for a lagging target
This is the important one, and the one people reach for last when they should reach for it first. If the target trails the source — a CDC stream, a batch load, a read replica — then rows written recently are on the source and not yet on the target. They are not missing. They are in flight. A reconciliation with no cutoff reports them as a difference, every time, and the difference moves as the lag moves. Acutoff: derives a watermark from the data itself, then filters both sides to at-or-below it. Rows in flight are excluded from the comparison rather than reported as errors.
Notice the cutoff is stricter than the lag — 415 rows, not 470.
truncate: HOUR snaps the watermark down to an hour boundary and offset: "-5m" pulls it back further. Both are deliberate margin, because a pipeline that writes slightly out of order can land a row below a watermark it has already passed.Three levels of control
- Simple
- Different column per side
- Full control
One column, both sides:Each side’s watermark is
MAX(created_at) over its own dataset; the lower of the two becomes the cutoff.How a cutoff resolves
1
Derive a watermark per configured side
MAX (or MIN) of the named column, or the result of your custom query.2
Combine the two into one value
combine: min by default.3
Truncate to a boundary
Optional. Skipped when the watermark is not a timestamp.
4
Apply the offset
Optional, after truncation. Negative buys margin.
5
Build the WHERE clause for each side
Against the derived column, or whatever
apply: names.WHERE clause per side — is recorded in the audit log. A run’s comparison window is always recoverable after the fact.
Time travel
as_of compares against a point-in-time snapshot rather than the current state, on platforms that support it — Snowflake, BigQuery and Databricks:
A re-check replays the original run’s queries, which for an
as_of reconciliation means it re-compares the same frozen snapshot and can therefore never turn green. Pass --reresolve to re-derive the queries against current data.Choosing between them
Use a cutoff when
The two systems are the same data at different times. You want to compare what both sides have, and ignore what is still on its way.
Use a window when
History is settled and you only care about recent data — usually for cost, not correctness.
Use variables when
You need an explicit, reproducible boundary in your own SQL, identical on both sides.
Use `as_of` when
You need a specific historical state, not “now”.
Next
Running locally
Overriding variables per run, and reading what a run resolved them to.
Investigating results
Confirming a difference is real, and re-checking after a fix.