-- 1. Index unic parțial în Postgres (rezolvă coliziunile la soft delete)
CREATE UNIQUE INDEX users_active_email_idx
ON users (email)
WHERE deleted_at IS NULL;
-- 2. Procedură simplă de anonimizare pentru GDPR (în loc de hard delete)
UPDATE users
SET
email = 'deleted_' || id || '@yourdomain.internal',
first_name = 'Anonim',
last_name = 'Utilizator',
phone = NULL,
deleted_at = NOW()
WHERE id = 42 AND deleted_at IS NULL;Salutare! Hai să vorbim despre o chestie pe care mulți o trecem la „las' că vedem noi mai încolo”, iar apoi ne lovește direct în producție: cum gestionăm ștergerile în baza de date fără să ne batem capul cu amenzi GDPR sau indexi sparți.
Cum m-am fript cu soft-delete-ul clasic
La un proiect trecut, o platformă SaaS de HR cu vreo 12.000 de utilizatori activi, am mers pe rețeta clasică: adăugăm deleted_at (nullable timestamp) pe toate tabelele importante. Sună excelent pe hârtie. Utilizatorul șterge un profil, noi doar îi punem timestamp-ul și gata, datele sunt „salvate” în caz că se răzgândește.
Problema a apărut când un utilizator „șters” a vrut să se înregistreze din nou cu aceeași adresă de email. Indexul unic pe coloana email a crăpat instantaneu, pentru că în baza de date adresa încă exista, chiar dacă avea deleted_at completat.
Dacă scoți indexul unic ca să permiți înregistrări multiple, riști să ai duplicate în rândul utilizatorilor activi din cauza unor race-condition-uri obscure. E un compromis mizerabil pe care nu vrei să-l faci.
Capcana GDPR: Soft delete-ul nu e ștergere
Aici e marea confuzie legală pe care o fac mulți developeri. Dacă un client îți cere „Dreptul de a fi uitat” (Right to be Forgotten) conform GDPR, iar tu doar îi pui deleted_at = NOW() în baza de date, ești în afara legii. Datele lui cu caracter personal (nume, prenume, telefon, email) sunt încă acolo, în serverul tău, gata de a fi expuse la un eventual SQL Injection.
Am avut un audit pe tema asta și am fost obligați să regândim tot fluxul în mai puțin de două săptămâni. N-a fost deloc distractiv.
Cum rezolvăm elegant problema?
Avem trei abordări mari, fiecare cu plusuri și minusuri:
-
Indexul parțial (Cea mai simplă chestie pentru Postgres): Dacă folosești Postgres, poți crea un index unic care ignoră rândurile șterse. Este genial de simplu și rezolvă problema coliziunilor la înregistrare. Merge brici pentru entități non-critice (cum ar fi postări pe un blog sau tag-uri), dar nu rezolvă problema GDPR pentru datele personale.
-
Anonimizarea (Anonymization): În loc să ștergi fizic rândul (ceea ce ar strica statisticile financiare sau rapoartele istorice), suprascrii datele sensibile cu valori dummy (ex:
deleted_user_123@yourdomain.internal). Păstrezi ID-urile pentru foreign keys, dar scapi de PII (Personally Identifiable Information). -
Hard Delete cu arhivă anonimizată: Ștergi definitiv userul din tabela principală, dar înainte muți datele agregate (fără nume/email) într-un tabel istoric pentru BI (Business Intelligence).
Ce alegem?
Pentru proiectele mele actuale, folosesc o regulă simplă. Pentru date operaționale fără caracter personal (comentarii, log-uri interne, task-uri), merg pe soft-delete cu index parțial. Pentru date personale (profile de utilizatori, adrese de livrare, detalii de plată), merg pe anonimizare completă sau hard-delete direct.
Voi cum ați rezolvat coliziunile de indexi unici la soft-delete? Ați avut vreodată audit pe GDPR pe tema asta?