diff --git a/src/Trax.Effect.Data.Postgres/Migrations/030_schedule_variance.sql b/src/Trax.Effect.Data.Postgres/Migrations/030_schedule_variance.sql
new file mode 100644
index 0000000..3c57c44
--- /dev/null
+++ b/src/Trax.Effect.Data.Postgres/Migrations/030_schedule_variance.sql
@@ -0,0 +1,2 @@
+ALTER TABLE trax.manifest ADD COLUMN IF NOT EXISTS variance_seconds integer;
+ALTER TABLE trax.manifest ADD COLUMN IF NOT EXISTS next_scheduled_run timestamp without time zone;
diff --git a/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs b/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs
index b7f84b3..1cc7539 100644
--- a/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs
+++ b/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs
@@ -81,5 +81,11 @@ public class CreateManifest
///
public string? Exclusions { get; set; }
+ ///
+ /// Maximum random delay in seconds added to each scheduled run.
+ /// Null means no variance (deterministic scheduling).
+ ///
+ public int? VarianceSeconds { get; set; }
+
#endregion
}
diff --git a/src/Trax.Effect/Models/Manifest/Manifest.cs b/src/Trax.Effect/Models/Manifest/Manifest.cs
index 663c98c..b220cc8 100644
--- a/src/Trax.Effect/Models/Manifest/Manifest.cs
+++ b/src/Trax.Effect/Models/Manifest/Manifest.cs
@@ -196,6 +196,32 @@ public class Manifest : IModel
[Column("exclusions")]
public string? Exclusions { get; set; }
+ ///
+ /// Gets or sets the maximum random delay in seconds added to each scheduled run.
+ ///
+ ///
+ /// When set, after each successful execution the scheduler computes the next run time
+ /// as baseSchedule + Random(0, VarianceSeconds) and stores it in
+ /// . This prevents thundering-herd problems and makes
+ /// scraping patterns less detectable. Only applies to Cron and Interval schedule types.
+ /// Null means no variance (deterministic scheduling).
+ ///
+ [Column("variance_seconds")]
+ public int? VarianceSeconds { get; set; }
+
+ ///
+ /// Gets or sets the pre-computed next execution time including any applied variance.
+ ///
+ ///
+ /// Set automatically by the scheduler after each successful run when
+ /// is configured. The ManifestManager uses this value
+ /// instead of computing the next run time on-the-fly, ensuring deterministic behavior
+ /// across polling cycles. Null means the scheduler computes the next run time from
+ /// and the schedule definition.
+ ///
+ [Column("next_scheduled_run")]
+ public DateTime? NextScheduledRun { get; set; }
+
#endregion
#endregion
@@ -288,6 +314,7 @@ public static Manifest Create(CreateManifest manifest)
MisfireThresholdSeconds = manifest.MisfireThresholdSeconds,
ScheduledAt = manifest.ScheduledAt,
Exclusions = manifest.Exclusions,
+ VarianceSeconds = manifest.VarianceSeconds,
};
if (manifest.Properties != null)