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
JobExecutionrecord 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 pastDataRetentionTtl, 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 (orFull, 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. targetArchivedClusterIdcan'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.