Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,14 @@ jobs:
# The bridge's pure parts; `node --test` needs no install.
- run: node --test catalog/bailey/bridge/images/bridge/src/*.test.js

postgres-credential:
name: Postgres credential container tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# The container's pure parts, on the standard library alone.
- run: python3 catalog/postgres/database/images/credential/test_bootstrap.py

# Kilo Code's Tangle plugin (`tangle/kilo-code/`): its tests are
# TypeScript run straight by `node --test`, which strips the types
# itself from Node 22.18 on, so they need no install either.
Expand Down Expand Up @@ -318,7 +326,7 @@ jobs:
# The name the main ruleset requires (see the `rust` job).
editor:
name: graph + editor
needs: [graph, connect, syntax, vscode, bridge, tangle, browser]
needs: [graph, connect, syntax, vscode, bridge, postgres-credential, tangle, browser]
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
env:
Expand Down
95 changes: 95 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,101 @@ What changed between versions of weft that you need to know about when you
update. To update, run `weft maintenance on`, pull and run `./setup.sh` again,
then run `weft maintenance off`.

## 2026-10-31 (dbefc004)

**What you have to change after this update, if you use a PostgresDatabase:**

- **Clients outside the program sign in as `app`, no longer as `weft`.** The
password, host and port stay the same; `weft infra env <node> --into .env
--set DATABASE_USER=User` writes the user again.
- **A frontend's pool must expect a connection idle for a minute to close.**
The database now closes it. Set the pool's idle timeout under a minute, or
have it test a connection before using it.
- **An extension that needs the superuser goes in the node's new `extensions`
setting** (`["pg_trgm", "postgres_fdw"]`). The superuser makes each one on
every start, and the program, signed in as `app`, uses it.

**A PostgresDatabase no longer runs out of connections when the program
scales.** Postgres serves a fixed number of connections, while its clients
keep changing: workers scale, a change of worker settings runs old and new
workers side by side, and a frontend connects too. Each worker used to keep up
to 20 connections open whether it used them or not, so once enough workers ran
at once, Postgres answered `too many clients` to them and to everything else
using that database. A PostgresDatabase now runs a pooler in front of Postgres,
in the same unit. The pooler never opens more connections than Postgres takes,
and a client that finds them all busy waits for one, so a run waits instead of
failing. Watching queries (PostgresWatchQuery) get a quarter of the
connections the pooler opens (what is left after 3 kept for the superuser and
2 spare), at least one, so a crowd of them never leaves the other queries
without a connection, or the other way round. If you want to see who holds the
connections, the node's card now shows them: Postgres's own by user and state,
each of the pooler's two shares with its clients served and waiting, and the
clients by address.

Your existing PostgresDatabase nodes get the pooler on their next `weft infra
upgrade`. When the database starts again it gets a new role, `app`, which
becomes the owner of everything the superuser owned in it (except objects an
extension made). After that:

- **Through the pooler a client reaches this one database only**, so a client
that also used `postgres` or another database made beside it can no longer
reach those. The superuser `weft` can only sign in from inside the unit. An
extension listed in `extensions` that this Postgres does not have fails the
start, and `weft infra status` names it.
- **A database whose name the pooler cannot read as written gets a second
name for clients.** Your database keeps its name and its data. When the
name is letters, digits, `_` and `-` only, at most 57 characters, and none
of `pgbouncer`, `all`, `sameuser`, `samerole`, `samegroup` or
`replication`, clients use it as before; otherwise they sign in by a name the
pooler makes from it (`db_my_app_1a2b3c4d`), which the connection carries
and the card shows under Database. A client of yours outside the program
takes it from there.
- **The database closes a connection that sat idle for a minute.** That gives
back a connection held by a worker that lost its CPU between requests, or by
an old worker a rollout left behind. If a client of yours keeps a connection idle on purpose (one that `LISTEN`s), have it lift that
limit for its own session when it connects, the way PostgresWatchQuery does:
`SELECT set_config('idle_session_timeout', '0', false) FROM pg_settings
WHERE name = 'idle_session_timeout'`.
- **The least `memory` is 565Mi** (it was 886Mi). At 1Gi Postgres serves 23
connections, at 2Gi 59, at 4Gi 131.
- **A new database needs Postgres 14 or later.** One you already have on an
older Postgres keeps running, but it cannot close idle connections, so its
card and `weft infra status` warn of it and list the commands that dump it
and restore it into a new node on 17.

**Every Postgres node closes a pooled connection that sat idle for 5
seconds**, and never hands out one idle for longer, whichever database it talks
to.

**A stopping worker gives back what its runs shared** (`ctx.shared`) as soon
as its last run has ended, instead of when the process goes, so a pool's
connections return to the database while the worker writes its last records.

**On Cloud Run, a change of worker settings retires the old workers.** Once
the new settings serve, weft moves the calls the old ones took to them and
removes that program's revisions under any other settings, so their copies stop
instead of running beside the new ones. Which settings are current is decided
from what the project has stored, so a dispatcher copy that briefly still held
the old settings and brought them back up never wins: that revision takes no
calls and scales to zero. It goes at the next settings change or activation,
unless it is the service's latest, which Cloud Run does not let anyone delete;
then it goes once a later settings change or build replaces it.

**When an infra node's setup fails, you are told why.** `weft infra start`,
`weft activate` and the other commands that wait on a setup used to answer
`infra setup failed` and nothing else; they now give the error the setup run
failed with (a trigger's setup the same), as `weft runs` shows it.

**A unit that knows why it is not ready says so in `weft infra status`.** When
a container's health check answers an error with a reason in its body (JSON
`{"error": "..."}`, or plain text), the status shows that reason after the
status code.

**A node's fake tests hold a published connection to its service's values.**
A value the service does not declare, or a required one missing, fails the
run the way the access store refuses it on an install; before, the fake rig
stored anything and only a real run found out.

## 2026-10-31 (ed587b61)

**On a cloud install, how many holders run follows how busy they are**, not
Expand Down
19 changes: 0 additions & 19 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -633,25 +633,6 @@ minute on one project.
[Update Notice Warning] If we touch `crates/weft-engine/src/door/limits.rs`
or `/v1/door/tick`, revisit this entry.

## Several workers of a project can open more Postgres connections than the database takes

**Problem.** The Postgres node keeps one pool per worker and database
(`catalog/postgres/postgres.rs`, `MOST_AT_ONCE`), held to 20 connections
so a few workers together stay under Postgres's default limit of 100.
That is a guess about how many workers there are. A project scaled to
many workers can still open more connections than the database accepts,
and the runs past it fail to connect.

**Direction.** Put a connection pooler (PgBouncer, in transaction mode)
in front of the database inside the Postgres node's own infra, so every
worker talks to the pooler and the pooler holds the few real
connections. The worker's own limit can then be generous again. The open
question is a database the user brings, which has no infra of ours to
put the pooler in.

[Update Notice Warning] If we touch `catalog/postgres/postgres.rs`'s
pool or the Postgres node's infra, revisit this entry.

## A Python process runs one script at a time

**Problem.** `ExecPython` keeps as many `python3` processes as the worker
Expand Down
1 change: 1 addition & 0 deletions catalog/bailey/bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl Node for BaileyBridgeNode {
}],
keep_on_terminate: Vec::new(),
health_rules: None,
notes: Vec::new(),
})
}

Expand Down
1 change: 1 addition & 0 deletions catalog/computer/computer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl Node for ComputerNode {
endpoints,
keep_on_terminate: Vec::new(),
health_rules: None,
notes: Vec::new(),
})
}

Expand Down
7 changes: 7 additions & 0 deletions catalog/postgres/access/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@
"secret": false,
"optional": true,
"placeholder": "require (or verify-ca / verify-full; disable turns TLS off)"
},
{
"name": "watchDatabase",
"label": "Database for watching queries",
"secret": false,
"optional": true,
"placeholder": "the same as Database (a pooler's separate name for long-held connections)"
}
]
}
Expand Down
Loading
Loading