-- Soluția 1: Index unic parțial în PostgreSQL
CREATE UNIQUE INDEX users_email_active_idx
ON users (email)
WHERE deleted_at IS NULL;
-- Soluția 2: Anonimizare la soft delete pentru conformitate GDPR
UPDATE users
SET
email = 'deleted_' || id || '@yourdomain.com',
first_name = 'Anonymized',
last_name = 'User',
deleted_at = NOW()
WHERE id = 42 AND deleted_at IS NULL;Am văzut prea multe proiecte care pornesc la drum cu un simplu flag de deleted_at adăugat peste tot, fără nicio strategie reală. Pare soluția perfectă: nu pierzi date și poți face restore oricând. Dar când vine primul audit GDPR sau o cerere de ștergere pe bune, schema ta se transformă într-un coșmar.
Am pățit-o acum vreo trei ani, la un SaaS din zona de HR unde aveam în baza de date în jur de 80.000 de candidați. Am implementat soft-delete mecanic peste tot. Când un candidat își ștergea contul, noi doar făceam un update pe deleted_at. Toate bune, până când departamentul legal ne-a bătut pe umăr: „Băieți, omul ăsta figurează încă cu nume, prenume și CNP în baza noastră de date, deși a cerut ștergerea acum o lună. Riscăm o amendă de ne ustură capul.”
Capcana soft delete-ului clasic
Cea mai mare problemă la soft delete nu e doar că ții datele în baza de date. E vorba de constrângerile de unicitate.
Să zicem că ai un index UNIQUE pe coloana de email. Dacă un utilizator își șterge contul (soft delete) și apoi vrea să se înregistreze din nou cu același email, baza de date va arunca o eroare. De ce? Pentru că rândul vechi încă există acolo, chiar dacă are deleted_at populat.
Ca să rezolvi asta, începi să faci artificii. Fie scoți constrângerea de unicitate (o idee extrem de proastă care duce la duplicare de date din cauza race condition-urilor), fie schimbi indexul unic să fie parțial (de exemplu, unic doar unde deleted_at IS NULL). Dar nu toate bazele de date suportă indexuri parțiale la fel de ușor, iar complexitatea crește inutil pe măsură ce aplicația crește.
Soluția de compromis: Anonimizarea pe soft-delete
Dacă business-ul cere neapărat să păstrezi statistici sau înregistrări financiare legate de acel utilizator (unde hard-delete-ul ar strica integritatea referențială), soluția mea preferată este anonimizarea selectivă.
În loc să lași datele personale intacte, în momentul în care userul face "delete", rulezi un proces care suprascrie datele sensibile cu valori dummy sau hash-uri unice, marcând în același timp rândul ca șters. Astfel, păstrezi ID-ul pentru cheile străine din tabelele de comenzi sau loguri, dar scapi de datele cu caracter personal.
Când să folosești Hard Delete?
Să fim sinceri: pentru 90% din tabelele de legătură sau entitățile mici, hard-delete-ul este sfânt. Economisești spațiu, queries-urile rămân simple și nu trebuie să adaugi WHERE deleted_at IS NULL la absolut fiecare query (am văzut bug-uri masive în producție doar pentru că un junior a uitat să pună clauza asta într-un query complex).
Dacă ai nevoie de audit, folosește tabele de istoric separate (shadow tables) unde muți datele înainte de ștergere, asigurându-te că în acele tabele nu ajung date personale necriptate sau neanonimizate.
Voi cum gestionați asta? Mergeți pe soft delete peste tot sau preferați să curățați baza de date direct cu hard delete când userul cere asta?