Database¶
PostgreSQL holds everything Axium persists: users, roles, providers and their encrypted credentials, models, proxy apps, API keys, the response cache and daily statistics. The core and the ui keep no state of their own.
Connection¶
| Pool per core instance | min 0, max 10 connections |
| Startup wait | up to 30 s, one SELECT 1 per second, then the process exits |
| TLS | whatever the DSN says (?sslmode=require); the driver is pg |
Multiply the pool maximum by the number of core replicas (plus 8 per replica
if the PostgreSQL observability destination points to the same server) and
make sure max_connections on the server allows it with headroom.
Cloud SQL and other Unix sockets¶
The localhost authority is mandatory filler: the connection goes through the
socket in host=, but a DSN with an empty authority makes the driver fall
back to a different database engine and silently try localhost:5432.
Supported versions¶
- PostgreSQL 17 is what development and CI run against, and what the installers deploy.
- The schema needs PostgreSQL 13 or newer: native
gen_random_uuid(),GENERATED ALWAYS AS … STOREDcolumns,GROUPING SETS,INET,JSONBwith GIN indexes and transaction-level advisory locks. - No extensions are required. No
pgcrypto, nouuid-ossp.
Managed services (Cloud SQL, RDS, Azure Database, Neon, Supabase, …) work as long as the user can create schemas in the database.
Permissions¶
The user in DATABASE_URL must be able to:
CREATE SCHEMAin the database (GRANT CREATE ON DATABASE "axium-api" TO axium;)- create tables, types, functions and triggers in those schemas
It creates the schemas core, llm, rm and stats. With
OBSERVABILITY_TYPE=postgres it also creates telemetry in whichever
database OBSERVABILITY_DB_POSTGRESQL points to.
Schema installation¶
On every start, after connecting, the core checks the schema and acts on what it finds:
| State | Meaning | Action |
|---|---|---|
not_installed |
No Axium schema at all | Installs the complete schema in one transaction and seeds the super-admin role and the admin@axium.local user. Log: [install] schema version N installed. |
installed, current version |
Normal case | Nothing. Log: [install] schema version N is current. |
installed, older version |
Image newer than the database | Applies the bundled migrations one version at a time, each in its own transaction. Log: [install] schema migrated 1 -> 3. A failed migration exits the process rather than serve an unknown schema. |
installed, newer version |
Database newer than the image | Exits with The database is at schema version N but this build ships M. Update the API to the latest version. |
partial |
Axium schemas exist but no version table | Refuses to touch it. Logs the failure and keeps running so /v1/install/status can be queried. Needs a human. |
Installation and migrations take a transaction-level advisory lock, so several replicas can start at the same time against an empty database and only one installs.
A failed first installation does not stop the process: the routes come
up and almost everything answers a database error. That is why
/v1/install/status, not /v1/health, is the check that matters. See
Health checks.
Checking the version¶
curl -s http://localhost:3000/v1/install/status
# {"status":true,"data":{"state":"installed","version":3}}
Or, authenticated, GET /v1/diagnostics, which also reports the version the
running build expects.
Manual installation¶
POST /v1/install installs the schema on demand when the state is
not_installed. It answers 409 CONFLICT once installed and 500 on a
partial database. Both /v1/install routes are public and stay open
forever, so restrict them at the ingress after installing; see
Security.
Time zone matters¶
Daily statistics use CURRENT_DATE and budget windows use
date_trunc('day' | 'week' | 'month', now()), both evaluated with the
PostgreSQL server's time zone. Set it deliberately:
Housekeeping the core does not do¶
- Response cache.
rm.request_iokeeps one row per (proxy app, request hash) when thecachepolicy isstrict. Expired rows are not purged automatically; useDELETE /v1/proxy-apps/cache/expiredorDELETE /v1/proxy-apps/:id/cache/expiredfrom a cron job. - Traces.
telemetry.proxy_traceshas no retention. See PostgreSQL destination.
Backups¶
See Backup & restore. Remember that a database
backup is only usable together with the MASTER_ENCRYPTION_KEY it was
written under.