Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/NikolayS/PgQue/llms.txt

Use this file to discover all available pages before exploring further.

PgQue installs from a single SQL file in one transaction. After installation, you need something that calls pgque.ticker() periodically — pg_cron is the recommended default and is pre-installed (or one command away) on every major managed Postgres provider. If you do not use pg_cron, any external scheduler works instead.
PgQue does not deliver messages without a working ticker. Enqueueing still works, but consumers will see nothing new because no tick boundaries are created. If you skip pgque.start(), you must call pgque.ticker(), pgque.maint_retry_events(), and pgque.maint() yourself on a regular cadence.
1

Get the SQL file

Clone the repository so \i sql/pgque.sql can resolve its relative path from the repo root.
git clone https://github.com/NikolayS/pgque
cd pgque
2

Install PgQue

Install inside a transaction so a failure leaves no half-built schema behind.
begin;
\i sql/pgque.sql
commit;
Verify the install:
select pgque.version();
The install creates the pgque schema, three roles (pgque_reader, pgque_writer, pgque_admin), and all functions.
3

Start the ticker

Choose the scheduler that matches your environment.pg_cron is pre-installed or one command away on RDS, Aurora, Cloud SQL, AlloyDB, Supabase, and Neon. On self-managed Postgres, follow the pg_cron setup guide.With pg_cron available in the same database as PgQue:
select pgque.start();
This schedules four cron jobs: pgque_ticker every second, pgque_retry_events every 30 seconds, pgque_maint every 30 seconds, and pgque_rotate_step2 every 10 seconds.The pgque_ticker job calls CALL pgque.ticker_loop(), a procedure that re-invokes pgque.ticker() every tick_period_ms milliseconds inside a single 1-second cron slot, committing between iterations. The default is 100 ms (10 ticks/sec), giving ~50 ms median end-to-end delivery.Tune the rate at runtime without rescheduling — the change takes effect on the next cron slot (≤ 1 s):
select pgque.set_tick_period_ms(50);    -- 20 ticks/sec, ~25 ms median e2e
select pgque.set_tick_period_ms(1000);  -- 1 tick/sec, the pgqd-compatible cadence
Allowed values are exact divisors of 1000 in the 1–1000 ms range. Inspect the current rate with select * from pgque.status();.
If pg_cron runs in a different database (typically postgres, set via cron.database_name) than the one where PgQue is installed, use the cross-database pattern to call pgque.ticker_loop(), pgque.maint_retry_events(), and pgque.maint() across databases.

With pg_timetable

Run the external pg_timetable worker against the database where PgQue is installed:
pg_timetable --dbname=mydb --clientname=pgque
The --clientname=pgque flag is required — pg_timetable only executes chains whose job_client_name matches the running worker’s client name. Keep this worker process running; unlike pg_cron, pg_timetable is an external scheduler process, not a Postgres background worker.Then register PgQue’s jobs:
select pgque.start_timetable();      -- default: 10 ticks/sec
-- or explicitly:
select pgque.start_timetable(10);
pgque.stop_timetable() removes the PgQue pg_timetable jobs. pgque.stop() stops whichever scheduler is active. Calling pgque.start_timetable() automatically removes existing PgQue pg_cron jobs first; pgque.start() does the same for pg_timetable jobs.

Without any scheduler

PgQue installs and works without pg_cron or pg_timetable. Drive ticking and maintenance from your application or an external scheduler (system cron, systemd, a worker loop):
PAGER=cat psql --no-psqlrc -d mydb -c "select pgque.ticker()"             # at your chosen tick period
PAGER=cat psql --no-psqlrc -d mydb -c "select pgque.maint_retry_events()" # every 30 seconds
PAGER=cat psql --no-psqlrc -d mydb -c "select pgque.maint()"              # every 30 seconds
For sub-second ticking from an external driver, loop pgque.ticker() at your target rate. tick_period_ms is only consulted by pgque.ticker_loop() (the pg_cron/pg_timetable path); outside that path, your driver controls the cadence.Skipping maint_retry_events() means nack’d events will never be redelivered.
4

Grant roles

The install creates three roles. Application users do not need superuser — grant whichever role matches the access pattern.pgque_reader (consume) and pgque_writer (produce) are siblings, not parent/child. Apps that both produce and consume must be granted both roles explicitly.
-- Produce + consume in the same app: grant BOTH roles.
create user app_orders with password '...';
grant pgque_reader to app_orders;
grant pgque_writer to app_orders;

-- Pure producer (e.g. a webhook ingester that only sends).
create user app_webhook with password '...';
grant pgque_writer to app_webhook;

-- Pure consumer / dashboard / metrics.
create user metrics with password '...';
grant pgque_reader to metrics;
RolePurposeAccess
pgque_readerConsumers, dashboards, metricssubscribe, receive, ack, nack, observability views
pgque_writerProducerssend, send_batch — does not inherit pgque_reader
pgque_adminOperators, migrationsMember of both, plus full schema access
DDL-class operations (create_queue, drop_queue, start, ticker, force_next_tick, set_queue_config) require pgque_admin.

Optional: install as a pg_tle extension

This is opt-in. The default \i sql/pgque.sql install is the recommended path. Use pg_tle only if you specifically want PgQue managed as a real Postgres extension (with alter extension pgque update and drop extension pgque cascade). Note that pg_tle is itself a C extension that requires shared_preload_libraries — the dependency the default install avoids. Available on AWS RDS / Aurora and self-hosted Postgres. On self-hosted Postgres, inspect the current list before modifying it to avoid overwriting existing entries:
show shared_preload_libraries;
alter system set shared_preload_libraries = 'pg_cron,pg_tle'; -- preserve existing entries
-- restart Postgres, then in the target database:
create extension pg_tle;
Once pg_tle is loaded:
\i sql/pgque-tle.sql
create extension pgque;
To uninstall: \i sql/pgque-tle-uninstall.sql.

pg_cron log hygiene

pg_cron logs every job execution to cron.job_run_details with no built-in purge. PgQue’s four scheduled jobs add roughly 5,000 rows per hour. The table grows forever unless you intervene. The recommended approach is to keep pg_cron logging enabled (for other jobs’ run history) and purge only PgQue’s high-volume entries:
select cron.schedule(
  'pgque_purge_cron_log',
  '0 * * * *',
  $$
  delete from cron.job_run_details d
  using cron.job j
  where d.jobid = j.jobid
    and j.jobname in (
      'pgque_ticker',
      'pgque_retry_events',
      'pgque_maint',
      'pgque_rotate_step2',
      'pgque_purge_cron_log'
    )
    and d.end_time < now() - interval '1 day'
  $$
);
If you do not need successful-run history for any pg_cron job on the instance, you can disable logging globally instead:
alter system set cron.log_run = off;
-- requires a Postgres restart; errors from failed jobs still land in
-- the Postgres server log via cron.log_min_messages (default WARNING)

Build docs developers (and LLMs) love