@shivaedev/sql 0.30.0 → 0.31.0
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/CHANGELOG.md +7 -0
- package/README.md +23 -0
- package/dist/applyLowMemoryPostgres.d.ts +7 -0
- package/dist/applyLowMemoryPostgres.d.ts.map +1 -0
- package/dist/applyLowMemoryPostgres.js +24 -0
- package/dist/applyLowMemoryPostgres.js.map +1 -0
- package/package.json +4 -4
- package/src/applyLowMemoryPostgres.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0 - 2026-11-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Apply an opt-in low-memory PostgreSQL preset with an explicit per-app point-in-time recovery choice.
|
|
8
|
+
- Trim PostgreSQL's clean filesystem cache without privileges through the server's Effect process boundary.
|
|
9
|
+
|
|
3
10
|
## 0.30.0 - 2026-11-01
|
|
4
11
|
|
|
5
12
|
### Added
|
package/README.md
CHANGED
|
@@ -29,6 +29,29 @@ Migrations are ordinary Effects loaded by native Migrator. `migratePostgres` add
|
|
|
29
29
|
|
|
30
30
|
The work has three parts: describe the model and provide its database once, choose repository operations per feature, and put each changed write inside the transaction that owns its commit.
|
|
31
31
|
|
|
32
|
+
### Low-memory PostgreSQL hosts
|
|
33
|
+
|
|
34
|
+
`applyLowMemoryPostgres({ pointInTimeRecovery })` from `@shivaedev/sql/applyLowMemoryPostgres.ts` is an opt-in database-owner operation for small applications on Linux hosts that bill cgroup memory, including filesystem page cache. It uses the provided native `SqlClient` to write `ALTER SYSTEM` settings and request a reload. Run it once, outside a transaction, against the app's dedicated PostgreSQL instance with a role allowed to change these settings. It changes the whole instance, including every database on it. Keep it out of normal application startup and numbered migrations.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Config, Effect } from "effect";
|
|
38
|
+
import { applyLowMemoryPostgres } from "@shivaedev/sql/applyLowMemoryPostgres.ts";
|
|
39
|
+
import { postgresLayer } from "@shivaedev/sql/postgresLayer.ts";
|
|
40
|
+
|
|
41
|
+
const configure = Effect.gen(function* () {
|
|
42
|
+
const url = yield* Config.Redacted("DATABASE_URL");
|
|
43
|
+
yield* applyLowMemoryPostgres({ pointInTimeRecovery: false }).pipe(Effect.provide(postgresLayer(url)));
|
|
44
|
+
}).pipe(Effect.scoped);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Restart PostgreSQL after this operation. `shared_buffers=32MB`, `max_connections=50` and `wal_buffers=4MB` take effect on restart. The reload settings are `jit=off`, `max_parallel_workers_per_gather=0`, `hash_mem_multiplier=1`, `autovacuum_max_workers=2`, `autovacuum_work_mem=32MB`, `idle_in_transaction_session_timeout=10min` and `wal_compression=zstd`. `idle_session_timeout=0` keeps idle LISTEN connections alive. Use PostgreSQL 18 with zstd support; older versions can require a restart for additional settings. Command-line `-c` settings override `ALTER SYSTEM`, so remove conflicting overrides. A failed operation can leave earlier settings written; correct the reported SQL error before restarting.
|
|
48
|
+
|
|
49
|
+
Point-in-time recovery is an explicit per-app memory and storage choice. With `pointInTimeRecovery: false`, the preset disables WAL archiving with `archive_mode=off`; it leaves `archive_timeout` alone and needs no backup bucket or pgBackRest configuration. Remove the host's archive and backup jobs and bucket configuration as well. This choice gives up recovery to a time between backups. With `true`, retain the host's working WAL archiving, backup bucket and restore procedure; the preset sets `archive_timeout=300` seconds. It does not provision backups or enable archiving. A timeout of 60 seconds can force a 16 MB WAL segment each minute even when the app is idle.
|
|
50
|
+
|
|
51
|
+
For PITR hosts, pgBackRest derives its process count from the CPU limit. Set a CPU limit of 2 or explicitly set `PGBACKREST_BACKUP_PROCESS_MAX=1` on the PostgreSQL service. Eight processes under a 32-vCPU limit exhausted a 477 MiB container in the reported Railway workload. Declare both CPU and memory limits in infrastructure as code: an omitted limit can be reset by the next plan. Keep enough headroom for backups, maintenance and the app's total connection budget across replicas and workers.
|
|
52
|
+
|
|
53
|
+
Pair this preset with server's [cache trim and connection lifetime](https://github.com/ShivaeDev/platform/tree/main/packages/server#low-memory-postgresql-hosts). Railway measurements supplied with this preset found billing within 0.2% of `memory.current`, shared memory around 45 MB instead of 175 MB, and small-app idle usage around 150–250 MB instead of 0.95 GB with preset, trim and connection recycling. Peaks returned within 5–10 minutes. These are workload observations, not memory limits or a promise for another app; measure your own cgroup before selecting a memory limit.
|
|
54
|
+
|
|
32
55
|
### One PostgreSQL pool per application
|
|
33
56
|
|
|
34
57
|
`postgresLayer(url, options)` from `@shivaedev/sql/postgresLayer.ts` provides the native `PgPool` service from `@effect/sql-pg/PgPool`, `PgClient` and `SqlClient` together. `options` accepts `applicationName`, `maxConnections`, `minConnections`, `connectionTTL`, `idleTimeout`, `transformQueryNames` and `transformResultNames`. Queries, transactions and raw connections reserved with `yield* pool.reserve` share the same `maxConnections` budget. A reservation holds its connection until its scope closes; release it before waiting for work that needs that slot.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
export interface LowMemoryPostgresOptions {
|
|
4
|
+
readonly pointInTimeRecovery: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const applyLowMemoryPostgres: (args_0: LowMemoryPostgresOptions) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
7
|
+
//# sourceMappingURL=applyLowMemoryPostgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applyLowMemoryPostgres.d.ts","sourceRoot":"","sources":["../src/applyLowMemoryPostgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACtC;AAED,eAAO,MAAM,sBAAsB,wHAoBjC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
export const applyLowMemoryPostgres = Effect.fn("Sql.applyLowMemoryPostgres")(function* ({ pointInTimeRecovery }) {
|
|
4
|
+
const sql = yield* SqlClient.SqlClient;
|
|
5
|
+
const settings = {
|
|
6
|
+
"autovacuum_max_workers": "2",
|
|
7
|
+
"autovacuum_work_mem": "32MB",
|
|
8
|
+
"hash_mem_multiplier": "1",
|
|
9
|
+
"idle_in_transaction_session_timeout": "10min",
|
|
10
|
+
"idle_session_timeout": "0",
|
|
11
|
+
jit: "off",
|
|
12
|
+
"max_connections": "50",
|
|
13
|
+
"max_parallel_workers_per_gather": "0",
|
|
14
|
+
"shared_buffers": "32MB",
|
|
15
|
+
"wal_buffers": "4MB",
|
|
16
|
+
"wal_compression": "zstd",
|
|
17
|
+
...(pointInTimeRecovery ? { "archive_timeout": "300" } : { "archive_mode": "off" }),
|
|
18
|
+
};
|
|
19
|
+
for (const [name, value] of Object.entries(settings)) {
|
|
20
|
+
yield* sql.unsafe(`ALTER SYSTEM SET ${name} = '${value}'`);
|
|
21
|
+
}
|
|
22
|
+
yield* sql `select pg_reload_conf()`;
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=applyLowMemoryPostgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applyLowMemoryPostgres.js","sourceRoot":"","sources":["../src/applyLowMemoryPostgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,EAAE,CAAC,4BAA4B,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,mBAAmB,EAA4B;IACzI,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,MAAM,QAAQ,GAAG;QAChB,wBAAwB,EAAE,GAAG;QAC7B,qBAAqB,EAAE,MAAM;QAC7B,qBAAqB,EAAE,GAAG;QAC1B,qCAAqC,EAAE,OAAO;QAC9C,sBAAsB,EAAE,GAAG;QAC3B,GAAG,EAAE,KAAK;QACV,iBAAiB,EAAE,IAAI;QACvB,iCAAiC,EAAE,GAAG;QACtC,gBAAgB,EAAE,MAAM;QACxB,aAAa,EAAE,KAAK;QACpB,iBAAiB,EAAE,MAAM;QACzB,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;KACnF,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,oBAAoB,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,KAAK,CAAC,CAAC,GAAG,CAAA,yBAAyB,CAAC;AACrC,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"@effect/platform-node": "4.0.6",
|
|
51
51
|
"@effect/sql-pg": "4.0.6",
|
|
52
52
|
"@effect/sql-sqlite-node": "4.0.6",
|
|
53
|
-
"@shivaedev/quality": "0.
|
|
54
|
-
"@shivaedev/test-sql": "0.
|
|
55
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/quality": "0.54.0",
|
|
54
|
+
"@shivaedev/test-sql": "0.16.0",
|
|
55
|
+
"@shivaedev/test-story": "0.32.0",
|
|
56
56
|
"@types/node": "24.19.2",
|
|
57
57
|
"effect": "4.0.6",
|
|
58
58
|
"typescript": "7.0.2",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
|
|
4
|
+
export interface LowMemoryPostgresOptions {
|
|
5
|
+
readonly pointInTimeRecovery: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const applyLowMemoryPostgres = Effect.fn("Sql.applyLowMemoryPostgres")(function* ({ pointInTimeRecovery }: LowMemoryPostgresOptions) {
|
|
9
|
+
const sql = yield* SqlClient.SqlClient;
|
|
10
|
+
const settings = {
|
|
11
|
+
"autovacuum_max_workers": "2",
|
|
12
|
+
"autovacuum_work_mem": "32MB",
|
|
13
|
+
"hash_mem_multiplier": "1",
|
|
14
|
+
"idle_in_transaction_session_timeout": "10min",
|
|
15
|
+
"idle_session_timeout": "0",
|
|
16
|
+
jit: "off",
|
|
17
|
+
"max_connections": "50",
|
|
18
|
+
"max_parallel_workers_per_gather": "0",
|
|
19
|
+
"shared_buffers": "32MB",
|
|
20
|
+
"wal_buffers": "4MB",
|
|
21
|
+
"wal_compression": "zstd",
|
|
22
|
+
...(pointInTimeRecovery ? { "archive_timeout": "300" } : { "archive_mode": "off" }),
|
|
23
|
+
};
|
|
24
|
+
for (const [name, value] of Object.entries(settings)) {
|
|
25
|
+
yield* sql.unsafe(`ALTER SYSTEM SET ${name} = '${value}'`);
|
|
26
|
+
}
|
|
27
|
+
yield* sql`select pg_reload_conf()`;
|
|
28
|
+
});
|