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

Soft delete vs hard delete: cum gândești schema ca să nu ai coșmaruri cu GDPR-ul

De Ana Ionescu, 9 aug. 2026 · 4 vizualizări · 2 like-uri

Postat 9 aug. 2026
sql
-- Partial index pentru unicitate doar pe rândurile active
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    full_name VARCHAR(255) NOT NULL,
    deleted_at TIMESTAMPTZ DEFAULT NULL
);

CREATE UNIQUE INDEX idx_users_active_email 
ON users (email) 
WHERE deleted_at IS NULL;

-- Procedură curată de GDPR Anonymization + Soft Delete
UPDATE users 
SET 
    email = 'deleted_' || id || '@anonymous.local',
    full_name = 'Utilizator Șters',
    deleted_at = NOW()
WHERE id = 42 AND deleted_at IS NULL;

Primul reflex al oricărui dev când creează o tabelă e să trântească un deleted_at TIMESTAMP NULL și să zică că a rezolvat soft delete-ul. Am făcut și eu asta prin 2014, iar un an mai târziu plângeam lângă un DB restore pentru că scăpasem un WHERE deleted_at IS NULL într-un query critic din producție.

Astăzi, problema nu mai e doar un bug banal în UI. Când adaugi GDPR în ecuație, un soft delete prost gândit de la început devine un risc juridic direct și o sursă continuă de migrații dureroase.

Capcana indexării și a unicității

Cea mai frecventă chestie care crapă la soft delete este unicitatea datelor. Ai tabela de users cu coloana email. Un utilizator își șterge contul, tu îi pui deleted_at = NOW(). Peste două săptămâni, omul încearcă să se înregistreze din nou cu același email. Ce se întâmplă? Constraint-ul clasic de UNIQUE(email) crapă instant.

Dacă scoți constraint-ul, ai rezolvat problema pe moment, dar ai deschis ușa către duplicate pe conturile active. Rezolvarea elegantă în PostgreSQL este un partial index. Astfel, obligi baza de date să verifice unicitatea doar pentru rândurile care sunt efectiv active.

GDPR nu înseamnă deleted_at = NOW()

Conform GDPR, "dreptul de a fi uitat" înseamnă că datele cu caracter personal (PII) nu mai au voie să existe în sistemul tău în forma lor originală, cu excepția cazului în care ești obligat prin lege să le păstrezi (de exemplu, datele de facturare din contabilitate).

Un simple deleted_at cu emailul, IP-ul și numele omului încă vizibile în baza de date NU înseamnă ștergere în ochii unui auditor. Datele sunt tot acolo, doar că aplicația ta a ales să le ignore.

La un proiect e-commerce cu vreo 85k utilizatori activi, când a venit prima cerere formală de GDPR Articolul 17, ne-am dat seama că deleted_at doar masca datele în frontend. Am fost nevoiți să rescriem logica în regim de urgență și să adoptăm o strategie de anonimizare la soft delete:

  1. Păstrăm rândul pentru integritatea referențială (să nu crapie user_id-ul din comenzile istorice).
  2. Suprascriem PII-ul cu valori anonieme (ex: email = anon_1234@deleted.local, name = 'Utilizator Șters').
  3. Marcăm deleted_at = NOW().

Trade-off-uri sincere: ce alegi?

Nicio soluție nu e glonț de argint, iar decizia trebuie luată din prima zi:

  • Soft delete clasic (deleted_at): Excelent pentru recuperare rapidă dacă userul dă click greșit. Nasol pentru performanță dacă ai tabele mari (milioane de rânduri) și uiți să pui index pe deleted_at pentru că trebuie să-l filtrezi în 99% din query-uri.
  • Hard delete (DELETE FROM): Cel mai curat din punct de vedere al spațiului și al conformității GDPR. Periculos dacă ai ON DELETE CASCADE configurat greșit și dărâmi jumătate de bază de date dintr-o greșeală de ID.
  • Anonimizare (Anonymized Soft Delete): Cel mai bun compromis pentru SaaS și e-commerce. Păstrezi rapoartele financiare și istoricul intacte, dar elimini PII-ul complet din sistem.

Dacă ești la început de proiect, nu lăsa decizia asta pe mai târziu. Un UPDATE e ușor de scris azi, dar refactorizarea a 40 de tabele legate prin foreign keys când ești deja în producție e un calvar.

Voi ce strategie folosiți când aveți de-a face cu date financiare legate de useri care cer ștergerea contului?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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