Skip to main content

Install

pg_reactive is a plain PostgreSQL C extension. It builds with PGXS, loads through shared_preload_libraries, and exposes everything through the pgr schema once you run CREATE EXTENSION. There is no daemon, no replication slot, no external coordinator — a LISTEN pgr from any libpq client is the entire delivery mechanism.

This page is the from-source path for a Postgres user who just wants the extension. The Go proxy, the SDK, and Docker are all optional layers on top — skip them entirely if you only need server-side live queries driven by LISTEN/NOTIFY.

If you would rather not compile anything, jump to All-in-one Docker or use pgStack, the batteries-included umbrella that bundles the extension, proxy, auth, and REST for you.

Prerequisites

RequirementNotes
PostgreSQL 16 or newerBoth the running server and its development headers.
pg_config on $PATHThe PGXS build reads it to locate headers, libs, and the install paths. It ships in the -dev / -devel package.
A C toolchaingcc or clang, plus make.

On Debian/Ubuntu the dev headers and toolchain are:

sudo apt-get install -y postgresql-server-dev-16 build-essential

On RHEL/Fedora the equivalent is postgresql16-devel plus gcc make.

Confirm pg_config resolves to the 16+ install you intend to build against — the build links against whatever pg_config points at:

pg_config --version # expect: PostgreSQL 16.x (or newer)

Build from source

Clone the repo and build inside ext/. The Makefile is a standard PGXS makefile: MODULE_big = pg_reactive links the five C objects (pg_reactive.o, dependency.o, invalidation.o, recompute.o, bgworker.o) into one shared library, and include $(PGXS) pulls the PostgreSQL build rules so make and make install resolve headers and install paths through pg_config automatically.

git clone https://github.com/ndokutovich/pg_reactive.git
cd pg_reactive/ext
make
sudo make install

sudo make install copies the shared library into the server's $libdir and the SQL/control files into SHAREDIR/extension/. If you own those directories (e.g. a per-user PostgreSQL build), plain make install without sudo works.

To build against a PostgreSQL that is not first on your PATH, point PGXS at the right pg_config:

make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config
sudo make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config install

The build compiles with -Wall -Werror, so any warning is a hard failure — a clean make means a clean library.

Load the library

pg_reactive registers shared memory and hooks at postmaster start, so it must be in shared_preload_libraries. Add it to postgresql.conf:

# postgresql.conf
shared_preload_libraries = 'pg_reactive'

Set the restart-only GUCs

Three of the five GUCs are PGC_POSTMASTER — they are read once at server start and can only be changed by editing postgresql.conf (or passing -c flags) and restarting. Set the ones you need alongside the preload line:

# postgresql.conf
shared_preload_libraries = 'pg_reactive'
pg_reactive.max_subscriptions = 1024 # shared-memory subscription slots
pg_reactive.async_recompute = off # optional background-worker recompute
pg_reactive.database = postgres # DB the async worker connects to

max_subscriptions sizes the shared-memory hash that holds your live queries — 1024 slots by default. async_recompute (default off, fine for most workloads) and database only matter if you enable the optional background worker. The two runtime-tunable GUCs — pg_reactive.batch_invalidation and pg_reactive.notify_channel — are PGC_SUSET and need no restart; see GUCs for the full table.

:::warning Containers on tmpfs: use -c flags, not ALTER SYSTEM ALTER SYSTEM SET pg_reactive.max_subscriptions = … writes to postgresql.auto.conf in the data directory. If your data directory lives on tmpfs (the dev Docker setup runs PG data on tmpfs so it is ephemeral), that file is wiped on every container restart and your setting silently reverts to the default. In a container, pass PGC_POSTMASTER GUCs through the compose command: instead, where they are reapplied at every boot:

# docker-compose.yml (db service)
command:
- postgres
- -c
- shared_preload_libraries=pg_reactive
- -c
- pg_reactive.max_subscriptions=1024

:::

Restart PostgreSQL

Because shared_preload_libraries and the PGC_POSTMASTER GUCs are read at postmaster start, a reload is not enough — restart the server:

sudo systemctl restart postgresql # or: pg_ctl restart -D <datadir>

Create the extension

With the library preloaded, create the extension in each database that needs live queries:

CREATE EXTENSION pg_reactive;

This runs the install script, which creates the pgr schema itself and installs every function (pgr.subscribe, pgr.unsubscribe, pgr.get_subscriptions, pgr.restore_subscriptions, pgr.stats) into it.

:::note Why the control file has no schema = pg_reactive.control deliberately omits a schema = line. The install script creates the pgr schema and its objects explicitly; declaring schema = pgr in the control file as well would make PostgreSQL try to create the schema a second time and the install would fail. (pgr is used rather than pg_reactive because the pg_ prefix is reserved for system schemas.) Leave the control file alone. :::

All pgr functions are REVOKEd from PUBLIC by default. As a superuser you can call them straight away; to let an application role manage or inspect subscriptions, GRANT EXECUTE explicitly. See the security model for the safe way to expose subscription registration to untrusted clients.

Smoke test

Verify the install end to end with a tiny query, an INSERT, and a LISTEN. Run this in psql:

-- A table to watch.
CREATE TABLE orders (
id serial PRIMARY KEY,
customer_id int,
total numeric,
status text
);

-- Subscribe to a live query under a stable id.
SELECT pgr.subscribe('orders_active', $$
SELECT id, customer_id, total, status
FROM orders
WHERE status IN ('open', 'pending')
$$);

-- Listen on the default channel.
LISTEN pgr;

-- This INSERT matches the query — it should fire a delta.
INSERT INTO orders (customer_id, total, status) VALUES (42, 99.99, 'open');

In psql, the asynchronous notification is printed after the next command (psql checks for notifications between statements). A no-op nudges it out:

SELECT 1;
Asynchronous notification "pgr" with payload
"{"query_id":"orders_active","seq":1,"inserted":[{"id":1,"customer_id":42,"total":99.99,"status":"open"}],"deleted":[]}"
received from server process with PID ...

Seeing that inserted delta means the extension is live: pgr.subscribe registered the dependency, the AFTER STATEMENT trigger fired, and the delta went out on the pgr channel. The wire format page documents every message shape (delta, overflow, invalidated). Clean up when you are done:

SELECT pgr.unsubscribe('orders_active');

:::tip Snapshots are ephemeral — restore after a restart Live-query snapshots live in UNLOGGED tables and the dependency hash lives in shared memory, so both are wiped on every server restart. The durable record of your subscriptions persists in pgr.persisted_subscriptions, but the in-memory state has to be rebuilt. Run this once per database after a restart to bring live queries back:

SELECT pgr.restore_subscriptions();

Until you do, subscribed queries emit no deltas. The all-in-one image runs this for you on boot. :::

All-in-one Docker

If you want PostgreSQL + the extension + the Go proxy in one container without compiling anything on your host, use the canonical all-in-one image. It is built from docker/Dockerfile.all-in-one: the build compiles and make installs the extension into a postgres:16-bookworm base, drops in the prebuilt pgstack-proxy, and ships an entrypoint that starts PostgreSQL, runs pgr.restore_subscriptions() for every database with the extension, then starts the proxy as the lifecycle leader.

Build it from the repo root (the build context is the whole repo):

docker build -f docker/Dockerfile.all-in-one -t pgstack-all-in-one .

PGC_POSTMASTER GUCs are forwarded to postgres through the compose command: — the entrypoint passes $@ straight to postgres:

services:
pgr:
image: pgstack-all-in-one
ports:
- "127.0.0.1:5432:5432" # PostgreSQL
- "127.0.0.1:8080:8080" # WebSocket proxy
command:
- -c
- shared_preload_libraries=pg_reactive
- -c
- pg_reactive.max_subscriptions=1024

For deployment-specific setup (migrations, seed SQL, edge-function registration), ship an executable /app/post-init.sh — the entrypoint runs it after PostgreSQL is ready and before the proxy starts.

Where to go next

  • Subscribe — the pgr.subscribe / pgr.unsubscribe API and delta vs notify modes.
  • Query support — which SELECT shapes are tracked.
  • GUCs — the complete five-parameter configuration reference.
  • Proxy — fan deltas out to browsers over WebSocket.
  • For the full backend stack (auth, REST, RLS, Studio), start at the pgStack quickstart.