This doc explains the design of the data_set_creation job: why it exists, how dealbot schedules it, how each run decides what to do, and how it interacts with on-chain dataset state.
data_set_creationwas originally added so dealbot could maintain enough datasets per provider for data-retention sampling and FWSS approval evaluation.- It now also serves as the repair path for terminated datasets that are still resolved by synapse-sdk in data storage checks but are no longer live.
- Operationally, it ensures each active storage provider has at least
MIN_NUM_DATASETS_FOR_CHECKSlive datasets available for checks. - The scheduler creates one
job_schedule_staterow per<sp_address, data_set_creation>whenMIN_NUM_DATASETS_FOR_CHECKS >= 1. Each run examines dataset slots for a provider in numerical order (0throughMIN_NUM_DATASETS_FOR_CHECKS - 1). A slot represents one required on-chain dataset for unique data set metadata. Only one non-live slot is processed per invocation. - Missing datasets are provisioned by uploading a minimal seed piece.
- Terminated datasets are repaired first; replacement is deferred to a later run (the handler exits after repair; the next scheduled tick sees the slot as
missingand provisions the replacement). - The job runs on the shared
sp.workqueue with the same per-SP singleton behavior asdeal,retrieval,piece_cleanup, andpull_check.
This job was originally added so dealbot could maintain enough datasets per provider for data_retention to accumulate enough samples to evaluate FWSS approval criteria. In practice, raising MIN_NUM_DATASETS_FOR_CHECKS increases the number of datasets per provider, which increases on-chain proof samples for storage proving check.
That original motivation is different from the job's current operational role. Today, data_set_creation is also the repair path for terminated datasets that still resolve via metadata in createContext(...) but are no longer usable because they are suffering unrecoverable proving failures on the SP side.
Those terminated datasets are often first surfaced by the deal job. When that happens, deal does not repair them inline; it defers repair to data_set_creation, which then reconciles the slot incrementally. See Terminated dataset repair flow for details.
This job remains intentionally separate from deal:
dealfocuses on running a data-storage check and may detect a terminated dataset.data_set_creationfocuses on maintaining dataset inventory over time and repairing terminated slots.- Keeping the repair path separate avoids mixing normal check execution with dataset lifecycle recovery.
The scheduler creates per-provider data_set_creation schedules in the same loop that upserts deal and retrieval schedules.
- Schedule creation is gated by
MIN_NUM_DATASETS_FOR_CHECKS >= 1. - The interval is derived from
DATASET_CREATIONS_PER_SP_PER_HOUR. - The initial
next_run_atuses the same phase offset logic as other SP jobs. - Enqueued jobs use payload
{ jobType: 'data_set_creation', spAddress, intervalSeconds }. - Jobs go to the shared
sp.workpg-boss queue. - The queue send path assigns
singletonKey=spAddress, so only one SP-scoped job can be active for a provider at a time across all workers.
That singleton behavior is important because data_set_creation mutates provider dataset state and should not race with the provider's other scheduled jobs.
Before the handler does any provisioning work, it applies the same operational guards used by other SP-scoped jobs:
- Maintenance windows: if a maintenance window is active, the job is deferred instead of running.
- SP blocklists: if the provider is blocked by configured blocklists, the job logs a skip and exits.
- Timeouts: the handler runs under an
AbortControllertimeout based onDATA_SET_CREATION_JOB_TIMEOUT_SECONDS, with an effective floor of 120 seconds.
The job treats required datasets as numbered slots from 0 to MIN_NUM_DATASETS_FOR_CHECKS - 1.
Slots are defined by unique combinations of dataset metadata. Dataset metadata consists of base metadata plus slot-specific metadata. Each distinct value of dealbotDS (from no value through MIN_NUM_DATASETS_FOR_CHECKS - 1) defines a different slot.
For each slot, it computes that deterministic metadata:
- Base metadata comes from
DealService.getBaseDataSetMetadata(). - Base metadata always includes the IPNI metadata key (
withIPFSIndexing=""). - If
DEALBOT_DATASET_VERSIONis configured, base metadata also includesdealbotDataSetVersion. - Slot
0uses only the base metadata. - Slots
1+adddealbotDS: String(index).
This makes each slot addressable and idempotent: repeated runs look up the same logical dataset slot using the same metadata.
For one provider, one invocation of data_set_creation works like this:
- Resolve the provider context and verify the provider is runnable (not on the SP blocklist).
- Read
MIN_NUM_DATASETS_FOR_CHECKSand the base dataset metadata. - Iterate slots from
0upward. - For each slot, call
DealService.getDataSetProvisioningStatus(spAddress, metadata, signal). - If the slot is
live, continue to the next slot. - If the slot is
terminated, repair it and stop for this tick. - If the slot is
missing, create it and stop for this tick. - If every slot is
live, log completion and exit.
The key design choice is incremental provisioning: one run repairs or creates at most one slot. That keeps runtime bounded and spreads background load across scheduler ticks instead of attempting a full reconciliation in one execution.
DealService.getDataSetProvisioningStatus() classifies a slot as missing, live, or terminated.
The lookup flow is:
- Resolve provider info from the wallet/registry layer.
- Call Synapse
storage.createContext({ providerId, metadata }). - If no
dataSetIdis present in the context, the slot ismissing. - If a
dataSetIdis present, probe liveness throughDatasetLivenessService. - Return
liveif the probes succeed, otherwise returnterminated.
terminated means the dataset identifier still resolves from metadata, but liveness checks say the dataset is no longer usable.
When the first missing slot is found, the job provisions exactly one dataset by calling DealService.createDataSetWithPiece().
That method:
- Resolves the provider from the registry.
- Creates the dataset using the same
createContext + executeUploadpath used by data-storage checks. - Uploads a minimal seed piece so the dataset is non-empty.
- Does not persist a
Dealrow. - Does not emit data-storage-check success or failure metrics.
- Does not perform retrieval checks or IPNI verification steps after upload.
The goal is only to ensure the dataset exists and can later be used by the real checks.
When the first terminated slot is found, the job calls DealService.repairTerminatedDataSet() and then exits without creating a replacement in the same run.
Repair is intentionally idempotent:
- Read the FWSS dataset state.
- If
pdpEndEpochis already non-zero, skip the terminate transaction. - Otherwise, call
terminateServiceon-chain, which setspdpEndEpochto the current epoch plus the lockup period (30 days) and marks the slot as missing. - Wait for the transaction receipt when possible.
- Poll FWSS until
pdpEndEpoch != 0. - Mark existing
Dealrows for thatdataSetIdascleanedUp=truein one transaction.
After that repair completes, the next scheduled run will see the slot as missing and provision a replacement dataset.
The slot appears as missing (not terminated) on the next run because the Synapse SDK automatically filters out datasets with pdpEndEpoch !== 0. Once step 5 confirms pdpEndEpoch !== 0 on-chain, getDataSetProvisioningStatus returns missing for that slot.
This two-step approach avoids mixing termination cleanup and replacement provisioning into one long, failure-prone handler execution.
dealdepends on these datasets being available.- If a
dealjob hits a terminated dataset, it logs that the dataset is PDP-terminated and waits fordata_set_creationrepair. - Because all SP-scoped jobs share the same singleton queue key,
data_set_creationcannot overlap with the same provider'sdeal,retrieval,piece_cleanup, orpull_checkjob.
The main controls for this job are:
MIN_NUM_DATASETS_FOR_CHECKS: required number of live dataset slots per provider.DATASET_CREATIONS_PER_SP_PER_HOUR: scheduling rate for reconciliation runs.DATA_SET_CREATION_JOB_TIMEOUT_SECONDS: job timeout before the abort signal fires.DEALBOT_DATASET_VERSION: optional version tag added to base dataset metadata.USE_ONLY_APPROVED_PROVIDERS: indirectly affects which providers receive schedules.
The job is observable in two layers:
- Structured logs for provisioning, repair, aborts, failures, maintenance deferrals, and completed reconciliation.
- Generic job metrics through the shared Prometheus job counters and duration histogram (
jobs_started_total,jobs_completed_total,job_duration_seconds).
Common log events include:
creating_provisioned_data_setdata_set_provisioning_progressdata_sets_provisioning_completeddataset_terminated_detecteddata_set_repair_completeddata_set_creation_job_aborteddata_set_creation_job_failed
- Scheduler and handler:
apps/backend/src/jobs/jobs.service.ts - Incremental provisioning logic:
apps/backend/src/jobs/data-set-creation.handler.ts - Dataset provisioning and repair:
apps/backend/src/deal/deal.service.ts - Job system overview:
docs/jobs.md