xpeditis2.0/docs/deployment/hetzner/07-database-postgresql.md
2026-03-26 18:08:28 +01:00

10 KiB
Raw Permalink Blame History

07 — Base de données PostgreSQL

Deux options selon votre palier et votre tolérance aux opérations.


Option A — Neon.tech (recommandé pour MVP)

Pourquoi Neon.tech

  • PostgreSQL 15 managé, compatible TypeORM
  • Extensions uuid-ossp et pg_trgm disponibles (requises par Xpeditis)
  • Connection pooling intégré (PgBouncer) → critique pour NestJS multi-pods
  • Backups automatiques + point-in-time recovery
  • Free tier pour le développement
  • $19/mois pour le plan Pro (production)
  • Pas de gestion de HA, de réplication, ni de backups à faire

Setup Neon.tech

  1. Créez un compte sur https://neon.tech
  2. "New Project" → Nom: xpeditis-prod → Region: AWS eu-central-1 (Frankfurt) (le plus proche de Hetzner FSN1)
  3. Sélectionnez Plan Pro ($19/mois)
  4. PostgreSQL version: 15

Créer la base de données

# Dans l'interface Neon → SQL Editor, ou via CLI neon
# Installer la CLI Neon
npm install -g neonctl
neonctl auth

# Créer les extensions requises par Xpeditis
neonctl sql --project-id <project_id> << 'EOF'
-- Extensions requises par Xpeditis
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";

-- Vérification
SELECT extname, extversion FROM pg_extension
WHERE extname IN ('uuid-ossp', 'pg_trgm');
EOF

Connection string

Dans l'interface Neon → Connection Details → choisissez Pooled connection :

# Connection string avec pooling (via PgBouncer) — pour la prod
postgresql://xpeditis:<password>@ep-xxx-xxx.eu-central-1.aws.neon.tech/xpeditis?pgbouncer=true&connection_limit=1&sslmode=require

# Connection string directe — pour les migrations TypeORM
postgresql://xpeditis:<password>@ep-xxx-xxx.eu-central-1.aws.neon.tech/xpeditis?sslmode=require

Important : TypeORM migrations doivent utiliser la connexion directe (sans pgbouncer). Pour le runtime NestJS, utilisez la connexion poolée.

Configuration TypeORM pour Neon

L'app utilise des variables séparées pour l'hôte/port. Modifiez pour utiliser DATABASE_URL :

Vérifiez le fichier apps/backend/src/app.module.ts. Si TypeORM est configuré avec des variables séparées (DATABASE_HOST, DATABASE_PORT, etc.), vous avez deux options :

Option 1 (recommandée) — URL complète :

Dans le app.module.ts, TypeOrmModule accepte une url :

TypeOrmModule.forRootAsync({
  useFactory: (configService: ConfigService) => ({
    type: 'postgres',
    url: configService.get('DATABASE_URL'),  // ← Utiliser si disponible
    ssl: { rejectUnauthorized: false },       // ← Requis pour Neon
    // ... reste de la config
  }),
})

Option 2 — Variables séparées (configuration actuelle) :

Décomposez l'URL Neon en variables séparées dans le .env :

DATABASE_HOST=ep-xxx-xxx.eu-central-1.aws.neon.tech
DATABASE_PORT=5432
DATABASE_USER=xpeditis
DATABASE_PASSWORD=<neon_password>
DATABASE_NAME=xpeditis
DATABASE_SSL=true

Et ajoutez ssl: { rejectUnauthorized: false } dans la config TypeORM.

Lancer les migrations

# Se placer dans le répertoire backend
cd apps/backend

# Copier l'env de prod
cp .env.example .env.production

# Éditer .env.production avec les vraies valeurs Neon
# DATABASE_HOST=ep-xxx-xxx.eu-central-1.aws.neon.tech
# DATABASE_USER=xpeditis
# DATABASE_PASSWORD=<password>
# DATABASE_NAME=xpeditis
# DATABASE_SSL=true

# Lancer les migrations (connexion directe, pas poolée)
NODE_ENV=production npm run migration:run

# Vérifier les migrations appliquées
NODE_ENV=production npm run typeorm query "SELECT version, name FROM typeorm_migrations ORDER BY id"

Option B — PostgreSQL self-hosted sur Hetzner (1 000+ users)

Architecture recommandée

CX22/CCX13 ─── PostgreSQL primary (lecture + écriture)
                     │
CCX13      ─── PostgreSQL replica (lecture seule + failover)
                     │
Volume Hetzner ─── /var/lib/postgresql/data (persistant)

1. Créer le serveur PostgreSQL dédié

# Créer un serveur CCX13 dédié pour PostgreSQL
hcloud server create \
  --name xpeditis-postgres \
  --type ccx13 \
  --image ubuntu-24.04 \
  --location fsn1 \
  --ssh-key xpeditis-deploy \
  --network xpeditis-network \
  --firewall xpeditis-firewall

# Attacher le volume de données
hcloud volume attach xpeditis-postgres-data \
  --server xpeditis-postgres \
  --automount

# Récupérer l'IP privée
POSTGRES_PRIVATE_IP=$(hcloud server ip xpeditis-postgres --private-ip)
echo "PostgreSQL IP privée: $POSTGRES_PRIVATE_IP"

2. Installer et configurer PostgreSQL

# Se connecter au serveur
ssh -i ~/.ssh/xpeditis_hetzner root@<IP_PUBLIQUE>

# Installer PostgreSQL 15
apt-get update
apt-get install -y postgresql-15 postgresql-client-15

# Monter le volume de données
DEVICE_NAME=$(lsblk -o NAME,SERIAL | grep HC | head -1 | awk '{print $1}')
mkfs.ext4 /dev/$DEVICE_NAME
mkdir -p /mnt/postgres-data
mount /dev/$DEVICE_NAME /mnt/postgres-data
echo "/dev/$DEVICE_NAME /mnt/postgres-data ext4 defaults 0 2" >> /etc/fstab

# Déplacer les données PostgreSQL vers le volume
systemctl stop postgresql
rsync -av /var/lib/postgresql /mnt/postgres-data/
rm -rf /var/lib/postgresql/15/main
ln -s /mnt/postgres-data/postgresql/15/main /var/lib/postgresql/15/main
systemctl start postgresql

# Créer la base de données et l'utilisateur
sudo -u postgres psql << 'PGSQL'
CREATE USER xpeditis WITH PASSWORD '<MOT_DE_PASSE_FORT>';
CREATE DATABASE xpeditis_prod OWNER xpeditis;
GRANT ALL PRIVILEGES ON DATABASE xpeditis_prod TO xpeditis;

-- Connecter à la base
\c xpeditis_prod

-- Extensions requises
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";

-- Vérification
SELECT extname FROM pg_extension;
PGSQL

3. Configuration PostgreSQL pour la production

# Éditer /etc/postgresql/15/main/postgresql.conf
cat >> /etc/postgresql/15/main/postgresql.conf << 'EOF'

# Performance tuning (pour CCX13 : 4 vCPU dédiés, 8 GB RAM)
shared_buffers = 2GB                    # 25% de la RAM
effective_cache_size = 6GB              # 75% de la RAM
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1                  # SSD NVMe
effective_io_concurrency = 200          # SSD
work_mem = 64MB
min_wal_size = 1GB
max_wal_size = 4GB

# Connexions
max_connections = 100
# Avec RDS Proxy / PgBouncer en front, 100 suffisent

# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = '/var/log/postgresql'
log_filename = 'postgresql-%Y-%m-%d.log'
log_rotation_age = 1d
log_min_duration_statement = 1000       # Log les queries > 1s
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on

# Réplication (pour replica future)
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
EOF

# Autoriser les connexions depuis le réseau privé Hetzner
cat >> /etc/postgresql/15/main/pg_hba.conf << 'EOF'

# Connexions depuis le réseau privé Hetzner (pods k3s)
host    xpeditis_prod   xpeditis    10.0.0.0/16   md5
EOF

# Écouter sur toutes les interfaces (nécessaire pour le réseau privé)
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '10.0.0.0\/16,localhost'/" \
  /etc/postgresql/15/main/postgresql.conf

systemctl restart postgresql
systemctl enable postgresql

# Test de connexion depuis le réseau privé
psql -h $POSTGRES_PRIVATE_IP -U xpeditis -d xpeditis_prod -c "SELECT version();"

4. Installer PgBouncer (connection pooling)

NestJS crée une connexion par pod. Sans pooler, 10 pods × 10 connexions = 100 connexions constantes. PgBouncer réduit ça drastiquement.

apt-get install -y pgbouncer

cat > /etc/pgbouncer/pgbouncer.ini << 'EOF'
[databases]
xpeditis_prod = host=localhost port=5432 dbname=xpeditis_prod

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction       # Mode le plus efficace pour NestJS
max_client_conn = 500         # Connexions clients max
default_pool_size = 20        # Connexions vers PostgreSQL par pool
reserve_pool_size = 5
server_reset_query = DISCARD ALL
log_connections = 1
log_disconnections = 1
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
EOF

# Créer le fichier d'authentification
echo '"xpeditis" "md5<hash>"' > /etc/pgbouncer/userlist.txt
# Pour générer le hash md5 :
echo -n "md5$(echo -n '<MOT_DE_PASSE>xpeditis' | md5sum | awk '{print $1}')"

systemctl enable pgbouncer
systemctl start pgbouncer

Avec PgBouncer, les pods NestJS se connectent sur le port 6432 :

# Variables d'environnement pour PgBouncer
DATABASE_HOST=<POSTGRES_PRIVATE_IP>
DATABASE_PORT=6432     # PgBouncer au lieu de 5432

5. Lancer les migrations TypeORM

# Depuis votre machine locale (ou depuis un pod de migration)
cd apps/backend
DATABASE_HOST=<POSTGRES_PRIVATE_IP> \
DATABASE_PORT=5432 \   # Direct PostgreSQL pour les migrations (pas PgBouncer)
DATABASE_USER=xpeditis \
DATABASE_PASSWORD=<password> \
DATABASE_NAME=xpeditis_prod \
npm run migration:run

# Vérifier
DATABASE_HOST=<POSTGRES_PRIVATE_IP> \
DATABASE_PORT=5432 \
DATABASE_USER=xpeditis \
DATABASE_PASSWORD=<password> \
DATABASE_NAME=xpeditis_prod \
npm run typeorm query "SELECT COUNT(*) as migrations FROM typeorm_migrations"

Comparaison des options

Critère Neon.tech (Option A) Self-hosted (Option B)
Coût (1 000 users) $19/mois ~€30/mois (CCX13 + volume)
HA Automatique Manuel (Patroni)
Backups Automatique (7 jours PITR) Script cron (doc 13)
Extensions uuid-ossp + pg_trgm Toutes
Migrations Simple Simple
Ops requis Aucun Maintenance mensuelle
Scale Jusqu'à $69/mois (Pro) Changement de serveur
Limite connexions PgBouncer inclus PgBouncer à installer

Recommandation :

  • < 500 users → Neon.tech (aucun ops, $19/mois)
  • 5005 000 users → Self-hosted CCX23 (plus économique à ce niveau)
  • 5 000 users → Self-hosted CCX33 + replica (contrôle total)