This document describes the V3 Blob Delta Jobs project, which builds on the V2 migration patterns to support recurring, delta-style blob loads suitable for direct DB restore.
- Problem: The original V2 migration was designed as a one-off/full migration of large blob tables (e.g.
ReferralAttachment,ClientAttachment). We now need to regularly send delta loads of blob data, at scale, without re-copying everything. - Goal: Provide a resumable, script-driven engine that:
- Runs regularly (e.g. weekly) to capture new and updated blobs.
- Uses metadata
[ModifiedOn]as the reliable signal for change, notLast_Write_Timeon the FileTable. - Produces deltas that can be directly restored as database slices, with consumers applying “latest per blob” semantics.
- Is per-BU aware, reusing the existing
LA_BUmodel.
-
New job DB:
BlobDeltaJobs- Owns all orchestration and run metadata:
TableConfig– per logical table config (source/target/metadata,ModifiedOncolumn, safety buffer, flags).HighWatermark– per table last processedModifiedOn, plus anIsRunninglease.Run/RunStep– run headers and per-step/batch progress.MissingParentsQueue– queue of parent blobs to back-fill.StepScript/QueuePopulationScript– script templates (Roots / MissingParents / Children) with placeholders.DeletionLog– optional log for rare deletions.
- References the existing blob/FileTable DB (
Gwent_LA_FileTable) and metadata DB (AdvancedRBS_MetaData) via three-part names.
- Owns all orchestration and run metadata:
-
Change detection via
[ModifiedOn]- Each run computes a time window per table:
WindowEnd = RunStart - SafetyBufferMinutesWindowStart = (LastHighWater - SafetyBufferMinutes)or an initial baseline if no high-watermark exists yet.
- The window is applied to the metadata table’s
[ModifiedOn]column, not the FileTable’sLast_Write_Time. - A configurable safety buffer (default 4 hours) reduces the risk of missing records due to late writes, scheduling jitter, or minor clock issues. Records may appear in multiple deltas; consumers should pick the latest by
ModifiedOn.
- Each run computes a time window per table:
-
Three-step pattern per table (reusing V2 concepts)
- Step 1 – Roots: insert roots where
parent_path_locator IS NULL, delta-windowed, BU-aware, and not already in the target. - Step 2 – Missing Parents:
- Populate a queue of “missing parents” for the current window, then
- Process the queue in batches using a
#Batchtable.
- Step 3 – Children: insert children where
parent_path_locator IS NOT NULL, delta-windowed, BU-aware, and not already in the target. - Each step is driven by scripts stored in tables, so new tables or logic variations can be added without altering the engine procedure.
- Step 1 – Roots: insert roots where
-
High-watermarks and resumability
- For each table,
HighWatermark.LastHighWaterModifiedOnis advanced toWindowEndonly after all three steps succeed. - Runs are resumable by
RunId:RunSteplogs per-batch status, and queues are keyed by(RunId, TableName, stream_id).
- For each table,
-
Schema script:
03_BlobDeltaJobs_Schema.sql- Creates the
BlobDeltaJobsdatabase and core tables:TableConfigHighWatermarkRunRunStepMissingParentsQueueStepScriptQueuePopulationScriptDeletionLog
- Creates the
-
Seed script:
04_BlobDeltaJobs_Seed_Config.sql- Seeds
TableConfigfor:Gwent_LA_FileTable.dbo.ReferralAttachmentGwent_LA_FileTable.dbo.ClientAttachment
- Initializes matching
HighWatermarkrows. - Populates
StepScriptwith delta-windowed, BU-aware templates:- Step 1
Roots - Step 2
MissingParentsBatch - Step 3
Children
- Step 1
- Populates
QueuePopulationScriptwith a shared delta-aware queue population template.
- Seeds
-
Engine script:
05_BlobDeltaJobs_Engine.sqlusp_BlobDelta_ResolveTableConfig- Helper proc that resolves full table names, metadata column names, and safety buffer for a given
TableName.
- Helper proc that resolves full table names, metadata column names, and safety buffer for a given
usp_BlobDelta_Run- Core engine that:
- Creates/updates a
Runrow. - Selects active tables from
TableConfig(or a single table, if specified). - For each table:
- Computes
WindowStart/WindowEndfromHighWatermark+SafetyBufferMinutes. - Acquires a simple lease (
IsRunning,RunLeaseExpiresAt) to prevent overlapping runs. - Executes Steps 1–3 using
StepScriptandQueuePopulationScript, passing:@BatchSize,@ExcludedStreamId,@WindowStart,@WindowEnd.
- Logs per-batch progress in
RunStep. - Advances
LastHighWaterModifiedOntoWindowEndand clears the lease on success.
- Computes
- On error:
- Logs a
Failedstep row per table. - Clears the lease for that table.
- Marks the overall run as
Failed.
- Logs a
- Creates/updates a
- Core engine that:
usp_BlobDelta_RunOperator- Thin, operator-friendly wrapper that supports:
@Mode = 'AllTables': run deltas for all active tables.@Mode = 'SingleTable': run deltas for a specific@TableName.- Returns the
RunIdfor inspection.
- Thin, operator-friendly wrapper that supports:
- Deploy schema and seed scripts (once per environment):
- Run
03_BlobDeltaJobs_Schema.sql. - Run
04_BlobDeltaJobs_Seed_Config.sql.
- Run
- Review/extend configuration:
- Confirm
BlobDeltaTableConfigrows forReferralAttachment/ClientAttachment:- Source/target/metadata DB/schema/table names.
MetadataModifiedOnCol(typicallyModifiedOn).SafetyBufferMinutes(default 240).
- Add additional tables by inserting rows into
BlobDeltaTableConfigand referencing the existing script templates (or new ones, if needed).
- Confirm
- Deploy engine:
- Run
05_BlobDeltaJobs_Engine.sql.
- Run
Typical SQL Agent job step (T-SQL) to run deltas for all active tables (all BUs):
USE BlobDeltaJobs;
GO
DECLARE @RunId uniqueidentifier;
EXEC dbo.usp_BlobDelta_RunOperator
@Mode = N'AllTables',
@TableName = NULL,
@BatchSize = 500,
@MaxDOP = 2;- This will:
- Create a new
BlobDeltaRunrow. - Process all active tables sequentially using the configured windows.
- Record per-batch progress into
BlobDeltaRunStep. - Advance high-watermarks on success.
- Create a new
USE BlobDeltaJobs;
GO
DECLARE @RunId uniqueidentifier;
EXEC dbo.usp_BlobDelta_RunOperator
@Mode = N'SingleTable',
@TableName = N'Gwent_LA_FileTable.dbo.ReferralAttachment',
@BatchSize = 500,
@MaxDOP = 2;- Use this when:
- Testing changes for one table.
- Catching up a specific table without touching others.
- Find recent runs
SELECT TOP (50)
RunId,
RunType,
RequestedBy,
RunStartedAt,
RunCompletedAt,
Status,
ErrorMessage
FROM dbo.BlobDeltaRun
ORDER BY RunStartedAt DESC;- Inspect per-table / per-step progress for a run
SELECT
TableName,
StepNumber,
BatchNumber,
RowsProcessed,
TotalRowsProcessed,
WindowStart,
WindowEnd,
BatchStartedAt,
BatchCompletedAt,
Status,
ErrorMessage
FROM dbo.BlobDeltaRunStep
WHERE RunId = @RunId
ORDER BY TableName, StepNumber, BatchNumber;- Check high-watermarks
SELECT
h.TableName,
h.LastHighWaterModifiedOn,
h.LastRunId,
h.LastRunCompletedAt,
h.IsInitialFullLoadDone,
h.IsRunning,
h.RunLeaseExpiresAt
FROM dbo.BlobDeltaHighWatermark h
ORDER BY h.TableName;- Investigate missing parents
SELECT
RunId,
TableName,
stream_id,
BusinessUnit,
Processed,
CreatedAt
FROM dbo.BlobDeltaMissingParentsQueue
WHERE RunId = @RunId
ORDER BY TableName, CreatedAt;-
Updates vs duplicates
- By design, frequently updated records may appear in multiple delta runs, due to the safety buffer overlapping windows.
- This is acceptable because:
- Consumers are expected to treat deltas as “latest by ModifiedOn” snapshots per blob ID.
- The primary guarantee is no missed changes, not strict uniqueness across deltas.
-
Deletions
- Deletions are currently very rare, so the first implementation:
- Provides
BlobDeltaDeletionLogas a place to record deletions if/when there is a reliable source. - Does not yet integrate deletes into the main delta pipeline.
- Provides
- This keeps the design simpler while leaving room for future evolution when downstream delete requirements are clearer.
- Deletions are currently very rare, so the first implementation:
-
Clock and safety buffer
- Job, metadata, and blob DBs are on the same SQL Server instance, so
[ModifiedOn]timestamps are broadly aligned. - The safety buffer (default 4 hours) provides a “belt and braces” guard against:
- Slight clock discrepancies.
- Delayed writes.
- Scheduling jitter.
- Job, metadata, and blob DBs are on the same SQL Server instance, so
-
Adding a new table to deltas
- Add a row to
BlobDeltaTableConfigwith:- Correct source/target/metadata DB/schema/table names.
MetadataIdColumnandMetadataModifiedOnCol.SafetyBufferMinutesand flags.
- Optionally seed a specific
BlobDeltaQueuePopulationScriptrow if the standard template does not fit.
- Add a row to
-
Changing safety buffer or windows
- Update
SafetyBufferMinutesper table inBlobDeltaTableConfig. - If needed, adjust the initial baseline logic in
usp_BlobDelta_Run(forWindowStartwhen there is no high-watermark).
- Update
-
Incorporating deletions
- Once a reliable delete signal exists (e.g. status flag, audit table, trigger):
- Populate
BlobDeltaDeletionLogas part of the existing jobs, or - Extend the engine to write delete events into a dedicated delta stream for consumers.
- Populate
- Once a reliable delete signal exists (e.g. status flag, audit table, trigger):
This design aims to be scalable for large blob volumes, safe against missed changes, and maintainable by keeping the orchestration logic in a dedicated DB, driven primarily by configuration and script templates rather than hard-coded SQL per table.