Bases de données postgresql Scale-To-Zero

created: mercredi, oct. 1, 2025

Nous visons toujours à construire des services aussi économes en ressources que possible. Cela est vrai pour les services que nous proposons à l’externe et, tout aussi important, pour notre propre infrastructure interne. Beaucoup de nos outils internes, comme les systèmes de facturation et de supervision, reposent sur des bases de données PostgreSQL. Bien qu’essentielles, ces bases de données restent souvent inactives pendant de longues périodes, consommant CPU et mémoire RAM sans raison.

Alors, nous nous sommes demandé : peut-on appliquer notre philosophie scale-to-zero à nos propres bases de données ? La réponse est oui. Nous avons développé un système pour provisionner des instances PostgreSQL qui ne tournent que lorsqu’elles sont activement utilisées. Cette conception est incroyablement économe en ressources, mais elle présente quelques compromis que nous explorerons.

Voici un schéma qui présente ce que nous avons construit et comment nous sommes parvenus à cette montée en charge dynamique.

flowchart LR
  subgraph Machine
    A[systemd.socket]
    B[systemd-socket-proxyd]
    D[(disque local)]
    A -- port 15432 --> B

    subgraph Docker-Compose
      C[conteneur postgres]
    end
    B -- port 5432 --> C
    C -- dossier-données --> D
  end

  X[Internet] -- port 25432 --> A

La Magie de l’Activation par Socket

Le cœur de cette configuration est l’activation par socket systemd. Plutôt que de faire tourner un conteneur PostgreSQL 24h/24 et 7j/7, nous laissons le système d’initialisation systemd écouter sur le port de la base de données. Lorsqu’une application tente de se connecter, systemd intercepte la requête, démarre le conteneur de base de données à la demande, puis transmet la connexion. Une fois que la base n’est plus utilisée, elle est arrêtée automatiquement.

Cette approche combine la puissance d’outils Linux standards et éprouvés : systemd pour la gestion des services et l’activation par socket, et Docker Compose pour définir notre environnement conteneurisé de base de données. C’est simple, robuste, et ne nécessite aucun logiciel personnalisé.

Nos Choix Technologiques : Pourquoi les Conteneurs et Docker Compose ?

Nous avons fait deux choix technologiques précis pour cette configuration : faire tourner PostgreSQL dans un conteneur et le gérer avec Docker Compose.

Décomposons les fichiers de configuration qui rendent cela possible.

Les Composants

Nous utilisons une combinaison d’un fichier docker-compose.yml pour définir la base de données et de trois unités systemd pour gérer le cycle de vie scale-to-zero.

1. La Définition de la Base : Docker Compose

C’est un fichier docker-compose.yml standard. Il définit un conteneur PostgreSQL 18, mappe un port interne vers l’hôte, et monte un volume pour persister les données de la base sur le disque local. Cela garantit que même si le conteneur s’arrête, les données restent en sécurité. Tous les paramètres documentés dans l’image officielle PostgreSQL sur Docker Hub peuvent être utilisés ici, permettant des personnalisations supplémentaires comme la création d’utilisateurs ou de bases spécifiques au démarrage.

/root/pg/pg1/docker-compose.yml

 1version: "3"
 2services:
 3  database:
 4    image: 'postgres:18'
 5    ports:
 6      - 127.0.0.1:14532:5432
 7    volumes:
 8      - /root/pg/pg1/data:/var/lib/postgresql
 9    environment:
10      POSTGRES_PASSWORD: SuperSecretAdminPassword

2. L’Écouteur : Socket systemd

Cette unité .socket demande à systemd d’écouter le port 24532 sur toutes les interfaces réseau. Lorsqu’une connexion TCP arrive, systemd active le service pg1-proxy.service. C’est le point d’entrée de toutes les connexions à la base.

/etc/systemd/system/pg1-proxy.socket

 1[Unit]
 2Description=Socket pour le proxy pg1 pg (24532->127.0.0.1:14532)
 3
 4[Socket]
 5ListenStream=0.0.0.0:24532
 6ReusePort=true
 7NoDelay=true
 8Backlog=128
 9
10[Install]
11WantedBy=sockets.target

3. Le Proxy et le Timer d’Inactivité : service systemd

C’est ici que réside la logique à la demande. Quand activé par le socket, ce service démarre d’abord le service réel de la base (Requires=pg1-postgres.service). La commande ExecStartPre est une petite boucle shell critique qui vérifie à plusieurs reprises si le port PostgreSQL interne est ouvert. Sans cette vérification, une condition de compétition pourrait survenir où le proxy démarre et redirige la connexion client avant que le conteneur PostgreSQL n’ait fini de s’initialiser. Cela entraînerait une erreur immédiate de “Connection Refused” pour le client. Ce script de pré-démarrage garantit une transition fluide et que le client ne se connecte qu’une fois la base complètement prête.

Le processus principal est systemd-socket-proxyd, un outil intégré qui redirige la connexion entrante vers le port interne sur lequel écoute le conteneur PostgreSQL (127.0.0.1:14532). L’élément crucial est --exit-idle-time=3min. Cela indique au proxy de quitter automatiquement s’il est inactif pendant trois minutes.

/etc/systemd/system/pg1-proxy.service

 1[Unit]
 2Description=Proxy TCP activé par socket vers Postgres local sur 14532
 3
 4Requires=pg1-postgres.service
 5After=pg1-postgres.service
 6
 7[Service]
 8Type=simple
 9Sockets=pg1-proxy.socket
10ExecStartPre=/bin/bash -c 'for i in {1..10}; do nc -z 127.0.0.1 14532 && exit 0; sleep 1; done; exit 0'
11ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=3min 127.0.0.1:14532

4. Le Gestionnaire de Conteneur : service systemd

Ce service gère le cycle de vie Docker Compose. Il est démarré par le service proxy. La directive clé est StopWhenUnneeded=true. Cela lie son cycle de vie au service proxy. Quand pg1-proxy.service s’arrête (car son timer d’inactivité a expiré), systemd considère que ce service n’est plus nécessaire et l’arrête automatiquement en lançant docker-compose down. Le conteneur est arrêté, libérant toutes ses ressources.

/etc/systemd/system/pg1-postgres.service

 1[Unit]
 2Description=conteneur postgres
 3PartOf=pg1-proxy.service
 4StopWhenUnneeded=true
 5
 6[Service]
 7WorkingDirectory=/root/pg/pg1
 8
 9Type=simple
10ExecStart=/usr/bin/docker-compose up
11ExecStop=/usr/bin/docker-compose down
12
13Restart=on-failure
14RestartSec=2s
15TimeoutStopSec=30s

Le Compromis : Démarrages à Froid

Cette configuration est incroyablement efficace, mais présente une considération majeure : la latence du “démarrage à froid”. La toute première connexion à la base après une période d’inactivité sera retardée. Le client doit attendre que systemd lance docker-compose up et que le conteneur PostgreSQL s’initialise. D’après notre expérience, cela prend environ une seconde pour une petite base, mais augmente avec la taille du stockage.

Pour beaucoup de systèmes internes — CI/CD, traitements par lot, ou tableaux de bord d’administration utilisés peu fréquemment — ce délai est un compromis parfaitement acceptable en échange d’économies substantielles de ressources. Pour des applications de production à fort trafic et sensibles à la latence, une base traditionnelle toujours active reste le bon choix.

Activation du Service

Pour mettre une nouvelle base en ligne, il suffit d’activer les unités systemd.

1systemctl daemon-reload
2systemctl enable pg1-proxy.service
3systemctl enable pg1-postgres.service
4systemctl enable --now pg1-proxy.socket

Une fois activée, la base est prête à accepter des connexions, mais ne consommera aucune ressource jusqu’à la première. C’est une petite étape supplémentaire dans notre mission d’éliminer le gaspillage, prouvant que même une infrastructure essentielle comme une base relationnelle peut être pilotée de manière légère et à la demande.