Étude de cas · Plateforme d’analytique confidentielle, Amérique du Nord

Débit d’ingestion multiplié par 10 sur un pipeline analytique PostgreSQL grâce aux tables UNLOGGED

Comment UnlockLive a utilisé les tables UNLOGGED de PostgreSQL, l’ingestion COPY par lots et un modèle rigoureux à deux tables pour faire passer un pipeline d’analytique à fort volume de 8 000 à 80 000 événements/s — sans perdre les garanties d’intégrité des données exigées par le chemin audité.

  • SecteurDonnées / Analytique
  • Année2024
  • PaysÉtats-Unis
  • Durée3 mois
10x Ingest Throughput on a PostgreSQL Analytics Pipeline Using UNLOGGED Tables hero screenshot

Résultats en un coup d'œil

  • 10xDébit d'ingestion soutenu (8K → 80K+ événements/s)
  • 60%Coût IOPS RDS réduit sur la même classe d’instance
  • <50msLatence d’insertion p95 de l’étape 1 sous charge soutenue
  • 0Régressions d’intégrité des données sur le chemin de reporting audité

Le défi

Une plateforme d’analytique ingérait les événements produit d’une base de clients en croissance dans une seule table « raw_events ». Le schéma d’origine était correct mais lent : chaque événement touchait une table journalisée avec sept index, le WAL était le goulot d’étranglement et le débit d’ingestion plafonnait autour de 8 000 événements par seconde. Les IOPS RDS constituaient le principal poste de coût, et une expansion client prévue d’un facteur 3 allait faire casser le pipeline avant la signature du prochain contrat.

L’équipe avait lu les conseils habituels — « utilisez une file », « shardez la table », « passez à une TSDB dédiée » — mais toutes ces réponses impliquaient une migration de plusieurs trimestres. La vraie question était : PostgreSQL peut-il suivre si nous cessons de lutter contre lui ? Le piège était une exigence stricte : tout ce qui atteignait les tables de reporting auditées devait être durable, cohérent et indexé. Nous ne pouvions pas sacrifier l’intégrité côté analytique pour sauver l’ingestion.

Notre solution

Nous avons scindé le pipeline en deux étapes clairement séparées, avec deux contrats de durabilité différents.

Étape 1 (ingestion) : une table de staging UNLOGGED qui absorbe le flux. Les tables UNLOGGED de PostgreSQL n’écrivent pas dans le WAL lors des insertions, ce qui est exactement le bon compromis pour des données de staging éphémères — nous obtenons un débit d’insertion brut 5 à 10 fois supérieur en échange de la perte des lignes en staging si le serveur plante. Combiné à un COPY par lots (et non INSERT) depuis un service d’ingestion Python et à une règle délibérée « aucun index sur la table de staging », l’ingestion brute est passée de 8 000 à plus de 80 000 événements/s sur la même instance RDS.

Étape 2 (durable) : un worker Celery vide la table de staging par micro-lots vers la vraie table `events`, entièrement journalisée et indexée, au sein d’une seule transaction avec des upserts idempotents. Le chemin de reporting audité ne lit que la table durable. Si le serveur plante en pleine ingestion, nous perdons au plus quelques secondes d’événements en transit de la table de staging — et les producteurs en amont réessaient, de sorte que la table durable converge vers l’état correct.

Nous y avons associé une couche opérationnelle réduite mais soignée : un tableau de bord Grafana pour le taux de remplissage de l’étape 1, une alerte stricte si le drainer prend du retard, une rotation des partitions sur la table durable et un runbook écrit pour les trois modes de défaillance qui comptent.

  • Table de staging UNLOGGED sans index — conçue spécifiquement pour le débit d’insertion brut
  • Ingestion COPY par lots depuis un service Python/FastAPI, et non des INSERT ligne par ligne
  • Drainer Celery déplaçant des micro-lots vers la table events durable et entièrement indexée
  • Upserts idempotents (ON CONFLICT DO NOTHING) pour que les nouvelles tentatives des producteurs soient sans risque
  • Partitionnement mensuel de la table durable pour borner le travail de vacuum et d’index
  • Tableaux de bord et alertes de latence de bout en bout dans Grafana / Datadog
  • Runbook écrit couvrant le basculement RDS, le plantage du drainer et la contre-pression des producteurs
  • Validation par l’audit du profil de durabilité avant le lancement — sans mauvaise surprise

Comment nous l'avons construit

  1. 01

    Identifier le véritable goulot d’étranglement

    Avant de toucher au schéma, nous avons passé une semaine avec pg_stat_statements, RDS Performance Insights et un suivi personnalisé du débit WAL. Les données étaient sans ambiguïté : les écritures WAL et la maintenance des index sur l'unique grosse table d'événements représentaient 78 % du temps d'écriture. Le problème n'était ni le CPU ni la mémoire, mais la durabilité.

  2. 02

    Conception du contrat à deux tables

    Nous avons rédigé un court document de conception qui définissait précisément ce que UNLOGGED nous apportait, ce qu'il nous coûtait et les garanties que les producteurs en amont devaient fournir (livraison au moins une fois, identifiants d'événements idempotents). Ce contrat a rendu le profil de perte explicite, de sorte que l'équipe d'audit a pu donner son accord à l'avance : « peut perdre jusqu'à N secondes d'événements en transit lors d'un basculement RDS ; la table durable n'est pas affectée. »

  3. 03

    COPY par lots, drainage en micro-lots

    Nous avons remplacé les insertions ligne par ligne par un service d'ingestion Python qui met les événements en tampon en mémoire jusqu'à 250 ms puis les transfère via PostgreSQL COPY dans la table de transit UNLOGGED. Un drainer Celery déplace des micro-lots de 5 000 lignes vers la table durable au sein d'une seule transaction, avec ON CONFLICT DO NOTHING pour l'idempotence.

  4. 04

    Exploitation, partitionnement, runbook

    Nous avons ajouté un partitionnement mensuel à la table durable (pour que le vacuum et les reconstructions d'index restent peu coûteux), des tableaux de bord Grafana pour le décalage de bout en bout, des alertes sur le remplissage de la zone de transit et le retard du drainer, ainsi qu'un guide d'exploitation écrit couvrant les trois vrais modes de défaillance : plantage du drainer, basculement RDS et contre-pression des producteurs. Passation à l'équipe d'astreinte du client avec accompagnement.

Stack technique

  • PostgreSQL 16
  • Python
  • FastAPI
  • Celery
  • Redis
  • AWS RDS
  • AWS S3
  • Grafana
  • Datadog
  • Ingénierie de la performance backend
  • Python et FastAPI
  • Solutions cloud
  • Architecture de bases de données
“Nous pensions consacrer un trimestre à la migration vers une base de données de séries temporelles dédiée. À la place, UnlockLive nous a montré comment faire faire le travail à Postgres, et nos auditeurs ont validé la nouvelle conception avant sa mise en production.”
Responsable de l’ingénierie des données · Plateforme d’analytique (nom confidentiel)

Questions fréquentes

Les tables UNLOGGED de PostgreSQL sont-elles sûres en production ?

Oui, pour le bon usage. Les tables UNLOGGED évitent les écritures WAL, ce qui offre un débit d'insertion brut 5 à 10 fois supérieur, en échange d'un profil de perte documenté : les lignes sont perdues en cas de crash du serveur et ne sont pas répliquées. Elles conviennent très bien aux données de transit de courte durée lorsqu'un système en amont peut rejouer les événements. Elles sont inadaptées à tout ce que vous devez relire de manière faisant foi. Nous les associons toujours à une table durable et journalisée, sur laquelle s'appuie le chemin audité.

Pourquoi ne pas simplement utiliser une base de séries temporelles dédiée comme TimescaleDB ou ClickHouse ?

Parce que le coût de migration et la surface opérationnelle sont bien réels. Si votre équipe exploite déjà bien PostgreSQL et que votre cible de débit se chiffre en dizaines de milliers d’événements par seconde, le modèle UNLOGGED + COPY par lots permet souvent d’y arriver en quelques semaines plutôt qu’en quelques trimestres. Nous recommandons une base de séries temporelles dédiée lorsque la charge exige réellement un stockage en colonnes, des index partitionnés par période ou des requêtes de plage à la milliseconde à grande échelle.

Puis-je ajouter des index à une table de transit UNLOGGED PostgreSQL ?

Vous pouvez, mais vous ne devriez presque pas. Tout l'intérêt de l'étage de transit est la vitesse d'insertion brute ; chaque index ajouté coûte du débit d'ingestion. Nous gardons la table de transit sans index et plaçons tous les index sur la table durable en aval, sur laquelle s'exécutent réellement les requêtes analytiques.

Comment garantissez-vous l’absence de perte de données avec des tables UNLOGGED dans un pipeline ?

Deux règles. Premièrement, les producteurs en amont doivent offrir une livraison au moins une fois avec des identifiants d'événement idempotents pour que les nouvelles tentatives soient sans risque. Deuxièmement, la partie durable du pipeline ne lit jamais que la table aval entièrement journalisée, jamais la table de transit UNLOGGED. Le pire scénario se limite à quelques secondes d'événements de transit rejouables en cas de crash, jamais à un rapport manquant.

Quel débit d'ingestion peut-on obtenir d'une seule instance PostgreSQL avec ce modèle ?

Cela dépend de la taille des lignes, du réseau et de la classe d'instance, mais sur un AWS RDS db.r6g.xlarge modeste avec ce modèle exact à deux tables, nous observons couramment 60 000 à 100 000 événements par seconde en régime soutenu, avec une latence d'insertion p95 de l'étape 1 inférieure à 50 ms. Au-delà, nous partitionnerions la table de transit ou monterions en charge horizontalement avec un routeur d'écriture.

Envie d'un résultat comme celui-ci ?

Parlez à l'équipe qui a construit Débit d’ingestion multiplié par 10 sur un pipeline analytique PostgreSQL grâce aux tables UNLOGGED. Nous définirons le périmètre de votre projet, vous remettrons une proposition à prix fixe et vous présenterons l'exemple le plus proche de notre portfolio.

Réserver un appel stratégique