PgQue is a two-layer system built on top of PgQ’s proven core engine. Understanding how ticks, batches, and rotation work explains both the zero-bloat guarantee and the transaction boundary requirements.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.
Two-layer architecture
pgque-core is a mechanical repackaging of PgQ: renamed for thepgque schema, modernized for PostgreSQL 14+ (pg_snapshot, xid8), hardened with security definers and role grants, and bundled into a single SQL file. The ~4,000 lines of PL/pgSQL are inherited from PgQ’s decade-plus production history at Skype.
pgque-api is the modern convenience layer (~1,500 lines): send, receive, ack, nack, the dead letter queue, delayed delivery, and the cooperative consumer API. Every API function reduces cleanly to pgque-core primitives — send() calls insert_event(), receive() calls next_batch() + get_batch_events(), ack() calls finish_batch().
Snapshot-based batching
UnlikeSKIP LOCKED queues that operate row by row, PgQue creates ticks — snapshot markers in the event stream. A tick records the PostgreSQL snapshot at the moment it runs. A batch is the set of events between two consecutive ticks, as seen through those snapshots.
This design has two key consequences:
-
Zero dead tuples in the hot path. There are no
UPDATEorDELETEoperations on event rows. Events accumulate in the current rotation table until the table isTRUNCATEd and swapped out. -
The snapshot rule.
send→ticker→receivemust each run in separate committed transactions. The ticker’s snapshot must be taken aftersendcommits;receiveonly returns events visible in the snapshot recorded by the most recent tick.
The snapshot rule
BEGIN/COMMIT block produces empty batches:
receive → process → ack belongs in one transaction (for exactly-once effects on the same database), but send → tick → receive requires committed boundaries between each step.
Go (pgxpool) and TypeScript (pg.Pool) satisfy this transparently since each query runs in its own implicit transaction. Python requires explicit commits between send and the consumer side; the high-level Consumer class handles this internally.
Three-table TRUNCATE rotation
Each queue owns three event tables. At any given time, one is the “hot” table receiving new events. When the rotation period elapses (ormaint() triggers it), PgQue rotates the tables:
- The hot table becomes the “middle” table (still queryable by active batches)
- The oldest table is
TRUNCATEd — removing all events in one operation with no dead tuples - A fresh empty table becomes the new hot table
n_dead_tup = 0 across event tables even under sustained load with a blocked xmin horizon. VACUUM never needs to reclaim dead tuples from event tables because there are none.
Consumer loop
The underlying PgQ consumer loop:pgque.receive() = next_batch() + get_batch_events(). pgque.ack() = finish_batch(). pgque.nack() = event_retry() (with DLQ routing when retry_count >= max_retries).
Glossary
Event
Event
One row in a queue table. Delivered at-least-once. Columns:
ev_id, ev_time, ev_txid (xid8), ev_retry, ev_type, ev_data, ev_extra1..4.Batch
Batch
The set of events between two consecutive ticks, served to a consumer together. All events in a batch share the same
batch_id.Queue
Queue
A named event stream. Backed by 3 rotating tables, purged by
TRUNCATE. Any number of queues can coexist in one database.Tick
Tick
A position marker in the event stream. Delimits batch boundaries. Contains a PostgreSQL snapshot that determines which events are visible in the batch.
Ticker
Ticker
The component that creates ticks. In PgQue’s default pg_cron path: one 1-second cron slot calls
pgque.ticker_loop(), which invokes pgque.ticker() every tick_period_ms milliseconds (100 ms / 10 ticks/sec by default).Consumer
Consumer
Subscribes to a queue, reads batches, calls ack or nack. Any number of consumers can subscribe to the same queue; each has its own cursor and independently sees every event (fan-out by default).
Rotation
Rotation
The periodic TRUNCATE-and-swap cycle that keeps event tables bloat-free. Managed by
pgque.maint() (step 1) and the pgque_rotate_step2 cron job (step 2).