-- Index parțial pentru a permite re-înregistrarea cu același e-mail
CREATE UNIQUE INDEX idx_users_active_email
ON users (email)
WHERE deleted_at IS NULL;
-- Funcție simplă de anonimizare la soft delete (GDPR compliant)
UPDATE users
SET
email = 'deleted_' || id || '@anonymized.local',
full_name = 'Utilizator Șters',
phone = NULL,
deleted_at = NOW()
WHERE id = 4092 AND deleted_at IS NULL;Am văzut prea multe baze de date legate cu sârmă unde s-a trântit un deleted_at pe fiecare tabelă și s-a declarat victoria. Până vineri la ora 17, când DPO-ul firmei îți trimite o cerere de "Right to be forgotten" pentru un client nervos, iar tu realizezi că datele lui personale rămân bine mersi în producție. Soft delete-ul e comod pentru developeri, dar devine o bombă cu ceas dacă nu înțelegi diferența dintre integritate referențială și stocare ilegală de PII.
Trapa de performanță și unicitate
Dacă pui deleted_at fără să te gândești la indecși, pregătește-te pentru probleme. Prima e că pierzi constrângerea de unicitate pe e-mail. Dacă popescu@gmail.com își șterge contul și vrea să se reînregistreze peste o săptămână, un index unic simplu pe email va arunca eroare Postgres la INSERT pentru că rândul vechi încă există.
A doua e umflarea indecșilor. La un proiect de e-commerce cu vreo 12 milioane de rânduri în tabela de sesiuni și coșuri, interogările încetineau văzând cu ochii pentru că baza de date scana și rândurile "șterse". Am economisit aproape 35% la RAM-ul consumat de indecși doar trecând pe indecși parțiali în PostgreSQL.
Strategia hibridă: Anonymize on Delete
Merge bine soft delete-ul pentru istoric, dar e nasol rău când trebuie să respecți GDPR. Nu poți da hard delete la o comandă plătită acum 2 ani pentru că îți distrugi rapoartele contabile și te caută ANAF-ul. Pe de altă parte, dacă păstrezi e-mailul și adresa de livrare în rândul ăla soft-deleted, ești în ilegalitate.
Soluția pe care o aplic pe toate proiectele noi e anonimizarea la momentul ștergerii:
- Nu ștergi rândul din
usersdacă are comenzi active. - Suprascrii câmpurile PII (nume, e-mail, telefon) cu un hash sau o valoare neutră (
deleted_user_8912@anon.local). - Setezi
deleted_at = NOW().
Așa păstrezi referințele externe intacte, cifra de afaceri rămâne calculată corect, iar datele personale chiar au dispărut definitiv.
Când faci Hard Delete fără milă
Sesiunile, token-urile de resetare parolă, logurile temporare de audit sau articolele din coșul de cumpărături nu au nicio valoare istorică. Acolo pui ON DELETE CASCADE și faci hard delete direct. Nu bloata baza de date cu deleted_at pe tabele de legătură (many-to-many).
Regula mea de aur: păstrezi structura și cifrele, dar radii identitatea.
Tu cum gestionezi cererile de ștergere pe proiectul tău? Anonimiezi direct în DB la request sau ai un job de cron care curăță datele după o perioadă de retenție?