eduardweb.
Database & PrismaIntermediar#architecture#postgresql#databases#gdpr

Soft delete vs hard delete: cum gândești schema ca să nu regreți la prima dispută GDPR

De Florin Manea, 19 iun. 2026 · 14 vizualizări · 3 like-uri

Postat 19 iun. 2026
sql
-- 1. Index unic parțial în PostgreSQL (permite re-înregistrarea cu același email după soft delete)
CREATE UNIQUE INDEX users_email_active_idx 
ON users (email) 
WHERE deleted_at IS NULL;

-- 2. Query de anonimizare (Hard Soft-Delete pentru conformitate GDPR)
UPDATE users 
SET 
    email = 'deleted_' || id || '@deleted.internal',
    first_name = 'Anonim',
    last_name = 'Utilizator',
    phone = NULL,
    deleted_at = NOW()
WHERE id = 1234;

Toți începem la fel: aruncăm o coloană deleted_at în tabelă și gata, avem soft delete. Sună simplu și elegant până când îți bate DPO-ul la ușă și-ți cere ștergerea definitivă a unui utilizator, conform GDPR.

Am pățit asta la un proiect trecut, un CRM de nișă cu vreo 45.000 de utilizatori activi. Când am primit prima cerere de tipul „dreptul de a fi uitat”, ne-am blocat complet. Dacă dădeam un DELETE clasic în baza de date, totul crăpa instant din cauza constrângerilor de cheie străină (aveam comenzi, facturi și log-uri de activitate legate de acel ID de utilizator). Dacă lăsam doar deleted_at populat, datele de identificare precum numele, email-ul sau numărul de telefon rămâneau fizic pe disc. Iar asta, din punct de vedere legal, nu este considerată o ștergere validă.

O altă greșeală pe care o văd des este folosirea excesivă a ON DELETE CASCADE. Pare comod când vrei să ștergi un utilizator și vrei să se curețe automat tot istoricul lui. În producție, la volume mari de date, o astfel de ștergere în cascadă poate pune baza de date în genunchi din cauza lock-urilor pe tabelele corelate. Am asistat la un incident unde un singur query de delete a blocat tabela de tranzacții timp de 12 secunde, generând un downtime masiv.

Aici apare adevăratul trade-off. Soft delete-ul este excelent pentru recuperarea rapidă a datelor șterse accidental și pentru păstrarea integrității istorice. Totuși, îți complică indexurile unice și te obligă să adaugi WHERE deleted_at IS NULL în aproape toate query-urile scrise manual.

Să luăm exemplul clasic cu adresa de email. Dacă ai o constrângere UNIQUE pe coloana email și un utilizator își șterge contul (soft delete), el nu se va mai putea înregistra din nou cu același email pentru că baza de date va respinge inserarea. În PostgreSQL, rezolv asta elegant folosind un index unic parțial, care ignoră rândurile marcate ca șterse.

But cum rezolvăm problema GDPR fără să rupem integritatea referențială? Soluția pe care o aplic acum este anonimizarea la soft delete (sau data scrubbing). În loc să șterg rândul fizic, rulez un update care suprascrie datele cu caracter personal cu valori dummy sau hash-uri ireversibile, generând în același timp un string aleatoriu pentru email ca să eliberez adresa reală pentru o eventuală nouă înregistrare. Câmpurile financiare anonime rămân intacte pentru rapoarte, dar identitatea persoanei dispare complet.

Pregătirea schemei de la început pentru acest scenariu te scapă de zeci de ore de refactoring și migrări dureroase când aplicația crește și apar cerințele legale.

Voi cum gestionați asta în producție? Mergeți pe anonimizare direct în tabelă sau folosiți o tabelă separată de arhivă unde mutați datele reci după o anumită perioadă?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.