Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migrating from ElasticPriorityScheduler

cronoq ships a compatibility layer for code written against the Python ElasticPriorityScheduler API (Task, Priority, submit, start, stop, get_metrics, get_cost). It runs on the cronoq engine, so existing code gets the engine's behaviour and performance with a one-line import change.

Step 1: swap the import

# before
from elastic_scheduler import ElasticPriorityScheduler, Priority, Task
# after
from cronoq.compat import ElasticPriorityScheduler, Priority, Task
import time

scheduler = ElasticPriorityScheduler(initial_workers=1, max_workers=4)
scheduler.start()
scheduler.submit(Task(id="report", duration=2.0, priority=Priority.HIGH))
scheduler.submit(Task(id="nightly", duration=0.5, fixed_start=time.time() + 60))
scheduler.submit(Task(id="email", duration=0.2, dependencies=["report"]))
...
print(scheduler.get_metrics())   # QualityMetrics(schedule_quality=..., priority_adherence=...)
scheduler.stop()

The shim is synchronous and thread-safe; it runs the native scheduler on a private event loop in a background thread. As in the original, a task "runs" by sleeping for its duration.

Behavioural differences

AreaOriginalcronoq
Fixed-start conflictsAny two overlapping fixed tasks conflict, regardless of worker count.Overlaps are allowed up to half of max_workers (at least one).
Recorded start of fixed tasksmax(now, fixed_start), even if dispatched early.The actual dispatch time.
DependenciesSatisfied only while the parent is among the last 1000 completed tasks.Late dependents are resolved from retained terminal statuses (kept for 1 hour by default).
Deadline urgencyNon-monotonic (an overdue task could rank below an almost-due one).Monotonic; overdue tasks always rank highest within their class.
Scaling triggerschedule_quality thresholds.Queue pressure; scale_up_threshold / scale_down_threshold are accepted and ignored.
schedule_qualityDivides by the worker count at call time.Total work ÷ worker-seconds actually provisioned.
DispatchPolls every 100 ms; one thread per task.Event-driven; no polling.

The native API gives you everything the shim hides: real executors instead of sleeps, results, retries, timeouts, cancellation, resources and per-pool configuration.

OriginalNative equivalent
Task(id=..., duration=d, priority=p)await sched.submit(payload, key=id, priority=p, duration_hint=d)
Task(..., fixed_start=t)submit(..., start_at=t)
Task(..., deadline=t)submit(..., deadline_at=t)
Task(..., dependencies=[a, b])submit(..., after=[a, b])
ElasticPriorityScheduler(initial_workers, max_workers)Scheduler([Pool("default", run, min=1, initial=i, max=m)])
get_metrics()await sched.snapshot() (per pool: priority_adherence, fixed_start_hit_rate, utilization, wait percentiles, cost)
get_cost()(await sched.snapshot()).pool("default").cost (set cost_per_worker_second in AutoscaleConfig)

What you get

  • A one-line migration. Existing code keeps its API, threads and call sites; the scheduling decisions move to the cronoq engine.
  • Correct semantics where the prototype was approximate. Dependencies are resolved from retained terminal statuses (1 hour by default) instead of the last 1000 completions, deadline urgency is monotonic, and fixed starts are admitted against real capacity instead of conflicting whenever two of them overlap.
  • No polling. Dispatch is event-driven: a task starts as soon as a worker is free instead of on the next 100 ms poll, and an idle scheduler uses no CPU.
  • A path forward. Once on the native API, the same code gains real executors, results, retries, timeouts, cancellation, shared rate limits, cron schedules, status events and, with cronoq-server, durable state and remote workers.