Opcje modelu danych multi-tenant SaaS dla backendu bez kodu
Wybór modelu danych multi-tenant wpływa na bezpieczeństwo, raportowanie i wydajność. Porównaj podejścia: tenant_id, osobne schematy i osobne bazy danych z jasnymi kompromisami.

Problem: oddzielić najemców bez spowalniania\n\nMulti-tenancy oznacza, że jeden produkt programowy obsługuje wielu klientów (najemców), i każdy najemca musi widzieć tylko swoje dane. Trudność polega na zapewnieniu tego konsekwentnie: nie tylko na jednym ekranie, ale w każdym wywołaniu API, panelu admina, eksporcie i zadaniu w tle.\n\nModel danych wpływa na codzienne operacje bardziej, niż wiele zespołów się spodziewa. Kształtuje uprawnienia, raportowanie, szybkość zapytań wraz ze wzrostem oraz to, jak ryzykowny może być „mały” błąd. Pomiń jeden filtr i możesz wyciec dane. Zbyt agresywna izolacja utrudni raportowanie.\n\nSą trzy powszechne sposoby organizacji modelu danych w multi-tenant SaaS:\n\n- Jedna baza danych, gdzie każda tabela zawiera tenant_id\n- Osobne schematy na najemcę w ramach jednej bazy\n- Osobne bazy danych na najemcę\n\nNawet jeśli budujesz wizualnie w backendzie bez kodu, te same kompromisy mają zastosowanie. Narzędzia takie jak AppMaster generują rzeczywisty kod backendu i struktury baz danych z twojego projektu, więc wczesne decyzje modelowe szybko pojawiają się w zachowaniu i wydajności produkcji.\n\nWyobraź sobie narzędzie helpdesk. Jeśli każdy wiersz zgłoszenia ma tenant_id, łatwo zapytać „wszystkie otwarte zgłoszenia”, ale musisz wymusić sprawdzenia najemcy wszędzie. Jeśli każdy najemca ma swój schemat lub bazę, izolacja jest silniejsza domyślnie, ale raportowanie między najemcami (np. „średni czas zamknięcia dla wszystkich klientów”) wymaga więcej pracy.\n\nCelem jest separacja, której możesz zaufać, bez utrudniania raportowania, wsparcia i rozwoju.\n\n## Szybki wybór: 4 pytania, które zawężają decyzję\n\nNie zaczynaj od teorii baz danych. Zacznij od tego, jak produkt będzie używany i co będziesz potrzebować do operacji co tydzień.\n\n### Cztery pytania, które zwykle rozwiewają wątpliwości\n\n1) Jak wrażliwe są dane i czy jesteś pod ścisłymi regulacjami? Ochrona zdrowia, finanse i twarde umowy z klientami często skłaniają do silniejszej izolacji (osobne schematy lub bazy). Może to zmniejszyć ryzyko i ułatwić audyty.\n\n2) Czy często potrzebujesz raportowania między najemcami? Jeśli regularnie analizujesz metryki „wszyscy klienci” (użycie, przychody, wydajność), pojedyncza baza z tenant_id zwykle jest najprostszym rozwiązaniem. Osobne bazy utrudniają to, bo musisz zapytywać wiele miejsc i łączyć wyniki.\n\n3) Jak bardzo najemcy będą się od siebie różnić? Jeśli najemcy potrzebują pól niestandardowych, odmiennych przepływów czy integracji, osobne schematy lub bazy mogą zmniejszyć ryzyko przenikania zmian. Jeśli większość najemców ma tę samą strukturę, tenant_id pozostaje czyste.\n\n4) Co twój zespół realnie potrafi obsłużyć? Większa izolacja zazwyczaj oznacza więcej pracy: więcej backupów, migracji, elementów do monitorowania i więcej miejsc, gdzie mogą ukryć się błędy.\n\nPraktyczne podejście to prototypowanie dwóch najlepszych opcji, a potem testowanie realnych punktów bólu: reguł uprawnień, zapytań raportowych i wdrażania zmian w miarę ewolucji modelu.\n\n## Podejście 1: jedna baza danych z tenant_id w każdym wierszu\n\nTo najpopularniejsze ustawienie: wszyscy klienci dzielą te same tabele, a każdy rekord należący do najemcy ma tenant_id. To proste operacyjnie, bo zarządzasz jedną bazą i jednym zestawem migracji.\n\nZasada jest surowa: jeśli wiersz należy do najemcy, musi zawierać tenant_id, a każde zapytanie musi po nim filtrować. Tabele należące do najemcy zwykle obejmują użytkowników, role, projekty, zgłoszenia, faktury, wiadomości, metadane plików i tabele łączące.\n\nAby zmniejszyć ryzyko wycieków, traktuj tenant_id jako niepodważalny:\n\n- Uczyń tenant_id wymaganym (NOT NULL) w tabelach należących do najemcy\n- Dodaj indeksy zaczynające się od tenant_id (np. tenant_id, created_at)\n- Włącz unikalności z uwzględnieniem tenant_id (np. unikalny email per najemca)\n- Przekazuj tenant_id przez każde API i każdy przepływ biznesowy, nie tylko formularze UI\n- Wymuszaj to w backendzie, nie tylko w filtrach po stronie klienta\n\nW PostgreSQL polityki RLS (row-level security) mogą dać solidną siatkę bezpieczeństwa, szczególnie gdy zapytania są generowane dynamicznie.\n\nDane referencyjne zwykle dzielą się na dwie grupy: tabele współdzielone (np. countries) bez tenant_id, oraz katalogi zakresu najemcy (np. tagi niestandardowe) z tenant_id.\n\nJeśli budujesz z AppMaster, prosty nawyk zapobiega większości incydentów: ustaw tenant_id z uwierzytelnionego użytkownika przed każdym tworzeniem lub odczytem w logice Business Process i zachowuj ten wzorzec konsekwentnie.\n\n## Wpływ na uprawnienia: co się zmienia w zależności od podejścia\n\nTo w uprawnieniach multi-tenancy wygrywa lub przegrywa. Wybrany układ danych zmienia sposób przechowywania użytkowników, zakresowania zapytań i unikania pomyłek w panelach administracyjnych.\n\nW jednej bazie z tenant_id zespoły często używają jednej wspólnej tabeli Users i łączą każdego użytkownika z najemcą oraz jedną lub kilkoma rolami. Najważniejsza zasada pozostaje niezmienna: każdy odczyt i zapis musi uwzględniać zakres najemcy, nawet dla „małych” tabel jak ustawienia, tagi czy logi.\n\nW przypadku osobnych schematów często zachowuje się wspólną warstwę tożsamości (logowanie, hasła, MFA) w jednym miejscu, podczas gdy dane najemcy żyją w schemacie przypisanym do niego. Uprawnienia stają się częściowo problemem routingu: aplikacja musi skierować zapytania do właściwego schematu zanim uruchomi logikę biznesową.\n\nW przypadku osobnych baz izolacja jest najsilniejsza, ale logika uprawnień przesuwa się do infrastruktury: wybór właściwego połączenia, zarządzanie poświadczeniami i obsługa kont „globalnych” pracowników.\n\nWszystkie trzy podejścia mają wzorce, które konsekwentnie zmniejszają ryzyko między-najemcowego dostępu:\n\n- Umieść tenant_id w sesji lub w roszczeniach tokenu i traktuj go jako wymagany.\n- Scentralizuj sprawdzenia najemcy w jednym miejscu (middleware lub wspólny Business Process), nie rozsypuj ich po endpointach.\n- W narzędziach admina pokazuj wyraźnie kontekst najemcy i wymagaj jawnego przełączenia najemcy.\n- Dla dostępu wsparcia używaj impersonacji z audytem.\n\nW AppMaster zwykle oznacza to przechowywanie kontekstu najemcy zaraz po uwierzytelnieniu i ponowne jego użycie w endpointach oraz Business Processes, aby każde zapytanie pozostało w zakresie. Agent wsparcia powinien widzieć zamówienia tylko wtedy, gdy aplikacja ustawiła kontekst najemcy, a nie dlatego, że UI przypadkowo przefiltrowało poprawnie.\n\n## Raportowanie i wydajność w modelu z tenant_id\n\nW podejściu pojedynczej bazy z tenant_id raportowanie jest zwykle proste. Globalne pulpity (MRR, rejestracje, użycie) mogą wykonać jedno zapytanie dla wszystkich, a raporty na poziomie najemcy to to samo zapytanie z filtrem.\n\nKosztem jest wydajność w czasie. W miarę wzrostu tabel, jeden bardzo aktywny najemca może stać się „hałaśliwym sąsiadem”, generując więcej wierszy, większą liczbę zapisów i spowalniając wspólne zapytania, jeśli baza musi skanować za dużo danych.\n\nIndeksowanie utrzymuje model w zdrowiu. Większość odczytów scoped do najemcy powinna móc użyć indeksu zaczynającego się od tenant_id, żeby baza skakała bezpośrednio do „kawałka” danego najemcy.\n\nDobry punkt odniesienia:\n\n- Dodaj indeksy złożone, gdzie tenant_id jest pierwszą kolumną (np. tenant_id + created_at, tenant_id + status, tenant_id + user_id)\n- Zachowaj globalne indeksy tylko gdy naprawdę potrzebujesz zapytań między najemcami\n- Obserwuj joiny i filtry, które „zapominają” o tenant_id, bo powodują długie skany\n\nRetencja i usuwanie też wymagają planu, bo historia jednego najemcy może rozrosnąć tabele dla wszystkich. Jeśli najemcy mają różne polityki retencji, rozważ miękkie usuwanie plus harmonogram archiwizacji per najemca albo przenoszenie starych wierszy do tabeli archiwum kluczowanej po tenant_id.\n\n## Podejście 2: osobne schematy per najemca\n\nW modelu z osobnymi schematami nadal używasz jednej bazy PostgreSQL, ale każdy najemca otrzymuje własny schemat (np. tenant_42). Tabele w tym schemacie należą tylko do tego najemcy. Może to przypominać „mini bazę” dla każdego klienta, bez kosztu uruchamiania wielu baz.\n\nCzęsto utrzymuje się usługi globalne we wspólnym schemacie, a dane najemcy w schematach najemców. Podział zwykle dotyczy tego, co musi być współdzielone między wszystkimi, a co nigdy nie może się mieszać.\n\nTypowy podział:\n\n- Wspólny schemat: tabela tenants, plany, zapisy billingowe, flagi funkcji, ustawienia audytu\n- Schemat najemcy: tabele biznesowe jak orders, tickets, inventory, projects, pola niestandardowe\n- Po jednej lub drugiej stronie (w zależności od produktu): użytkownicy i role, zwłaszcza jeśli użytkownicy mogą mieć dostęp do wielu najemców\n\nTen model zmniejsza ryzyko krzyżowych joinów, bo tabele żyją w innych namespace'ach. Może też ułatwić backup lub przywracanie jednego najemcy przez celowanie w jego schemat.\n\nMigrations (migracje) potrafią zaskoczyć zespoły. Kiedy dodajesz nową tabelę lub kolumnę, musisz zastosować zmianę we wszystkich schematach najemców. Przy 10 najemcach jest to wykonalne. Przy 1 000 potrzebujesz procesu: śledzenia wersji schematów, uruchamiania migracji partiami i bezpiecznego przerywania, by jeden uszkodzony schemat nie blokował reszty.\n\nUsługi współdzielone jak auth i billing zwykle żyją poza schematami najemców. Praktyczny wzorzec to współdzielone auth (jedna tabela użytkowników z tabelą członkostwa najemców) i współdzielny billing (ID klientów Stripe, faktury), podczas gdy schematy najemców przechowują dane biznesowe należące do najemcy.\n\nJeśli używasz AppMaster, zaplanuj wcześnie mapowanie modeli Data Designer na schematy współdzielone vs najemcy i utrzymuj stabilność usług globalnych, aby schematy najemców mogły ewoluować bez psucia logowania czy płatności.\n\n## Raportowanie i wydajność z osobnymi schematami\n\nOsobne schematy domyślnie dają silniejszą separację niż czysty filtr tenant_id, ponieważ tabele są fizycznie oddzielone i uprawnienia można ustawić per schemat.\n\nRaportowanie jest świetne, gdy większość raportów to raporty per najemca. Zapytania pozostają proste, bo czytasz z tabel jednego najemcy bez ciągłego filtrowania tabel współdzielonych. Ten model także pozwala na „specjalnych” najemców, którzy potrzebują dodatkowych tabel czy kolumn bez obciążania pozostałych.\n\nAgregacja danych między wszystkimi najemcami to miejsce, gdzie schematy zaczynają przeszkadzać. Musisz albo mieć warstwę raportową, która potrafi zapytać wiele schematów, albo utrzymywać wspólne tabele podsumowujące w schemacie globalnym.\n\nPowszechne wzorce:\n\n- Pulpity per najemca, które zapytują tylko tabele tego schematu\n- Centralny schemat analityczny z nocnymi rollupami z każdego najemcy\n- Zadania eksportujące, które kopiują dane najemcy do formatu przyjaznego hurtowni\n\nWydajność zwykle jest dobra dla obciążeń na poziomie najemcy. Indeksy są mniejsze per najemca, a silne zapisy w jednym schemacie rzadziej wpływają na inne. Kosztem jest obciążenie operacyjne: tworzenie nowego najemcy oznacza utworzenie schematu, uruchomienie migracji i utrzymanie zgodności schematów przy zmianach modelu.\n\nSchematy są dobrym wyborem, gdy chcesz ściślejszej izolacji bez kosztów wielu baz, lub gdy spodziewasz się personalizacji per najemca.\n\n## Podejście 3: osobna baza danych per najemca\n\nPrzy osobnej bazie danych na najemcę każdy klient dostaje własną bazę (lub własną bazę na tym samym serwerze). To opcja o najsilniejszej izolacji: jeśli dane jednego najemcy zostaną uszkodzone, źle skonfigurowane lub obciążone, znacznie mniejsze prawdopodobieństwo, że wpłynie to na innych.\n\nPasuje to do środowisk regulowanych (zdrowie, finanse, rząd) lub klientów enterprise, którzy oczekują ścisłej separacji, dedykowanych polityk retencji lub wydajności.\n\nOnboarding staje się workflow provisioningowym. Gdy nowy najemca się rejestruje, system musi utworzyć lub sklonować bazę, zastosować podstawowy schemat (tabele, indeksy, ograniczenia), bezpiecznie utworzyć i przechować poświadczenia oraz kierować zapytania API do właściwej bazy.\n\nJeśli budujesz z AppMaster, kluczowym wyborem projektowym jest, gdzie trzymasz katalog najemców (centralna mapa najemca -> połączenie do bazy) oraz jak zapewnić, że każde żądanie używa właściwego połączenia.\n\nAktualizacje i migracje to główny kompromis. Zmiana schematu to nie „uruchom raz”, lecz „uruchom dla każdego najemcy”. To dodaje pracy operacyjnej i ryzyka, więc zespoły często wersjonują schematy i uruchamiają migracje jako kontrolowane zadania śledzące postęp per najemca.\n\nZaletą jest kontrola. Możesz migrować dużych najemców najpierw, obserwować wydajność, a potem wdrażać zmiany stopniowo.\n\n## Raportowanie i wydajność z osobnymi bazami\n\nOsobne bazy są najprostsze do rozumienia. Accidental cross-tenant reads są dużo mniej prawdopodobne, a błąd uprawnień zwykle dotyczy tylko jednego najemcy.\n\nWydajność też jest silną stroną. Ciężkie zapytania, duże importy czy niekontrolowane raporty w Najemcy A nie spowolnią Najemcy B. To dobre zabezpieczenie przed „hałaśliwymi sąsiadami” i pozwala dopasować zasoby per najemca.\n\nKompromis pojawia się w raportowaniu. Analizy globalne są najtrudniejsze, bo dane są fizycznie rozdzielone. Skuteczne wzorce to kopiowanie kluczowych zdarzeń lub tabel do centralnej bazy raportowej, wysyłanie zdarzeń do hurtowni danych, uruchamianie raportów per najemcy i agregowanie wyników (gdy liczba najemców jest mała) oraz trzymanie metryk produktu oddzielnie od danych klientów.\n\nKoszt operacyjny to drugi duży czynnik. Więcej baz oznacza więcej backupów, aktualizacji, monitoringu i reagowania na incydenty. Możesz też szybciej osiągnąć limity połączeń, bo każdy najemca może potrzebować własnej puli połączeń.\n\n## Częste błędy powodujące wycieki danych lub problemy później\n\nWiększość problemów multi-tenant nie wynika z „wielkiego błędu projektowego”. To małe pominięcia, które rosną w błędy bezpieczeństwa, nieporządne raporty i kosztowne naprawy. Multi-tenancy działa, gdy separacja najemców jest nawykiem, a nie funkcją doklejaną później.\n\nCzęsty wyciek to zapomnienie pola najemcy w jednej tabeli, zwłaszcza tabelach łączących jak user_roles, invoice_items czy tagi. Wszystko wygląda dobrze, dopóki raport lub wyszukiwanie nie połączy przez tę tabelę i nie wydobędzie wierszy z innego najemcy.\n\nInnym częstym problemem są panele admina, które omijają filtrowanie najemcy. Zwykle zaczyna się od „tylko dla wsparcia”, a potem jest wielokrotnie wykorzystywane. Narzędzia no-code nic tu nie zmieniają: każde zapytanie, Business Process i endpoint czytający dane najemcy potrzebuje tego samego zakresu.\n\nID też potrafią zaskoczyć. Jeśli współdzielisz „przyjazne” ID między najemcami (np. order_number = 1001) i zakładasz, że są globalnie unikalne, narzędzia wsparcia i integracje będą mieszać rekordy. Trzymaj identyfikatory zakresowe najemcy oddzielnie od wewnętrznych kluczy i zawsze uwzględniaj kontekst najemcy przy wyszukiwaniach.\n\nNa koniec zespoły bagatelizują migracje i backupy w miarę wzrostu. To, co łatwe przy 10 najemcach, staje się powolne i ryzykowne przy 1 000.\n\nSzybkie kontrole zapobiegające większości problemów:\n\n- Uczyń właścicielstwo najemcy wyraźnym w każdej tabeli, włącznie z tabelami łączącymi.\n- Użyj jednego wzorca zakresowania najemcy i stosuj go wszędzie.\n- Upewnij się, że raporty i eksporty nie uruchomią się bez zakresu najemcy (chyba że są naprawdę globalne).\n- Unikaj niejednoznacznych identyfikatorów w API i narzędziach wsparcia.\n- Przećwicz kroki przywracania i migracji wcześnie, nie po wzroście liczby klientów.\n\nPrzykład: agent wsparcia szuka „faktury 1001” i wyświetla zły najemca, bo wyszukiwanie pominęło zakres najemcy. To mały błąd ze znaczącymi konsekwencjami.\n\n## Krótka lista kontrolna zanim się zobowiążesz\n\nZanim na stałe wybierzesz model danych multi-tenant, przeprowadź kilka testów. Celem jest wykrycie wycieków danych wcześnie i potwierdzenie, że wybór działa, gdy tabele rosną.\n\n### Szybkie kontrole, które możesz zrobić w dzień\n\n- Dowód izolacji danych: utwórz dwóch najemców (A i B), dodaj podobne rekordy, a potem zweryfikuj, że każdy odczyt i aktualizacja jest ograniczona do aktywnego najemcy. Nie polegaj tylko na filtrach UI.\n- Test łamania uprawnień: zaloguj się jako użytkownik Najemcy A i spróbuj otworzyć, edytować lub usunąć rekord Najemcy B zmieniając tylko ID rekordu. Jeśli cokolwiek zadziała, uznaj to za blocker wydania.\n- Bezpieczeństwo ścieżki zapisu: potwierdź, że nowe rekordy zawsze otrzymują właściwy tenant_id (lub trafiają do właściwego schematu/bazy), nawet gdy są tworzone przez zadania w tle, importy lub automatyzacje.\n- Próba raportowania: upewnij się, że możesz wykonać raport tylko dla najemcy oraz raport „wszystkich najemców” (dla personelu wewnętrznego), z jasnymi zasadami kto może widzieć widok globalny.\n- Sprawdzenie wydajności: wdrażaj strategię indeksów już teraz (szczególnie dla (tenant_id, created_at) i innych typowych filtrów) i świadomie zmierz przynajmniej jedno wolne zapytanie, żeby wiedzieć, jak wygląda „źle”.\n\nAby test raportowania był konkretny, wybierz dwa pytania, które będą potrzebne (jedno scoped do najemcy, drugie globalne) i uruchom je na danych testowych.\n\nsql\n-- Tenant-only: last 30 days, one tenant\nSELECT count(*)\nFROM tickets\nWHERE tenant_id = :tenant_id\n AND created_at \u003e= now() - interval '30 days';\n\n-- Global (admin): compare tenants\nSELECT tenant_id, count(*)\nFROM tickets\nWHERE created_at \u003e= now() - interval '30 days'\nGROUP BY tenant_id;\n\n\nJeśli prototypujesz w AppMaster, wbuduj te kontrole w Business Process (odczyt, zapis, usuwanie) i zainicjuj dwóch najemców w Data Designer. Gdy testy przejdą przy realistycznym wolumenie danych, możesz podjąć decyzję z większą pewnością.\n\n## Przykładowy scenariusz: od pierwszych klientów do skalowania\n\n20-osobowa firma wypuszcza portal klienta: faktury, zgłoszenia i prosty pulpit. Spodziewają się 10 najemców w pierwszym miesiącu z planem rozwoju do 1 000 w ciągu roku.\n\nNa początku najprostszy model to zwykle jedna baza, gdzie każda tabela przechowująca dane klientów zawiera tenant_id. To szybkie do zbudowania, łatwe w raportowaniu i unika duplikacji konfiguracji.\n\nPrzy 10 najemcach największym ryzykiem nie jest wydajność. To uprawnienia. Jeden zapomniany filtr (np. zapytanie „lista faktur” bez tenant_id) może spowodować wyciek danych. Zespół powinien egzekwować sprawdzenia najemcy w jednym konsekwentnym miejscu (wspólna logika biznesowa lub wielokrotne używalne wzorce API) i traktować zakres najemcy jako nienegocjowalny.\n\nW miarę przejścia z 10 do 1 000 potrzebujesz zmian. Raportowanie staje się cięższe, wsparcie prosi o „eksport dla tego najemcy”, a kilku dużych klientów zaczyna dominować ruch i spowalniać współdzielone tabele.\n\nPraktyczna ścieżka migracji często wygląda tak:\n\n1) Zachowaj tę samą logikę aplikacji i zasady uprawnień, ale przenieś najciężej obciążonych najemców do osobnych schematów.\n2) Dla największych lub zgodnościowo wymagających najemców przenieś ich do oddzielnych baz danych.\n3) Zachowaj wspólną warstwę raportową, która czyta z wszystkich najemców, i uruchamiaj ciężkie raporty poza godzinami szczytu.\n\nWybierz najprostszy model, który bezpiecznie oddziela dane dzisiaj, a potem zaplanuj ścieżkę migracji dla „kilku ogromnych najemców” zamiast optymalizować pod ten przypadek od pierwszego dnia.\n\n## Następne kroki: wybierz model i prototypuj go w backendzie bez kodu\n\nWybierz w oparciu o to, co chcesz chronić najpierw: izolację danych, prostotę operacji czy skalowanie per najemca. Pewność przychodzi z zbudowania małego prototypu i próby jego złamania przy realistycznych przypadkach uprawnień i raportowania.\n\nProsty przewodnik startowy:\n\n- Jeśli większość najemców jest mała i potrzebujesz prostego raportowania między najemcami, zacznij od jednej bazy i tenant_id w każdym wierszu.\n- Jeśli potrzebujesz silniejszej separacji, ale nadal chcesz zarządzać jedną bazą, rozważ osobne schematy per najemca.\n- Jeśli najemcy wymagają twardej izolacji (zgodność, dedykowane backupy, ryzyko hałaśliwych sąsiadów), rozważ osobną bazę per najemca.\n\nZanim zbudujesz, zapisz granice najemcy prostym językiem. Zdefiniuj role (owner, admin, agent, viewer), co każda może robić i co oznacza „globalne” (plany, szablony, logi audytu). Zdecyduj jak ma działać raportowanie: tylko per najemca, czy też „wszystkie najemcy” dla personelu wewnętrznego.\n\nJeśli używasz AppMaster, możesz szybko prototypować te wzorce: modeluj tabele w Data Designer (włączając tenant_id, ograniczenia unikalności i indeksy, na których polegasz), a potem wymuszaj reguły w Business Process Editor, by każdy odczyt i zapis był scoped do najemcy. Jeśli chcesz punktu odniesienia dla platformy, AppMaster jest dostępny pod adresem appmaster.io.\n\nPraktyczny test końcowy: utwórz dwóch najemców (A i B), dodaj podobnych użytkowników i zamówienia i uruchom te same przepływy dla obu. Spróbuj wyeksportować raport dla najemcy A, a potem celowo podstaw ID najemcy B do tych samych endpointów. Twój prototyp jest „wystarczająco bezpieczny”, gdy te próby zawsze zawodzą, a kluczowe raporty dalej działają szybko przy realistycznych rozmiarach danych.
FAQ
Domyślnie wybierz jedną bazę danych z tenant_id na każdej tabeli należącej do najemcy, jeśli chcesz najprostszej operacji i częstych analiz między najemcami. Przejdź do osobnych schematów, gdy potrzebujesz silniejszej izolacji lub personalizacji per najemca bez uruchamiania wielu baz danych. Wybierz osobne bazy danych, gdy wymogi zgodności lub klienci enterprise wymagają twardej separacji i kontroli wydajności per najemca.
Traktuj zakres najemcy jako obowiązkowy w backendzie, a nie filtr w UI. Uczyń tenant_id wymaganym na tabelach należących do najemcy i zawsze pobieraj go z kontekstu uwierzytelnionego użytkownika, zamiast ufać wejściu klienta. Dodaj siatkę bezpieczeństwa, np. polityki RLS w PostgreSQL, jeśli pasuje do stosu, i pisz testy, które próbują dostać się do rekordu innego najemcy zmieniając tylko ID.
Umieść tenant_id jako pierwszą kolumnę w indeksach, które odpowiadają typowym filtrom, aby baza mogła od razu przeskoczyć do kawałka danych jednego najemcy. Dobry punkt wyjścia to indeks (tenant_id, created_at) dla widoków czasowych oraz (tenant_id, status) lub (tenant_id, user_id) dla częstych filtrów pulpitowych. Także zapewnij unikalności w zakresie najemcy, np. „email unikatowy per tenant”, by uniknąć kolizji.
Osobne schematy zmniejszają przypadkowe łączenia między najemcami, bo tabele żyją w różnych przestrzeniach nazw, i pozwalają ustawiać uprawnienia na poziomie schematu. Głównym minusem są migracje: każdemu schematowi trzeba zastosować tę samą zmianę, co staje się problemem procesowym w miarę wzrostu liczby najemców. To dobry kompromis, gdy chcesz silniejszej izolacji niż tenant_id, ale nadal zarządzać jedną bazą.
Osobne bazy minimalizują zakres potencjalnych problemów: skok obciążenia, błąd konfiguracji lub korupcja zwykle dotkną tylko jednego najemcy. Kosztem jest większa praca operacyjna — provisioning, backupy, monitoring i migracje mnożą się wraz z liczbą najemców. Potrzebujesz też wiarygodnego katalogu najemców i routingu żądań, by każde wywołanie API korzystało z właściwej bazy.
Najprościej zrobisz to w jednej bazie z tenant_id, bo globalne pulpity to zapytania bez filtra najemcy. W przypadku schematów lub osobnych baz, analityka globalna zwykle realizowana jest przez kopiowanie kluczowych zdarzeń lub podsumowań do wspólnego magazynu raportowego w harmonogramie. Zasada jest prosta: metryki produktowe idą do warstwy raportowania, a dane najemcy pozostają izolowane.
Eksponuj kontekst najemcy wyraźnie w narzędziach wsparcia i wymagaj świadomego przełączenia najemcy przed przeglądaniem rekordów. Jeśli używasz wejścia w imieniu użytkownika (impersonation), loguj kto, co i kiedy oglądał — i ograniczaj to czasowo. Unikaj przepływów wsparcia, które akceptują tylko ID rekordu bez kontekstu najemcy, bo to główna droga do błędów typu „faktura 1001” prowadzących do wycieków.
Gdy najemcy potrzebują różnych pól lub procesów, schematy albo osobne bazy zmniejszają ryzyko, że zmiany jednego klienta wpłyną na pozostałych. Jeśli większość najemców jest podobna, trzymaj jeden wspólny model z tenant_id i obsługuj różnice przez konfigurowalne opcje (feature flags) lub pola opcjonalne. Kluczowe jest unikanie tabel „prawie globalnych”, które mieszają znaczenia współdzielone i specyficzne dla najemcy bez jasnego właściciela.
Zaprojektuj granicę najemcy wcześnie: zdecyduj, gdzie przechowywany jest kontekst najemcy po uwierzytelnieniu i upewnij się, że każdy odczyt/zapis go używa. W AppMaster zwykle oznacza to ustawienie tenant_id z uwierzytelnionego użytkownika w logice Business Process przed tworzeniem lub zapytaniem o rekordy należące do najemcy, żeby endpointy nie zapomniały o tym. Traktuj to jako wzorzec wielokrotnego użytku, a nie coś do ponownego implementowania dla każdego ekranu.
Utwórz dwóch najemców z podobnymi danymi i próbuj złamać izolację zmieniając tylko ID rekordu przy odczytach, aktualizacjach i usuwaniu. Sprawdź, czy zadania w tle, importy i eksporty zapisują zawsze w właściwym zakresie najemcy, bo te ścieżki łatwo przeoczyć. Uruchom także jedno raportowanie na poziomie najemcy i jedno globalne raportowanie administracyjne na realistycznym wolumenie danych, by potwierdzić wydajność i zasady dostępu.


