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)