Installation Guide
Prerequisites
| Requirement | Version |
|---|---|
| PostgreSQL | 18.x |
Building from source additionally requires Rust 1.82+ and pgrx 0.17.x. Pre-built release artifacts only need a running PostgreSQL 18.x instance.
Installing from a Pre-built Release
1. Download the release archive
Download the archive for your platform from the GitHub Releases page:
| Platform | Archive |
|---|---|
| Linux x86_64 | pg_stream-<ver>-pg18-linux-amd64.tar.gz |
| macOS Apple Silicon | pg_stream-<ver>-pg18-macos-arm64.tar.gz |
| Windows x64 | pg_stream-<ver>-pg18-windows-amd64.zip |
Optionally verify the checksum against SHA256SUMS.txt from the same release:
sha256sum -c SHA256SUMS.txt
2. Extract and install
Linux / macOS:
tar xzf pg_stream-0.1.1-pg18-linux-amd64.tar.gz
cd pg_stream-0.1.1-pg18-linux-amd64
sudo cp lib/*.so "$(pg_config --pkglibdir)/"
sudo cp extension/*.control extension/*.sql "$(pg_config --sharedir)/extension/"
Windows (PowerShell):
Expand-Archive pg_stream-0.1.1-pg18-windows-amd64.zip -DestinationPath .
cd pg_stream-0.1.1-pg18-windows-amd64
Copy-Item lib\*.dll "$(pg_config --pkglibdir)\"
Copy-Item extension\* "$(pg_config --sharedir)\extension\"
3. Using with CloudNativePG (Kubernetes)
pg_stream is distributed as an OCI extension image for use with CloudNativePG Image Volume Extensions.
Requirements: Kubernetes 1.33+, CNPG 1.28+, PostgreSQL 18.
# Pull the extension image
docker pull ghcr.io/grove/pg_stream-ext:0.1.1
See cnpg/cluster-example.yaml and cnpg/database-example.yaml for complete Cluster and Database deployment examples.
4. Local Docker development (without Kubernetes)
For local development without Kubernetes, install the extension files manually into a standard PostgreSQL container from a release archive:
# Extract extension files from the release archive
tar xzf pg_stream-0.1.1-pg18-linux-amd64.tar.gz
cd pg_stream-0.1.1-pg18-linux-amd64
# Run PostgreSQL with the extension mounted
docker run --rm \
-v $PWD/lib/pg_stream.so:/usr/lib/postgresql/18/lib/pg_stream.so:ro \
-v $PWD/extension/:/tmp/ext/:ro \
-e POSTGRES_PASSWORD=postgres \
postgres:18.1 \
sh -c 'cp /tmp/ext/* /usr/share/postgresql/18/extension/ && \
exec postgres -c shared_preload_libraries=pg_stream'
Building from Source
1. Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
2. Install pgrx
cargo install --locked cargo-pgrx --version 0.17.0
cargo pgrx init --pg18 $(pg_config --bindir)/pg_config
3. Build the Extension
# Development build (faster compilation)
cargo pgrx install --pg-config $(pg_config --bindir)/pg_config
# Release build (optimized, for production)
cargo pgrx install --release --pg-config $(pg_config --bindir)/pg_config
# Package for deployment (creates installable artifacts)
cargo pgrx package --pg-config $(pg_config --bindir)/pg_config
PostgreSQL Configuration
Add the following to postgresql.conf before starting PostgreSQL:
# Required — loads the extension shared library at server start
shared_preload_libraries = 'pg_stream'
# Recommended — must accommodate scheduler + refresh workers
max_worker_processes = 8
Note:
wal_level = logicalandmax_replication_slotsare not required. The extension uses lightweight row-level triggers for CDC, not logical replication.
Restart PostgreSQL after modifying these settings:
pg_ctl restart -D /path/to/data
# or
systemctl restart postgresql
Extension Installation
Connect to the target database and run:
CREATE EXTENSION pg_stream;
This creates:
- The
pgstreamschema with catalog tables and SQL functions - The
pgstream_changesschema for change buffer tables - Event triggers for DDL tracking
- The
pgstream.pg_stat_stream_tablesmonitoring view
Verification
After installation, verify everything is working:
-- Check the extension version
SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_stream';
-- Or get a full status overview (includes version, scheduler state, stream table count)
SELECT * FROM pgstream.pgs_status();
Inspecting the installation
-- Check the installed version
SELECT extversion FROM pg_extension WHERE extname = 'pg_stream';
-- Check which schemas were created
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name IN ('pgstream', 'pgstream_changes');
-- Check all registered GUC variables
SHOW pg_stream.enabled;
SHOW pg_stream.scheduler_interval_ms;
SHOW pg_stream.max_concurrent_refreshes;
-- Check the scheduler background worker is running
SELECT * FROM pgstream.pgs_status();
-- List all stream tables
SELECT pgs_schema, pgs_name, status, refresh_mode, is_populated
FROM pgstream.pgs_stream_tables;
-- Check that the shared library loaded correctly
SELECT * FROM pg_extension WHERE extname = 'pg_stream';
-- Verify the catalog tables exist
SELECT tablename
FROM pg_tables
WHERE schemaname = 'pgstream'
ORDER BY tablename;
Quick functional test
CREATE TABLE test_source (id INT PRIMARY KEY, val TEXT);
INSERT INTO test_source VALUES (1, 'hello');
SELECT pgstream.create_stream_table(
'test_st',
'SELECT id, val FROM test_source',
'1m',
'FULL'
);
SELECT * FROM test_st;
-- Should return: 1 | hello
-- Clean up
SELECT pgstream.drop_stream_table('test_st');
DROP TABLE test_source;
Uninstallation
-- Drop all stream tables first
SELECT pgstream.drop_stream_table(pgs_schema || '.' || pgs_name)
FROM pgstream.pgs_stream_tables;
-- Drop the extension
DROP EXTENSION pg_stream CASCADE;
Remove pg_stream from shared_preload_libraries in postgresql.conf and restart PostgreSQL.
Troubleshooting
Unit tests crash on macOS 26+ (symbol not found in flat namespace)
macOS 26 (Tahoe) changed dyld to eagerly resolve all flat-namespace symbols
at binary load time. pgrx extensions reference PostgreSQL server-internal
symbols (e.g. CacheMemoryContext, SPI_connect) via the
-Wl,-undefined,dynamic_lookup linker flag. These symbols are normally
provided by the postgres executable when the extension is loaded as a shared
library — but for cargo test --lib there is no postgres process, so the test
binary aborts immediately:
dyld[66617]: symbol not found in flat namespace '_CacheMemoryContext'
This affects local development only — integration tests, E2E tests, and the extension itself running inside PostgreSQL are unaffected.
The fix is built into the just test-unit recipe. It automatically:
- Compiles a tiny C stub library (
scripts/pg_stub.c→target/libpg_stub.dylib) that provides NULL/no-op definitions for the ~28 PostgreSQL symbols. - Compiles the test binary with
--no-run. - Runs the binary with
DYLD_INSERT_LIBRARIESpointing to the stub.
The stub is only built on macOS 26+. On Linux or older macOS, just test-unit
runs cargo test --lib directly with no changes.
Note: The stub symbols are never called — unit tests exercise pure Rust logic only. If a test accidentally calls a PostgreSQL function it will crash with a NULL dereference (the desired fail-fast behavior).
If you run unit tests without just (e.g. directly via cargo test --lib),
you can use the wrapper script instead:
./scripts/run_unit_tests.sh pg18
# With test name filter:
./scripts/run_unit_tests.sh pg18 -- test_parse_basic
Extension fails to load
Ensure shared_preload_libraries = 'pg_stream' is set and PostgreSQL has been restarted (not just reloaded). The extension requires shared memory initialization at startup.
Background worker not starting
Check that max_worker_processes is high enough to accommodate the scheduler worker plus any refresh workers. The default of 8 is usually sufficient.
Check logs for details
The extension logs at various levels. Enable debug logging for more detail:
SET client_min_messages TO debug1;