Chapter 18 · Change Tracking, CDC, Service Broker, and Integration Patterns

Change Tracking for Lightweight Synchronization and Version-Based Reads

Choose deliberately among AGs, replication, log shipping, CDC/ETL and application replication from the information and failover contract actually required.

Advanced170–210 minutesChange Tracking synchronization labSQL Server 2025 CU7 · 17.0.4065.4Compatibility 170 · Express/DeveloperSSMS 22.8.2 · Last reviewed August 2026

Learning outcomes

A field-service tablet that reconnects after an hour does not need a full history of every intermediate value. It needs to know which rows changed since the last successful synchronization, retrieve the current row state, and detect when its checkpoint is too old to trust. SQL Server Change Tracking (CT) is designed for that lightweight contract. The crucial discipline is to use its version metadata as a synchronization protocol rather than pretending it is an audit log.

01

Explain database-level and table-level Change Tracking, versions, side metadata, retention, and automatic cleanup.

02

Use CHANGE_TRACKING_CURRENT_VERSION, CHANGETABLE and CHANGE_TRACKING_MIN_VALID_VERSION as a safe synchronization handshake.

03

Distinguish CT change metadata from current base-table values and from CDC before/after history.

04

Design recovery when a client checkpoint falls behind the retention window instead of returning an incomplete delta silently.

05

Account for SQL Server 2025 adaptive shallow cleanup when diagnosing large Change Tracking side tables.

Lab baseline. SQL Server 2025 (17.x), CU7 build 17.0.4065.4; database compatibility level 170 unless the lesson says otherwise. Use Enterprise Developer or Standard Developer for free non-production learning when the feature is edition-gated. Change Tracking and Service Broker can be explored on Express; boxed SQL Server CDC requires Standard/Enterprise capability and SQL Server Agent. SSMS 22.8.2, VS Code + the current MSSQL extension, or current sqlcmd are supported paths. Azure Data Studio is retired.

1. The information contract: “what changed?”, not “what were all the values?”

Change Tracking records lightweight metadata keyed by the primary key of a tracked table. It can tell a synchronizer that a row was inserted, updated, or deleted after a version checkpoint, and it can optionally record which tracked columns participated in an update. It does not preserve a durable sequence of all intermediate row images. For an insert or update, the normal synchronization pattern joins the change metadata back to the base table to fetch the row’s current values. A delete has no current base row, so the change row’s primary-key metadata is what lets a consumer remove its local copy.

The version number is database-scoped. CHANGE_TRACKING_CURRENT_VERSION() returns the current version boundary. CHANGETABLE(CHANGES table, last_sync_version) asks which rows changed after a prior checkpoint. Before using that checkpoint, the client must compare it with CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(...)). If the saved version is older than the minimum valid version, cleanup may already have removed required metadata and the delta is no longer complete.

sql · enable Change Tracking safely for the disposable ServiceHub table
USE master;GOALTER DATABASE ServiceHubLab  SET CHANGE_TRACKING = ON  (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);GOUSE ServiceHubLab;GOIF SCHEMA_ID(N'lab18') IS NULL  EXEC(N'CREATE SCHEMA lab18 AUTHORIZATION dbo;');GODROP TABLE IF EXISTS lab18.SyncItem;GOCREATE TABLE lab18.SyncItem(  item_id       int           NOT NULL PRIMARY KEY,  work_order_id bigint        NOT NULL,  status        varchar(20)   NOT NULL,  note          nvarchar(200) NULL,  modified_at   datetime2(3)  NOT NULL DEFAULT SYSUTCDATETIME());GOALTER TABLE lab18.SyncItem  ENABLE CHANGE_TRACKING  WITH (TRACK_COLUMNS_UPDATED = ON);GO

The database option establishes retention and cleanup policy; the table option opts this particular table into tracking. The primary key is fundamental because the synchronization consumer must identify the row whose state changed. TRACK_COLUMNS_UPDATED adds a bit-mask that can help a client decide whether a particular column participated in an update, but it does not turn CT into a column-value history.

2. Capture a version boundary, change rows, then read a delta

sql · observe a complete synchronization window
USE ServiceHubLab;GODECLARE @last_sync_version bigint = CHANGE_TRACKING_CURRENT_VERSION();INSERT lab18.SyncItem(item_id, work_order_id, status, note)VALUES (101, 1001, 'ASSIGNED', N'Initial dispatch'),       (102, 1002, 'NEW',      N'Awaiting technician');UPDATE lab18.SyncItemSET status = 'ONSITE', modified_at = SYSUTCDATETIME()WHERE item_id = 101;DELETE lab18.SyncItem WHERE item_id = 102;DECLARE @next_sync_version bigint = CHANGE_TRACKING_CURRENT_VERSION();SELECT  CT.SYS_CHANGE_VERSION,  CT.SYS_CHANGE_CREATION_VERSION,  CT.SYS_CHANGE_OPERATION,  CT.SYS_CHANGE_COLUMNS,  CT.item_id,  B.work_order_id,  B.status,  B.note,  B.modified_atFROM CHANGETABLE(CHANGES lab18.SyncItem, @last_sync_version) AS CTLEFT JOIN lab18.SyncItem AS B  ON B.item_id = CT.item_idORDER BY CT.SYS_CHANGE_VERSION, CT.item_id;SELECT @last_sync_version AS last_version,       @next_sync_version AS checkpoint_to_persist;GO

The expected result is one logical change row per key representing the net tracked change since the requested version. Item 101 should be present with its current base-table status ONSITE. Item 102 should appear as a delete; the left join yields no base-row payload because the row is gone. The synchronization transaction should persist @next_sync_version only after the client has durably applied every row in the window.

Do not confuse version order with a business event log. CT is optimized to support synchronization and can collapse multiple changes to the same key. If ServiceHub must know that a work order moved NEW → ASSIGNED → ENROUTE → ONSITE with every intermediate timestamp, use a history/event mechanism such as CDC, temporal design, an audit table, or an application event/outbox according to the required contract.

3. The failure mode that matters: a checkpoint older than retained metadata

A common implementation bug is to store a checkpoint forever and blindly call CHANGETABLE. A client that was offline longer than the configured retention interval can then receive a partial set and incorrectly conclude that it is synchronized. The safe algorithm validates the checkpoint first. When it is stale, perform a full re-snapshot of current data and establish a new version boundary.

sql · reject a stale checkpoint and choose re-synchronization
USE ServiceHubLab;GODECLARE @client_version bigint = -1; -- deliberate stale-client simulationDECLARE @min_valid bigint = CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'lab18.SyncItem'));DECLARE @current bigint = CHANGE_TRACKING_CURRENT_VERSION();SELECT @client_version AS client_version,       @min_valid AS minimum_valid_version,       @current AS current_version;IF @min_valid IS NULL  THROW 51000, 'Change Tracking is not available for the tracked table.', 1;IF @client_version < @min_validBEGIN  SELECT 'FULL_RESYNC_REQUIRED' AS decision,         @current AS new_baseline_version;  -- Application path: read the authoritative table snapshot,  -- commit it at the client, then store @current as the new checkpoint.ENDELSEBEGIN  SELECT *  FROM CHANGETABLE(CHANGES lab18.SyncItem, @client_version) AS CT;END;GO

This intentionally “fails closed”: it refuses to represent an incomplete delta as success. Retention should be chosen from the maximum expected client outage plus operational margin, observed side-table growth, and cleanup performance. Do not extend retention without capacity analysis merely to hide synchronization design defects.

4. SQL Server 2025 cleanup changes and operational evidence

SQL Server 2025 changes Change Tracking automatic cleanup for large side tables. Earlier versions used a deeper periodic cleanup approach; 17.x introduces adaptive shallow cleanup below a safe cleanup point, informed by retention and cleanup depth. The architectural lesson is not to depend on undocumented cleanup timing. Observe configured retention, minimum valid versions, synchronization age, and side-table pressure; design clients around validity checks.

sql · inspect effective Change Tracking state
SELECT name, is_auto_cleanup_on, retention_period, retention_period_units_descFROM sys.change_tracking_databasesWHERE database_id = DB_ID(N'ServiceHubLab');USE ServiceHubLab;SELECT OBJECT_SCHEMA_NAME(object_id) AS schema_name,       OBJECT_NAME(object_id) AS table_name,       is_track_columns_updated_on,       begin_version,       min_valid_versionFROM sys.change_tracking_tablesWHERE object_id = OBJECT_ID(N'lab18.SyncItem');GO

These catalog views establish configuration and table enrollment. They do not prove every client is healthy. Production monitoring needs client checkpoint age, full-resync counts, synchronization duration, row volume, and errors alongside the server metadata.

5. Production judgment and bridge

Change Tracking is a strong fit for occasionally connected clients, cache synchronization, and incremental refresh where the source table remains authoritative. Protect its contract with primary keys, bounded retention, checkpoint durability, minimum-valid-version checks, and a tested full-resync path. Do not sell it as audit evidence or an exactly-once event stream. Lesson 2 moves to CDC, which retains relational change rows and LSN-based windows for ETL-style consumption—but brings more storage, retention, Agent and edition responsibilities.

Check your understanding

  1. Why must a CT client call CHANGE_TRACKING_MIN_VALID_VERSION before requesting changes?
  2. Does SYS_CHANGE_COLUMNS contain old and new values?
  3. Why is a delete handled differently from an insert/update?
  4. What changed operationally in SQL Server 2025 CT cleanup?
  5. When is CT preferable to CDC?
Review the answers

1. Because cleanup can remove metadata older than the retention window. A checkpoint below the minimum can yield an incomplete delta and must trigger a full re-synchronization.

2. No. It is metadata indicating columns that participated in an update when TRACK_COLUMNS_UPDATED is enabled; current row values come from the base table.

3. The base row no longer exists, so the CT row’s key and delete operation tell the consumer what local row to remove.

4. Large side tables can use adaptive shallow cleanup rather than relying only on the older deep-cleanup pattern; clients must still use the validity protocol instead of timing assumptions.

5. When a consumer mainly needs lightweight “which rows changed?” synchronization and does not require every intermediate change or before/after history.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.