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. If only your local runs need the files, put them in a local: block.
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.Settings for local runs only
Some settings are correct only whensynq-recon runs on your computer: setup that builds test fixtures, and reporting.level: detailed, which Coalesce Quality refuses. Put them in a local: block, at the suite level or in a reconciliation. Then one suite file works for your local tests and for the workspace:
local: block can hold setup, setup_file, teardown, teardown_file and reporting. Any other setting in it is an error.
run,run-check,run-drill,check-configandplanmerge the block into the suite.- A connection under
local.setuporlocal.teardownreplaces the setup or teardown of that connection outside the block. The other connections keep their queries. - A field under
local.reportingreplaces the same field outside the block. The fields that it does not set keep their values. - The suite’s
local.reportingapplies to every reconciliation. Thelocal.reportingof a reconciliation takes precedence. upload-configdoes not send the block and does not read its files. Upload config in the app drops the block. Thus the workspace never runs your fixtures against its warehouse, andsuite yamldoes not show the block.
check-config and the app check the block. They report a problem in it, such as an unknown connection or detailed without consent_acknowledged, at its line in the file.
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. To keep it for your local runs, put it in alocal:block.setup_fileandteardown_file. The app has no files.upload-configcopies the SQL of each file into the suite.- A
local:block. The app drops it when you upload a 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.
Validate in the warehouse
The checks while you edit do not connect to a warehouse. Validate in warehouse does the checks ofcheck-config --db on a workspace suite. It uses the same code as the CLI, so the two give the same result.
In the app, the button is on the suite and in each reconciliation’s editor. You need the permission to run the suite.
- On the suite, it checks the saved version.
- In a reconciliation, it checks your changes before you save them. If you did not change anything, it checks the saved version.
- Each problem shows next to its field, together with the problems of the checks while you edit, and is marked “found in the warehouse”. A problem that has no field, for example a connection that only
setuporteardownuses, shows under the button. - If a connection does not bind to an integration, the app asks you to pick one, as for Run.
- Validation runs only when you click the button. When you change the suite after a validation, the app says that the result is out of date. Click the button again to see the problems of the current version.
SuiteConfigService.CheckSuiteInWarehouse:
- Send the
suite_idof a saved suite, or asuitethat is not saved, such as a draft. - Optionally, send
connection_mappingsto bind each connection to an integration, as for a backend run. A connection without a mapping binds to the integration with the same name. - Read the
issuesof the response. Each issue has a severity, the path of its field and a message, in the same form as the checks while you edit.
LIMIT 0, so it scans no table. It reports a query that refers to a table or column that does not exist, each column that its comparison settings cannot be applied to, and a key column that no index covers. It does not run setup SQL. It has the same time limit as a run of the suite.
If the suite has an error that check-config finds, the check does not connect, and the response has those errors. If a connection does not bind, for example because backend runs are not enabled for the integration, the request fails with FAILED_PRECONDITION and names the connection.
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.