-- 1. Crearea unui index unic parțial în PostgreSQL
CREATE UNIQUE INDEX idx_users_active_email
ON users (email)
WHERE deleted_at IS NULL;
-- 2. Procedura de anonimizare în loc de hard delete (GDPR friendly)
UPDATE users
SET
email = 'anon_' || id || '@yourdomain.internal',
first_name = 'Anonim',
last_name = 'Utilizator',
phone = NULL,
deleted_at = NOW()
WHERE id = 1042 AND deleted_at IS NULL;Am văzut zeci de baze de date puse în genunchi sau arhitecturi blocate dintr-o decizie luată pe genunchi în prima săptămână de proiect: „punem și noi o coloană de deleted_at peste tot și vedem noi după”.
Sună cunoscut, nu? Soft delete-ul pare soluția ideală când vrei să fii sigur că nu pierde clientul date din greșeală. Dar când te lovești de primul audit GDPR sau de o cerere oficială de tipul „right to be forgotten”, schema aia simplă devine un coșmar de mentenanță.
Capcana indexurilor unice și a tabelelor relaționale
Am pățit-o acum viva-fără-număr ani la un proiect cu vreo 45k de useri activi. Aveam un index unic pe coloana de email. Când un utilizator își ștergea contul (soft delete, adică deleted_at = acum), datele lui rămâneau în baza de date.
Problema? Omul voia să își facă un cont nou după trei zile cu același email. Ghici ce? Baza de date arunca eroare de duplicat din cauza indexului unic. Dacă scoteam indexul unic, riscam să am duplicate la userii activi din cauza unor race-condition-uri din API.
Pentru asta, în PostgreSQL există o soluție elegantă: indexurile parțiale. Pui index unic doar pe înregistrările active. Merge brici pentru performanță, dar are un trade-off sincer: dacă ai multe tabele relaționate (comenzi, profil, adrese), tot trebuie să cari logica de deleted_at IS NULL în absolut toate query-urile și join-urile. Dacă uiți un singur join, ai expus date care legal nu mai trebuiau să existe în interfață.
Hard delete și cascada care dărâmă tot
La polul opus, hard delete (DELETE FROM users WHERE id = x) pare curat. Scapi de date, baza de date e aerisită. Dar ce faci cu integritatea referențială?
Dacă ai ON DELETE CASCADE pe tabela de comenzi, când ștergi userul, îi ștergi și istoricul de achiziții. Din punct de vedere contabil și fiscal, asta e o ilegalitate uriașă în România. Trebuie să păstrezi facturile și tranzacțiile financiare timp de 10 ani. Dacă ai șters userul fizic din DB, cheia străină din tabela de comenzi (sau facturi) va indica spre nicăieri sau va crăpa baza de date dacă ai constrângeri stricte.
Soluția hibridă: Anonimizarea (Anonymization over Deletion)
Cea mai curată metodă pe care am aplicat-o în producție și care ne-a salvat la auditurile de compliance combină ambele abordări. Nu ștergem rândul din tabela de users, dar îl golim de date cu caracter personal (PII - Personally Identifiable Information).
Când userul cere ștergerea:
- Trecem statusul în
deleted. - Supescriem câmpurile sensibile (nume, prenume, email, telefon, adresă) cu valori randomizate sau hash-uri.
- Păstrăm ID-ul intact pentru ca legăturile cu facturile și statisticile financiare să nu se rupă.
În felul acesta, din punct de vedere GDPR, datele sunt complet anonimizate (nu mai pot fi corelate cu o persoană fizică reală), iar integritatea bazei de date rămâne intactă. Rapoartele financiare merg în continuare fără să altereze sumele sau numărul de tranzacții.
Voi cum ați rezolvat problema asta? Mergeți pe soft-delete clasic cu indexuri parțiale sau preferați să mutați datele șterse într-o bază de date separată de arhivă?