Blueprints Blog Contact About

Scheduling

Run your pipeline on cron — no daemon, no infrastructure

One command installs an OS-native scheduler. No daemon. No service to monitor.

ondatrasql schedule "*/5 * * * *"

That’s it. The pipeline now runs every 5 minutes.

Mental Model

OndatraSQL doesn’t run a scheduler process. It writes config to your OS — systemd, launchd, or Task Scheduler — and lets the OS handle execution.

You get cron without a cron daemon. You get persistence across reboots. You get logs in standard places. You get nothing to monitor.

Commands

ondatrasql schedule "*/5 * * * *"   # Install + activate
ondatrasql schedule                 # Show status
ondatrasql schedule remove          # Uninstall

Standard 5-field cron syntax: minute hour day month weekday.

Cross-Platform

OndatraSQL detects your OS and installs the right thing.

Linux — systemd user timer

~/.config/systemd/user/ondatrasql-<id>.service
~/.config/systemd/user/ondatrasql-<id>.timer

Activated with systemctl --user enable --now. Logs in journalctl --user -u ondatrasql-<id>.

macOS — launchd

~/Library/LaunchAgents/sh.ondatra.<id>.plist

Activated with launchctl load. Logs at ~/Library/LaunchAgents/sh.ondatra.<id>.log.

Windows — Task Scheduler

Created via schtasks /create /xml. Visible in Task Scheduler GUI.

Why DAG-Level, Not Per-Model

ondatrasql schedule triggers ondatrasql run — the whole pipeline, in DAG order. Per-model schedules would break dependency ordering.

This works because of Smart CDC: models that have no upstream changes skip automatically. Scheduling the whole DAG every 5 minutes is cheap when most models skip.

13:00:00  scheduler triggers
13:00:00  raw.events       (CDC: 0 new rows, skip)
13:00:00  staging.orders   (CDC: source unchanged, skip)
13:00:00  mart.revenue     (CDC: source unchanged, skip)
13:00:01  done — 0.4s

13:05:00  scheduler triggers
13:05:00  raw.events       (CDC: 247 new rows)
13:05:00  staging.orders   (CDC: 12 changed)
13:05:00  mart.revenue     (CDC: rebuild)
13:05:03  done — 3.1s

The DAG runs every 5 minutes but only does work when something changed.

Project Identity

Each project gets a stable identifier in .ondatra/project-id (8 hex characters, generated on first schedule install). This file should be committed to git.

The identifier is used in the scheduler unit name so:

  • Two projects with the same basename don’t collide
  • Renaming or moving the project doesn’t break the schedule
  • Two collaborators cloning the same repo get the same identifier

If you’re on a read-only filesystem, OndatraSQL falls back to a path hash (deterministic but path-bound).

Cron Syntax

Standard 5-field syntax:

PatternDescription
*/5 * * * *every 5 minutes
0 * * * *every hour
0 9 * * *daily at 09:00
0 0 * * 0weekly on Sunday at midnight
0 0 1 * *monthly on the 1st
0 22 * * 1-5weekdays at 22:00

Each backend supports a slightly different subset of cron syntax. OndatraSQL validates and rejects expressions the target backend cannot represent — better to fail at install than to silently install a wrong schedule.

Reading the Status

$ ondatrasql schedule

Schedule for "myproject"
  Backend:  systemd
  Unit:     ondatrasql-3f2a91b4.timer
  Cron:     */5 * * * *  (every 5 minutes)
  Status:   active
  Last run: Tue 2026-04-08 14:00:01 UTC
  Next run: Tue 2026-04-08 14:05:00

The original cron expression is round-tripped from the unit file — what you installed is what you see.

Removing

$ ondatrasql schedule remove

Removed schedule for "myproject"

This stops the timer, disables it, and deletes the unit files. Any running pipeline finishes; no in-flight commits are interrupted.

Why This Matters

Most data tools require a separate scheduler — Airflow, Prefect, Dagster, cron containers. They each come with their own deployment, monitoring, and failure modes.

OndatraSQL doesn’t add a scheduler. It uses the one your OS already has.

NeedTraditional stackOndatraSQL
SchedulerAirflow / Prefect / cron containerBuilt into the OS
DaemonAlways-on Python processNone
MonitoringCustom dashboardsjournalctl / Console.app / Event Viewer
Restart on rebootsystemd unit wrapping the daemonsystemd unit
Restart on crashHealth checkssystemd Restart=
SetupHoursOne command

The best scheduler is the one you don’t have to install.