PostgreSQL central
Version anglaise : en/postgresql.md.
Connexion
EAI_DATABASE_URL:postgresql://utilisateur@hôte:5432/eai?sslmode=verify-full&sslrootcert=/etc/eai/pg-ca.pem. En prod, le démarrage refuse une URL sanssslmode=require,verify-caouverify-full.- Pool
psycopg_pool(EAI_DB_POOL_MIN,EAI_DB_POOL_MAX), chaque connexion est testée avant usage (remplacée après redémarrage ou bascule),application_name=eai-control-planedanspg_stat_activity. - Isolation : chaque transaction locataire bascule sur le rôle
eai_app(RLS forcée,FORCE ROW LEVEL SECURITY). - Identifiant runtime séparé (ADR-0156) :
EAI_RUNTIME_DATABASE_URLest le login de l'API et du worker, membre du rôleeai_runtime(V165 : membre deeai_app, aucun droit CREATE, propriétaire de rien), créé par l'exploitant :CREATE ROLE eai_api LOGIN PASSWORD '...' IN ROLE eai_runtime;. Les migrations gardentEAI_DATABASE_URL(identifiant d'administration, propriétaire du schéma). Vide : l'API utiliseEAI_DATABASE_URL. En prod, la même exigence TLS s'applique aux deux URL.
Migrations
python -m eai.migrateappliquesql/postgres/V###__nom.sqlsous verrou consultatif ;--statuslisteappliedoupending. Une migration appliquée puis modifiée est refusée (checksum).
Haute disponibilité
- CloudNativePG (
deploy/data/postgres/cluster.yaml) : 3 instances réparties par zone, bascule automatique, archivage WAL chiffré vers S3 et sauvegarde de base quotidienne ; PITR par un nouveauClusterenbootstrap.recovery.
Sauvegarde et restauration logiques (commandes vérifiées)
La connexion passe uniquement par l'environnement libpq (PGHOST, PGPORT, PGUSER, PGDATABASE, PGSSLMODE, PGPASSFILE en mode 0600) : jamais de mot de passe ni d'URL en argument, les scripts le refusent.
cd services/control-plane
export PGHOST=pg.interne PGUSER=eai_backup PGSSLMODE=verify-full PGPASSFILE=/run/secrets/pgpass
PGDATABASE=eai scripts/pg_backup.sh /backup/eai-$(date +%F).dump # pg_dump --format=custom, fichier 0600, vérifié par pg_restore --list
createdb eai_drill
PGDATABASE=eai_drill EAI_PYTHON=.venv/bin/python \
EAI_DATABASE_URL='postgresql://eai_backup@pg.interne/eai_drill?sslmode=verify-full' \
scripts/pg_restore.sh /backup/eai-2026-10-30.dump # une seule transaction, échec si migration en attente
Les rôles (eai_app, propriétaire) sont globaux au cluster et doivent exister sur la cible. Le test tests/test_core_pg_backup.py exécute ces deux scripts sur un vrai PostgreSQL 16 : locataire et données retrouvés, aucune migration en attente, mêmes tables en RLS forcée et mêmes politiques, eai_app ne voit rien sans portée locataire.