Jest to pierwszy artykuł z serii Aplikacje multi-tenant w PHP, w której będę opisywał różne zagadnienia dotyczące tej architektury.  W dzisiejszym wpisie przedstawię różne strategie składowania danych w tym podejściu. W kolejnych wpisach postaram się omówić inne aspekty takie jak dynamiczna konfiguracja, tworzenie instancji, deployment, dedykowane rozwiązania.

Aplikacje multi-tenant

Minęły czasy, w których aby wysłać maila do tysięcy odbiorców musieliśmy sami pisać cały system newslettera, a żeby wystawić fakturę potrzebowaliśmy zainstalować program do fakturowania. Teraz te i masę innych rzeczy jesteśmy w stanie zrobić z każdego miejsca na Ziemi i z dowolnego komputera za pomocą kilku kliknięć, a na każdy nasz problem przypada kilku, kilkunastu czy nawet kilkudziesięciu dostawców danej usługi.

Dawniej z reguły kupowaliśmy licencję na dany software i instalowaliśmy go na swoim komputerze czy serwerze w sieci. Dzisiaj kupujemy dostęp do usługi, zamiast licencji otrzymujemy login i hasło czy klucz API pozwalające na korzystanie z aplikacji providera.

Tego typu aplikacje są udostępniane w modelu SaaS (Software as a Service) i oparte o architekturę multi-tenant polegającą na obsłudze wielu dzierżawców (tenantów) za pomocą jednej instancji aplikacji. Na podstawie requestu (np. subdomeny, klucza API, loginu) aplikacja rozpoznaje który z tenantów będzie jej używał i odpowiednio się konfiguruje tak aby korzystać z jego danych, ustawień a nawet plików z dedykowanymi mu rozwiązaniami.

Poniżej przedstawiam 3 sposoby na to jak możemy przechowywać dane klientów oraz wady i zalety każdego z rozwiązań.

Jedna baza danych dla wszystkich

Dane wszystkich klientów trzymamy we wspólnej bazie danych, w każdej tabeli posiadamy kolumnę zawierającą jego identyfikator.

Tabela tenants zawierająca listę tenantów w aplikacji.

Tabela products zawierająca produkty wszystkich tenantów, każdy rekord zawiera odpowiednie tenant_id.

Plusy:

  • proste w utrzymaniu, nie trzeba tworzyć nowych baz dla kolejnych klientów,
  • podczas deploymentu zmieniamy schemat tylko jednej bazy.

Minusy:

  • konieczność przechowywania ID tenanta,
  • jeśli gdzieś zapomnimy sprawdzić ID może to skutkować dostępem do danych innych klientów, a nawet ich usunięciem,
  • wraz ze wzrostem liczby klientów mogą pojawić się problemy z wydajnością.

Wspólna baza z listą tenantów oraz kilka baz z danymi klientów

Rozwiązanie jest podobne do Jedna baza dla wszystkich z tą różnicą, że mamy kilka baz danych:

  • baza zawierająca tabelę tenants,
  • bazy z danymi klientów – możemy rozlokować instancje na kilku bazach np. grupując wiele „małych” instancji w jednej w bazie, a „duże” w dedykowanych tylko dla nich.

Tabela tenants umieszczona jest teraz w osobnej bazie (np. db_common) oraz dodatkowo przechowuje informacje o tym w której bazie danych znajdują się dane instancji.

Tabela products w bazie db_1

Tabela products w bazie db_2

Struktura tabeli products nie zmienia się jednak dane są rozmieszczone w kilku bazach.

Plusy:

  • rozwiązanie jest skalowalne, możemy przenosić klientów do dedykowanych baz danych a nawet na osobne serwery (oczywiście musielibyśmy jeszcze przechowywać host do serwera BD),
  • poprawiło się bezpieczeństwo, dane są podzielone na więcej baz.

Minusy:

  • nadal w razie błędu programisty istnieje możliwość wymieszania się danych różnych instancji,
  • musimy zadbać o utrzymanie schematu w wielu bazach,
  • w aplikacji mamy 2 połączenia do różnych baz.

Jedna baza – jeden klient

W tym podejściu każdy klient ma swoją dedykowaną bazę danych, oprócz tego tak jak w poprzednim przykładzie istnieje jedna baza wspólna z informacjami o instancjach oraz nazwach baz.

Tabela tenants.

Tabela products w bazie db_1

Tabela products w bazie db_2.

W związku z tym, że w każdej bazie są dane tylko jednego klienta nie ma potrzeby przechowywania jego identyfikatora.

Plusy:

  • brak konieczności przechowywania ID tenanta w każdym rekordzie i podawania go w każdym zapytaniu,
  • bezpieczeństwo danych, nie zachodzi możliwość dostępu do danych innych klientów,
  • lepsza wydajność, dużo mniej rekordów w tabelach,
  • ułatwione debugowanie.

Minusy:

  • tworząc nową instancję musimy stworzyć nową bazę danych, schemat i zapewne również uzupełnić ją podstawowymi danymi potrzebnymi aplikacji,
  • musimy zadbać o utrzymanie schematu w wielu bazach
  • w aplikacji mamy 2 połączenia do różnych baz,
  • większe zużycie zasobów,
  • niezależnie od tego jak dużo danych zawiera instancja i tak musi mieć dedykowaną bazę.

Co wybrać?

Jak widać nie ma złotego środka i jak to zwykle w programowaniu bywa, odpowiedź na pytanie „co wybrać?” brzmi „to zależy”. Każde rozwiązanie ma swoje plusy i minusy, należy więc dokładnie przeanalizować nasze potrzeby i wymagania naszych klientów, jest to bowiem jedna z decyzji, z której może być trudno się wycofać.

W kolejnej części zajmiemy się praktyką, implementacją tych metod w PHP, a konkretnie we frameworku Symfony z użyciem Doctrine.