Skip to main content
A mismatch is the start of the work, not the end of it. This page is the path from “these two disagree” to “here is the row, and here is why”.

Two statuses, and they mean different things

A succeeded run reporting mismatched is the normal, healthy case — the tool did its job and found something. Filter on the result status, not the run status, when you want “did anything differ?”.

Reading a finished run

The segment tree

In row_count and row_checksum modes, a drill-down produces a tree of key ranges. Each node carries both sides’ count and checksum, and the leaves are where drilling stopped:
20/1,000 rows in mismatched segments is the useful number at each level: it is how much of the dataset is still in play. If it stays close to the total as you descend, the data is not mostly-identical and you are in the expensive case. The app renders the same tree graphically, plus a drift map — the key space with the divergent regions highlighted, which makes “the whole tail is wrong” versus “three rows scattered” obvious at a glance.

What a mismatch leaf tells you

Read the types, not the counts. Every type that applies is reported, so a segment that differs in both size and content reports two:
Count direction alone does not classify a difference. A target with one row more than the source can still be missing a live row and carrying two orphans. The counts net out; the types do not.
Each leaf also carries a drill_stop_reason, which tells you whether the finding is as narrow as it can get:

The aggregate drill tree

In aggregate mode the equivalent output is a hierarchy of groups, each labelled with why it diverged — MEASURE_MISMATCH for its own numbers, SUBGROUP_MISMATCH for something below it. Comparison modes has a worked example.

Follow a finding to the rows

Every mismatch leaf comes with investigation queries: ready-to-run SQL for both sides, scoped to that leaf’s key range.
Or from a saved audit log:
They look like this — the reconciliation’s own dataset, wrapped in the leaf’s key bounds:
Run them against the two databases and you are looking at the rows themselves — ten of them, not a million.
This is why raising the privacy level is rarely necessary. count_only already tells you exactly which rows to look at; running the query yourself keeps the values in your SQL client rather than in a run record.

Re-check and Drill deeper

Two operations on a finished run, rather than on a suite. Both take either a local audit-log file or, for a run stored in the workspace, its invocation id.
A re-check answers the only question that matters after a remediation, and answers it in one line:
And a drill deeper picks up where the previous run stopped rather than starting over. Continuing the run from Getting started at --threshold 2:
Twenty-two queries, and the answer is exact: order 384 is absent from the target, and order 617 exists on both sides with a different value. Note that it re-entered at depth 0 with 20 rows, not 1,000 — only what the previous run left open is in scope.

Four things that govern a replay

The audit log holds each query after variable interpolation and cutoff resolution, so a re-check compares the same window as the original run. That is precisely what makes “did the gap move?” a meaningful question rather than two unrelated measurements.The consequence: a replay does not pick up an edited query, a changed variable, or a new cutoff until you pass --reresolve. And --reresolve is required for an as_of reconciliation, which otherwise re-compares the same frozen snapshot forever and can never turn green.
drill-deeper picks up the segments the previous run stopped at on its threshold or depth limit. --depth counts from the depth they already reached, so it means “this many more levels”.Segments that could not be split further, and aggregate groups present on only one side, are reported and skipped rather than silently dropped. An aggregate drill already visits every configured group column, so resuming one needs at least one --add-group-column.
By default. --all re-runs every reconciliation in the run.
It records connection names only, never credentials. Pass --connections, exactly as for a normal run — no connections available to replay run ... is what a missing one looks like.
--remote hands the replay to our backend, against workspace integrations instead of local credentials. It needs a stored run’s invocation id — a local file has nothing server-side to reference — and accepts --include but not --exclude.

Reading an audit log directly

The audit log is the machine-readable form of everything above, and its shape is published:

Audit log reference

Every field, rendered.

audit-log.schema.json

Validate a log against the schema you were sent.
Running locally has the jq starting points, and the two traps worth internalising: snake_case in a local file versus camelCase from the API, and counts encoded as strings.

Next

Time windows and cutoffs

Before you chase a difference, rule out the comparison window.

CI/CD and automation

Turning a reconciliation into a gate that blocks a bad release.