Skip to main content

Recurring jobs for MENA SaaS: missed runs and catch-up

Design recurring work with durable occurrence identities, explicit missed-run policies and controlled recovery for Saudi and UAE deployments.

Updated 9 min readMarkdown
Part of seriesBuilder Guides
Recurring jobs for MENA SaaS: missed runs and catch-up

A nightly maintenance task misses its slot during an outage. Service returns, but nobody has agreed whether the task should catch up or wait until tomorrow. The cron expression is valid; the recovery policy is missing.

For SaaS teams serving MENA customers, recurring work needs a policy for lateness as well as a clock. This guide proposes an occurrence ledger for scheduled maintenance, reminders and periodic processing. It separates the intended run from its delivery attempts, then makes catch-up an explicit decision. It is an architecture proposal, not a measured reliability result.

Cover: original SultanByte artwork showing scheduled occurrences entering a durable ledger before execution.

Write the missed-run policy before the cron expression

Consider a hypothetical product with separate Saudi and UAE customer deployments. A nightly search refresh may need only the newest state after an outage. A period-based reconciliation may need every missing period. A reminder sent after its appointment has passed may be actively unhelpful.

Give each schedule an owner and one of these proposed recovery policies:

  • Skip expired occurrences. Record that a run was omitted and why. Suitable when the work loses its value after a deadline.
  • Coalesce missed occurrences. Perform one current refresh and record which earlier occurrences it supersedes. Suitable only when the newer operation genuinely replaces the older work.
  • Backfill bounded occurrences. Process each missing business period within an approved horizon, with a concurrency limit and an escalation path for older gaps.

These are application choices, not interchangeable scheduler settings. A reminder service and a reconciliation service should not inherit the same recovery behaviour merely because both run at night.

Define the distinction between a start deadline and a completion deadline. Starting before a cutoff does not prove the operation finished while it was still useful. Decide whether an already-running task may continue after expiry and where that rule is enforced.

Choose local-time intent or elapsed cadence

Store the schedule's meaning, not just a provider-specific expression. A daily branch task needs a named zone and local-time rule. An infrastructure poll may need an elapsed interval instead. Display language must not choose either one.

For the proposed deployments, store the relevant branch zone explicitly, such as Asia/Riyadh or Asia/Dubai, and show it beside the schedule in both Arabic and English. Keep the business calendar separate. A weekday expression alone does not describe a customer's holidays or exceptional opening days.

AWS documents distinct rate, cron and one-time schedules. For rate expressions measured in days, a day means a 24-hour duration. Its cron handling skips a nonexistent local time during a forward clock transition and invokes once during a repeated local time. Those are EventBridge Scheduler behaviours, not a universal cron contract.

Google Cloud Scheduler warns about wall-clock anomalies around daylight-saving changes and recommends UTC when the required cadence must avoid them. UTC is useful for infrastructure timing; it does not automatically preserve a customer's requirement to run at a particular local hour.

Before moving a schedule between providers, test its expression and transition behaviour on the target service. Google uses a five-field unix-cron format, while the AWS expression includes a year field and different day-field rules. Do not copy the string unchanged and assume the schedule survived the migration.

Give every intended occurrence a durable identity

A delivery attempt needs to point to the work originally intended, not create a new identity from its arrival time. Otherwise a delayed retry can become tomorrow's task or acquire a fresh deduplication key.

Cloud Scheduler's overview documents at-least-once delivery and possible repeated execution. It also identifies the job name plus X-CloudScheduler-ScheduleTime as useful deduplication inputs: the scheduled time remains constant across retries. Authenticate the request before using its metadata; a header is not proof of a trusted caller.

For this design, assign an occurrence identity within the tenant and logical schedule. Preserve the original due time, schedule revision, business interval and deployment assignment. Retries and operator redrives retain that identity. A manual request to repeat already-completed work is a separate, authorised operation with an explicit relationship to the original.

Use a database uniqueness constraint to arbitrate creation rather than a read-then-insert check. PostgreSQL 18 documents INSERT ... ON CONFLICT for handling unique or exclusion conflicts. In this design, DO NOTHING can prevent a second occurrence row; it does not make an external side effect unique or prove the first run completed.

When an occurrence already exists, inspect its durable state. A failed or stalled occurrence may still need recovery. Treating every duplicate as “already done” can permanently hide unfinished work.

Keep deduplication records long enough to cover the permitted retry and redrive horizon. Deleting the ledger early can make an old message look new. If detailed history must expire, retain the minimum record needed to reject obsolete delivery, with an explicit retention policy.

Do not mistake concurrency control for completion

Kubernetes' CronJob documentation says scheduling is approximate: duplicate Jobs or missing Jobs are possible, so the work should be idempotent. Its Forbid concurrency policy applies only to Jobs belonging to the same CronJob. It is not a global lock across another CronJob, a manual replay or a second cluster.

The same documentation distinguishes suspension from stopping work already started. It also explains that changes to a CronJob affect newly created Jobs, not existing ones. An operator's “disabled” or “updated” screen therefore needs a precise meaning.

For the proposed service, keep separate controls for admitting future occurrences, dispatching recorded work and cancelling an active attempt. Disabling future scheduling should not falsely report that an external action already underway has stopped.

Let a worker claim an occurrence through a short, durable state transition. If claims can expire, require a current ownership generation for subsequent state changes. An old worker that resumes after replacement must not overwrite the replacement's result. Any external system involved needs compatible duplicate protection or reconciliation; a database claim cannot undo a message or command already accepted elsewhere.

SultanByte's background-export guide covers worker attempts and result release in more detail. The extra responsibility here is deciding which scheduled occurrences ought to exist after time has passed.

Proposed recurring-job recovery flow: identify the intended occurrence, record it once, apply skip, coalesce or bounded-backfill policy, then reconcile the outcome. Retries retain the same occurrence identity.

Infographic: SultanByte. Proposed application framework informed by Kubernetes, AWS, Google Cloud and PostgreSQL documentation, November 2026. The diagram describes responsibilities, not an exactly-once execution guarantee.

Recover missing work from the ledger, not the queue length

A queue can be empty because everything finished or because nothing was dispatched. Reconcile expected occurrences against recorded outcomes.

For this design, maintain a bounded reconciliation cursor for each schedule. Enumerate due occurrences from its versioned definition, apply the missed-run policy, and record the decisions durably. Advance the cursor only with the corresponding records committed. Multiple reconcilers must compete through the same uniqueness and ownership rules.

Separate occurrence creation from message delivery without losing the dispatch intent. Record an outbox entry in the same transaction as the admitted occurrence, then forward it asynchronously. A duplicate forward still refers to the same occurrence. Backfill should use this path rather than a special script that bypasses the ordinary controls.

An EventBridge Scheduler dead-letter queue records failed target invocation details and, when a retry policy is configured, receives failures after the configured retries are exhausted. That helps diagnose delivery. It cannot establish that business processing succeeded after a target accepted the invocation.

Track due-to-admission delay and admission-to-completion delay separately. Alert on overdue occurrences without a terminal decision, not only on scheduler errors. Keep skipped and superseded outcomes visible so an empty backlog does not disguise omitted work.

Make schedule edits and regional recovery explicit

Editing a schedule raises an awkward question: which definition owns a period straddling the change? For this design, record an effective boundary and preserve the previous revision. Resolve already-materialised occurrences deliberately; do not silently regenerate them under the new expression.

Use a business-period key when the operation must happen once per period, independently of schedule revisions. Otherwise changing “run at night” to “run in the morning” can produce two valid occurrence records for one business obligation. Schedule identity and business-effect identity solve different problems.

Before enabling a replacement schedule, inspect existing queued and running work. Record which pending occurrences remain valid, which are superseded and whether any require operator review. Moving to a new provider must not reset the business deduplication boundary.

For separately approved Saudi and UAE deployments, preserve the server-assigned deployment through retries and backfill. An outage does not itself authorise routing customer work to a different region. Review the scheduler, queue, failed-message store, ledger and worker route together. This is an architectural recommendation, not a statement that every workload in either country has identical residency requirements.

Give support staff a readable occurrence history: intended local time and zone, actual start, current state, recovery decision and outcome reference. Arabic and English screens should translate the explanation while preserving the same identifiers and underlying state.

Prove the recovery policy before enabling automation

Use isolated tenants and synthetic work. Deliver the same occurrence concurrently and require one durable occurrence record. Lose the acknowledgement after successful processing, retry it, and verify that the business effect is not repeated.

Stop dispatch after the occurrence commits. Recovery must find and forward it. Then stop the scheduler across several intended runs and verify each policy: expired work stays skipped, coalesced work has explicit supersession records, and backfill respects its horizon and concurrency limit.

Change a schedule while an older occurrence is running. Start the replacement scheduler before the old one has fully drained. Verify that both still share the appropriate business-period boundary. Finally, redrive an old message after normal history cleanup and confirm that retention has not reopened a completed operation.

These are proposed acceptance tests, not results demonstrated by this article. Product owners should approve the expected outcome for each case before engineering automates it. A useful scheduled service can explain both the work it completed and the work it deliberately did not run.

About the author

  • I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.

No comments yet. Be the first to comment.