Multi-Database Deployments
pg_trickle is multi-database aware. A single PostgreSQL server can host pg_trickle in any number of databases simultaneously, and the extension's background workers handle the fan-out automatically. You do not need to start anything per database; the launcher discovers them.
Architecture in one diagram
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL 18 server │
│ │
│ ┌────────────┐ │
│ │ Launcher │ ── scans pg_database every ~10 s │
│ └─────┬──────┘ │
│ │ spawns │
│ ┌────┼────────────────────┬────────────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Scheduler │ │Scheduler │ │Scheduler │ │
│ │ db_a │ │ db_b │ │ db_etl │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ refresh jobs │ refresh jobs │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Shared dynamic refresh worker pool │ │
│ │ (max_dynamic_refresh_workers, per-DB quotas) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- One launcher per PostgreSQL server.
- One scheduler per database that has
pg_trickleinstalled. - One shared dynamic worker pool for parallel refreshes.
The launcher restarts crashed schedulers automatically. Each scheduler is fully independent; failure in one database does not affect the others.
Enabling pg_trickle in additional databases
Just install the extension. The launcher will pick it up on the next discovery cycle (within ~10 s).
\c db_b
CREATE EXTENSION pg_trickle;
Verify the scheduler started:
SELECT * FROM pgtrickle.cluster_worker_summary();
Expected: one row per database with a column showing the scheduler PID and uptime.
Resource budgeting across databases
Background-worker slots are a finite, server-wide resource. The formula:
max_worker_processes ≥ launchers(1)
+ schedulers(N_databases)
+ max_dynamic_refresh_workers
+ autovacuum_max_workers
+ max_parallel_workers
+ other_extensions
For 4 databases each running pg_trickle, with 8 dynamic refresh workers and modest autovacuum:
max_worker_processes = 32
max_parallel_workers = 8
pg_trickle.max_dynamic_refresh_workers = 8
pg_trickle.per_database_worker_quota = 4
Without per_database_worker_quota, one busy database can starve
the others. Set it to max_dynamic_refresh_workers / N_databases
or higher.
Cluster-wide observability
-- One row per database with per-database stats
SELECT * FROM pgtrickle.cluster_worker_summary();
-- Combined health across every database
SELECT datname, severity, message
FROM pgtrickle.cluster_health_check() -- (where exposed)
WHERE severity != 'OK';
The pgtrickle workers command also aggregates
across databases when invoked against a server-level URL.
Common patterns
Per-tenant database
If you isolate tenants by database, each gets its own scheduler.
Combined with tiered_scheduling = on, low-traffic tenants pay
almost no scheduler cost.
App database + analytics database
A common topology: app_db runs OLTP and a few IMMEDIATE stream
tables; analytics_db (often a logical replica) runs the heavy
DIFFERENTIAL aggregates. The launcher handles both.
Tenant-of-tenants (Citus)
For very large multi-tenant deployments, see Citus — distributed sources can replace per-tenant databases.
Caveats
- pg_trickle does not create cross-database stream tables. Stream tables live in exactly one database; their sources must live there too. Use logical replication or downstream publications to bridge databases.
shared_preload_libraries = 'pg_trickle'is server-wide. The launcher then discovers per-database state.pg_trickle.enabled = offdisables the launcher (and therefore every scheduler) — use it for maintenance windows.
See also: Scaling Guide · Capacity Planning · Configuration