Schedules¶
A schedule runs a method once per period, from one process of the installation, however many replicas run the service:
from datetime import timedelta
from myxo.schedule import scheduled
class Reports(Service):
name = "reports"
@scheduled(every=timedelta(hours=1))
async def digest(self) -> None:
...
To read the period from the layout, name a field of the service's Config, which holds seconds. A process whose field is missing, or holds no number, refuses to start, and myxo check reports it:
class ReportsConfig(msgspec.Struct, forbid_unknown_fields=True):
period: float = 60.0
class Reports(Service):
name = "reports"
Config = ReportsConfig
@scheduled(every="period")
async def digest(self) -> None:
...
The guarantees¶
Periods are aligned to the clock: a one-hour schedule runs near the start of each hour. Each schedule has its own leader, which records the period before running the method. So:
- At most once per period, even when the leader changes. Across a change of leader in E14, each period got one report.
- A period that no process led is skipped.
- A run that fails is not retried.
Unavailableis logged. The next period runs as usual. Any other error restarts the schedule's task under its supervisor, and that period stays recorded.
Work that must happen¶
For work that must happen, the method puts a job, with a key that names the period, so that the job is durable and retried:
The service declares jobs = Jobs(), as in that guide:
import time
@scheduled(every=timedelta(hours=1))
async def digest(self) -> None:
hour = int(time.time() // 3600)
await self.jobs.put(BuildDigest(hour), key=f"digest:{hour}")
How leadership works¶
On PostgreSQL, the leader holds an advisory lock on a session of its own, and speaks on it every second. A leader cut off from the database stops. The server ends the silent session within 5 s, which frees the lock for another process. On SQLite, the leader holds a file lock beside the database, which ends with its process. These files look like notes.db.schedule.notes.tick.lock. Leave them in place.