-- Cum rezolvăm conflictul de UNIQUE constraint pe email când folosim soft delete (exemplu Postgres)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
deleted_at TIMESTAMP NULL
);
-- Soluția: index unic parțial.
-- Permite același email de mai multe ori doar dacă e șters, dar garantează unicitatea pentru utilizatorii activi.
CREATE UNIQUE INDEX users_active_email_idx ON users (email)
WHERE deleted_at IS NULL;Pun pariu că primul tău impuls când proiectezi o bază de date este să trântești un câmp deleted_at în toate tabelele. Pare safe, ai un istoric de siguranță în caz că se răzgândește clientul, dar abordarea asta devine rapid o bombă cu ceas când primești primul mail oficial pentru "dreptul de a fi uitat". Am pățit-o pe pielea mea și am învățat că decizia asta se ia cu arhitectura în față, nu din inerția ORM-ului.
Capcana de care m-am lovit la 120k useri
La un magazin online cu vreo 120.000 de clienți activi, am implementat clasicul soft delete oferit out-of-the-box de framework. Ne gândeam că ne salvăm spatele dacă vreun operator șterge un cont din greșeală. Toate bune până când un client supărat ne-a trimis o solicitare oficială GDPR de ștergere a datelor.
Ce faci atunci? Dacă ai doar deleted_at IS NOT NULL, datele lui personale (nume, prenume, telefon, adrese de livrare) sunt încă în baza ta de date de producție și în backup-uri. În caz de audit, argumentul că "e marcat ca șters în aplicație" nu ține în fața legii. Am pierdut două nopți scriind scripturi ad-hoc de anonimizare pentru că baza de date era strâns legată prin chei străine de comenzi, facturi și istoric de navigare.
Trade-off-ul sincer pe care nu ți-l spune nimeni
Soft delete-ul este o scuză leneșă pentru lipsa unui sistem de backup bun sau a unui istoric de audit separat. Merge brici când vrei o plasă de siguranță rapidă pentru admini zăpăciți. În schimb, e complet nasol din trei motive mari:
- Indexurile unice se duc naibii. Dacă ai un index unic pe email, un utilizator șters (soft) va bloca înregistrarea unui utilizator nou cu același email.
- Toate query-urile scrise manual devin un coșmar. Uiți un singur
WHERE deleted_at IS NULLîntr-un raport intern și ai livrat cifre greșite la management. - Performanța scade pe tabele mari deoarece baza de date scanează și indexează mii de rânduri care teoretic nu mai există pentru business.
Cum rezolvi elegant problema
Dacă legislația sau business-ul îți cere să păstrezi istoricul tranzacțional (de exemplu, nu poți șterge o comandă din motive fiscale), nu folosi soft delete pe tabela de utilizatori. Folosește anonimizarea (sau pseudonimizarea).
Când userul cere ștergerea, rulezi un update care suprascrie câmpurile sensibile cu valori dummy (ex: 'Utilizator Anonimizat', email-ul devine un hash unic), dar lași ID-ul intact în baza de date. Astfel, comenzile lui vechi rămân legate de un ID valid, dar identitatea lui e complet ștearsă din punct de vedere GDPR.
Dacă totuși ai nevoie de soft delete tehnic și vrei să eviți problemele cu indexurile unice, soluția cea mai curată pe baze de date moderne (cum e Postgres) este un index parțial, așa cum am scris în exemplul de mai jos.
Voi cum procedați la proiectele voastre? Mergeți pe hard delete direct în cascadă sau scrieți scripturi de anonimizare la cerere?