Actors/Packages/Postgres/Installation
@sigx/actors-pg · Preview

Installation#

One pool, four providers, and a schema you own.

Install#

Terminal
pnpm add @sigx/actors-pg pg

Postgres ≥ 13, pg ≥ 8.

Create the schema#

Tables live in one Postgres schema, sigx by default. DDL is explicit — nothing issues it implicitly, so a production role that never runs the bootstrap needs only DML grants.

TypeScript
import pg from 'pg';
import { ensurePgSchema, pgSchemaSql } from '@sigx/actors-pg';

const pool = new pg.Pool({ connectionString: process.env.PG_URL });

await ensurePgSchema(pool);

Every replica may call this at boot, concurrently. ensurePgSchema() takes pg_advisory_xact_lock as the first statement of the same multi-statement string as the DDL, so the lock spans the whole thing and releases at commit — before the pooled client is recycled. A bounded, jittered retry sits underneath as a backstop.

Two things to know about it:

  • pgSchemaSql() takes no lock. It is pure DDL, for a migration tool that brings its own serialization.
  • ensurePgSchema() expects to own its transaction. Called with a PgQueryable already inside your open transaction, a failure poisons that transaction and the retry burns its attempts on 25P02.

In production, running the SQL through your migration tool remains the better shape:

TypeScript
console.log(pgSchemaSql('sigx'));

Wire it up#

TypeScript
import { defineActorApp } from '@sigx/actors/host';
import { cluster } from '@sigx/actors/cluster';
import { pgCluster, pgReminders, pgStorage } from '@sigx/actors-pg';

export const app = defineActorApp({
    actors,
    storage: pgStorage({ pool }),
    reminders: pgReminders({ pool }),
}).use(cluster({
    providers: pgCluster({ pool }),
    advertise: `http://${process.env.POD_IP}:7311`,
    secret: process.env.HOST_SECRET,
}));

Every provider takes pool or url — with url, the package constructs its own pg.Pool. Sharing one pool is the point of this package.

Options#

OptionDefaultApplies toMeaning
pool / urlalla pg.Pool, or a URL to construct one
schemasigxallvalidated as an SQL identifier
heartbeatMs5000membershipheartbeat cadence
ttlMs15000membershipexpiry, on the database clock
pollMs5000membershipview poll cadence, and the propagation bound

Pool sizing#

Every provider shares the pool, and pgMembership() checks out a connection to hold LISTEN when it can. Size the pool with that in mind — a pool of one works, but pushes membership onto the poll path permanently.

Verify#

Terminal
PG_URL=postgres://postgres:postgres@localhost:5432/postgres pnpm test -- actors-pg

The provider suite is env-gated on PG_URL; CI provides a postgres:16 service container and the rest of the matrix skips cleanly.

Next steps#