Apply the migrations as a step of the deploy, before the new version
starts: a release job, a Kubernetes Job, a step of the pipeline.
Terminal window
bunxtetsu-sqlmigrations:run--json
It runs where the project’s dependencies are installed, as after bun install, so that bunx runs the command of the project’s own
@tetsujs/sql: a binary of bun build --compile holds the application,
not the command.
A failure stops the deploy before any instance runs code the database
is not ready for.
The migrations can run as a role of their own, given by the step’s
DATABASE_URL, with rights to change the schema that the application’s
role does not need.
One run applies them. Instances started together would be safe as
well, since a run waits for another to finish, but each would hold a
connection while it waits.
The version before keeps running while the migrations apply, and until the
new one is out, so a migration must not break it: Changing a schema the
application runs on.
A database built before its migrations takes a baseline once, before its
first run: Adopting a database.
The migrations are read from disk by the step that applies them: ship the
directory with it. The application itself reads none of them, so a binary
built with bun build --compile holds only the generated files, which are
ordinary modules.
A postgres:// URL of the database: the server, the user and the
database it names are the ones used. The database is given apart from
the URL as well, since Bun 1.4.4 would take PGDATABASE over it.
A deploy whose migrations did not run then fails as it starts, with a
MigrationError, rather than on the first request that needs them:
behind — the database has not applied the migration, or has no journal;
changed — it applied another text of that migration;
table — the journal’s table is not a journal.
Newer migrations may follow it: after a rollback, the code is older than
the database, and runs on it as it did before. The journal is read with
db’s own connection, so the application’s role needs to read it. A journal
in a schema the connection does not search is named as schema.table, with
--table, when the types are generated.
types:generate --check writes nothing. It fails when a file differs from
what the migrations and the query files make, in anything but its
formatting, and says which: src/db.generated.ts is out of date: run tetsu-sql types:generate. A migration changed without generating the types
again, or a query file that no longer fits the schema, fails the build
rather than a request in production.
The image is the Postgres of production, so that the types and the tests
see what production does.
Without sslmode in the URL, or PGSSLMODE in the environment, Bun connects
without TLS, where Postgres’s own clients would try TLS first. Name the mode
in the URL: sslmode=require
encrypts, and sslmode=verify-full also checks the server’s certificate and
its name. A certificate signed by an authority of your own goes in Bun’s
tls option, which database() passes
on. tetsu-sql and generate() take only a URL: name the authority’s file
in NODE_EXTRA_CA_CERTS, which Bun trusts for every connection, theirs
included.
Bun 1.4.4 takes PGDATABASE, when it is set, over the database a URL
names. database(), tetsu-sql and testDatabase() give Bun the URL’s
database apart from the URL, so the URL wins. A client of your own, such as
one handed to migrate(), needs the same: new SQL({ url, database: "shop" }).
Bun reaches a server on the unix socket that path names in the URL:
postgres://shop@localhost/shop?path=/var/run/postgresql/.s.PGSQL.5432.
tetsu-sql types:generate and generate() build the types over it, but
describe query files on a connection of their own, which reaches a server
only by its host and port: with query files, they refuse a URL with path.
max is per process, and a process that cancels a query opens one
connection more, to send the cancel on. Postgres’s max_connections, 100 by
default, is shared by every process of every instance. A query holds no connection of
its own, so the pool’s size is a matter of the transactions that run at
once: they take turns at max − 1 connections, and the one left over runs
everything else.
Behind a pooler in transaction mode, such as PgBouncer, a session lasts one
transaction: db.connection(), a session’s advisory lock and set do not
hold across transactions there. Bun prepares statements on each
connection; where the pooler does not keep prepared statements, pass
prepare: false.
statement_timeout is the server’s limit on a query: Cancelling a
query sets it for the pool. Bun’s
own options limit the pool: connectionTimeout, 30 seconds by default,
idleTimeout and maxLifetime, in seconds.