Toate articolele
SaaS multi-tenant · 5 min citire ·

Bază de date per tenant sau schemă partajată — cum alegi

Comparația practică între cele două modele de izolare multi-tenant: ce câștigi, ce plătești și ce se schimbă la migrări, backup și onboarding.

multi-tenantarhitecturăSaaSbaze de date

Am construit produse în ambele feluri: SaaS-uri cu o singură bază de date și izolare logică, și un produs cu bază de date separată pentru fiecare client. Concluzia la care am ajuns e că întrebarea „care e mai bună“ e greșit pusă. Cele două modele optimizează lucruri diferite, iar alegerea se face pe operare, nu pe eleganță.

Care sunt opțiunile?

Practic, trei — deși în discuții apar de obicei doar două:

  1. Schemă partajată — toți clienții în aceleași tabele, separați printr-o coloană TenantId.
  2. Schemă per tenant — aceeași bază de date, scheme separate.
  3. Bază de date per tenant — fiecare client cu baza lui, rutată după subdomeniu sau după un registru de tenanți.

Restul articolului compară 1 și 3, pentru că astea sunt alegerile pe care le văd cel mai des. Merită totuși să fiu corect cu opțiunea din mijloc: schema per tenant e curentul principal în PostgreSQL, unde search_path face rutarea aproape gratuită și un singur pg_dump -n îți dă backup-ul unui client. Nu e o alegere greșită. Modul ei real de eșec e altul decât se povestește: la câteva mii de tenanți, catalogul de sistem se umflă — pg_class și pg_attribute cresc cu numărul de tabele × numărul de scheme —, iar autovacuum-ul pe catalog, planificarea interogărilor și pg_dump la nivel de cluster încep să sufere. E o opțiune bună până pe la ordinul sutelor de tenanți și devine dureroasă exact când crești.

Ce te costă fiecare?

Criteriu Schemă partajată Bază per tenant
Izolarea datelor Logică — depinde de corectitudinea codului Impusă de motor — separarea nu mai depinde de un predicat din cod
Blast radius la un bug de scoping Toți clienții Un singur client
Restaurare pentru un client Dificilă — extragi din backup comun Banală — restaurezi baza lui
Migrări de schemă O singură rulare N rulări, cu orchestrare și stări parțiale
Cost la mulți clienți mici Foarte bun Slab — cost fix per bază
Conexiuni / pooling Un singur pool Pool per bază; devine o constrângere reală
Zgomot între vecini Real — un client greu afectează restul Redus
Raportare cross-tenant Banală — un GROUP BY Costisitoare — agregare din N surse
Onboarding client nou Un INSERT Provizionare + migrare + configurare
Argument de conformitate „Avem filtre“ „Datele sunt în baze separate“

Două rânduri au nevoie de nuanțare, ca să nu vinzi mai mult decât livrezi.

„Izolare fizică“ e aproape întotdeauna o exagerare. N baze de date pe aceeași instanță împart procesul, discul, memoria și contul de serviciu. Ce câștigi e altceva, și e real: izolarea nu mai depinde de faptul că fiecare interogare poartă predicatul corect. Devine fizică doar dacă chiar pui instanțe separate — ceea ce schimbă complet costul.

„Blast radius“ se referă doar la bug-urile de scoping. O migrare greșită, o eroare de logică de business sau un cod de calcul greșit ating toți clienții în ambele modele; le rulezi pe toate bazele. Ce izolează baza per tenant e categoria „am uitat WHERE TenantId“.

Ultimul rând din tabel e cel care schimbă adesea decizia — și nu din motive tehnice. Într-o discuție cu un client din zona medicală, „datele dumneavoastră sunt într-o bază separată“ e un argument pe care îl înțelege oricine. „Avem global query filters“ nu e.

Când alegi bază per tenant?

Din experiență, când cel puțin două dintre următoarele sunt adevărate:

  • clienții sunt puțini și mari (zeci, nu zeci de mii);
  • datele sunt sensibile și izolarea trebuie demonstrată, nu doar implementată;
  • ai nevoie de restaurare punctuală pentru un singur client, fără să atingi restul;
  • clienții cer termene de păstrare sau localizări geografice diferite;
  • un client mare poate genera încărcare care ar afecta restul.

Pe produsul de programări medicale am mers pe bază per client exact din motivele astea: date medicale, clienți relativ puțini, cerință clară de separare. Am adăugat și izolare de resurse la nivel de sistem de operare, ca un client să nu poată sufoca CPU-ul celorlalți.

Când alegi schemă partajată?

  • clienți mulți și mici, cu volume mici fiecare;
  • onboarding self-service, unde provizionarea unei baze e o fricțiune inacceptabilă;
  • nevoie reală de raportare agregată peste toți clienții;
  • echipă mică, care nu poate întreține orchestrarea migrărilor pe N baze.

E alegerea implicită rezonabilă pentru majoritatea produselor B2B. Cu global query filters aplicate consecvent și cu validare la scriere, izolarea logică e solidă — problema nu e modelul, ci disciplina.

Ce se schimbă cel mai mult: migrările

Asta e partea pe care o subestimează toată lumea, inclusiv eu, prima dată.

Cu o bază partajată, o migrare e un eveniment. Cu N baze, e un proces:

  • rulezi migrarea pe fiecare bază, cu reîncercări și jurnalizare;
  • accepți că vei avea, temporar, baze la versiuni diferite — codul trebuie să tolereze asta;
  • ai nevoie de un registru care știe versiunea fiecărei baze;
  • rollback-ul nu mai e „restaurezi backup-ul“, ci o decizie per client.

Regula practică: dacă mergi pe bază per tenant, scrie orchestratorul de migrări în ziua unu. Dacă îl amâni până ai 30 de clienți, îl vei scrie sub presiune, în producție.

Se poate combina?

Da, și e adesea răspunsul corect pentru produsele care cresc: schemă partajată ca implicit, bază dedicată pentru clienții care o cer (și o plătesc). Costul e că menții două căi de rutare și de migrare — deci merită doar dacă cererea e reală, nu ipotetică.

Ce funcționează bine ca pregătire, indiferent de alegerea inițială: ține rezolvarea tenant-ului într-un singur loc. Dacă tot codul obține conexiunea printr-un serviciu care știe să rezolve tenant-ul, trecerea de la un model la altul rămâne o schimbare localizată, nu o rescriere.

Dacă ești la începutul unui produs multi-tenant și vrei să iei decizia asta cu ochii deschiși, e exact discuția pe care am purtat-o de câteva ori.

Proiectul din spatele articolului Sistem de Programări & Reminder-e SMS Sincronizează Google Calendar și trimite SMS-uri automat — pentru orice business cu programări.

Ai un proiect similar?

Dacă lucrezi la ceva din zona asta, hai să vorbim 30 de minute.

Programează o discuție
Toate articolele