@rotorsoft/act-pg 1.10.0 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,7 +70,11 @@ All fields are optional and have sensible defaults:
70
70
  | `schema` | `public` | Schema for event + streams tables |
71
71
  | `table` | `events` | Base name (`<table>` for events, `<table>_streams` for subscriptions) |
72
72
  | `notify` | `false` | Opt-in `LISTEN`/`NOTIFY` for cross-process commit wakeup (see below) |
73
- | `max`, `idleTimeoutMillis`, …pg.PoolConfig | (pg defaults) | Pass-through to node-postgres pool config |
73
+ | `max` | `20` | Pool size ceiling (see [Pool tuning](#pool-tuning)) |
74
+ | `connectionTimeoutMillis` | `10000` | Client-acquisition timeout — a saturated pool fails fast as `StoreError` instead of hanging |
75
+ | `idleTimeoutMillis` | `30000` | Idle clients stay warm across drain cycles before being closed |
76
+ | `statement_timeout` | `60000` | Per-statement server-side ceiling — a wedged statement releases its client instead of holding it hostage |
77
+ | …rest of `pg.PoolConfig` | (pg defaults) | Pass-through to node-postgres pool config |
74
78
 
75
79
  ```ts
76
80
  // Production deployment via env vars
@@ -81,12 +85,30 @@ store(new PostgresStore({
81
85
  user: process.env.DB_USER,
82
86
  password: process.env.DB_PASSWORD,
83
87
  schema: process.env.DB_SCHEMA ?? "public",
84
- max: 20, // pool size — raise for drain-heavy workloads
88
+ max: 40, // pool size — raise for drain-heavy workloads
85
89
  }));
86
90
  ```
87
91
 
88
92
  Multi-tenant deployments often want one schema per tenant. The store accepts both — use them rather than namespacing stream IDs.
89
93
 
94
+ ### Pool tuning
95
+
96
+ Nearly every store method checks out a client for a multi-statement transaction — `commit`, `claim`, `ack`, `block`, `subscribe`, `truncate`, `restore` all hold one from `BEGIN` to `COMMIT`. Concurrency stacks up from three directions:
97
+
98
+ - **Drain lanes.** Every declared lane (plus the implicit `"default"`) runs its own `DrainController`, and all controllers drain in parallel. Within a lane, up to `streamLimit` (default 10) reaction handlers run concurrently — each handler that commits (`app.do` inside a slice) holds a client for the duration of its commit transaction.
99
+ - **API traffic.** Every in-flight action handled by your HTTP/tRPC layer holds a client during its commit.
100
+ - **`notify: true`.** One extra dedicated long-lived LISTEN client per process.
101
+
102
+ Sizing rule per process:
103
+
104
+ ```
105
+ max ≥ Σ(streamLimit per lane) + peak concurrent API commits + (notify ? 1 : 0) + headroom (2–4)
106
+ ```
107
+
108
+ The default `max: 20` covers the single-default-lane case (10) plus modest API concurrency and the LISTEN client. Raise it when you add lanes or expect bursty API traffic; keep the sum across all worker processes under your Postgres `max_connections` budget (minus superuser/maintenance reservations).
109
+
110
+ When the pool does saturate, acquisition fails after `connectionTimeoutMillis` as a `StoreError` whose `operation` names the starved method (`"commit"`, `"claim"`, …) and whose `cause` carries the driver error — a clear signal to resize, instead of an indefinite hang.
111
+
90
112
  ## Common patterns
91
113
 
92
114
  ### Cross-process `LISTEN`/`NOTIFY` (opt-in)