Skip to main content

Archiving

DataRetentionTtl (see Cluster Configuration) purges finalized jobs, terminated recurring schedules, and logs once they age past the TTL. By default that purge is a hard delete. If you want that data kept longer term — for audit, compliance, or historical reporting — without bloating the cluster that actually processes jobs, point the purge at a dedicated archive cluster instead.

Enabling archiving​

Pass a targetArchivedClusterId as the second argument to DataRetentionTtl on the source cluster:

config.DataRetentionTtl(TimeSpan.FromDays(30), targetArchivedClusterId: "payroll-archive");

Once a job or recurring schedule ages past the TTL, it's moved to the target cluster instead of being deleted outright.

What gets archived​

Archiving a job carries its full execution history along with it, not just the job row itself:

  • The job's own row (same Id, same final status).
  • Every JobExecution record for that job — one per attempt, with start time, outcome, and any error message.
  • Its structured logs, but only the JobExecution-category ones (the ones tied to a specific attempt, e.g. a failure or a timeout). Every other log category (Job, Bucket, Cluster, AgentWorker, RecurringSchedule, Api) is always hard-deleted once it ages past DataRetentionTtl, regardless of whether archiving is configured — those are operational noise, not something worth preserving long-term.

This means you can look up a job on the archive cluster after the fact and still see exactly what happened on each attempt, not just its final outcome.

Setting up the archive cluster​

The target must be a dedicated cluster, configured with:

  • Mode(ClusterMode.Archived)
  • Only Coordinator-mode workers (or Full, which behaves identically here — no agent connection needed either way)
  • No agent connections at all
// Source cluster — processes jobs normally
builder.Services.AddJobMasterCluster("payroll-cluster", config =>
{
config.UsePostgresForMaster(masterConnectionString);
config.DataRetentionTtl(TimeSpan.FromDays(30), targetArchivedClusterId: "payroll-archive");
config.AddAgentConnectionConfig("agent-1").UsePostgresForAgent(agentConnectionString);
config.AddWorker();
});

// Archive cluster — receives finalized data only, never processes anything itself
builder.Services.AddJobMasterCluster("payroll-archive", config =>
{
config.UsePostgresForMaster(archiveConnectionString);
config.Mode(ClusterMode.Archived);
config.AddWorker(); // Coordinator mode only — no AddAgentConnectionConfig
});

All of this is enforced at startup:

  • The archive cluster can't have buckets, agent connections, or any worker mode other than Coordinator/Full.
  • targetArchivedClusterId can't point at the cluster itself.
  • It must resolve to another configured cluster, and that cluster must actually be Mode(ClusterMode.Archived).
  • An archive cluster refuses to start if it already contains any job or recurring schedule in a non-final status — it may only ever hold finalized data, arriving exclusively through the archive path.

Cascading tiers​

An archive cluster can set its own targetArchivedClusterId, pointing at a further archive cluster — so retention can cascade through multiple tiers, e.g. a 30-day hot archive that itself archives into a 365-day cold archive.

If the archive cluster is unreachable​

If the target can't be reached when a purge runs, the data is deleted directly instead of being lost in limbo — a Critical log is written so this doesn't go unnoticed, since data is being deleted rather than archived.

See: Cluster Configuration · Migrating a Cluster