Przejdź do treści
Avenit
Blog

Dlaczego każdy tenant dostaje własną bazę danych

Schema-per-tenant, shared tables czy database-per-tenant? W Avenit wybraliśmy najbardziej rygorystyczne podejście. Oto dlaczego — i jakie są tego konsekwencje.

ŁD
Łukasz Dobrowolski
Founder, Avenit
· 6 min
architektura postgres multi-tenancy

Multi-tenancy to pytanie, które w każdym SaaSie zadajesz sobie raz — i odpowiadasz na nie na kolejne pięć lat. W Avenit mamy jedną bazę danych PostgreSQL per tenant. Nie schema-per-tenant. Nie shared tables z kolumną tenant_id. Osobną, pełnoprawną bazę.

To nie jest popularny wybór. Zobaczmy dlaczego go zrobiliśmy.

Trzy opcje, które miałem do wyboru

1. Shared tables

Wszyscy użytkownicy wszystkich klientów w jednej tabeli users, rozdzieleni kolumną tenant_id. Standard w typowych SaaSach.

Plusy: prosto, tanio, łatwo zaczyna się. Minusy: jedno źle napisane zapytanie i wyciek danych między klientami. Indeksy rosną proporcjonalnie do sumy danych wszystkich klientów. Backup całości to backup wszystkiego.

2. Schema-per-tenant

Jedna baza, osobne schematy PostgreSQL per klient. Izolacja logiczna bez izolacji fizycznej.

Plusy: nadal jedno połączenie DB, tańsze niż osobne bazy. Izolacja lepsza niż shared tables. Minusy: pg_catalog rośnie liniowo. Backup jednego klienta wymaga pg_dump --schema=.... Ograniczenia PostgreSQL na liczbę schematów (praktycznie ~1000).

3. Database-per-tenant

Osobna baza PostgreSQL dla każdego tenanta. Izolacja fizyczna i logiczna.

Plusy: pełna izolacja. Backup i restore per klient. Osobne migracje, osobne wersje. Klient enterprise może dostać własny serwer. Minusy: trudniej zarządzać połączeniami. Potrzebujesz puli po stronie aplikacji (my używamy PgBouncer + LRU cache po stronie NestJS).

Dlaczego wybraliśmy opcję 3

Polski rynek. Nasi potencjalni klienci enterprise pytają zanim podpiszą umowę: gdzie są moje dane, jak są izolowane, czy można je wyeksportować i zmienić region. Shared tables nie przejdzie audytu bezpieczeństwa w żadnym większym klientze B2B.

Drugi powód: migracje bez paniki. Gdy klient X ma produkcyjne zlecenia a klient Y testuje nową wersję, nie chcemy gromadzić się przy jednej migracji ALTER TABLE na 50 milionów wierszy. Z osobnymi bazami to tak, jakbyśmy wdrażali aktualizację na 50 osobnych środowisk — rolling, bezpiecznie, bez blokad.

Trzeci: no-code builder. Nasz generator DDL tworzy prawdziwe kolumny PostgreSQL, gdy klient dodaje pole do formularza. W modelu shared tables każde pole to albo JSONB (wolno, bez indeksów), albo kolumna pojawiająca się na wszystkich wierszach — u wszystkich klientów. Żaden kompromis nie pachnie dobrze.

Jaki jest koszt?

Dwa realne:

  1. Pooling połączeń — nie można po prostu otworzyć pg.Pool na start aplikacji. Mamy LRU cache Map<dbName, postgres.Sql> w TenantConnectionService, zamykający nieużywane połączenia po czasie.
  2. Operacje cross-tenant — jeśli chcesz liczyć “ile wszystkich faktur wystawiono dziś na platformie”, musisz orkiestrować zapytania w aplikacji. Rzadko potrzebne, ale koszt jest.

Na dziś — 0 tenantów w produkcji, 3 w dev — nie czujemy bólu. Ale wiem gdzie on będzie, gdy przekroczymy 200 baz. Mamy przygotowany plan dzielenia tenantów między shardy PostgreSQL.

Jak się to ma do Odoo?

Odoo używa schema-per-tenant w wersji SaaS. To działa dla nich, bo mają ~lata dojrzałości, własne rozwiązania do backupów, własny fork PostgreSQL w Odoo.sh. My w 2026 nie mamy tego luksusu, ale mamy coś innego: czystą kartkę. Budujemy od razu tak, żeby klient enterprise mógł kupić u nas to, czego by nie dostał od konkurencji — własną bazę, na własnym serwerze, z własnym regionem.

Podsumowanie

Database-per-tenant nie jest dla każdego. Dla nas — polski B2B z ambicjami na enterprise — był jedynym sensownym wyborem. Zapłaciliśmy kosztem architektonicznej złożoności w warstwie połączeń. Zyskaliśmy argument sprzedażowy i spokojny sen operacyjny.

Jeśli budujesz SaaS i wahasz się między opcjami — zadaj sobie pytanie: “Czy mój największy przyszły klient podpisze u mnie, gdy zobaczy, że jego dane leżą w tej samej tabeli co dane jego konkurencji?”