Sauvegarder automatiquement une base PostgreSQL vers Google Drive avec AWS Lambda

Comment mettre en place une solution serverless pour sauvegarder automatiquement une base de données PostgreSQL vers Google Drive, avec rotation automatique et alertes courriel en cas d'échec.


Toute application en production a besoin d’une stratégie de sauvegarde. Les pannes matérielles, les erreurs humaines ou les migrations ratées peuvent conduire à une perte de données irrémédiable. Dans ce retour d’expérience, je présente l’architecture que j’ai mise en place : une solution entièrement serverless, pilotée par AWS Lambda, qui sauvegarde automatiquement une base PostgreSQL vers Google Drive chaque nuit.

Le besoin

Le contexte : une application web en production avec une base PostgreSQL hébergée sur Aiven. La base grossit de quelques mégaoctets par mois. L’objectif était simple : avoir une copie fraîche chaque nuit, accessible depuis n’importe où, sans maintenance manuelle, pour un coût marginal, et pouvoir la restaurer en cas de besoin (avec son mode dry-run pour vérifier l’intégrité avant d’appliquer réellement la restauration).

Architecture générale

Le système repose sur trois composants AWS :

  • EventBridge : déclenche la Lambda chaque nuit à minuit via une expression cron
  • Lambda Node.js : exécute la sauvegarde, compresse le fichier et l’envoie sur Drive
  • SNS : envoie un courriel de confirmation chaque nuit et une alerte immédiate en cas d’échec

Tout est décrit en code via le Serverless Framework v4, ce qui rend le déploiement reproductible en une seule commande (serverless deploy).

Les étapes de la Lambda

1. Dump PostgreSQL

La Lambda exécute pg_dump via le module child_process de Node.js. Les informations de connexion sont stockées dans AWS Parameter Store (jamais en clair dans les variables d’environnement).

const { spawn } = require('child_process')

const pgDumpProcess = spawn('pg_dump', [
  `--host=${host}`,
  `--port=${port}`,
  `--username=${user}`,
  `--dbname=${database}`,
  '--format=plain',
  '--no-owner',
  '--no-privileges',
  '--lock-wait-timeout=30s',
], { env: { ...process.env, PGPASSWORD: password, PGSSLMODE: 'require' } })

Le dump est au format plain (SQL lisible), compressé à la volée en gzip via un pipeline de streams, aucun buffer intermédiaire en mémoire :

const { createGzip } = require('zlib')

const gzipStream = createGzip({ level: 9 })
pgDumpProcess.stdout.pipe(gzipStream).pipe(outputStream)

La partie la plus difficile n’a pas été d’écrire ce code, mais de faire fonctionner pg_dump dans l’environnement Lambda. En local, tout fonctionnait parfaitement. Dans le cloud, c’est une autre affaire : Lambda ne dispose pas des binaires PostgreSQL par défaut. Il a fallu configurer un Lambda Layer contenant les binaires pg_dump et pg_restore compilés pour Amazon Linux en architecture ARM64, avec la bonne version (PostgreSQL 17) et les bons chemins d’exécution. Deux jours de débogage pour trouver la configuration fonctionnelle. C’est un point que la documentation AWS n’explicite pas clairement, et qui peut faire perdre beaucoup de temps à quiconque part de zéro.

2. Upload vers Google Drive

Avant Google Drive, la première destination envisagée était OneDrive : le client l’utilisait déjà et souhaitait retrouver ses sauvegardes dans son environnement habituel. Deux jours ont été consacrés à l’implémentation. La documentation de l’API Microsoft Graph est dense, l’authentification par application nécessite une configuration Azure AD complexe, et les exemples disponibles sont souvent incomplets ou obsolètes. Résultat : abandonné.

Google Drive a pris une journée. L’API est mieux documentée, la bibliothèque googleapis est maintenue directement par Google, et le flux d’authentification est plus simple à mettre en place dans un contexte automatisé.

Trois modes d’authentification sont possibles avec l’API Google Drive :

  • Compte de service : authentification machine à machine sans interaction, mais nécessite un accès partagé explicite sur chaque ressource Drive
  • Clé API : limitée aux ressources publiques, inopérante pour un Drive privé
  • OAuth 2.0 avec refresh_token : autorisation initiale unique par l’utilisateur, puis renouvellement automatique du token sans aucune intervention

C’est l’OAuth 2.0 qui a été retenu : client_id, client_secret et refresh_token sont stockés dans Parameter Store. Au démarrage, la Lambda rafraîchit son access token de façon autonome.

const { google } = require('googleapis')

const oauth2Client = new google.auth.OAuth2(clientId, clientSecret)
oauth2Client.setCredentials({ refresh_token: refreshToken })

const drive = google.drive({ version: 'v3', auth: oauth2Client })

await drive.files.create({
  requestBody: { name: filename, parents: [folderId] },
  media: { mimeType: 'application/gzip', body: Readable.from(compressed) },
})

Le nom du fichier suit le format backup-YYYY-MM-DD.sql.gz, ce qui facilite l’identification et le tri.

3. Rotation automatique

Après chaque upload, la Lambda liste les fichiers dans le dossier Drive et supprime les plus anciens pour ne conserver que N sauvegardes (N est paramétrable via Parameter Store) :

const files = await drive.files.list({
  q: `'${folderId}' in parents`,
  orderBy: 'createdTime',
  fields: 'files(id, name, createdTime)',
})

const toDelete = files.data.files.slice(0, -maxBackups)
for (const file of toDelete) {
  await drive.files.delete({ fileId: file.id })
}

4. Notifications SNS

Un bloc try/catch global gère les deux cas : succès et échec. SNS est configuré pour envoyer un courriel via une souscription directe. Le client reçoit chaque matin une confirmation que la sauvegarde de la nuit s’est bien passée, et une alerte immédiate en cas de problème.

try {
  await runBackup()
  await sns.publish({
    TopicArn: process.env.ALERT_TOPIC_ARN,
    Subject: `[BACKUP] Sauvegarde réussie`,
    Message: `Backup du ${new Date().toISOString().slice(0, 10)} effectué avec succès.`,
  }).promise()
} catch (error) {
  await sns.publish({
    TopicArn: process.env.ALERT_TOPIC_ARN,
    Subject: `[BACKUP] Échec du backup PostgreSQL`,
    Message: error.message,
  }).promise()
  throw error
}

Restauration manuelle

Un second endpoint REST (déclenché manuellement, jamais exposé publiquement) permet de restaurer une sauvegarde avec un mode dry-run :

POST /restore/execute
{ "dryRun": true, "backupPattern": "backup-2025-08-15" }

En mode dry-run, la Lambda télécharge et décompresse le fichier sans l’appliquer à la base (utile pour vérifier l’intégrité du backup avant d’aller plus loin).

Coût réel

La Lambda s’exécute une fois par nuit et dure entre 10 et 30 secondes selon la taille de la base. Sur AWS Free Tier (1 million de requêtes et 400 000 Go-secondes par mois), le coût mensuel est inférieur à 0,10 €. Google Drive avec 15 Go gratuits suffit largement pour des bases de taille modeste.

Bilan

Cette architecture couvre les cas essentiels avec un minimum de maintenance :

  • Sauvegarde automatique quotidienne sans intervention
  • Rotation pour éviter de saturer Google Drive
  • Confirmation quotidienne par courriel et alerte immédiate en cas de panne
  • Restauration pilotable via API avec mode dry-run
  • Coût quasi nul

Le code source n’est pas public (projet interne client), mais l’architecture décrite ici est suffisamment détaillée pour être reproduite intégralement.

Mise à jour : la base a depuis été migrée hors d’Aiven vers un autre provider PostgreSQL. La Lambda embarque une factory qui instancie le bon service selon le provider configuré et construit la chaîne de connexion en conséquence. La migration n’a nécessité aucune modification du code : seule la configuration dans Parameter Store a changé.

Bon dév, et bisou.

Keiro

Lire aussi