Self-hosted service
cybertec-postgresql/pg_timetable avatar
cybertec-postgresql/pg_timetable

pg_timetable: Database-Driven PostgreSQL Scheduling

pg_timetable: Advanced scheduling for PostgreSQL

1,404 stars76 forksGoNOASSERTION

At a glance

What is it?
pg_timetable is a standalone scheduler whose chains, tasks, schedules, retries, and logs live in PostgreSQL. It fits teams that need repeatable database work, but it adds a Go process and its own execution model.
Who is it for?
pg_timetable fits PostgreSQL operators whose recurring work needs database-visible configuration, chain dependencies, or retry behavior. It is a poor fit for a machine that cannot run an additional scheduler process or for workloads that require simple one-off execution.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why pg_timetable Belongs Beside PostgreSQL

pg_timetable addresses recurring work that is already tied to a PostgreSQL database. Its intended users are database administrators, application operators, and teams that want scheduled SQL, external commands, or predefined operations managed from the same database as their data. The README contrasts it with cron and describes it as a standalone, database-driven job scheduler rather than a PostgreSQL extension.

That boundary matters. pg_timetable is a Go executable connected to PostgreSQL, not a job runner embedded in the database server. It can keep schedule definitions and execution logs in PostgreSQL, but someone must start the executable, keep it available, and supply a working database connection. A deployment also needs a PostgreSQL role with suitable privileges. The quick start explicitly asks for a role with `CREATE` privilege on the target database.

The central benefit is operational visibility through PostgreSQL. Jobs are created with SQL calls such as `timetable.add_job`, and the README lists database-driven logging as a supported feature. This is more useful than a purely host-local schedule when operators already inspect jobs through PostgreSQL. It does not remove the usual scheduler concerns, though. Host restarts, database availability, credentials, executable permissions, and duplicate processes all remain part of the operating boundary.

Jobs, Chains, and PostgreSQL Execution Flow

A pg_timetable job has a schedule, an executable payload, and a kind. The README examples show SQL work such as `VACUUM` and `REFRESH MATERIALIZED VIEW`, built-in behavior through `run-vacuum` and `refresh-matview`, and operating-system programs through `job_kind := 'PROGRAM'`. Cron-style expressions and interval schedules share the same job model.

The schedule argument for `SELECT 1` is `NULL`, which the README associates with execution on every scheduler loop tick, with a default interval of 60 seconds. A `NULL` schedule is therefore not an omitted cron field here. It selects loop-tick behavior.

Chains add ordering and lifecycle control. The YAML format includes a chain name, schedule, `live` state, `max_instances`, `timeout`, and an ordered `tasks` list. Tasks can be autonomous, accept parameters, and run stored procedures or SQL commands. The README also documents retries for missed chains, configurable repetitions, optional concurrency protection, and enabling or disabling definitions without deletion. Built-in tasks include email, downloads, imports, logging, sleep, and no-op operations, while the samples directory provides dedicated files for retries, exclusive execution, autonomous transactions, error handling, and client notifications.

This model differs from executing isolated cron entries. Chains express a pipeline inside the scheduler, while each task still uses SQL, a built-in operation, or a program. Database logs and optional OpenTelemetry metrics or traces provide observability, but the executable remains responsible for connecting to the database and applying its configured loop.

Installing pg_timetable and Scheduling VACUUM

The README directs users to official release packages, Docker images, or source builds. Its quick start assumes that the `pg_timetable` executable has already been downloaded. First, create the scheduler role and grant permission to create objects in the target database:

sql
CREATE ROLE scheduler PASSWORD 'somestrong';
GRANT CREATE ON DATABASE my_database TO scheduler;

After connecting as an authorized role, register the recurring job. The README's example schedules `VACUUM` every day at 00:30 and reports the returned job identifier as `3` in its example session:

sql
SELECT timetable.add_job('frequent-vacuum', '30 0 * * *', 'VACUUM');

Start the scheduler with the connection string and a client name:

bash
pg_timetable postgresql://scheduler:somestrong@localhost/my_database --clientname=vacuumer

The command must remain connected to PostgreSQL for scheduled work to run. The documented SQL result confirms that the job was registered, not that a later `VACUUM` execution succeeded. Execution status should be checked through the database-driven logging documented by the project.

For a self-contained local demonstration, the repository also provides `docker-compose.yml`. It defines PostgreSQL and pg_timetable services, and the comments list its exact top-level usage:

bash
docker compose up

The compose file publishes PostgreSQL on host port `5432` and loads self-contained samples. This is a demonstration environment, not a production template, because its committed credentials and settings are intentionally simple.

YAML Chains and External Program Boundaries

YAML startup files provide an alternative to inserting every chain definition through SQL. The README shows one or more `--file` arguments followed by the connection target:

bash
pg_timetable --file chains.yaml postgresql://user:pass@host/db

The format supports schedule strings, task parameters, autonomous tasks, `max_instances`, and timeouts in milliseconds. The example marks an ETL chain as live at 02:00 daily, limits it to one instance, and assigns a one-hour timeout. This is convenient for ordered workflows, but it adds another representation that operators must understand alongside the database schema.

`PROGRAM` tasks extend pg_timetable beyond PostgreSQL. A task can invoke `reindexdb`, pass JSON arguments, or start `bash` with a command that sets `PGPASSWORD`. Those examples also expose an uncomfortable security boundary: database scheduling can initiate operating-system commands, and connection credentials may appear in process arguments or stored configuration.

The README shows this invocation:

sql
SELECT timetable.add_job('reindex-job', '0 0 * * 7', 'reindexdb',
          '["--table=foo", "--dbname=postgres", "--verbose"]'::jsonb, 'PROGRAM');

Do not treat a database role that can define jobs as harmless. Administrators must decide which users may create or alter executable jobs, and they must constrain the scheduler host and program arguments. The README documents password passing through a shell but does not describe secret storage, credential rotation, or command allowlisting in its instructions.

Failure Handling, Retries, and Concurrency Limits

pg_timetable's chain model addresses failures that a plain cron entry cannot represent well. The README lists automatic retry for missed chains, configurable repetitions, delayed retry with back-off in `samples/DelayedRetry.sql`, and error handling in `samples/ErrorHandling.sql`. These features are useful when a multi-step process starts but a later task or external program fails.

They also make behavior more involved. A retry policy can produce delayed attempts after the original schedule, so operators need to distinguish the intended schedule from the effective execution time. Autonomous tasks and exclusive execution alter transaction and concurrency semantics. The repository includes `samples/Autonomous.sql` for mid-chain `COMMIT` behavior and `samples/Exclusive.sql` for an exclusive execution lock.

Optional concurrency protection and YAML's `max_instances: 1` should be evaluated against the actual workload. Limiting instances can prevent overlapping chain runs, but it can also allow backlog or missed schedules when a task runs longer than expected. The README gives a one-hour timeout in its example, yet it does not provide a universal recommendation for timeout or retry values.

The scheduler is also a separate operational dependency. The documentation states that missed chains can be retried, but the README does not explain exactly which chain definitions qualify as missed, how far back recovery extends, or how retries interact with disabled definitions. Those points need direct verification before relying on missed-job recovery for compliance or time-sensitive processing.

pg_timetable Versus cron and Database-Native Queues

cron is the clearest alternative because pg_timetable explicitly compares itself with traditional schedulers. pg_timetable instead requires a continuously available process, reads definitions and logs through PostgreSQL, and models SQL, built-in tasks, and external programs as chains. It is the better fit when the schedule must be administered alongside database objects or when task dependencies and recovery matter.

A database-native queue or a task table is different. Such a design can make workers part of the application and avoid a general-purpose scheduler, but the repository does not describe a specific competing product. The practical comparison is architectural: pg_timetable owns triggering, chain progression, retries, and logs. A simple application queue owns those concerns only if the application implements them, and it may duplicate scheduling logic if reporting is also required.

pg_timetable is the wrong tool for isolated machine maintenance with no PostgreSQL dependency. It is also a poor choice where hosting policy permits only application processes, where external programs must not be initiated from the database, or where simple per-minute work has no need for chains, logging, and scheduler-side retries. In those cases, host scheduling or the existing application worker model can have less operational surface.

Release State, Upgrade Work, and Licence Verification

The repository is not archived, and its last push was on 2026-10-12. Recent releases include v7.1.0 on 2026-08-24, v7.0.0 on 2026-07-22, and v6.3.0 on 2026-03-11. These dates indicate recent repository activity. They do not by themselves describe upgrade compatibility, migration effort, or production support.

The README points to release packages, Docker images, and source builds. An upgrade can therefore involve changing the executable or image, reviewing database migrations, and revalidating schedules, roles, YAML files, and program permissions. The repository contains migration-related Go dependencies and a `spec` directory, while the documentation includes installation and YAML format pages. The README does not provide an explicit upgrade command or compatibility matrix, so operators should not assume that replacing the binary is the complete upgrade procedure.

Licence classification also needs care. The README badge says `MIT`, but the repository metadata classifies the licence as a custom licence that GitHub cannot classify and directs readers to `LICENSE`. That is not enough to conclude that every release or bundled component has identical terms. Review the exact `LICENSE` file for the release being deployed, and separately account for dependencies such as Docker base images and imported Go modules. This is a compliance review, not legal advice.

The Dockerfile builds from `golang:1.27-alpine`, produces a static binary with `CGO_ENABLED=0`, and places it in an Alpine runtime containing `postgresql-client` and `curl`. That build has a clear image shape, but the Dockerfile does not define a non-root runtime user, so its access policy should not be inferred from the build recipe.

Editorial conclusion

pg_timetable fits PostgreSQL operators whose recurring work needs database-visible configuration, chain dependencies, or retry behavior. It is a poor fit for a machine that cannot run an additional scheduler process or for workloads that require simple one-off execution. Before adoption, inspect the repository's LICENSE file, verify the installed release against v7.1.0, and test the intended PROGRAM tasks with restricted operating-system permissions. For a starting deployment, confirm that the scheduler role and connection URL provide only the database access required by the scheduled SQL and programs.

Frequently asked questions

Does pg_timetable run as a PostgreSQL extension?

No. The README describes pg_timetable as a standalone Go executable connected to PostgreSQL, with configuration and logging stored in the database.

How can I run a PostgreSQL command every day at 00:30 with pg_timetable?

Register it with `SELECT timetable.add_job('frequent-vacuum', '30 0 * * *', 'VACUUM');`, then keep the `pg_timetable` executable connected to the target database.

Can pg_timetable execute operating-system programs?

Yes. A job can use `job_kind := 'PROGRAM'`, as shown by the README for `reindexdb`, including JSON arguments for program options.

Official sources

  1. cybertec-postgresql/pg_timetable on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cybertec-postgresql-pg-timetable.svg)](https://hysenlabs.com/projects/cybertec-postgresql-pg-timetable)

Was this useful?