eduardweb.
Database & PrismaIntermediar#postgresql#database#sql#gdpr

Soft delete vs Hard delete: Cum schițezi baza de date ca să nu transpiri la primul audit GDPR

De Răzvan Matei, 6 iul. 2026 · 11 vizualizări · 2 like-uri

Postat 6 iul. 2026
sql
-- Rezolvarea problemei de unicitate cu soft delete în PostgreSQL
-- Permite un singur email activ (unde deleted_at este NULL),
-- dar permite oricâte înregistrări șterse cu același email.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL
);

-- Creăm un index parțial unic
CREATE UNIQUE INDEX unique_active_email 
ON users (email) 
WHERE deleted_at IS NULL;

Am văzut zeci de proiecte pornite la drum cu o regulă nescrisă în echipă: „Nu ștergem nimic fizic, punem deleted_at peste tot”. Pare decizia perfectă ca să dormi liniștit noaptea, dar te lovește direct în cap când vine primul mail de la legal cu subiectul „GDPR - solicitare ștergere date”.

La un proiect cu vreo 12.000 de useri activi, am pățit-o direct. Un client supărat ne-a cerut „dreptul de a fi uitat”. Soft delete-ul nostru clasic doar punea un timestamp în coloana deleted_at și ascundea userul din interfață. În baza de date, datele lui de identificare — nume, prenume, telefon, adresa IP — erau intacte. Din punct de vedere legal, asta nu e ștergere, ci doar ascundere. Dacă aveam un control pe bune, eram buni de plată.

Trade-off-ul sincer: de ce soft delete-ul simplu nu e de ajuns

Soft delete e excelent pentru recuperarea rapidă a datelor șterse din greșeală și pentru păstrarea integrității referențiale în tabele uriașe. E mult mai simplu să pui un filter global în ORM decât să te asiguri că un DELETE în cascadă nu îți rupe jumătate de bază de date.

Totuși, vine la pachet cu trei mari probleme:

  1. Problema unicității: Cum pui o constrângere UNIQUE pe coloana de email? Dacă userul ion@popescu.ro își șterge contul (soft delete) și apoi vrea să își facă unul nou cu aceeași adresă, baza de date va crăpa la insert pentru că emailul există deja în tabelă.
  2. Performanța: Indexurile devin din ce în ce mai mari și mai ineficiente deoarece înregistrările „șterse” ocupă spațiu pe disc și memorie în buffer pool.
  3. Legalitatea: GDPR cere explicit ca datele cu caracter personal să fie distruse sau complet anonimizate.

Cum rezolvăm problema tehnic și legal?

Cea mai curată variantă pe care am aplicat-o ulterior constă într-un model hibrid. Pentru tabelele tranzacționale (comenzi, facturi), unde ești obligat prin legea fiscală să păstrezi datele 5-10 ani, folosești soft delete (sau mai bine, le lași așa, fiindcă legea fiscală bate GDPR-ul în acest caz).

Pentru datele de profil (utilizatori, sesiuni, preferințe), ai două opțiuni reale:

Anonimizarea la soft delete

Când userul dă click pe „Șterge contul”, rulezi un update care suprascrie datele sensibile cu valori dummy și marchează deleted_at. Emailul devine deleted_12345@anonim.ro, numele devine Utilizator Șters, iar IP-ul devine 0.0.0.0. Ai rezolvat și GDPR-ul, ai păstrat și ID-ul pentru statistici interne, iar indexul de unicitate nu mai crapă la înregistrări noi.

Hard delete cu indexuri parțiale

Dacă vrei neapărat hard delete pentru datele personale, dar vrei să păstrezi alte entități legate prin foreign key, poți folosi indexuri parțiale (dacă baza ta de date suportă asta, cum face PostgreSQL). Astfel, poți avea un index unic doar pe înregistrările active.

Voi cum ați rezolvat problema asta? Mergeți pe anonimizare automată prin cron joburi sau aveți un flow de hard delete în cascadă bine pus la punct?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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