Skip to content

Release notes: v0.2.0 #227

@NikolayS

Description

@NikolayS

v0.2.0 — release notes (draft, updated through rc.1 → GA)

This issue tracks the release notes for v0.2.0, growing from the rc.1 draft into the final GA copy. Open for comments/edits while we move through the rc.

The published rc.1 release notes mirror this body and live at the GitHub Release:
https://github.com/NikolayS/pgque/releases/tag/v0.2.0-rc.1.

Install

npm i pgque@rc
pip install --pre pgque-py
go get github.com/NikolayS/pgque-go@latest

All three resolve to 0.2.0-rc.1. SQL schema: \i sql/pgque.sql from this tag (or \i sql/pgque-tle.sql for the pg_tle wrapper).

What rc.1 actually ships

Headline

  • API parity across three client libraries. Python (pgque-py), TypeScript (pgque), and Go (pgque-go) all expose send / send_batch, receive, ack, nack (typed errors, retryAfter seconds, unknown-handler policy), force_next_tick/force_tick, subscribe/unsubscribe, plus a high-level Consumer and the experimental cooperative-consumer API.
  • Set-based send_batch() — 3-5× producer throughput. pgque.send_batch() is now a single set-based insert instead of a PL/pgSQL loop over insert_event(). Local-Postgres microbenchmarks at N=10 000 rows per batch (full numbers in #160):
    • send_batch(jsonb[]): 24,822 → 85,683 ev/s (~3.5× vs the legacy loop).
    • send_batch(text[]): 27,457 → 133,717 ev/s (~4.9× vs the legacy loop).
    • vs single-send() client loop (3,289 ev/s), set-based send_batch is ~26× the throughput on JSON.
    • Big batches benefit the most — the legacy loop's per-row PL/pgSQL overhead grew super-linearly past 50k.
  • Experimental cooperative consumers. A single logical consumer can now be split across multiple workers via subconsumers, with the SQL core handling batch allocation and lease tracking. SQL + all three clients.
  • Sub-second tick rate. Default ticker cadence drops from 1/s to 10/s; tunable via pgque.set_tick_period_ms(). Producer→consumer latency is now sub-second out of the box.
  • Scheduler choice. Alongside pg_cron, a new pg_timetable backend can drive the ticker on managed Postgres deployments where pg_cron is unavailable.
  • Optional pg_tle install path. For platforms that allow trusted-language extensions, \i sql/pgque-tle.sql registers pgque as a pg_tle extension; the default \i sql/pgque.sql install is unchanged.

SQL surface (additions)

  • pgque.send_at(queue, type, payload, deliver_at) — delayed delivery (experimental).
  • Cooperative consumers: pgque.subscribe_subconsumer, pgque.unsubscribe_subconsumer, pgque.receive_coop, pgque.touch_subconsumer (experimental).
  • pgque.set_tick_period_ms(ms) and pgque.config.tick_period_ms column.
  • pgque.config.scheduler column ('pg_cron' | 'pg_timetable').
  • pgque.force_next_tick(queue) as the canonical name; pgque.force_tick(queue) retained as compatibility alias.
  • DLQ refinements: pgque.dlq_replay_all, harder validation of pgque.dlq_purge, idempotent pgque.nack terminal handling.
  • Observability views (experimental): queue_stats, consumer_stats, queue_health, otel_metrics, stuck_consumers, in_flight, throughput, error_rate.

SQL surface (hardenings, behavior)

  • Roles: pgq's producer/consumer split is restored. pgque_writer and pgque_reader are siblings — neither inherits the other. Apps that produce and consume must be granted both. (See upgrade notes.)
  • Security definer: every SECURITY DEFINER function pins search_path = pgque, pg_catalog. pgque.get_batch_cursor(extra_where) is now restricted to pgque_admin.
  • Atomic batch send + 3-5× throughput: pgque.send_batch is set-based (single SQL insert, all-or-nothing) instead of a PL/pgSQL loop over insert_event(). See the headline bullet for benchmark numbers and Benchmark: producer batch insert options and send_batch tradeoffs #160 for the full comparison vs single-send() loops, the legacy implementation, and direct INSERT/COPY.
  • Validation: queue names rejected at >57 bytes; pgque.receive(max_return) rejects values <1; queue creation hardened.
  • PUBLIC EXECUTE: revoked from internal maintenance functions.

Client libraries (Python, TypeScript, Go)

All three drivers reach parity:

  • send / sendBatch, receive, ack, nack (typed errors, retryAfter seconds, unknown-handler policy), forceNextTick/forceTick, subscribe/unsubscribe.
  • High-level Consumer with handler dispatch, LISTEN/NOTIFY wakeup (Python), and a subconsumer option for the experimental coop API.
  • Stale-ack warning: ack() returning rowcount 0 logs a warning rather than failing silently.
  • Cross-driver tested against the same SQL core (audit summary: #211).

Tooling / install

  • \i sql/pgque.sql — single-file install, no extension, no shared_preload_libraries. Works on RDS / Aurora / Cloud SQL / AlloyDB / Supabase / Neon / Crunchy Bridge.
  • \i sql/pgque-tle.sql — optional pg_tle wrapper for platforms that allow trusted-language extensions.
  • pgque.start() schedules pgque_ticker (CALL pgque.ticker_loop() every 1s, internally re-tick at tick_period_ms), pgque_retry_events (30s), pgque_maint (30s), pgque_rotate_step2 (10s).
  • pgque.start_timetable() is the equivalent for pg_timetable deployments.

Behavior changes vs v0.1.0 (read before upgrading)

  • Roles: pgque_writer no longer inherits pgque_reader. Apps that both produce and consume must explicitly hold both roles. The install runs the role-split idempotently; tests/test_upgrade_grants.sql covers the regression. See docs/reference.md → Roles and grants.
  • pgque.create_queue rejects queue names longer than 57 bytes (was: silently truncated/broken).
  • pgque.receive(max_return) rejects values < 1.
  • Default tick cadence is 10/s (was 1/s). Producer→consumer latency improves; WAL/metadata churn rises proportionally. Tune with pgque.set_tick_period_ms() if needed.
  • pgque.maint() no longer issues VACUUM internally (PL/pgSQL cannot). Vacuum scheduling is the operator's responsibility — see docs.

Upgrade from v0.1.0

The supported upgrade path for v0.2.0-rc.1 is:

psql -d <db> -f sql/pgque.sql   # re-run on top of the existing v0.1.0 install

The install is idempotent: every create table is if not exists, every function is create or replace, and new columns are added with guarded alter table … add column blocks. Existing data, queues, consumers, and pending batches are preserved.

A CI test that proves "install v0.1.0, then re-install HEAD on top, everything still works" is tracked at #226 and is a blocker for v0.2.0 GA (not for rc.1).

If you grant roles to your application user, double-check the role split (above) so produce-and-consume apps hold both pgque_reader and pgque_writer.

Known limitations / experimental APIs

These ship under v0.2.0 but are experimental and may change shape in subsequent releases:

  • Cooperative consumers (subconsumers) — function names and edge-case semantics may evolve.
  • Delayed delivery (pgque.send_at + maint_deliver_delayed).
  • Observability views (queue_stats, otel_metrics, etc.) are not loaded by default; opt in via \i sql/experimental/observability.sql.

What's next (post-v0.2.0)

  • Upgrade-test CI job (#226) → blocker for GA.
  • docs/upgrading.md and CHANGELOG.md.
  • Stable cooperative consumer API.
  • Tooling modernisation: Python 3.13/3.14 in CI matrix, Go 1.22+ minimum.
  • Configure npm Trusted Publisher binding so subsequent publishes (rc.2 / GA) ship with provenance attestation.

Stamping milestones

  • rc.1: this draft, audited per #211. Tag v0.2.0-rc.1 on main after #225 + #223 landed; clients distributed via #222, #225, #228, and #230.
  • GA: requires #226 (upgrade test) + docs/upgrading.md + CHANGELOG.md.

Full changelog (v0.1.0 → v0.2.0-rc.1)

77 commits since v0.1.0, grouped by type. Order within each group is chronological.

Features

  • feat(clients/python): bring driver to parity with Go (API, tests, packaging) (#82)
  • feat(clients/typescript): bring driver to parity with Go (API surface, tests, packaging) (#83)
  • feat(clients): expose atomic batch send (#161)
  • feat: optional install as a pg_tle extension (#177)
  • feat: configurable sub-second tick rate (default 10 ticks/sec) (#204)
  • feat: add experimental cooperative consumers (#208)
  • feat(clients/typescript): experimental cooperative consumers (#214)
  • feat(clients/python): experimental cooperative consumers (#216)
  • feat(clients/go): experimental cooperative consumers (#215)
  • feat: add pg_timetable scheduler support (#219)

Performance / benchmarks

  • bench: xmin-horizon torture test for PG-backed queues (#80)
  • charts: add R8 analyzers to benchmark/charts/ (#72)
  • benchmark: add R10 + awa, retire legacy /benchmarks/ (#157)

Fixes

  • fix: multiple bug fixes and dead code cleanup (#75)
  • fix: follow-ups to fix: multiple bug fixes and dead code cleanup #75 (quote_ident, dlq partial-success, Nack tests) (#79)
  • fix(clients/go): Nack placeholder, eager Connect ping, Consumer panic recovery (#115)
  • fix(pgque.receive): reject max_return < 1 and document batch-ownership (#114)
  • fix(pgque.nack): canonical + idempotent DLQ terminal handling (#116)
  • fix(pgque.receive): finish empty batch instead of stranding consumer (#117)
  • fix(pgque.create_queue): reject queue names > 57 bytes (#122)
  • fix(pgque.batch_retry): cast NULL to xid8 not int8 (#120)
  • fix(clients/python): make Consumer thread-safe and correct test xact pattern (#129)
  • fix(pgque.maint): drop VACUUM (cannot run inside PL/pgSQL) (#119)
  • fix(clients/typescript): per-queue ticker overload, fix nack test schema, serialise vitest (#130)
  • fix: revoke PUBLIC EXECUTE and harden SECURITY DEFINER queue_extra_maint (#118)
  • fix(clients/python): consumer warns + acks unhandled event types (#121)
  • fix(clients/go): green up live-pg test suite (#132)
  • fix(roles): restore PgQ producer/consumer split (#163)
  • fix: make send_batch set-based (#159)
  • fix(security): restrict get_batch_cursor(extra_where) to pgque_admin (#169)
  • fix(clients/python): bounded LISTEN/NOTIFY wait honors stop() and notify (#168)
  • fix(clients/go): nack-fail handling, unknown-type policy, options, SendBatch (#153)
  • fix(clients/typescript): nack-fail handling, unknown-type policy, undefined payload, safe max_messages default (#154)
  • fix(clients/python): nack unknown types by default + safe max_messages default (#155)
  • fix(clients/go): typed errors for cross-driver parity (#200)
  • fix(clients/typescript): surface ticker/forceTick SQL returns (#188)
  • fix(clients/typescript): scope bigint parser to pgque pool (#189)
  • fix(clients): surface SQL ack() rowcount (TS+Go) (#192)
  • fix(clients/python): tighten test transactions + assertions (#193)
  • fix: docs scrub + upgrade-grants regression test (#167)
  • fix(clients/python): warn on stale ack (#203)
  • fix: harden queue validation (#195)
  • fix(clients/typescript): nack reasons, retryAfter seconds, coverage (#186)
  • fix(clients/python): rename to unknown_handler_policy, align with Go/TS (#187)
  • fix(ci): pin checkout SHA + PEP 440 versions (#225)
  • fix(ci/release-ts): npm publish --tag for prereleases (#228)

Refactors

  • refactor: add force_next_tick alias for force_tick (#206)
  • refactor(clients/go): NackOptions struct API (#185)

Tests

  • test(clients/go): comprehensive coverage — error paths, concurrency, edge cases, benchmarks (#81)
  • test(clients/python): commit before force_tick in 2 tests (#139)
  • test: add SQL contract + e2e coverage for last ~20 merged PRs (#205)
  • test: add receive two-session lock harness (#175)
  • test: synchronize two-session receive lock harness (#220)

CI / chore

  • chore(clients/go): polish for pkg.go.dev (LICENSE, doc.go, README, examples) (#78)
  • chore(clients/typescript): standardize on bun (#162)
  • ci: remove Claude Code Review workflow (#173)
  • ci: split client library checks (#174)
  • chore: ignore .claude/worktrees/ (#191)
  • chore(clients): add distribution release plumbing
  • ci: make client release dry-runs approval-free (#222)
  • chore: stamp v0.2.0-rc.1 (#223)
  • docs: update install commands; drop skip-locked keyword (#230)

Docs

  • docs(README): add buyer self-qualification + HN discussion link (#73)
  • docs: three-latencies explanation (#68)
  • docs: bench methodology + tooling under benchmark/ (#66)
  • docs(README): mention benchmark/ directory (#128)
  • docs: reference + examples + roles cleanup (#126)
  • docs: consolidate benchmark directories (#172)
  • docs: add roadmap (#171)
  • docs(clients): cross-driver parity matrix (#152)
  • docs: warn extra_where is trusted SQL fragment (#176)
  • docs: focus README and docs on new users
  • docs: expand roadmap table (#202)
  • docs: document the separate-transactions rule across user-facing surfaces (#190)
  • docs: align ticker latency wording with 10hz default (#207)
  • docs: document WAL budget for tick cadence (#209)
  • docs: clarify default delivery latency estimate (#213)
  • docs: surface PgQ heritage in README intro (#218)

Other

  • small formatting tweak in reference.md (#77)
  • Polish README (#221)

This draft will be updated in this issue body until GA. Comments welcome — anything missing, miscategorised, or worth highlighting?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions