The row checksum
Each row is reduced to a single 48-bit signed integer:- Each column is converted to text, and the values are joined with
|before hashing, in the order the comparison resolved them.NULLbecomes the sentinel<NULL>, so a null and an empty string are different. Decimal, floating-point, timestamp, boolean and UUID columns get one text form on both sides first, so the same value gives the same text on two different databases. For numbers, that text form has a precision: see numeric precision. - Only 48 of the 128 bits are kept. Thus the sum of millions of row checksums fits in a 64-bit integer without overflow. This lets the database calculate the sum itself.
- Subtracting 2^47 centres the values around zero, so a sum over many rows drifts towards zero rather than towards the top of the range.
HASHBYTES on SQL Server, STANDARD_HASH on Oracle, and MD5 with a base conversion on the other platforms. Thus two different platforms give the same number for the same data. Which hash function is used is negotiated per reconciliation.
On Oracle, STANDARD_HASH accepts at most 4000 bytes of joined text. To compare longer rows, the connection’s user needs the optional grant EXECUTE ON SYS.DBMS_CRYPTO. Without it, a longer row fails the reconciliation with ORA-01489. See step 4 of the Oracle integration.
What leaves the database, in full: one row per query containing a count and a sum. No row values, no keys, no column contents. That is true of the quick check and of every level of the drill-down, unless you explicitly raise the privacy level.
The quick check
One query per side:The bisection drill-down
When the totals disagree, the run needs to find where. It does that by splitting the key space and re-asking the same question of each piece.1
Split the key range into N segments
bisection.factor (default 32, maximum 1024) sets N. The boundaries come from the segmentation strategy below.2
Compare every segment in one query per side
One query per side, not N queries. A boundary query on the source finds the N ranges. Then a single bucketed aggregate per side returns the counts and checksums of all N segments. Thus the number of queries for each split does not depend on the factor.
3
Recurse into the segments that differ; ignore the ones that agree
A segment whose count and checksum match on both sides is identical and is never queried again.
4
Stop at the threshold
Drilling stops when a segment’s row count falls below
bisection.threshold (default 16384), when --depth is reached, or when the segment cannot be split any further. Those segments are the run’s mismatch leaves.bisection.enabled: false keeps a reconciliation to the quick check. run --auto-drill, recheck --drill and every backend run (ad-hoc, scheduled or triggered) then do not drill into it, also when --drill asks for a drill-down. Only run-drill and drill-deeper drill into it, because you start them to drill. A bisection: block without enabled is enabled, and so is a reconciliation without a bisection: block.
Because each level discards everything that matched, the number of queries scales with the logarithm of the row count for data that is mostly identical. Locating one bad row in ten million takes a handful of round trips.
Segmentation strategies
How the key space is divided is configurable, and the choice is a trade-off between drill efficiency and what the boundaries reveal.When to choose something other than the default
- hash: sensitive keys
- time: time-partitioned tables
Privacy levels
By default nothing but counts and checksums is retrieved.reporting.level opts into more, per reconciliation:
detailed also requires consent_acknowledged: true. This is a deliberate extra step, because detailed is the only setting that puts row values into a run record: the local audit log.
detailed works only in a local run, which you start with synq-recon on your own machine. A backend run never reads row values. Coalesce Quality refuses to save, promote or run a suite with reporting.level: detailed, with a message that names the reconciliations. Use with_keys or count_only in suites that run there.
A local run at detailed shows the values of the mismatched rows in the terminal, and writes them into the audit log file of --audit-log. Each value is shown as the database holds it: text as stored, a decimal with all its digits, a timestamp in RFC 3339 in UTC, a date as the day, a boolean as true or false, and NULL as <NULL>.
Row values never go to your workspace. When a local run reports its results, the uploaded audit log has no row values. It keeps the keys, the mismatch types and the row hashes, as at with_keys. Coalesce Quality also removes row values from each audit log that it receives, for example from an older synq-recon.
Cost, in queries
What each query scans depends on your schema, which is why key column indexing matters more than any setting on this page. Running locally covers the pre-run scan estimate and the gate flags.
Next
Comparison modes
Choosing between counts, checksums and aggregate measures.
Investigating results
Reading a segment tree and following a leaf to the rows.