π Usage
This page explains how to register cron jobs, run the scheduler, and use the provided CLI and Rake tasks.
π§© Registering Cron Jobs
Define your recurring tasks during application boot (e.g. in config/initializers/kaal_jobs.rb):
Kaal.register(
key: "reports:weekly_summary",
cron: "0 9 * * 1", # every Monday at 9 AM
enqueue: ->(fire_time:, idempotency_key:) {
WeeklySummaryJob.perform_later(fire_time: fire_time, key: idempotency_key)
}
)
Parameters:
| Name | Description |
|---|---|
key | Unique identifier for the cron task |
cron | Cron expression ("*/15 * * * *", "@daily", etc.) |
enqueue | Lambda or proc to run when the cron fires |
fire_time | UTC time when the tick was due |
idempotency_key | Deterministic key used to prevent duplicate runs |
π‘ Each task is dispatched exactly once, even if multiple Rails or Sidekiq nodes are running.
π Starting the Scheduler
Kaal does not auto-start by default. Start it explicitly via Kaal.start! or kaal:start.
You can start the scheduler loop in one of two ways:
Option 1 β Inline in Rails
# config/initializers/kaal.rb
Kaal.start!
Use this mainly for development/testing. In production, prefer a standalone scheduler process.
Option 2 β Standalone process (recommended for production)
bundle exec rails kaal:start
Example Procfile entry:
web: bundle exec puma -C config/puma.rb
scheduler: bundle exec rails kaal:start
Avoid running scheduler inside web server processes by default. Keep scheduler lifecycle independent from request-serving processes.
systemd Example
[Unit]
Description=Kaal scheduler
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/var/apps/myapp/current
Environment=RAILS_ENV=production
ExecStart=/usr/bin/bash -lc 'bundle exec rails kaal:start'
ExecStartPre=/usr/bin/bash -lc 'bundle exec rails kaal:status'
ExecReload=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Kubernetes Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: kaal-scheduler
spec:
replicas: 1
selector:
matchLabels:
app: kaal-scheduler
template:
metadata:
labels:
app: kaal-scheduler
spec:
containers:
- name: scheduler
image: your-app:latest
command: ["bash", "-lc", "bundle exec rails kaal:start"]
For Kubernetes, the scheduler is the containerβs main process; if it exits, Kubernetes restarts it.
Do not use kaal:status as a scheduler liveness/readiness probe because it runs in a separate process and cannot inspect in-memory scheduler state.
Prefer:
- Process-level checks from your runtime/supervisor.
- A shared heartbeat/lease (Redis, Postgres, pidfile, etc.) written by the scheduler and read by probes.
π§° CLI & Rake Tasks
Explain and Preview Cron Expressions
kaal explain "*/15 * * * *"
# => Every 15 minutes
kaal next "0 9 * * 1" --count 3
# => 2025-11-03 09:00:00 UTC
# => 2025-11-10 09:00:00 UTC
# => 2025-11-17 09:00:00 UTC
Rails Tasks
bin/rails kaal:start # Start scheduler loop
bin/rails kaal:tick # Trigger a single scheduler tick
bin/rails kaal:status # Show active configuration & registry
bin/rails kaal:explain["*/5 * * * *"] # Humanize a cron expression
π§ Cron Utilities
Kaal provides convenience helpers for validating, simplifying, and linting cron expressions.
Kaal.valid?("*/5 * * * *")
# => true
Kaal.simplify("0 0 * * *")
# => "@daily"
Kaal.lint("*/61 * * * *")
# => ["minute step '61' is out of range. Allowed step: 1-60.", "Invalid cron expression '*/61 * * * *'. Examples: '*/5 * * * *', '@daily'."]
Kaal.to_human("0 9 * * 1")
# => "At 09:00 every Monday"
Kaal.to_human("@daily")
# => "Daily"
Kaal.to_human("0 9 * * 1", locale: :fr)
# => Uses :fr locale when available
Supported predefined macros include: @yearly, @annually, @monthly, @weekly, @daily, @midnight, @hourly.
Kaal.simplify raises ArgumentError for invalid expressions with helpful examples:
Kaal.simplify("not-a-cron")
# raises ArgumentError: Invalid cron expression 'not-a-cron'. Examples: '*/5 * * * *', '@daily'.
Kaal.to_human also raises ArgumentError for invalid expressions and unsupported macros.
π Checking Status
bin/rails kaal:status
Displays the current configuration, backend adapter, and registered jobs.
Example output:
Kaal v1.0.0
Backend adapter: Redis
Tick interval: 5s
Registered jobs:
- reports:weekly_summary ("0 9 * * 1")
Next tick: 2025-11-10 09:00:00 UTC
π§ Tips
- Use
Rails.logger.infoinside your job block for observability. - Use
window_lookbackto replay missed ticks (e.g., after downtime). - Never duplicate the same
keyβ it uniquely identifies each job. - The scheduler gracefully stops on
TERMorINT, finishing the current tick first.