Async SQL execution in PostgreSQL using pg_background, showing a client session launching a query in a background worker and later retrieving or canceling it with a safe handle.

There’s a special kind of joy in watching a database do something heavy… without making your app threads cry.

That’s the promise of pg_background: execute SQL asynchronously in background worker processes inside PostgreSQL, so your client session can move on—while the work runs in its own transaction. 

It’s a deceptively simple superpower:

  • Kick off a long-running query (or maintenance) without holding the client connection open
  • Run “autonomous transaction”-style side effects (commit/rollback independent of the caller)
  • Monitor, wait, detach, or cancel explicitly
  • Keep the operational model “Postgres-native” instead of adding another job system  

What pg_background is (and what it isn’t)

pg_background enables PostgreSQL to execute SQL commands asynchronously in dedicated background worker processes. Unlike approaches such as opening another connection (e.g., dblink) or doing client-side async orchestration, these workers run inside the server with local resources and their own transaction scope. 

It’s not a full-blown scheduler. It’s not a queueing platform. It’s a sharp tool: “run this SQL over there, and let me decide how to interact with it.”

Why you’d want this in production

I’m opinionated here: most teams don’t need more moving parts. They need fewer, with better failure modes.

pg_background shines when you need:

  • Non-blocking operations: launch long queries without pinning client connections  
  • Autonomous transactions: commit/rollback independently of the caller’s transaction  
  • Resource isolation & safer cleanup: worker lifecycle is explicit (launch / result / detach / cancel / wait)  
  • Server-side observability: list workers and see state/errors (v2)  

Common production patterns:

  • background VACUUM / ANALYZE / REINDEX
  • async backfills or data repairs
  • fire-and-forget audit/outbox writes
  • “do the expensive part later” workflows (especially in OLTP-heavy apps)

The big deal: v2 API (recommended)

The project now strongly nudges new deployments toward the v2 API, which uses a (pid, cookie) handle to protect against PID reuse issues (a real-world footgun in long-lived systems). 

From the repo docs, v2 brings: cookie-based identity, explicit cancellation vs detach, synchronous wait, and better monitoring via list functions. 

Quick taste (v2)

-- Launch work asynchronously and capture a safe handle (pid + cookie)
SELECT * FROM pg_background_launch_v2(
'SELECT pg_sleep(5); SELECT count(*) FROM large_table'
) AS handle;
-- Retrieve results (note: results can only be consumed once)
SELECT * FROM pg_background_result_v2(<pid>, <cookie>) AS (count BIGINT);
-- If you only need side effects (no results), submit instead
SELECT * FROM pg_background_submit_v2(
'INSERT INTO audit_log(ts, event) VALUES (now(), ''system_check'')'
);
-- Cancel vs detach are distinct (detach does NOT cancel)
SELECT pg_background_cancel_v2(<pid>, <cookie>);
SELECT pg_background_detach_v2(<pid>, <cookie>);

That last point—detach is not cancel—is called out as an important semantic distinction. 

Installation + one operational rule you shouldn’t ignore

Install is standard extension build + CREATE EXTENSION, but the “gotcha” is not subtle:

background workers consume max_worker_processes.

So size it intentionally.

The README walks through enabling the extension and setting max_worker_processes. 

What’s new in the latest releases (v1.6 → v1.8)

If you haven’t looked recently: the project has had a meaningful “production hardening” arc.

v1.6 (released Feb 5, 2026): production stabilization + v2 API becomes the star

This release explicitly positions itself as “stabilizes pg_background for production use” and introduces the recommended v2 API with cookie-based worker handles. 

Highlights include:

  • v2 API with PID + cookie protection (safe against PID reuse)  
  • Improved DSM lifecycle handling
  • Safer error handling & memory cleanup
  • Better observability/diagnostics
  • PostgreSQL 12–18 compatibility  

Fixed items called out: NULL safety in error translation, race conditions in cleanup, memory context leaks in error paths, clearer error codes for cookie mismatches. 

Known limitations that remain (still important in real deployments):

  • Windows cancel limitations
  • COPY protocol intentionally disabled
  • transaction control inside workers not allowed  

v1.7 (merged Feb 13, 2026): security + memory + CPU efficiency

v1.7 is where the internals got sharper edges sanded down—especially around handle security and long-running sessions.

Notable improvements listed in the v1.7 PR:

  • cookie generation switched to pg_strong_random() (cryptographically secure) instead of predictable composition  
  • a dedicated WorkerInfoMemoryContext to avoid long-session memory bloat  
  • exponential backoff polling in wait/cancel loops to reduce CPU churn  
  • helper refactors + better constants/docs, plus explicit NULL checks around DSM creation  

v1.8 (released Feb 13, 2026): operational controls + observability perks + packaging updates

The v1.8 release rolls up CI/packaging work and modernizes supported version stance: Docker-based CI refactor, tighter .gitignore, plus dropping PostgreSQL 13 and older to align with supported majors. 

It also adds real operator-friendly features:

New in 1.8 (from the extension SQL and docs):

  • pg_background_stats_v2() for session statistics  
  • pg_background_progress() + pg_background_get_progress_v2() for progress reporting  
  • new GUCs: pg_background.max_workers, pg_background.worker_timeout, pg_background.default_queue_size  
  • internal improvements called out: strong random cookies, dedicated memory context, exponential backoff polling, UTF-8 aware truncation, max-workers enforcement  

And if you’re upgrading, the README’s compatibility table is blunt: PG 14+ is the current minimum supported line, with guidance to use older pg_background versions for older Postgres majors. 

A practical checklist for using pg_background safely

Here’s the “don’t make tomorrow-you hate today-you” list:

  1. Use v2 unless you have a legacy dependency. PID reuse protection is worth it.  
  2. Treat max_worker_processes as a capacity budget. Background work competes with everything else.  
  3. Remember: detach ≠ cancel. Detach is “stop tracking”, not “stop running.”  
  4. Design for one-time result consumption. Read results once; store them if you need replay.  
  5. Use the new knobs (v1.8+): cap per-session workers, set timeouts, right-size queue memory.  
  6. Use progress + stats for real operations. Observability prevents “mystery background gremlins.”  

Closing thought

The best infrastructure is the kind that quietly does the work and refuses to be dramatic.

pg_background is that kind of tool: it gives PostgreSQL a clean, explicit async execution lane—now with safer handles (v2), stronger internals (v1.7), and operator-friendly controls and telemetry (v1.8). 

If you want, paste your target use case (VACUUM jobs, backfills, “autonomous transaction” patterns, async ETL, etc.) and I’ll suggest a production-ready pattern with guardrails using the v2 API.

Leave a comment

Quote of the week

“Success is not the key to happiness. Happiness is the key to success. If you love what you are doing, you will be successful.”

– Albert Schweitzer