Multi-tenant SaaS: błędy, które kosztują najwięcej przy 1000. kliencie
Spis treści
Większość problemów architektury multi-tenant SaaS nie ujawnia się przy 20 klientach, tylko przy 300. Wtedy wdrożenie nowej wersji trwa całą noc, a import danych jednego klienta spowalnia wszystkich pozostałych. Do tego sprzedaż obiecała klientowi enterprise SSO, którego model uprawnień nie udźwignie. Decyzje z etapu MVP były rozsądne, bo pozwoliły szybko dowieźć produkt. Część z nich kosztuje jednak tydzień pracy przy 50 tenantach i kwartał przy 1000.
Poniżej opisuję dziewięć takich decyzji. Przy każdej pokazuję, jak wygląda na początku i co psuje się w skali. Podpowiadam też, co zrobić, zanim jedyną opcją będzie przepisanie systemu.
Izolacja danych w multi-tenant SaaS: decyzje, których nie cofniesz tanio
Błąd 1: model izolacji wybrany przypadkiem
Na początku: wszyscy klienci trafiają do jednej bazy z kolumną tenant_id albo, „bo bezpieczniej”, każdy dostaje własną bazę. Przy 10 klientach oba podejścia działają, więc wybór wynika z tutoriala, a nie z analizy.
Przy 1000 klientach każdy model ma swoją cenę:
- Wspólna baza: jest najtańsza w utrzymaniu. Tabele rosną jednak do setek milionów wierszy, a jeden duży klient potrafi spowolnić zapytania wszystkich. Usunięcie danych klienta na żądanie z RODO to operacja na dziesiątkach tabel.
- Schema-per-tenant: daje lepszą separację, ale tysiące schematów obciążają bazę, backupy i narzędzia administracyjne.
- Database-per-tenant: zapewnia najsilniejszą izolację i prosty restore jednego klienta. Ceną jest czas migracji: 30 sekund na bazę przy 1000 bazach to ponad 8 godzin. Backup, monitoring i minimalny koszt instancji mnożą się przez tysiąc.
Co zrobić: dla większości produktów najlepszy jest model hybrydowy. Mniejsi klienci zostają na wspólnej bazie. Enterprise z wymogami umownymi (lokalizacja danych, osobne klucze, osobny backup) dostaje dedykowaną bazę, wycenioną w planie. Warunek jest jeden: logika biznesowa nie wie, gdzie leżą dane, a o wyborze bazy decyduje jedna warstwa systemu.
Jeśli dopiero zaczynasz, wybierz wspólną bazę, dodaj tenant_id do każdej tabeli z danymi klienta i używaj identyfikatorów UUID. Dzięki temu przeniesienie klienta do osobnej bazy będzie później eksportem, a nie projektem.
Błąd 2: izolacja pilnowana „na honor”
Na początku: każdy programista pamięta, żeby dodać do zapytania filtr po kliencie. Przy kilkudziesięciu ekranach code review wyłapuje braki.
Przy 1000 klientach: endpointów są setki. Dochodzą zadania w tle, raporty i eksporty pisane przez nowe osoby w zespole. Wystarczy jedno pominięte zapytanie, żeby klient A zobaczył faktury klienta B. To nie jest zwykły bug, tylko naruszenie ochrony danych osobowych. Trzeba je ocenić i, jeśli niesie ryzyko, zgłosić do UODO w ciągu 72 godzin. Przy kliencie enterprise oznacza to też realne ryzyko utraty kontraktu.
Co zrobić: izolację ma egzekwować system, a nie dyscyplina zespołu. Potrzebne są trzy niezależne warstwy:
- Automatyczny filtr w warstwie dostępu do danych, np. global scope w Laravelu lub filtr w Doctrine. Gdy brakuje kontekstu klienta, filtr zatrzymuje operację.
- Row-level security w PostgreSQL, która działa jako siatka bezpieczeństwa dla zapytań pisanych ręcznie.
- Automatyczne testy w CI. Tworzą dwóch klientów i sprawdzają, czy dane jednego nigdy nie pojawiają się u drugiego.
Zapytaj zespół, jak kontekst klienta trafia do zadań w kolejce. Jeśli usłyszysz „przekazujemy ręcznie”, to właśnie tam powstanie pierwszy wyciek.
Zasada fail closed: każde miejsce w systemie, które nie wie, którego klienta dotyczy operacja, ma ją przerwać, a nie wykonać bez filtra. Zabezpieczenie, które przy braku kontekstu po prostu się wyłącza, jest gorsze niż jego brak, bo daje fałszywe poczucie bezpieczeństwa.
Wydajność i wdrożenia: noisy neighbor i migracje przy tysiącu tenantów
Ta grupa błędów zwykle daje o sobie znać jako pierwsza. Objaw jest charakterystyczny: SaaS nie wytrzymuje wzrostu, choć serwery nie są przeciążone na stałe. Przeciążenia są okresowe i powoduje je jeden klient.
Błąd 3: wspólne zasoby bez limitów (noisy neighbor)
Na początku: jest jedna kolejka zadań, jeden cache i brak limitów API. Przy 10 klientach zasobów jest w nadmiarze.
Przy 1000 klientach: jeden klient importuje 2 mln rekordów i zapycha kolejkę. Maile z resetem hasła u pozostałych czekają 40 minut. Integracja innego klienta odpytuje API kilkadziesiąt razy na sekundę, a raport z pięciu lat danych blokuje połączenia do bazy. Dokładanie serwerów nie pomaga, bo problemem nie jest brak mocy, tylko brak granic.
Co zrobić:
- Wprowadź rate limiting per tenant wynikający z planu. Działa jednocześnie jako ochrona i jako element oferty.
- Rozdziel kolejki na pracę krytyczną (logowanie, płatności), standardową i masową (importy, eksporty).
- Ustaw limit współbieżności per klient w kolejce masowej, żeby nikt nie zajął wszystkich workerów.
- Przenieś ciężkie raporty na replikę bazy i zapisz w planach limity rozmiaru importów.
Na starcie to kilka godzin pracy. Po roku oznacza przegląd każdego zadania w systemie.
Błąd 4: wdrożenia jak w małym monolicie
Na początku: pipeline uruchamia migracje i wdraża kod na wszystkie serwery naraz. Przy małych tabelach zmiana schematu trwa sekundy.
Przy 1000 klientach: zmiana na tabeli z setkami milionów wierszy potrafi ją zablokować. Zapytania ustawiają się w kolejce i aplikacja przestaje odpowiadać. Przy bazie per klient problem wygląda inaczej: 1000 schematów × 2 minuty to około 33 godzin, przez które część klientów ma nowy schemat, a część stary. Skutek biznesowy jest taki, że wdrożenia odbywają się nocą, rzadziej i ze strachem.
Co zrobić: wymagaj migracji w modelu expand/contract. Najpierw dodajesz nowe struktury, potem przenosisz dane w tle, a stare usuwasz dopiero w kolejnym wydaniu. Kod musi działać z obecną i poprzednią wersją schematu. Feature flagi per tenant oddzielają wdrożenie od udostępnienia funkcji. Nową funkcję włączasz najpierw dla siebie, potem dla 5% klientów, a na końcu dla wszystkich.
Konfiguracja i tożsamość: dług, którego nie spłacisz ratami
Błąd 5: „jeśli to klient X, zrób inaczej”
Na początku: największy klient chce innej numeracji faktur, więc w kodzie pojawia się warunek na jego identyfikator. Potem dochodzą jego logo, domena i integracja z ERP. Przy trzech wyjątkach cały zespół wie, skąd się wzięły.
Przy 1000 klientach: wyjątków są setki i nikt nie wie, które są jeszcze aktywne. Każda zmiana grozi zepsuciem czegoś klientowi z indywidualną umową, więc refaktoryzacja staje, a rozwój produktu zwalnia.
Co zrobić: kod nie powinien znać żadnego klienta z nazwy. Zachowanie wynika z konfiguracji, a konfiguracja jest danymi. W praktyce oznacza to:
- typowane ustawienia tenanta;
- wymienne strategie, np. numeracji faktur, workflow akceptacji czy szablonów dokumentów;
- integracje jako osobne konektory poza rdzeniem aplikacji;
- własne domeny z automatycznymi certyfikatami.
Customizacja staje się wtedy produktem do sprzedania, a nie długiem technicznym.
Błąd 6: użytkownik należy do jednego tenanta
Na początku: konto jest przypisane do jednej firmy, ma jedną rolę i logowanie przez e-mail i hasło. Przy 10 klientach odpowiada to rzeczywistości.
Przy 1000 klientach rzeczywistość się rozjeżdża:
- księgowa obsługuje 15 firm i ma 15 kont na różne adresy;
- partner wdrożeniowy potrzebuje dostępu do wielu klientów;
- klient enterprise wymaga SSO (SAML lub OIDC) oraz automatycznego zakładania i usuwania kont przez SCIM;
- ktoś ma być administratorem w jednej firmie i tylko obserwatorem w drugiej.
Zmiana tego modelu dotyka każdego uprawnienia i każdego ekranu z użytkownikami. To jeden z najdroższych refaktorów w SaaS i często blokuje podpisanie dużego kontraktu.
Co zrobić od razu: zaprojektuj globalną tożsamość użytkownika i członkostwa w tenantach, z rolą przypisaną osobno w każdym z nich. SSO oprzyj na sprawdzonym dostawcy tożsamości, np. Keycloak, Auth0, Cognito czy Entra ID. Własne implementacje SAML to klasyczne źródło luk bezpieczeństwa.
Architektura multi-tenant nie psuje się nagle. Psuje się po cichu, w każdym miejscu, w którym system zakłada, że zna odpowiedź na pytanie: czyje to dane i co temu klientowi wolno?
Operacje i pieniądze: koszty, cykl życia tenanta i billing
Błąd 7: nie wiesz, ile kosztuje każdy klient
Na początku: logi nie zawierają informacji o kliencie, metryki są globalne, a rachunek z chmury przychodzi jeden. Przy 10 klientach to wystarcza, bo znasz wszystkich z nazwy.
Przy 1000 klientach: czas odpowiedzi rośnie, a Ty nie wiesz, czy problem mają wszyscy, czy jeden tenant. Support nie znajduje logów do zgłoszenia. Nie wiesz też, czy klient na najtańszym planie nie generuje 30% kosztów modeli AI. W efekcie cennik ustalasz na wyczucie.
Co zrobić: dodaj identyfikator klienta do każdego logu i trace'a. Koszty bezpośrednie, takie jak tokeny LLM, SMS-y, płatne API czy storage, przypisuj dokładnie. Koszty współdzielone (serwery, baza) rozdzielaj proporcjonalnie, np. według czasu przetwarzania. Celem jest miesięczne zestawienie kosztu i przychodu dla każdego tenanta.
Błąd 8: onboarding i offboarding robione ręcznie
Na początku: nowego klienta zakłada programista, uruchamiając skrypt. Dane klienta, który odszedł, zostają w systemie na zawsze.
Przy 1000 klientach: przez self-service przychodzi kilkudziesięciu klientów tygodniowo, a przerwane zakładanie konta zostawia osierocone zasoby. Żądanie usunięcia danych z RODO wymaga sprzątania bazy, plików, wyszukiwarki i systemów zewnętrznych. Gdy klient omyłkowo skasuje dane, przywrócenie ich z backupu całej bazy zajmuje dzień ręcznej pracy.
Co zrobić:
- Zautomatyzuj provisioning jako powtarzalny proces ze stanami (aktywny, zawieszony, w trakcie usuwania).
- Prowadź rejestr wszystkich miejsc, w których leżą dane klienta.
- Zbuduj eksport danych jako funkcję produktu.
- Usuwaj dane dwuetapowo, z okresem karencji.
- Zaplanuj backupy: jasna retencja w umowie albo osobny klucz szyfrowania per tenant, którego usunięcie czyni dane w kopiach nieczytelnymi.
Błąd 9: billing i limity dopisane na końcu
Na początku: subskrypcja jest w Stripe, a nazwa planu w bazie. Limity są sprawdzane w kilku miejscach albo wcale.
Przy 1000 klientach: sprzedaż chce limitów użytkowników, rozliczeń za zużycie, dodatków i umów enterprise. Tymczasem warunek „jeśli plan to Pro” siedzi w dziesiątkach miejsc w kodzie, a historii zużycia nie ma. Zmiana cennika zamienia się w kilkumiesięczny projekt.
Co zrobić: rozdziel trzy pojęcia:
- plan: produkt handlowy;
- uprawnienia: co tenant może, czyli funkcje i limity;
- metering: co tenant zużył.
Kod pyta wyłącznie o uprawnienia, nigdy o nazwę planu. Zużycie zapisuj jako niezmienny rejestr zdarzeń. Ten sam rejestr zasila billing i rachunek kosztu per klient.
Jeśli w tym kwartale możesz zrobić tylko dwie rzeczy, dodaj identyfikator klienta do logów i zacznij zapisywać zdarzenia zużycia. Obie zmiany są tanie i nie wymagają przebudowy danych. Dają za to informacje potrzebne do każdej kolejnej decyzji: o limitach, cenniku i inwestycjach w izolację.
Checklista przed 100. klientem
Jeśli planujesz budowę platformy SaaS albo masz już kilkudziesięciu klientów, potraktuj tę listę jako minimum. Każdy punkt jest dziś tani, a po przekroczeniu kilkuset tenantów robi się drogi.
- Każda tabela z danymi klienta ma tenant_id, a identyfikatory to UUID.
- O wyborze bazy decyduje jedna warstwa systemu, a logika biznesowa nie wie, gdzie leżą dane.
- Izolacja ma trzy warstwy: filtr w ORM, row-level security i testy wyciekowe w CI.
- Brak kontekstu klienta zatrzymuje operację, także w zadaniach w tle.
- Rate limiting działa per tenant, a limity wynikają z uprawnień planu.
- Kolejki są rozdzielone (krytyczna, standardowa, masowa) i mają limit współbieżności per klient.
- Migracje stosują expand/contract, a nowe funkcje włączasz feature flagami per tenant.
- W kodzie nie ma żadnego identyfikatora klienta, a customizacja jest konfiguracją.
- Model użytkowników opiera się na członkostwach z rolą per tenant i ma gotowy plan na SSO i SCIM.
- Logi zawierają identyfikator klienta, a koszty bezpośrednie są przypisane do tenantów.
- Masz opisany cykl życia tenanta: provisioning, eksport, usunięcie zgodne z RODO i restore jednego klienta.
Podsumowanie: multi-tenant SaaS to decyzje odroczone w czasie
Żaden z tych błędów nie wynika z niekompetencji. To rozsądne skróty z etapu MVP, których koszt rośnie razem z liczbą klientów. Większość da się naprawić ewolucyjnie, bez przepisywania systemu, pod warunkiem że zaczniesz przy 100 klientach, a nie przy 1000.
Multi-tenant SaaS, który dobrze się skaluje, ma kilka wspólnych cech. Izolację egzekwuje w nim infrastruktura, zasoby mają limity, a konfiguracja i uprawnienia są danymi. Koszt każdego klienta jest znany.
W Midapt projektujemy takie fundamenty w ramach rozwoju produktów SaaS i porządkujemy je w istniejących systemach podczas modernizacji. Jeśli czujesz, że architektura z MVP zaczyna trzeszczeć, umów bezpłatną konsultację. Przejdziemy razem przez checklistę i wskażemy, od czego zacząć.
Najczęściej zadawane pytania o multi-tenant SaaS
Tak, jeśli izolacja jest egzekwowana technicznie, a nie tylko dyscypliną programistów. Minimum to automatyczny filtr w warstwie dostępu do danych, który przy braku kontekstu klienta zatrzymuje operację, do tego row-level security w bazie i testy sprawdzające, czy dane jednego tenanta nie pojawiają się u drugiego. Dedykowana baza bywa potrzebna głównie z powodów umownych lub regulacyjnych.
Schema-per-tenant jest tańsza w infrastrukturze, ale przy setkach schematów obciąża bazę i spowalnia backupy. Database-per-tenant daje najsilniejszą izolację i łatwy restore jednego klienta, za to mnoży migracje, połączenia i koszty. Dla większości produktów najlepiej sprawdza się wspólna baza dla mniejszych klientów i dedykowane bazy dla enterprise, wycenione w planie.
To sytuacja, w której jeden tenant zużywa tyle wspólnych zasobów, że spowalnia pozostałych. Może chodzić o kolejkę zadań, połączenia do bazy, cache albo CPU. Typowe przyczyny to duże importy, intensywne integracje API i ciężkie raporty. Pomagają rate limiting per tenant, osobne kolejki dla pracy masowej, limity współbieżności i limity planów egzekwowane w systemie.
Koszty bezpośrednie, czyli tokeny LLM, SMS-y, płatne API i storage, mierz dokładnie, przypisując każde zdarzenie do klienta. Koszty współdzielone, takie jak serwery i baza, rozdzielaj proporcjonalnie według przybliżonej miary, np. czasu przetwarzania lub wolumenu danych. Zestawienie kosztu z przychodem per tenant pokazuje, którzy klienci i plany są nierentowni.
Da się, ale koszt zależy od tego, jak bardzo kod zakłada konkretny model. Najłatwiej jest, gdy każda tabela ma tenant_id, identyfikatory to UUID, a wybór bazy odbywa się w jednej warstwie. Wtedy klientów przenosi się stopniowo, pojedynczo i bez przestoju. Bez tych fundamentów zmiana zwykle oznacza wielomiesięczny refaktor.


