Browse documentationOpen

byrcsc/laravel-data-sync · 1.x

File identity and runs.

How the checksum ledger decides what has already been processed, what a run records, and the full path a file takes from discovery to archive.

Two records carry the audit trail: a file in the sync_files ledger, and a run in sync_runs for each attempt at that file.

A file is its contents

Discovery computes the SHA-256 checksum of every candidate file and keys the ledger on (definition, checksum). That has three consequences worth internalizing:

  • Renaming changes nothing. products-final.csv and products.csv with identical bytes are the same file.
  • One changed byte makes a new file. A supplier that re-exports the same data with a new timestamp column produces a new checksum and a new run.
  • The same file can belong to two definitions. The ledger is per definition, so two syncs reading the same directory each track it separately.

sync_files also records the path, filename, size, mtime, first-seen time, and last-processed time, but none of those decide identity.

File statusMeaning
pendingDiscovered and staged, not yet processed
processingA run is in flight
completedA run finished; the checksum is now closed
failedThe last attempt failed; it is retried freely

Only a completed checksum blocks reprocessing. A failed file is picked up again on the next sync:run without --force.

The ledger is never pruned

sync:prune removes runs, failed rows, archived files, and preserved failed files. It never removes ledger rows, because a deleted checksum is an old file that can be imported a second time. Ledger growth is one narrow row per file per definition.

A run is one attempt

Every processing attempt creates a run — including forced reprocessing and every retry — so history shows each attempt rather than overwriting the last.

ColumnWhat it carries
ulidThe public identifier used by sync:status and sync:retry
definitionThe registered name
statusSee below
write_pathbulk, eloquent, or null for a transfer
dry_runWhether the run wrote anything
batch_idThe queued batch, when the run fanned out
rows_totalrows_failedCounters
unmapped_columnsHeader columns no field mapped
error_summaryThe failure message, or a JSON histogram of row errors
warningsNon-fatal problems, such as a failed source cleanup
archive_pathThe archived copy, or the preserved failed copy
Run statusMeaning
pendingRecorded, waiting on a worker
runningIn progress
completedEvery row written
completed_with_errorsFinished; some rows failed and were recorded
failedThe run did not finish
cancelledThe queued batch was cancelled

The last four are terminal. sync:retry only accepts a terminal run.

The path a file takes

  1. Discovery takes a cache lock per definition and lists the source directory. Files younger than minFileAge() are deferred. Files whose path, size, and modification time match an already-completed ledger row are skipped without being read.
  2. Staging streams each remaining file to the staging disk under a temporary name, checksums it, and moves it to <staging path>/<definition>/<checksum>-<filename>. If that checksum has already completed, the staged copy is deleted and the file is counted as skipped.
  3. A run is recorded for each file that survives, with its write path resolved from the definition.
  4. Processing reads from staging — never from the source. Queued runs plan the file first, then fan chunks out through Bus::batch(); --now and atomic() runs read and write in one process. See running a sync.
  5. Finalizing archives the staged file to the archive disk and verifies the copy's checksum. A mismatch deletes the copy and fails the run. Only after a verified archive does the configured source action run.
  6. Source cleanup applies archiveTo(), moveSourceTo(), or deleteFromSource(). A failure here does not fail the run; it is recorded in the run's warnings.

A failed run takes a different exit: the staged file is copied to the failed disk, verified, and removed from staging, and its path is stored on the run so sync:retry can restore it later.

Dry runs

A dry run records a run and reports counts, then discards the staged file. It does not archive, does not touch the source, and does not mark the checksum processed, so the same file is discovered again on the next run.

Reading the records

Both models are ordinary Eloquent models and can be queried directly:

use ByRcsc\LaravelDataSync\Models\SyncFile;
use ByRcsc\LaravelDataSync\Models\SyncRun;

$run = SyncRun::query()->where('ulid', $ulid)->firstOrFail();

$run->file;                 // the ledger row
$run->failures;             // SyncRowFailure records
$run->status;               // a RunStatus enum
$run->write_path;           // a WritePath enum, or null for transfers

SyncFile::query()
    ->where('definition', 'products')
    ->where('status', 'failed')
    ->get();

Table names come from data-sync.tables, so both models resolve their table at runtime rather than hard-coding it.