Documentation
¶
Overview ¶
Package pgqueue is a small Postgres-backed durable job queue and pub/sub layer built on database/sql and lib/pq.
Jobs are rows in the pgqueue_jobs table (see Schema). Producers enqueue with Queue.Enqueue or, to make a job part of a business transaction, Queue.EnqueueTx — the wakeup NOTIFY rides the caller's transaction, so workers are only woken for jobs that actually committed. Workers claim batches with FOR UPDATE SKIP LOCKED, so any number of processes can consume the same queue without double-processing. Failed jobs retry with backoff until MaxAttempts, then remain in the table with status 'dead' for inspection. A rescue sweep re-queues jobs orphaned by a crashed worker, which makes delivery at-least-once: handlers must tolerate being invoked more than once for the same job.
Publish and Subscriber wrap Postgres NOTIFY/LISTEN for fire-and-forget fan-out to every listening process. Notifications carry no durability: a subscriber that is disconnected when one fires never sees it.
Dependency policy: this package imports only the standard library and github.com/lib/pq, so it stays trivial to vendor or embed in any database/sql-based application.
Index ¶
- Constants
- func DefaultBackoff(attempt int) time.Duration
- func IsPermanent(err error) bool
- func Permanent(err error) error
- func Publish(ctx context.Context, db DBTX, channel string, payload []byte) error
- type DBTX
- type EnqueueOption
- type Handler
- type Job
- type Logger
- type Option
- type Queue
- type Status
- type Subscriber
- type Worker
- type WorkerConfig
Constants ¶
const Schema = `` /* 889-byte string literal not displayed */
Schema creates the pgqueue_jobs table and its indexes. Hosts that manage their own migrations should copy this SQL into a migration; if a release ever changes Schema, add a follow-up migration in the host application.
const SchemaDown = `DROP TABLE IF EXISTS pgqueue_jobs;`
SchemaDown drops everything Schema creates.
Variables ¶
This section is empty.
Functions ¶
func DefaultBackoff ¶
DefaultBackoff doubles from 2s up to a 5m cap, plus up to 25% jitter.
func IsPermanent ¶
IsPermanent reports whether err is (or wraps) a Permanent error.
func Permanent ¶
Permanent wraps an error to tell the worker that retrying is pointless (e.g. an unparseable payload): the job goes straight to 'dead'.
Types ¶
type DBTX ¶
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
DBTX is the subset of database/sql operations this package needs. It is satisfied by both *sql.DB and *sql.Tx.
type EnqueueOption ¶
type EnqueueOption func(*enqueueSettings)
func WithMaxAttempts ¶
func WithMaxAttempts(n int) EnqueueOption
func WithRunAt ¶
func WithRunAt(t time.Time) EnqueueOption
WithRunAt schedules the job for a future time instead of immediately.
type Handler ¶
Handler executes a claimed job. A nil return completes the job; an error schedules a retry (or moves the job to 'dead' once MaxAttempts is reached, immediately if the error is wrapped with Permanent). Delivery is at-least-once: handlers must tolerate re-execution of the same job.
type Job ¶
type Job struct {
ID int64
Queue string
Type string
Payload json.RawMessage
Attempts int
MaxAttempts int
RunAt time.Time
LastError string
}
Job is a claimed job as handed to a Handler. Attempts includes the attempt currently executing (it is 1 on the first delivery).
type Logger ¶
type Logger interface {
Debug(msg string, keyVals ...any)
Info(msg string, keyVals ...any)
Warn(msg string, keyVals ...any)
Error(msg string, keyVals ...any)
}
Logger is the minimal structured-logging port used by this package. It is deliberately a subset of common structured logger interfaces so that host applications can pass their existing logger without an adapter.
type Option ¶
type Option func(*Queue)
func WithDefaultMaxAttempts ¶
WithDefaultMaxAttempts changes the per-queue default (5) applied to jobs enqueued without WithMaxAttempts.
func WithLogger ¶
func WithQueueName ¶
WithQueueName routes jobs to a queue other than "default".
type Queue ¶
type Queue struct {
// contains filtered or unexported fields
}
Queue enqueues jobs onto a single named queue.
type Subscriber ¶
type Subscriber struct {
// contains filtered or unexported fields
}
Subscriber delivers NOTIFY payloads to registered callbacks over a single dedicated LISTEN connection that reconnects automatically.
func NewSubscriber ¶
func NewSubscriber(dsn string, logger Logger) *Subscriber
NewSubscriber takes a lib/pq connection string for the dedicated LISTEN connection.
func (*Subscriber) Handle ¶
func (s *Subscriber) Handle(channel string, fn func(payload []byte))
Handle registers a callback for one channel. Not safe to call after Start. Callbacks run sequentially on the subscriber's goroutine — keep them fast or hand off internally.
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker claims and executes jobs from one queue. Register handlers, then run Start in a goroutine; cancel its context to begin a graceful drain and use Stop to wait for the drain to finish.
type WorkerConfig ¶
type WorkerConfig struct {
// QueueName selects which queue to consume. Default "default".
QueueName string
// Concurrency is the number of jobs executed simultaneously. Default 2.
Concurrency int
// PollInterval is the safety-net poll cadence; LISTEN provides prompt
// wakeups when ListenDSN is set. Default 5s.
PollInterval time.Duration
// ListenDSN is a lib/pq connection string for the dedicated LISTEN
// connection that wakes the worker as soon as a job is enqueued. Empty
// means poll-only.
ListenDSN string
// Backoff maps the just-failed attempt number (1-based) to the delay
// before the next attempt. Default DefaultBackoff.
Backoff func(attempt int) time.Duration
// RescueAfter re-queues jobs stuck in 'running' longer than this —
// orphans left by a crashed process. It must comfortably exceed the
// longest handler runtime. Default 5m.
RescueAfter time.Duration
Logger Logger
}