Pausing, Resuming, and Running Now¶
Withdrawing a job throws away its identity; disabling its channel affects
every producer of that channel and logs noise on each missed slot. Often what
you actually want is smaller: stop this one job for a while, or run it once
right now. That's what pause_job(), resume_job(), and run_job_now()
are for. Each returns the job's full listing row, so you see the effect in the
same statement.
Pausing a job¶
-- Indefinitely — until someone resumes it:
SELECT * FROM pgrelay.pause_job('nightly_report');
-- Bounded — resumes by itself after the freeze:
SELECT * FROM pgrelay.pause_job('nightly_report', '2026-09-15 00:00+10');
While paused, the job claims nothing and logs nothing — it is simply not due. Its id, audit history, and run counters all survive, and the listing shows what's going on:
SELECT schedule_name, next_run, paused, paused_until
FROM pgrelay.list_scheduled_jobs() WHERE schedule_name = 'nightly_report';
-- schedule_name | next_run | paused | paused_until
-- ----------------+----------------------+--------+----------------------
-- nightly_report | 2026-09-15 03:00+10 | t | 2026-09-15 00:00+10
A bounded pause needs no follow-up at all: the next run is positioned at
the first occurrence of the spec at or after your p_until, so the job wakes
up by itself — there is no timer to remember, nothing running in the
background, and nothing to clean up. An indefinite pause shows
paused_until = infinity and waits for resume_job().
Details that matter in practice:
- A run already executing when you pause finishes normally; the pause takes effect from the next occurrence.
- Pausing is not cancelling:
unschedule_job()still withdraws a paused job, and re-registering it withschedule_job()also lifts the pause (a re-registration fully resets the job). - A
p_untilin the past raises — if you want it running now, that'sresume_job(). - An unknown job name raises (unlike
unschedule_job(), which is a best-effort "make it gone" and reportsskipped; pause and resume are precise instructions about a job you believe exists).
Resuming a job¶
The pause is cleared and the next run becomes the next occurrence of the spec from now — a job paused past several occurrences does not replay them (the same skip-missed rule as everywhere else).
resume_job() is idempotent: resuming a job that isn't paused changes
nothing and simply returns its row. In particular it will never postpone a
job that is currently due — safe to call from scripts without checking first.
Running a job now¶
The job becomes due immediately; the Processor launches it on its next 1-second poll. This is an extra occurrence through the job's completely ordinary machinery, which buys three guarantees pg_cron users usually have to improvise:
- It cannot overlap. If the job happens to be mid-run, the on-demand run waits for it to finish — same rule as always: a job never overlaps itself.
- It's part of the record. The run is audited under the job's stable id
and counted in
run_count, indistinguishable from a scheduled launch inscheduled_job_runs(). - The rhythm restores itself. After the run, the next occurrence is
computed as usual. For a cron spec that's the normal calendar slot (a
03:00 job poked at 14:00 still runs at 03:00 tomorrow). For an interval
spec, the cadence re-anchors to this launch — a
'2 hours'job poked at 14:00 next runs at 16:00. That's the standard fixed-cadence rule, worth knowing before poking interval jobs whose alignment you care about.
Typical uses: proving a new job's action works without waiting for 03:00; re-running last night's failed report after fixing the data; forcing a refresh ahead of a demo.
-- The classic new-job smoke test:
SELECT pgrelay.schedule_job('nightly_report', '0 3 * * *', 'CALL build_nightly_report()');
SELECT * FROM pgrelay.run_job_now('nightly_report');
-- ...a second or two later:
SELECT * FROM pgrelay.scheduled_job_runs('nightly_report', 1);
run_job_now() raises on an unknown name, and it refuses a paused job —
deliberately, so a pause always means what it says. Resume first:
SELECT * FROM pgrelay.resume_job('nightly_report');
SELECT * FROM pgrelay.run_job_now('nightly_report');
Job-level versus fleet-level¶
These functions act on one job. The existing
fleet controls —
pgrelay.stop(), pause_for(), pause_to(), start() — still pause every
Processor and therefore every job and every ordinary event at once. Both
levels compose: a job paused with pause_job() stays paused when the fleet
resumes.
Continue to Monitoring and History.