-- Crearea unui index parțial în PostgreSQL pentru a permite
-- re-înregistrarea cu același email după soft delete
CREATE UNIQUE INDEX idx_users_email_active
ON users (email)
WHERE deleted_at IS NULL;Am văzut prea multe proiecte pornite la drum cu ideea simplistă „punem un câmp deleted_at peste tot și am rezolvat problema”. Sună bine la kohala, e confortabil pentru că nu pierzi date din greșeală, dar în producție devine rapid o bombă cu ceas. Când vine primul audit GDPR sau când un client îți cere oficial „dreptul de a fi uitat”, arhitectura ta „inteligentă” s-ar putea să te trimită direct la refăcut baza de date.
Am pățit asta acum câțiva ani la o platformă SaaS cu vreo 18.000 de utilizatori activi. Ne-am trezit blocați între cerințele legale și integritatea referențială a bazei de date.
Capcana unicității și indexul care crapă
Cea mai rapidă palmă peste ceafă pe care o primești de la soft delete vine de la constrângerile de unicitate. Să luăm exemplul clasic: tabela de utilizatori unde câmpul email trebuie să fie unic.
Userul Ion își șterge contul (soft delete, deci facem UPDATE users SET deleted_at = NOW() WHERE id = 1). După două săptămâni, Ion se răzgândește și vrea să își facă un cont nou cu același email. În mod normal, baza de date va arunca o eroare de tipul Duplicate entry, pentru că rândul vechi încă există fizic în tabelă, chiar dacă aplicația ta îl ignoră prin WHERE deleted_at IS NULL.
Cum rezolvi asta? În PostgreSQL, poți folosi un index parțial. E o soluție curată care ne-a salvat de la multe dureri de cap. Practic, forțezi unicitatea doar pentru înregistrările active.
GDPR nu înseamnă doar „să nu se mai vadă în UI”
Să fim foarte clari: din punct de vedere legal, soft delete NU este ștergere. Dacă datele personale (nume, prenume, IP, email, telefon) încă există pe serverul tău SQL, chiar și cu o bifă de is_deleted = true, ești pasibil de amendă. Inspectorii nu se uită doar la ce afișezi în interfață, ci la ce stochezi fizic.
Pe de altă parte, dacă faci hard delete direct (DELETE FROM users), riști să strici toate statisticile financiare și istoricul comenzilor din cauza constrângerilor de cheie străină (FOREIGN KEY). Nu vrei să ștergi o factură doar pentru că ai șters userul care a emis-o.
Compromisul salvator: Anonimizarea
La proiectele medii și mari, am început să aplicăm o strategie hibridă care împacă și capra (GDPR) și varza (integritatea bazei de date). O numesc „soft delete cu mascare”.
Când utilizatorul cere ștergerea contului:
- Rulăm un proces de anonimizare: suprascriem câmpurile sensibile cu valori dummy sau hash-uri (
email = 'deleted_' || id || '@anon.local',nume = 'Utilizator Anonim'). - Păstrăm ID-ul pentru consistența tranzacțiilor financiare.
- Setăm
deleted_at = NOW()pentru a ști când s-a produs acțiunea.
În felul acesta, datele cu caracter personal dispar definitiv (hard-ish delete pe date, nu pe rând), dar structura bazei de date rămâne intactă.
Ce strategie folosiți în proiectele voastre? Mergeți pe hard delete direct în baze de date separate de arhivă, sau vă chinuiți cu anonimizarea în tabela principală?