Large data volumes

Salesforce Incremental Data Sync — SystemModstamp vs LastModifiedDate, Bulk API 2.0 and Record Locking

By Rishabh Panwar · 5 min read · Advanced

The nightly job reports success, yet the data warehouse is missing a few hundred accounts, and a handful of contacts failed with UNABLE_TO_LOCK_ROW. Salesforce incremental data sync looks simple on paper: pull what changed since the last run and push it across. Whether it stays trustworthy depends on three quieter decisions: which timestamp marks a change, what order records load in, and when the job runs. The practices below come from the official Salesforce Integration Patterns guide, with the reasoning behind each.

Data model choices for very large objects are covered in large data volume architecture patterns, and keeping extract queries selective is its own topic in SOQL best practices for large data volumes. A one-time migration has different priorities again; the data migration strategy handles that case.

The sync loop

A dependable incremental sync follows the same cycle every run:

  1. Read the watermark: the timestamp of the last successful run.
  2. Extract only records changed since then.
  3. Match records using primary keys from both systems (a Salesforce ID and an External ID).
  4. Load the changes.
  5. Write the new watermark only after the load finishes successfully.

Keep the watermark in a small tracking table inside the ETL tool. If Salesforce is down or a job fails partway through, the next run still knows where to start.

SystemModstamp vs LastModifiedDate

Both fields look like “last changed” timestamps, but they don’t change for the same reasons.

LastModifiedDateSystemModstamp
Changes when a user or API call edits a fieldYesYes
Changes on some system-driven updatesNoYes
WritableYes, on insert when Set Audit Fields upon Record Creation is enabledNo, strictly read-only
Used by getUpdated() replicationOnly as a fallbackYes, as the primary field
Standard indexNoYes

System-driven updates that can move SystemModstamp without touching LastModifiedDate include:

  • Some workflow and flow field updates
  • Formula fields recalculated because a referenced field on a related record changed
  • Roll-up summary recalculation
  • Sharing recalculation
  • Certain background platform processes

Why the writable field is a trap

When “Set Audit Fields upon Record Creation” is enabled, a migration or data load can insert records with a backdated LastModifiedDate. If your sync filters on LastModifiedDate, those records fall before the watermark and never get extracted. SystemModstamp can’t be backdated, so it doesn’t have this gap.

The counter-argument

On very large objects that use skinny tables, LastModifiedDate is included by default, so some teams argue it is the faster filter there. That is a performance argument only. SystemModstamp is already one of the standard indexed fields, and for correctness it remains the safer default.

SELECT Id, Name, External_Id__c, SystemModstamp
FROM Account
WHERE SystemModstamp > 2026-09-14T02:00:00Z
ORDER BY SystemModstamp

Record locking during loads

Saving a child record briefly locks its parent. That matters as soon as a load runs in parallel.

The failure. Contacts for the same Account are scattered across several batches. Two batches running at the same time both try to lock that Account. One gets the lock, and saves in the other batch fail with UNABLE_TO_LOCK_ROW.

The fix. Sort the file by parent ID before loading, so all children of one parent sit in the same batch. The batches still run in parallel, but they stop competing for the same parent rows.

Bulk API 2.0 always runs in parallel

Bulk API 2.0Bulk API 1.0
Batch processingAlways parallelParallel or serial
ChunkingAutomaticYou size the batches
Failure scopeEach batch succeeds or fails independentlyEach batch succeeds or fails independently

Because Bulk API 2.0 has no serial option, sorting by parent is the main defence against lock contention. Bulk API 1.0 in serial mode is sometimes used as a fallback when sorting isn’t enough, at the cost of a much slower load.

ETL practices from the official guide

  • Extract only what changed. Full-table extracts on every run add load and hide real changes in noise.
  • Match on keys from both systems. Store the other system’s primary key in an External ID field and upsert on it.
  • Keep post-load automation selective. Triggers and flows that fire on every loaded record multiply the cost of each batch. Add bypass logic or entry criteria for integration users where it’s safe.
  • Load outside business hours. A daytime load competes with users for the same records, which brings back the locking problems described above.
  • Keep the watermark outside Salesforce so a failed job can restart cleanly.

Quick reference

ProblemFix
Records missing from the incremental extractFilter on SystemModstamp; check for backdated audit fields
UNABLE_TO_LOCK_ROW on child loadsSort by parent ID before loading
Load slows down or fails during the dayMove the job to off-peak hours
Job fails halfway and restarts from scratchStore the watermark in the ETL tool; write it only after success
Duplicates after a rerunUpsert on an External ID

If this comes up in an interview

If asked why a nightly load fails on some child records but not others, describe parent locking across parallel batches and the sort-by-parent fix. If asked which timestamp to sync on, choose SystemModstamp and give both reasons: it captures system-driven changes, and it can’t be backdated.

Frequently asked questions

Should I use SystemModstamp or LastModifiedDate for incremental sync?

SystemModstamp. It changes on user edits and also on system-driven updates that leave LastModifiedDate untouched, and it is read-only, so a data load can't backdate it. Salesforce's own getUpdated() replication call uses it.

Why do child record loads fail with UNABLE_TO_LOCK_ROW?

Saving a child record locks its parent. When children of the same parent are spread across batches that run in parallel, two batches try to lock the same parent at once and some saves fail. Sort the file by parent ID so each parent's children land in the same batch.

Can Bulk API 2.0 run batches serially?

No. Bulk API 2.0 always processes batches in parallel, and each batch succeeds or fails independently. Bulk API 1.0 offers a serial mode, which is sometimes used to work around locking problems.

Can LastModifiedDate be changed during a data load?

Yes, when the Set Audit Fields upon Record Creation permission is enabled, a load can write a backdated LastModifiedDate on insert. Those records can then be missed by an incremental extract that filters on LastModifiedDate. SystemModstamp can't be written.

Where should an ETL job store its last successful run time?

In the ETL or middleware layer, outside Salesforce. That keeps the watermark readable when Salesforce is unavailable and lets a failed job restart from the right point.