YAML is the primary surface. You can write every setting in the file and keep the file in a repository. The app has a form for every setting too, so you can author a suite without writing YAML. Authoring in the app says where each setting is.
Anatomy of a suite
suite.yaml
Configuration reference
Every field, rendered and browsable.
config.schema.json
The machine-readable schema, for editors and validators.
The smallest useful suite
A connection pair and one reconciliation:suite.yaml
Choosing the dataset
Each side of a reconciliation is a dataset: a connection plus what to read.- table (preferred)
- query
table:, the tool finds the column list for you. Thus it can compare columns that you did not list, warn when the two sides have different columns, and narrow the comparison without SQL from you:columns and exclude_columns are mutually exclusive. The table reference accepts a dotted string (db.schema.table) or a structured object with database, schema and name.Key columns decide everything
key_columns: (or key_column:, both accept a string or a list) names the columns that identify a row. It is the most important choice in a suite, because the drill-down sorts and filters on these columns.
Two rules:
- It must be unique. A non-unique key makes segment boundaries ambiguous and the drill-down unreliable.
- It should be indexed. Each drill-down level range-filters on the key. With a matching primary key or index, the database prunes; without one, every segment is a full scan.
check-config --dbtells you which you have.
timestamp_precision and the time zone settings.
A single key column is written back as the scalar key_column; several as a key_columns list. This is only a format detail of the synq-recon suite yaml output. Write the form that you prefer.
Matching columns across the two sides
Column names that differ only in case match automatically, souser_id on Postgres lines up with USER_ID on Snowflake with no configuration. For genuinely different names, map them:
case_insensitive: false to turn the automatic behaviour off entirely.
Keep credentials out of the suite
Move the wholeconnections: block into a separate file, git-ignored, keyed by the same connection names:
.connections.yaml
.connections.yaml or connections.yaml in the working directory or in a parent directory. --connections <path> names a file explicitly. Commit a .connections.yaml.example with ${ENV_VAR} placeholders so the next person knows which names to fill in.
Anywhere in a suite, ${VAR} reads an environment variable and ${VAR:-default} supplies a fallback.
Setup and teardown
SQL to run before and after the reconciliations, for each connection. Use it to stage a snapshot, to materialise a view, or to build fixtures in a test suite:setup_file and teardown_file take a path per connection instead of inline statements. Both blocks also exist per reconciliation, where teardown_on_failure and ignore_setup_errors inherit the suite’s value unless you set them.
A file path is correct only on the computer that runs synq-recon. upload-config reads the files and copies their SQL into the suite, so the suite also runs in the workspace. The app cannot read your files. Thus Upload config in the app refuses a suite that has setup_file or teardown_file. In the app, write the SQL under setup and teardown.
ignore_setup_errors: true is correct for bootstrap SQL that you can run again safely (CREATE TABLE IF NOT EXISTS). It is not correct when the setup makes the data that you compare: a failed setup then gives two empty tables, and they match.Annotations
Name/value labels on a suite or a single reconciliation. Reconciliation-level annotations merge with the suite’s, and they follow a promoted suite into the platform, where they annotate the resulting assets and checks.Run timeout and parallelism
How a run of the suite executes. These settings are stored with the suite, so a local run, an ad-hoc backend run and a Production deployment read the same values:timeout, a local run stops after 5 minutes and a backend run after 15 minutes. Without concurrency the reconciliations run one after another, so one slow table holds up the rest.
A run that hits its timeout is cancelled. Reconciliations that finished keep their results, the ones still running are reported as failed.
Without reconciliation_timeout, a single slow query, like a cutoff watermark over an unindexed column, can use up the run’s whole timeout, and every reconciliation after it then fails without running. With it, a reconciliation that runs past its budget (setup, check and any drill together) is reported as failed with a timeout, and the run moves on to the next one. Set timeout on a reconciliation to give that one a different budget:
error_handling.query_timeout. It covers the reconciliation’s setup and teardown statements too. A query that hits it fails with a timeout and is not retried, because a query that took that long usually takes that long again.
If the query that times out is a cutoff watermark, point cutoff.query at something cheaper to read than the full table, like a load-metadata table or the most recent partition, or give the reconciliation a window: its watermark is then read from inside the window only.
Teardown still runs after the run’s timeout expires, as long as teardown_on_failure asks for it.
Each reconciliation that runs in parallel sends queries to both of its databases at the same time. Thus a higher concurrency puts more load on them. In a local run, parallelism: on a connection sets the maximum number of queries that run on it at the same time. The default is 8. With disabled: true on a connection, a local run does not run the reconciliations that use that connection in a dataset, in setup or in teardown, and does not send the suite’s setup and teardown to it. If --include names one of these reconciliations, the command fails. These two settings apply only to local runs. A suite in the workspace uses integrations for its connections, and these settings do not apply to it.
A more specific setting wins over the suite’s:
A backend run limits the timeout to a supported range, and can use less concurrency than the suite sets.
reconciliation_timeout has no per-run override: every run reads it from the suite. In the app, all three settings are on the suite’s Execution tab, and a reconciliation’s own timeout is on its Overview tab.
Validate before you spend a query
Three steps, cheapest first. Do not skip ahead; each one catches a class of problem the next would surface as a confusing mid-run error.1
Parse and check the suite at no cost
Configuration check passed!.2
Check the wiring
connection: values of your suite are workspace integration ids and not local definitions, the check without a connections file reports no connections defined. This step tells you why.3
Check against the databases
LIMIT 0. It scans nothing and reads no rows. It reports the resolved columns, table size, primary and partition keys, a suggested key column, and a warning when the key column you chose is not indexed. For a reconciliation with comparison settings, it also reports each column that numeric_scale: exact cannot compare.Move on when every connection is OK and every query validates. A failure here is a wrong table name, a missing grant, or a column that does not exist.On BigQuery, the table analysis in
--db enumerates dataset metadata rather than jumping to the tables you named, so its cost scales with the size of the warehouse rather than the size of your suite. For this reason, the analysis runs only in this interactive command, and not before every run.Authoring in the app
In the app, Health → Reconciliations → Development has a New suite wizard with four steps: Data sources, Comparison, Suite info and Review. It also has an Upload config button, which opens a dialog that accepts a suite as pasted or uploaded YAML. The wizard is quicker for a first suite and better at discovery: it searches your catalog, so you can pick a table you already have rather than typing warehouse coordinates, and it shows which columns are keys or indexed while you choose. On the Review step, Create and add another saves the suite and starts the next reconciliation in it. After you save a suite, you can change every setting in a form. Open the suite for the settings of the whole suite, and open a reconciliation for its own settings:
Some settings are for local runs only, and the app does not offer them:
reporting.level: detailed. A run in Coalesce Quality never reads row values, so it refuses this level. A suite that still has it cannot run until you pick another level.setup_fileandteardown_file. The app has no files.upload-configcopies the SQL of each file into the suite.- Connection
parallelismanddisabled. A workspace suite names its connections only, and binds each one to an integration.
check-config does and shows each problem next to its field. In a reconciliation, each tab shows how many problems it has. You cannot save a suite that has an error, but you can save one with warnings.
If someone else saved the suite after you opened it, the app does not overwrite their version. It shows who saved it and when. Choose Reload to see their version, or Save mine anyway to save yours over it. Every save is a new version. On the suite’s History tab, See changes shows what a version changed, and Restore makes an older version the newest one again.
Two capabilities exist only in the app, and are worth knowing about:
- Authoring from the catalog. The entity picker resolves a table you have already catalogued to its warehouse coordinates. From the CLI you supply coordinates yourself.
- Execution-plan preview. The app renders the resolved plan for a stored suite before you run it.
A complete example
Two reconciliations over the same pair of systems, in two different modes: row checksums for orders, and grouped totals for inventory, where a row-by-row comparison is the wrong tool:ecommerce.yaml
Next
Comparison modes
Which mode to use, aggregate measures, and threshold resolution.
Time windows and cutoffs
Comparing two live systems without inventing differences.
Running locally
Filters, concurrency, JSON output, audit logs and exit codes.
Workspace suites
Save it, start a backend run, promote it to Production.