Skip to main content
This connector is in Beta. Where SQL Server CDC is available, prefer the SQL Server Source (CDC) for production workloads until this one leaves beta.
This connector captures inserts, updates and deletes from Microsoft SQL Server by polling Change Tracking. Change Tracking is available in every edition, Azure SQL Database included, and needs no SQL Server Agent, no sysadmin and no access to the transaction log. That makes it the method to use when CDC cannot be enabled, for example on Express edition, on Azure SQL Database tiers without CDC, or when the DBA will not grant elevated rights. Its events use the same Debezium envelope and the same topic layout (<prefix>.<database>.<schema>.<table>) as the other SQL Server sources, so existing data models and targets work unchanged. In the Popsink UI, pick the SQL Server tile, then Change Tracking as the capture method.

Choosing a capture method

What it captures

Change Tracking records which primary key changed, how, and at which version, never the row’s values. The connector reads each changed row back from the table. As a result:
  • Net changes only. A row updated several times between two polls arrives once, with its latest values. Intermediate states are not captured.
  • No before-image. An update carries before = null. A delete carries the primary key columns only.
  • The after-image is the row as the connector reads it, not as it was at commit time.
What two polls can see: A target must therefore accept a d for a key it does not hold. Every Popsink target does. source.ts_ms is the commit time of the change when the login holds the optional commit-time right (see Permissions). Without it, recent changes carry the time they were read instead.

What is not captured

Column types

Types are published as the CDC source publishes them. Worth knowing:

Compatible versions

Prerequisites

1. Enable Change Tracking on the database

  • Retention: at least 3 days, and always longer than the longest connector downtime you expect. A connector stopped longer than the retention cannot read what changed in between (see Retention gap).
  • Keep AUTO_CLEANUP = ON, otherwise the internal change tables grow without bound.
Turning AUTO_CLEANUP back on alone resets the retention to the 2-day default. Always restate CHANGE_RETENTION in the same statement.

2. Allow snapshot isolation

The connector reads each batch inside one SNAPSHOT transaction. Without it, every read fails (error 3952).
This setting applies to the whole database:
  • the statement waits for every open transaction in the database to finish: run it in a quiet window;
  • from then on, UPDATE and DELETE keep row versions in tempdb while a snapshot transaction may need them, and add 14 bytes to each updated row. Size tempdb accordingly.

3. Enable Change Tracking on each table

Every captured table needs a primary key.
TRACK_COLUMNS_UPDATED is not needed: the connector reads the whole row.

Permissions

Nothing else: no db_owner, no sysadmin, no write access. The connector never writes to the source.
On Amazon RDS, the master user may be refused VIEW SERVER PERFORMANCE STATE on 2022+. Grant VIEW SERVER STATE instead. The commit-time right is optional: without it the connector still runs.

Checking the prerequisites

Test Credentials checks the database prerequisites and lists each one that is not met, with the statement that fixes it. The table picker then greys out each table missing a prerequisite of its own (primary key, Change Tracking, SELECT, VIEW CHANGE TRACKING).

Configuration

Keep Enable Initial Load on. No backfill job covers this source: with it off, streaming starts from the current Change Tracking version and existing rows reach the topics only through an incremental load triggered from the API.

Connecting through an SSH Tunnel

If the database is only reachable through a bastion host, enable SSH Tunnel and provide: The Host and Port above stay as the database is addressed from the bastion. For key generation, bastion hardening and the other connectivity options (IPsec VPN, Tailscale), see Tunnels and private connectivity.

Initial load

The initial load runs in chunks of 10,000 rows, read in primary-key order, while the table already streams. Each chunk is its own short snapshot transaction, so tempdb and Change Tracking cleanup are held for one chunk, not for the whole table, and the other tables keep streaming meanwhile. The same chunked read runs for a table added to the whitelist later. Snapshot rows carry op = r. A table whose primary key contains a datetimeoffset, sql_variant or hierarchyid column cannot be read in chunks. It is read in one snapshot transaction before streaming starts: size tempdb for it or start the connector in a quiet window.

Load on the source

  • Every write to a captured table adds one row to that table’s internal change table, a cost Microsoft compares to maintaining one more index.
  • Snapshot isolation adds the tempdb version store and 14 bytes per updated row, database-wide.
  • Each poll runs one CHANGETABLE query per captured table, then reads the changed rows by primary key. Its cost follows the number of rows changed since the last poll, not the size of the table. A backlog is read in pages, so catching up after a pause does not slow down as it drains.

Retention gap

A connector stopped longer than the Change Tracking retention can no longer read what changed. It stops with an error naming the affected tables, without a restart loop: nothing is lost silently. A TRUNCATE TABLE on a captured table has the same effect. To recover, reload the affected tables with an incremental load through the API.
A reload sends rows as they are now. A row deleted during the gap, or emptied by a TRUNCATE, is not sent as a delete, and a target that held it keeps it. Empty the target table first when it must match the source exactly.

Reloading tables

The UI offers no reload for this source. Trigger an incremental load through the API: the selected tables are read again in chunks while the change stream keeps flowing, and every subscriber of the connector’s topics receives the rows.
Omit the body to reload every table of the connector. A call made while a load is already running is rejected with 409; follow the running load with GET /connectors/{connector_id}/incremental-load-status.

Housekeeping

Check Change Tracking

Raise the retention

Raising the retention takes effect without restarting the connector:

Disable Change Tracking

Stop or delete the connector first. Disable it on every table before the database: