A propos Compétences Expérience Services Blog Contact

Le droit à l'effacement à l'échelle : pourquoi on a remplacé le hard delete par des files Right to erasure at scale: why we replaced hard delete with queues

Le RGPD donne à chaque utilisateur le droit de faire effacer ses données. Côté backend, ce droit a un nom plus concret : la suppression de compte. Et c'est un des chantiers les plus sous-estimés que je connaisse.

TL;DR : à 10 000 utilisateurs, supprimer un compte est une requête SQL. À 25 millions, c'est un workflow distribué. Sur la plateforme santé où je travaille, on a migré le hard delete vers des files de suppression. Des étapes idempotentes, rejouables, tracées. Voici pourquoi, et comment construire pareil chez toi.

Cet article est pour les devs backend qui doivent prendre l'article 17 du RGPD au sérieux. Donc, tôt ou tard, à peu près tout le monde.

Le contexte

Je travaille sur une plateforme d'authentification pour la santé. Plus de 25 millions d'utilisateurs, plus de 1 000 sites, plusieurs régions.

Sur des données de santé, le droit à l'effacement n'est pas un sujet théorique. Les demandes arrivent, toutes les semaines, et chacune a une échéance légale.

Au départ, la suppression reposait sur du hard delete direct. Un hard delete efface la donnée pour de bon, tout de suite. L'API recevait la demande, et la base exécutait les DELETE dans la foulée.

On a fini par migrer tout ça vers des files distribuées. Une file est une liste de travaux qu'un programme traite plus tard, à son rythme. Ce n'était pas un caprice d'architecte. Voici ce qui casse.

Ce qui casse en premier : la transaction géante

Un compte ancien touche des dizaines de tables. Profil, préférences, consentements, historique, liens vers les applications.

Un DELETE en cascade transforme tout ça en une seule transaction énorme. Les locks s'accumulent. La réplication prend du retard. Les autres requêtes attendent.

Pendant ce temps, la requête HTTP a un timeout. Elle abandonne au bout de 30 secondes. La transaction, elle, continue ou s'annule, sans que personne ne lise la réponse.

L'utilisateur reclique. Une deuxième transaction géante démarre sur les mêmes lignes. Bonne chance.

Un DELETE synchrone marche très bien en dev. Il marche encore à 10 000 comptes. Puis un jour, il ne marche plus.

Le vrai problème : aucune transaction ne couvre tout

Le pire n'est pas la taille de la transaction. Le pire, c'est que les données d'un utilisateur ne vivent plus dans une seule base.

Fais l'inventaire chez toi. La base principale. Le cache Redis. L'index de recherche. Le stockage de fichiers. Les logs. L'analytics. Les sous-traitants.

Aucune transaction SQL ne couvre cet ensemble. Supprimer, c'est coordonner plusieurs systèmes qui peuvent chacun échouer.

Le régulateur a fait le même constat. L'EDPB, le comité européen de la protection des données, a audité le droit à l'effacement dans toute l'Europe en 2025. Son rapport de février 2026 pointe des procédures insuffisantes et des systèmes incapables d'effacer partout.

La pression vient aussi des utilisateurs. Aux Pays-Bas, l'effacement pesait 18,6 % des plaintes reçues par l'autorité en 2024. Première catégorie de plaintes.

Le design qui tient : une file et des étapes idempotentes

La demande de suppression devient un enregistrement, pas une requête à rallonge. L'API répond en quelques millisecondes : demande acceptée.

Le compte est désactivé sur-le-champ. Sessions révoquées, connexion refusée. Pour l'utilisateur, le compte a disparu.

Ensuite, un orchestrateur déroule la purge, étape par étape. Une étape par domaine de données.

# La purge, une étape par domaine de données
steps := []DeletionStep{
    revokeSessions,    // jetons et sessions
    deleteProfile,     // base principale
    purgeSearchIndex,  // index de recherche
    deleteFiles,       // stockage objet
    notifyProcessors,  // sous-traitants
    writeErasureProof, // journal de preuve
}

Chaque étape est idempotente. Idempotent veut dire : rejouer l'étape donne le même résultat. Supprimer une ligne déjà supprimée ne fait rien. C'est ce qui rend les retries sûrs.

Chaque étape a ses retries et sa dead-letter. La dead-letter est la file des travaux qu'on n'arrive pas à traiter. Elle doit déclencher une alerte, pas dormir dans un coin.

Chez nous, l'orchestration tourne sur des files distribuées et un moteur de workflows, avec Temporal et Azure Service Bus. Les briques comptent moins que les trois règles : découpé, idempotent, tracé.

Désactiver tout de suite, effacer pour de bon ensuite

L'article 12 du RGPD te laisse un mois pour traiter la demande. Tu n'as pas besoin de tout effacer dans la seconde. Tu as besoin de le faire pour de bon.

Attention au piège inverse. Un soft delete n'est pas de l'effacement. Poser un deleted_at sur une ligne ne supprime rien.

L'EDPB pointe exactement cette dérive : du marquage ou de l'anonymisation maison présentés comme de l'effacement. Une anonymisation réversible, c'est des données avec un post-it dessus.

Le bon enchaînement tient en trois temps. Désactivation immédiate. Une fenêtre courte d'annulation, si ton produit le justifie. Puis la purge définitive, système par système.

Les sauvegardes, l'angle mort

Les sauvegardes gardent des copies complètes de ta base. Tu ne peux pas les ouvrir une par une pour en retirer un utilisateur.

L'EDPB cite les sauvegardes parmi les limites techniques les plus fréquentes. Le sujet mérite une règle écrite, pas un silence gêné.

Ma position : une rétention courte et documentée. Les sauvegardes expirent, et l'effacement devient complet à l'expiration. Plus une procédure écrite pour la restauration : rejouer le journal des suppressions après un restore.

Netflix a documenté le même chantier à son échelle. Une plateforme centralisée de suppression : 76,8 milliards de lignes effacées, 1 300 jeux de données. Personne n'échappe au sujet en grandissant.

Prouver que tu as effacé

Une autorité de contrôle ne te demandera pas ta bonne foi. Elle te demandera des preuves.

Chaque étape écrit donc une ligne de journal : quelle catégorie de données, quel système, quand. Jamais le contenu. Un journal d'effacement qui contient des données personnelles serait un contresens.

La demande a un état terminal : effacée partout, avec la date. Une métrique suit les demandes en cours. Une alerte part quand une demande dépasse son budget de temps.

Le jour du contrôle, tu réponds avec un journal. Pas avec une promesse.

La checklist avant ta prochaine demande d'effacement

Passe cette liste sur ton système actuel. Chaque case vide est un incident futur.

  • Cartographie où vivent les données d'un utilisateur : bases, caches, index, fichiers, logs, sous-traitants
  • Transforme la demande en enregistrement : réponse immédiate, compte désactivé sur-le-champ
  • Découpe la purge en étapes idempotentes, une par domaine de données
  • Donne à chaque étape ses retries et une dead-letter qui alerte
  • Journalise chaque effacement par catégories, jamais par contenu
  • Écris la rétention des sauvegardes et la règle de rejeu après restauration
  • Teste le parcours de suppression complet en staging, régulièrement

Ce qu'il faut retenir

Un DELETE n'est pas un système d'effacement. À l'échelle, l'effacement est un workflow : des files, des étapes idempotentes, un journal qui prouve.

Le sujet sort de l'angle mort. Les autorités européennes ont mené leur première action coordonnée sur le droit à l'effacement, et les plaintes montent.

Le bon moment pour construire ça, c'est avant la demande qui coince.

Tu dois industrialiser le droit à l'effacement sur ton backend ? Parlons-en.

GDPR gives every user the right to have their data erased. On the backend, that right has a concrete name: account deletion. It is one of the most underestimated projects I know.

TL;DR: at 10,000 users, deleting an account is a SQL statement. At 25 million, it is a distributed workflow. On the healthcare platform I work on, we migrated hard delete to deletion queues. Idempotent steps, replayable, logged. Here is why, and how to build the same thing.

This article is for backend developers who have to take GDPR Article 17 seriously. So, sooner or later, almost everyone.

The setup

I work on an authentication platform for healthcare. More than 25 million users, more than 1,000 sites, several regions.

With health data, the right to erasure is not a thought experiment. Requests come in every week, and each one has a legal deadline.

At the start, deletion relied on direct hard delete. A hard delete erases the data for good, immediately. The API received the request, and the database ran the DELETEs on the spot.

We ended up migrating all of it to distributed queues. A queue is a list of jobs that another program processes later, at its own pace. This was not an architect's whim. Here is what breaks.

What breaks first: the giant transaction

An old account touches dozens of tables. Profile, preferences, consents, history, links to applications.

A cascading DELETE turns all of that into one huge transaction. Locks pile up. Replication falls behind. Other queries wait.

Meanwhile, the HTTP request has a timeout. It gives up after 30 seconds. The transaction keeps going, or rolls back, with nobody left to read the answer.

The user clicks again. A second giant transaction starts on the same rows. Good luck.

A synchronous DELETE works fine in dev. It still works at 10,000 accounts. Then one day it does not.

The real problem: no transaction covers everything

The worst part is not the size of the transaction. The worst part is that a user's data no longer lives in one database.

Run the inventory on your own system. The main database. The Redis cache. The search index. File storage. Logs. Analytics. Third-party processors.

No SQL transaction covers that set. Deleting means coordinating several systems, and each one can fail.

The regulator reached the same conclusion. The EDPB, the European Data Protection Board, audited the right to erasure across Europe in 2025. Its February 2026 report points at weak internal procedures and systems unable to erase everywhere.

Users push too. In the Netherlands, erasure complaints were 18.6% of everything the authority received in 2024. The largest single category.

The design that holds: a queue and idempotent steps

The deletion request becomes a record, not a long-running query. The API answers in milliseconds: request accepted.

The account is deactivated immediately. Sessions revoked, login refused. From the user's point of view, the account is gone.

Then an orchestrator runs the purge, step by step. One step per data domain.

# The purge, one step per data domain
steps := []DeletionStep{
    revokeSessions,    // tokens and sessions
    deleteProfile,     // main database
    purgeSearchIndex,  // search index
    deleteFiles,       // object storage
    notifyProcessors,  // third-party processors
    writeErasureProof, // proof journal
}

Every step is idempotent. Idempotent means: replaying the step gives the same result. Deleting an already deleted row does nothing. That is what makes retries safe.

Every step has its retries and its dead-letter. The dead-letter is the queue of jobs you failed to process. It must trigger an alert, not sleep in a corner.

In our case, the orchestration runs on distributed queues and a workflow engine, with Temporal and Azure Service Bus. The bricks matter less than the three rules: split, idempotent, logged.

Deactivate now, erase for good later

GDPR Article 12 gives you a month to handle the request. You do not need to erase everything within a second. You need to erase for good.

Watch out for the opposite trap. A soft delete is not erasure. Setting a deleted_at on a row deletes nothing.

The EDPB flags exactly that drift: flags or homemade anonymization presented as erasure. Reversible anonymization is just data with a sticky note on it.

The sequence that works has three phases. Immediate deactivation. A short cancellation window, if your product justifies one. Then the final purge, system by system.

Backups, the blind spot

Backups keep full copies of your database. You cannot open them one by one to remove a single user.

The EDPB lists backups among the most frequent technical limits. The topic deserves a written rule, not an embarrassed silence.

My position: short, documented retention. Backups expire, and erasure becomes complete at expiry. Plus a written restore procedure: replay the deletion journal after any restore.

Netflix documented the same project at its own scale: a centralized deletion platform, 76.8 billion rows erased across 1,300 datasets. Nobody outgrows this problem.

Prove that you erased

A supervisory authority will not ask for your good faith. It will ask for evidence.

So every step writes a journal line: which data category, which system, when. Never the content. An erasure journal that contains personal data would defeat its own purpose.

The request has a terminal state: erased everywhere, with a date. A metric tracks requests in flight. An alert fires when a request exceeds its time budget.

When the audit comes, you answer with a journal. Not with a promise.

The checklist before your next erasure request

Run this list against your current system. Every unchecked box is a future incident.

  • Map where a user's data lives: databases, caches, indexes, files, logs, processors
  • Turn the request into a record: immediate response, account deactivated on the spot
  • Split the purge into idempotent steps, one per data domain
  • Give every step retries and a dead-letter that alerts
  • Journal every erasure by category, never by content
  • Write down backup retention and the replay-after-restore rule
  • Test the full deletion journey in staging, regularly

What to remember

A DELETE is not an erasure system. At scale, erasure is a workflow: queues, idempotent steps, a journal that proves it.

The topic is leaving the blind spot. European authorities ran their first coordinated action on the right to erasure, and complaints keep climbing.

The right time to build this is before the request that gets stuck.

Need to industrialize the right to erasure on your backend? Let's talk.