10 KiB
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-osspetpg_trgmdisponibles (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
- Créez un compte sur https://neon.tech
- "New Project" → Nom:
xpeditis-prod→ Region:AWS eu-central-1 (Frankfurt)(le plus proche de Hetzner FSN1) - Sélectionnez Plan Pro ($19/mois)
- 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)
- 500–5 000 users → Self-hosted CCX23 (plus économique à ce niveau)
-
5 000 users → Self-hosted CCX33 + replica (contrôle total)