Skip to content

feat: distributed job scheduler with lease-based worker claiming (Closes #123) - #149

Merged
elizabetheonoja-art merged 2 commits into
Utility-Protocol:mainfrom
aaronmanuel309-bot:feat/123-job-scheduler
Aug 29, 2026
Merged

feat: distributed job scheduler with lease-based worker claiming (Closes #123)#149
elizabetheonoja-art merged 2 commits into
Utility-Protocol:mainfrom
aaronmanuel309-bot:feat/123-job-scheduler

Conversation

@aaronmanuel309-bot

Copy link
Copy Markdown
Contributor

Summary

Implements Issue #123 — Distributed Job Scheduler with Lease-based Worker Claiming for the webhook delivery service.

The previous queue processor used a single in-process isProcessing flag, which both limits delivery concurrency and cannot protect against duplicate delivery when the service runs multiple replicas/workers. This change replaces it with a distributed job scheduler where workers claim due jobs under short-lived leases: a lease-holding worker is the only one allowed to execute a job, so concurrent workers and replicas can never double-deliver the same webhook.

Changes

New: webhook-delivery-service/src/jobScheduler.ts

  • LeaseStore — shared lease registry. claim() is synchronous (atomic within the event loop, serialisable against a shared store like Redis/etcd across processes) and each lease carries a monotonic fencing token. Renew/release are owner-checked.
  • JobScheduler — runs workerCount worker loops that poll for due jobs and claim them:
    • submit({ id, runAt, execute, onError }) registers a unit of work.
    • Heartbeat renewal: a worker renews its lease on an interval while executing, so a healthy long-running delivery is never stolen.
    • Crash recovery: if a worker stops renewing, its lease expires and another worker reclaims the job (exactly-once under normal operation, at-least-once on failure).
    • Retry via rescheduling: ctx.reschedule(runAt) returns a job to the claimable pool at a future time — used for the webhook exponential-backoff retries.
    • stop(), clear(), and status helpers for dashboards.

Delivery integration (delivery.ts)

  • Webhook jobs are now submitted to the scheduler instead of an array queue; retries reschedule the job rather than re-pushing it.
  • Default worker pool: 3 workers, configurable via WEBHOOK_WORKER_COUNT for horizontal scaling.
  • getSchedulerStatus() exposes workers / pending count / active leases and is surfaced on GET /health.

Metrics (metrics.ts)

  • webhook_scheduler_workers_current
  • webhook_scheduler_active_leases_current
  • webhook_scheduler_jobs_submitted_total
  • webhook_scheduler_jobs_processed_total
  • webhook_scheduler_jobs_failed_total
  • webhook_scheduler_lease_reclaimed_total (crash-recovery events)

Tests

  • tests/jobScheduler.test.ts — 15 tests covering lease store semantics (claim/take/reclaim/renew/release), exactly-once execution under competing workers, work distribution, delayed start, rescheduling, heartbeat anti-theft, expired-lease reclaim, clear(), onError, and stop().
  • tests/webhook.test.ts — 4 integration tests proving deliveries complete exactly once under multiple workers and lease fencing prevents double execution.

Docs

Updated WEBHOOK_ARCHITECTURE.md (scheduler design section + metrics), WEBHOOK_DEPLOYMENT.md (worker concurrency / scaling), WEBHOOK_RUNBOOK.md (scheduler diagnostics and tuning), and the root README.md.

Verification

  • tsc --noEmit passes.
  • Full Jest suite: 38 tests passing (19 pre-existing, all unchanged and green + 19 new).
  • jobScheduler.ts at ~96% line coverage.

Closes #123

aaronmanuel309-bot and others added 2 commits August 29, 2026 14:25
…ming

Replace the single-flight queue processor with a distributed job scheduler
(jobScheduler.ts) in which multiple workers claim due webhook jobs under
short-lived leases. Lease fencing prevents concurrent workers/replicas from
double-delivering the same job, heartbeat renewal keeps healthy long-running
deliveries from being stolen, and expired leases are reclaimed by other workers
for crash recovery (at-least-once on failure). Worker count is configurable via
WEBHOOK_WORKER_COUNT, scheduler metrics are exposed via /metrics, and
architecture/deployment/runbook docs are updated.

Closes Utility-Protocol#123
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Distributed Job Scheduler with Lease-based Worker Claiming

2 participants