SyncJob Guía de bolsilloPocket Guide
GitHub

Mueve las filas sin arruinar el destino Move the rows without wrecking the destination

Todos los comandos de SyncJob.exe en una página. Las filas aterrizan primero en una tabla de staging y solo se publican cuando el conteo pasa el umbral.

Every SyncJob.exe command on one page. Rows land in a stage table first and are published only when the row count clears the threshold.

SELECTSqlBulkCopySTAGE MinRowThresholdsp_renameFINAL
Windows · .NET 9 · SQL Server 2016+. El modo servicio y el cifrado DPAPI de contraseñas requieren Windows. Windows · .NET 9 · SQL Server 2016+. Service mode and DPAPI password encryption require Windows.
01

InstalaciónInstall

Tres caminos: binario autocontenido, herramienta de .NET, o compilar desde el código.

Three ways in: a self-contained binary, a .NET tool, or a build from source.

Binario o herramienta .NETBinary or .NET tool

El release trae syncjob-win-x64.zip: un solo ejecutable, sin runtime que instalar.

The release ships syncjob-win-x64.zip: a single executable, no runtime to install.

# binario del releaserelease binary
.\SyncJob.exe --version

# o como herramienta globalor as a global tool
dotnet tool install -g PeopleWorks.SyncJob.Cli
syncjob --version

Último release ↗ · NuGet ↗

Latest release ↗ · NuGet ↗

Compilar desde el códigoBuild from source

git clone https://github.com/peopleworks/syncjob.git
cd syncjob
dotnet publish src/SyncJob.Cli/SyncJob.Cli.csproj `
  -c Release -r win-x64 `
  --self-contained true `
  -p:PublishSingleFile=true `
  -p:IncludeNativeLibrariesForSelfExtract=true `
  -o ./publish
IncludeNativeLibrariesForSelfExtract no es opcional. Sin él, el archivo único deja fuera Microsoft.Data.SqlClient.SNI.dll y e_sqlite3.dll, y el ejecutable lanza DllNotFoundException al abrir la primera conexión. IncludeNativeLibrariesForSelfExtract is not optional. Without it the single file leaves out Microsoft.Data.SqlClient.SNI.dll and e_sqlite3.dll, and the executable throws DllNotFoundException the moment it opens a connection.
02

Inicio rápidoQuick start

Un archivo JSON describe el origen, el destino y las opciones. Se valida antes de escribir nada.

One JSON file describes source, destination and options. Validate it before writing a row.

appsettings.json

{
  "SalesSync": {
    "Source": {
      "ConnectionString": "Server=SRC;Database=App;…",
      "Query": "SELECT Id, Name, Amount FROM dbo.Sales"
    },
    "Destination": {
      "ConnectionString": "Server=DW;Database=Rpt;…",
      "StageTable": "dbo.Sales_Stage",
      "FinalTable": "dbo.Sales"
    },
    "Options": {
      "BatchSize": 10000,
      "MaxDegreeOfParallelism": 4,
      "KeepIdentity": true,
      "MinRowThresholdToCommit": 1000
    }
  }
}

Varias secciones en un mismo archivo son normales: -s elige cuál correr.

Several sections in one file is the normal case: -s picks which one runs.

Validar, ensayar, ejecutarValidate, rehearse, run

# 1 · conexiones, esquema y mapeos1 · connections, schema and mappings
SyncJob.exe validate -c appsettings.json -s SalesSync

# 2 · ensayo: no escribe una sola fila2 · rehearsal: not one row is written
SyncJob.exe run -c appsettings.json -s SalesSync --dry-run

# 3 · prueba con pocas filas3 · try it with a few rows
SyncJob.exe run -c appsettings.json -s SalesSync --top 100

# 4 · en serio4 · for real
SyncJob.exe run -c appsettings.json -s SalesSync

¿Treinta columnas a mano? config-init genera la sección desde una lista de campos.

Thirty columns by hand? config-init writes the section from a list of field names.

SyncJob.exe config-init -f fields.txt -o appsettings.json `
  -s SalesSync --stage dbo.Sales_Stage --final dbo.Sales `
  --batch-size 10000 --maxdop 4 --min-commit 1000
03

runopcionesoptions

Todo lo que está en Options se puede sobrescribir en la línea de comandos para una sola corrida, sin tocar el archivo.

Anything under Options can be overridden on the command line for a single run, without touching the file.

OpciónOption Qué haceWhat it does
-c, --config <PATH>Archivo JSON. Default appsettings.jsonJSON file. Defaults to appsettings.json
-s, --section <NAME>Sección dentro del JSON. Default SyncJobSection inside the JSON. Defaults to SyncJob
--allEjecuta todas las secciones, en orden de archivoRuns every section, in file order
--continue-on-errorCon --all: sigue aunque una sección falleWith --all: keep going when a section fails
--dry-runValida sin escribir ninguna filaValidates without writing a single row
--directEscribe directo en Final, sin StageWrites straight to Final, skipping Stage
--appendNo trunca Final antes de cargarDoes not truncate Final before loading
--full-refreshIgnora el tracking incremental y sincroniza todoIgnores incremental tracking and syncs everything
--init-trackingCrea e inicializa la tabla de trackingCreates and initializes the tracking table
--top <N>Lee solo N filas del origen (pruebas)Reads only N rows from the source (testing)
--min-commit <N>Sobrescribe MinRowThresholdToCommitOverrides MinRowThresholdToCommit
--force-commitComitea aunque las filas no lleguen al umbralCommits even when rows fall below the threshold
--skip-commitCarga Stage y no publica en FinalLoads Stage and does not publish to Final
--batch-size <N>Sobrescribe BatchSizeOverrides BatchSize
--maxdop <N>Sobrescribe MaxDegreeOfParallelismOverrides MaxDegreeOfParallelism
--sp <NAME>Usa un stored procedure como origenUses a stored procedure as the source
--sp-param <N=V>Parámetro del stored procedure (repetible)Stored procedure parameter (repeatable)
--log-level <L>Trace · Debug · Info · Warn · Error · Fatal
--log-file <PATH>Archivo de log de esta corridaLog file for this run
--log-dir <PATH>Directorio de logs, con archivo diario automáticoLog directory, auto-named daily file
--json-logLogs en JSONL, para agregadoresJSONL logs, for aggregators
--quietSin salida en consola, solo archivoNo console output, file only
--trust-server-certNo valida la CA del servidor SQL (temporal / dev)Skips SQL Server CA validation (temporary / dev)

Correr todo el archivo de una vezRunning the whole file at once

Un archivo suele tener varias sincronizaciones. Encadenarlas a mano en un script es exactamente donde una se olvida y nadie se entera.

A config file usually holds several syncs. Chaining them by hand in a script is exactly where one gets forgotten and nobody notices.

# todas las secciones, parando en el primer falloevery section, stopping at the first failure
SyncJob.exe run -c appsettings.json --all

# terminar y reportar al final cuáles fallaronfinish, then report which ones failed
SyncJob.exe run -c appsettings.json --all --continue-on-error

Una sección cuenta como sincronizable cuando tiene Source y Destination, así que bloques como ConnectionStrings o Logging se saltan solos.

A section counts as syncable when it has both Source and Destination, so unrelated blocks like ConnectionStrings or Logging are skipped automatically.

Parar en el primer fallo es el default a propósito: si la tabla de hechos no cargó, seguir cargando sus dimensiones deja el warehouse internamente inconsistente, que es más difícil de detectar que una parada limpia. Stopping at the first failure is the default on purpose: if the fact table did not load, continuing to load its dimensions leaves the warehouse internally inconsistent, which is harder to detect than a clean stop.
04

Stage → Final

Las filas se escriben en una tabla de staging antes de que exista una transacción. Publicar es solo renombrar.

Rows are written to a stage table before any transaction opens. Publishing is just a rename.

Diagrama del flujo de SyncJob: el origen SQL Server se lee con un SELECT, un stored procedure o un filtro incremental; las filas viajan por SqlBulkCopy hasta una tabla de staging; MinRowThresholdToCommit decide entre COMMIT y ABORT antes de publicar; el destino queda con la tabla final y un registro de auditoría. Abajo, la comparación entre TRUNCATE + INSERT, que bloquea lectores unos 15 000 ms, y el swap con sp_rename, que no bloquea y toma unos 370 ms.
Las filas ya están escritas cuando abre la transacción, así que el intercambio es una operación de metadatos.
The rows are already written when the transaction opens, so the swap is a metadata operation.

Cómo funciona el intercambioHow the swap works

En un full refresh, SyncJob no trunca la tabla final para copiar filas dentro. Intercambia las dos tablas por nombre:

For a full refresh, SyncJob does not truncate the final table and copy rows into it. It swaps the two tables by name:

Final      Final_swap_a1b2c3d4
Stage      Final        ← ya cargada, ahora publicada← already loaded, now published
temporal   Stage        ← datos viejos, se descartan← old data, discarded

TRUNCATE toma un lock de modificación de esquema que se mantiene hasta el commit, y todo lector se bloquea contra él, incluso con NOLOCK. En una tabla de 122 590 filas eso midió ~15 s de dashboards congelados. El swap midió ~370 ms.

TRUNCATE takes a schema-modification lock held until commit, and every reader blocks against it — even one using NOLOCK. On a 122,590-row table that measured ~15 seconds of blocked dashboards. The rename swap measured ~370 ms.

Cuándo cae al camino lentoWhen it falls back

SyncJob vuelve a TRUNCATE + INSERT — mismo resultado, solo más lento — cuando el swap no aplica:

SyncJob falls back to TRUNCATE + INSERT — identical results, just slower — when the swap does not apply:

view
Final es una vista, no una tablaFinal is a view, not a table
schema
Final y Stage viven en esquemas distintosFinal and Stage live in different schemas
--append
hay que conservar las filas existentesexisting rows must be preserved

El log deja constancia de cuál camino corrió: dest.swap.rename o dest.swap.truncate.

The log records which path ran: dest.swap.rename or dest.swap.truncate.

Los índices viajan con la tabla física, no con el nombre. sp_rename cambia qué tabla responde a qué nombre; índices, constraints y permisos siguen a la tabla física. Si agrega un índice solo a Final, tras el próximo swap ese índice vive en Stage. Cree los índices en ambas. Indexes travel with the physical table, not the name. sp_rename changes which table answers to which name; indexes, constraints and permissions follow the physical table. Add an index to Final only, and after the next swap it lives on Stage. Create indexes on both.
05

Red de seguridadSafety net

Copiar una tabla es fácil. Copiarla cada noche, sin nadie mirando, sin dejar nunca el destino peor que antes, no lo es.

Copying a table is easy. Copying it every night, unattended, without ever leaving the destination in a worse state than before, is not.

MinRowThresholdToCommit

El guardia. Si el origen devuelve menos filas de las esperadas, no se publica nada.

The guard. If the source returns fewer rows than expected, nothing is published.

# umbral en la corrida, sin tocar el archivothreshold for this run only
SyncJob.exe run -c appsettings.json -s SalesSync --min-commit 1000

# sé lo que hago: publica igualI know what I am doing: publish anyway
SyncJob.exe run -c appsettings.json -s SalesSync --force-commit
Un umbral de 0 no protege de nada. Póngalo en un número que signifique algo: es lo que impide que un origen roto vacíe el destino. A threshold of 0 protects nothing. Set it to a number that means something: it is the guard that stops a broken source from wiping the destination.

Ensayar antes de estrenarRehearse before opening night

# conexiones, esquema y mapeos; nada se escribeconnections, schema and mappings; nothing is written
SyncJob.exe validate -c appsettings.json -s SalesSync

# valida contra la tabla Final (modo directo)validate against the Final table (direct mode)
SyncJob.exe validate -c appsettings.json -s SalesSync --direct

# carga Stage y se detiene antes de publicarload Stage and stop before publishing
SyncJob.exe run -c appsettings.json -s SalesSync --skip-commit

# 100 filas para ver la forma de los datos100 rows to see the shape of the data
SyncJob.exe run -c appsettings.json -s SalesSync --top 100

Mapeos de columnasColumn mappings

Son obligatorios cuando los nombres difieren o cuando quiere mover un subconjunto. Cuando coinciden uno a uno, deje la lista vacía y SyncJob la deriva del origen leyendo la metadata con CommandBehavior.SchemaOnly: SQL Server devuelve las columnas sin ejecutar la consulta.

Required when source and destination names differ, or when you want a subset of the columns. When the names match one to one, leave the list out and SyncJob derives it from the source, reading the metadata with CommandBehavior.SchemaOnly: SQL Server returns the column list without executing the query.

"ColumnMappings": [
  { "Source": "Id",       "Dest": "CustomerId" },
  { "Source": "FullName", "Dest": "Name"       }
]

"ColumnMappings": []   # o quitar la propiedad: se deriva solaor omit it entirely: derived automatically

Escribir treinta nombres a mano es exactamente donde se esconde el error tipográfico que aparece cuando los datos salen corridos una columna.

Hand-writing thirty column names is exactly where a typo hides until the data comes out shifted by one.

06

Sincronización incrementalIncremental sync

Solo las filas que cambiaron desde la última corrida. SyncJob crea y mantiene dbo.SyncJobTracking en el destino.

Only the rows that changed since the last run. SyncJob creates and maintains dbo.SyncJobTracking in the destination.

ConfiguraciónConfiguration

"Incremental": {
  "Enabled": true,
  "Mode": "RowVersion",
  "TrackingColumn": "RowVer",
  "MergeStrategy": "Upsert",
  "PrimaryKeyColumns": ["Id"],
  "DeleteDetection": "SoftDelete",
  "SoftDeleteColumn": "IsDeleted",
  "SoftDeleteValue": "1"
}
# primera vez: crear la tabla de trackingfirst time: create the tracking table
SyncJob.exe run -c appsettings.json -s SalesSync --init-tracking

# volver a traer todo, ignorando el watermarkpull everything again, ignoring the watermark
SyncJob.exe run -c appsettings.json -s SalesSync --full-refresh

Modos de trackingTracking modes

ModoMode Cómo filtraHow it filters
TimestampWHERE UpdatedAt > @LastSync
RowVersionWHERE RowVer > 0x{last}
ChangeTrackingChange Tracking nativoNative Change Tracking
ChangeDataCaptureCDC, con valores anterioresCDC, including old values
RowVersion es el modo de producción: es monótono y no sufre desfases de reloj entre servidores. RowVersion is the production mode: it is monotonic and immune to clock skew between servers.

Estrategia de mergeMerge strategy

InsertSolo filas nuevasNew rows only
UpsertInserta nuevas + actualiza existentes por PKInsert new + update existing, by primary key
FullInserta + actualiza + eliminaInsert + update + delete

Detección de bajasDelete detection

SoftDeleteColumna bandera igual a un valorFlag column equals a value
AutoDetectEventos de Change Tracking / CDCChange Tracking / CDC events
ComparisonComparación de PK entre origen y destinoPK comparison between source and destination
07

SecretosSecrets

Las cadenas de conexión viven en un archivo, en un servidor. secrets cifra las contraseñas con Windows DPAPI: la llave la gestiona el sistema operativo y nunca está en el archivo ni en el binario.

Connection strings live in a file on a server. secrets encrypts the passwords with Windows DPAPI: the key is managed by the operating system and never lives in the file or the binary.

Ver y cifrarInspect and encrypt

# qué está expuestowhat is exposed
SyncJob.exe secrets status -c appsettings.json

# cifrar cada contraseña del archivoencrypt every password in the file
SyncJob.exe secrets protect -c appsettings.json

# para un servicio: alcance de máquinafor a service: machine scope
SyncJob.exe secrets protect -c appsettings.json --scope machine

Solo se cifra la contraseña. Servidor, base y usuario quedan legibles: durante un incidente hay que poder ver a dónde apunta un job sin descifrar nada, y un diff del archivo tiene que seguir sirviendo.

Only the password is encrypted. Server, database and user stay readable, because during an incident you need to see where a job points without decrypting anything — and a diff of the file has to stay useful.

Marcadores de alcanceScope markers

El valor cifrado lleva su propio alcance, así que descifrar nunca tiene que adivinar.

Each encrypted value carries its own scope, so decryption never has to guess.

MarcaMarker Quién puede descifrarWho can decrypt
enc:u:Solo la cuenta que cifró, en esa máquinaOnly the account that encrypted it, on that machine
enc:m:Cualquier cuenta de esa máquinaAny account on that machine
(none)Texto plano — cualquieraPlain text — anyone
Elegir el alcance no es cosmético. Si cifra desde una sesión interactiva con el alcance user por defecto y el servicio de Windows corre bajo otra cuenta, el servicio no puede leer el archivo. Use --scope machine o cifre estando logueado como la cuenta del servicio. Choosing a scope is not cosmetic. If you encrypt from an interactive session with the default user scope and the Windows Service runs under a different account, the service cannot read the file. Use --scope machine, or encrypt while signed in as the service account.

.plain.bak

protect escribe una copia .plain.bak la primera vez que corre. Ese respaldo tiene las contraseñas en claro: muévalo a un lugar seguro y bórrelo del servidor. Volver a correr protect nunca lo sobrescribe, así que el original no se pierde.

protect writes a .plain.bak copy the first time it runs. That backup holds the passwords in clear text — move it somewhere safe and delete it from the server. Re-running protect never overwrites that backup, so the original is not lost.

SyncJob.exe secrets protect -c appsettings.json --no-backup   # sin copia en clarono clear-text copy

Qué protege DPAPI: copiar el archivo a otra máquina es inútil, la llave no viaja con él. Qué no protege: quien ya ejecuta código como el mismo usuario, en la misma máquina, puede leer el secreto. Ese es el límite del mecanismo, y conviene saberlo en vez de suponerlo.

What DPAPI protects: copying the file to another machine is useless — the key does not travel with it. What it does not protect: anyone already executing code as the same user, on the same machine, can read the secret. That is the boundary of the mechanism, and it is worth knowing rather than assuming.

08

Flujo SQLiteSQLite workflow

La alternativa al JSON: conexiones, configuraciones y mapeos guardados en una base SQLite local, con las contraseñas cifradas con DPAPI.

The alternative to JSON: connections, configs and mappings stored in a local SQLite database, with passwords encrypted using DPAPI.

Montar una sincronizaciónSet up a sync

# 1 · conexiones1 · connections
SyncJob.exe connection add source `
  --server SQL01 --database SourceDB `
  --username etl --password "secret"

SyncJob.exe connection add dest `
  --server SQL02 --database DestDB `
  --username etl --password "secret"

SyncJob.exe connection test source

# 2 · configuración2 · config
SyncJob.exe config create sales `
  --display-name "Sales Sync" `
  --source-conn source --dest-conn dest `
  --source-query "SELECT Id, Name, Amount FROM dbo.Sales" `
  --dest-stage dbo.Sales_Stage --dest-final dbo.Sales

# 3 · mapeos3 · mappings
SyncJob.exe mapping add sales `
  --source Id --dest Id --primary-key
SyncJob.exe mapping add sales `
  --source Name --dest Name

# 4 · ejecutar4 · run it
SyncJob.exe run-db sales --dry-run
SyncJob.exe run-db sales

Todos los subcomandos aceptan --interactive para pedir los valores paso a paso.

Every subcommand accepts --interactive to be walked through the values.

Historial de ejecucionesExecution history

Cada corrida se guarda sola: inicio y fin, duración, filas leídas, insertadas, actualizadas, eliminadas y fallidas, detalle del error, máquina y ruta del log.

Every run is stored automatically: start and end time, duration, rows read, inserted, updated, deleted and failed, error detail, host machine and log path.

SyncJob.exe history list
SyncJob.exe history list sales --status Failed --limit 20
SyncJob.exe history show <execution-id>
SyncJob.exe history stats --days 30
SyncJob.exe history clear --older-than 90

La base localThe local database

SyncJob.exe db info
SyncJob.exe db backup --output C:\Backup\syncjob.db
SyncJob.exe db restore --file C:\Backup\syncjob.db
SyncJob.exe db cleanup --older-than 90
SyncJob.exe db vacuum
09

AutomatizaciónAutomation

El mismo binario corre como tarea programada, como servicio de Windows, o reportando a un hub central.

The same binary runs as a scheduled task, as a Windows Service, or reporting into a central hub.

Task Scheduler

SyncJob.exe run `
  -c C:\SyncJob\appsettings.json -s SalesSync `
  --min-commit 1000 `
  --log-file C:\Logs\sync.sales.log `
  --log-level Info --json-log --quiet

JSONL es compatible con Loki, Splunk y Azure Monitor. Los logs registran progreso, conteos y errores, nunca contraseñas.

JSONL is compatible with Loki, Splunk and Azure Monitor. Logs record progress, counts and errors, never passwords.

# archivo diario automático en un directorioauto-dated daily file in a directory
SyncJob.exe run -c config.json -s SalesSync `
  --log-level Debug --log-dir C:\Logs

Servicio de WindowsWindows Service

Sin argumentos, el binario arranca en modo agente. Con run o run-db, en modo CLI.

With no arguments the binary starts in agent mode. With run or run-db, in CLI mode.

New-Service -Name "PeopleWorks SyncJob" `
  -BinaryPathName "C:\SyncJob\SyncJob.exe" `
  -StartupType Automatic `
  -DisplayName "PeopleWorks SyncJob Service"

Start-Service "PeopleWorks SyncJob"

El servicio se registra en el Visor de eventos con el origen "PeopleWorks SyncJob" y consulta tareas cada 30 segundos.

The service registers in the Windows Event Log under source name "PeopleWorks SyncJob" and polls for tasks every 30 seconds.

Hub centralCentral hub

Agrega el historial de varias máquinas en una sola base SQL Server. Tras central enable, cada run-db exitoso empuja su registro a ExecutionHistory_Central. Si el envío falla, la sincronización igual tuvo éxito: el push es fire-and-forget.

Aggregates execution history from several machines into one SQL Server database. After central enable, every successful run-db pushes its record to ExecutionHistory_Central. If the push fails the sync still succeeded — central sync is fire-and-forget.

SyncJob.exe central setup     # asistente interactivointeractive wizard
SyncJob.exe central test      # verificar la conexiónverify the connection
SyncJob.exe central enable    # push automático tras cada corridaauto-push after every run
SyncJob.exe central status    # configuración actualcurrent configuration
SyncJob.exe central sync      # empujar el historial pendientepush pending history
SyncJob.exe central disable

En modo servicio, el servidor central además puede despachar tareas a los agentes conectados a través de la tabla SyncTasks.

In service mode the central server can also dispatch sync tasks to connected agents through the SyncTasks table.

Antes de la primera corrida en producciónBefore the first production run

validateCorrerlo en toda configuración nuevaRun it on every new config
--min-commitUn valor con sentido, nunca 0A meaningful value, never 0
secrets protectNinguna contraseña en claro en el servidorNo password sitting in clear text on the server
.plain.bakSacarlo del servidor tras cifrarMove it off the server after encrypting
--scope machineSi un servicio va a leer el archivoIf a Windows Service will read the file
--top 100Probar tablas grandes antes de la carga completaTest large tables before the first full load
--init-trackingInicializar el tracking antes del incrementalInitialize tracking before the first incremental run
indexesCrear en Stage los índices que tenga FinalCreate every Final index on Stage as well
grantsPermisos de escritura y bulk insert en el destinoBulk insert and write permissions on the destination
10

ChuletaCheat sheet

Todos los comandos. Los de la ruta JSON no necesitan base de datos; los de la ruta SQLite guardan el estado en syncjob.db.

Every command. The JSON path needs no database; the SQLite path keeps its state in syncjob.db.

ComandoCommand RutaPath Qué haceWhat it does
SyncJob.exe --versionVersiónVersion
SyncJob.exe --helpAyudaHelp
examplesEjemplos de usoUsage examples
run -c … -s …JSONEjecuta una sincronizaciónExecutes a sync
run -c … --allJSONEjecuta todas las secciones del archivoExecutes every section in the file
validate -c … -s …JSONValida conexiones, esquema y mapeosValidates connections, schema and mappings
config-init -f … -o …JSONGenera la sección desde una lista de camposWrites a section from a list of field names
secrets status -c …JSONMuestra qué contraseñas están expuestasShows which passwords are exposed
secrets protect -c …JSONCifra las contraseñas con DPAPIEncrypts the passwords with DPAPI
connection add|list|test|deleteSQLiteConexiones a SQL ServerSQL Server connections
config create|list|show|deleteSQLiteConfiguraciones de sincronizaciónSync configurations
mapping add|list|remove|clearSQLiteMapeos de columnas por configuraciónColumn mappings per config
run-db <CONFIG_ID>SQLiteEjecuta desde la configuración guardadaExecutes from the stored config
history list|show|stats|clearSQLiteHistorial de ejecucionesExecution history
db info|backup|restore|cleanup|vacuumSQLiteAdministra la base localManages the local database
central setup|test|status|syncSQLiteHub central de sincronizaciónCentral sync hub
central enable|disable|resetSQLiteActiva o reinicia el push automáticoTurns the auto-push on, off, or resets it
SyncJob.exe (sin argumentos / no args)Arranca como servicio de WindowsStarts as a Windows Service

Herramienta hermana — SQLDiffCompanion tool — SQLDiff

SyncJob mueve datos. SQLDiff mueve estructura. Y se pasan trabajo: primero la forma, después las filas.

SyncJob moves data. SQLDiff moves structure. And they hand work to each other: structure first, then data.

# el drift sale con código 2 cuando las bases divergendrift exits with code 2 when the databases diverge
SQLDiff.exe drift --source "…" --target "…" || exit 1
SyncJob.exe run -c appsettings.json --all

Una sincronización hacia un destino cuya forma cambió falla ruidosamente o, peor, tiene éxito hacia las columnas equivocadas. Verificar la forma cuesta segundos.

A sync into a destination whose shape has drifted either fails loudly or, worse, succeeds into the wrong columns. Checking the shape costs seconds.