Wróć do bloga

Architektura multi-tenancy przy 40M kontaktów

Marek KoncewiczMarek Koncewicz
3 stycznia 20263 min czytania
Architektura multi-tenancy przy 40M kontaktów

W pierwszej kolejności rekomenduję zapoznać się z naszą realizacją aplikacji SaaS, z której wywodzi się ten temat.

Sedno zagadnienia

Podczas prac dla naszego klienta z branży e-mail marketingowej zastanawialiśmy się, jak przechowywać dane.

Osobie spoza branży informatycznej największe zagadnienie może kojarzyć się z dyskusją o technologii backendowej. „Jestem programistą Java" bądź „Jestem programistą PHP" brzmi dość szufladkująco i może się wydawać jedynym zagadnieniem w kwestii wytwarzania oprogramowania.

Może zabrzmieć zaskakująco, ale technologia backendowa to najmniej ważna sprawa podczas projektowania aplikacji.

Organizacja dostępu do danych oraz projektowanie przyjaznego UI/UX to kwestie bardziej wymagające i bardziej fundamentalne przy wytwarzaniu oprogramowania niż wybór technologii backendowej.

Przy projekcie EngageMail postąpiliśmy zgodnie z naszym doświadczeniem: skupiliśmy się na strategii przechowywania danych.

Struktura danych

Samo spisanie struktury danych nie rozwiązuje zagadnienia. Należy również wziąć pod uwagę sposób zarządzania danymi, wymagania w zakresie dostępności danych oraz ryzyka biznesowe.

Dopiero po zapoznaniu się z szerszym kontekstem można przystąpić do budowania struktury bazy danych.

W ten sam sposób działaliśmy przy EngageMailu i po wszystkich analizach podjęliśmy decyzję: idziemy w kierunku multi-tenancy.

Multi-tenancy to model architektoniczny, w którym jedna instancja aplikacji obsługuje wielu klientów (tenantów) – firm, zespołów, organizacji – a każdy z nich widzi wyłącznie własne dane i konfigurację. Przeciwieństwem jest single-tenancy, gdzie każdy klient dostaje osobną, dedykowaną instalację.

Oto jakiego rodzaju dane mieliśmy przechowywać:

  • Contacts – kontakty użytkowników, zawierające adres e-mail oraz dodatkowe atrybuty (imię i nazwisko, wiek, płeć itd.)
  • Groups – grupy realizujące segmentację kontaktów użytkowników
  • Campaigns – kampanie realizowane na konkretnej grupie

To podstawowe typy danych, z których każdy zawiera własne typy podrzędne. W przypadku grup kontaktów są to wspomagające tagi. W przypadku kampanii – informacje o kliknięciach, impresjach, statystykach realizacji.

Jednak ten zestaw informacji nie dowodzi konieczności wprowadzenia architektury multi-tenancy. Dopiero wgląd w procesy biznesowe naszego klienta był punktem przełomowym.

Wątek biznesowy

Nasz klient zarządza odrębnymi zbiorami danych. Każdy z tych zbiorów jest zamknięty we własnym ekosystemie. To znaczy, że każdy ze zbiorów ma własną nazwę, własne ograniczenia prawne oraz konkretnego właściciela tych danych. Podział kontaktów na grupy i realizacja kampanii mają się odbywać w ramach konkretnego zbioru.

Mieliśmy w zasadzie dwie opcje:

  • Jedna fizyczna baza danych z jednym zestawem tabel i polem tenant_id w każdym wierszu
  • Tworzenie nowej bazy danych pod każdy zestaw danych, o identycznej strukturze

Zdecydowaliśmy się na drugie rozwiązanie.

Oto czynniki, które okazały się decydujące w tej kwestii:

  • Możliwość skalowania zasobów w przypadku nadmiernego obciążenia zapytaniami konkretnego zbioru danych – zawsze można konkretny zbiór wynieść na osobną maszynę
  • Separacja dostępu do konkretnego zbioru danych na poziomie fizycznym – jeśli użytkownik nie posiada dostępu do zbioru danych, to żadne zapytanie do tej konkretnej relacyjnej bazy danych nie zostanie wykonane
  • Brak przysłowiowego pola tenant_id czyni proces importu, eksportu i usuwania danych bardziej wydajnym – w przeciwnym razie bylibyśmy zmuszeni utrzymywać dodatkowy indeks z udziałem tego pola, a natychmiastowe usunięcie danych całego zbioru byłoby obciążające

Gdy uzmysłowiliśmy sobie wymagania techniczne, funkcjonalne oraz realia biznesowe naszego klienta, odpowiedź przyszła odruchowo: realizujemy architekturę multi-tenancy.

Oczywiście nie było to takie proste w realizacji. Przy wgraniu nowego zbioru danych musieliśmy automatycznie tworzyć nową fizyczną bazę danych oraz zorganizować proces automatycznych migracji struktury (tabele, indeksy, wartości domyślne). Analityka na wszystkich zbiorach razem nie była już tak wydajna i szybka, jak mogłoby to być w przypadku wspólnej bazy danych. Części wbudowanych narzędzi Laravela nie byliśmy w stanie użyć z powodu wybranej przez nas architektury.

Podsumowanie

Klient nie nakładał na nas ograniczeń ani wymagań odnośnie do architektury. Przedstawił nam wszystkie niezbędne informacje i wymagania oraz zaufał nam w kwestii doboru odpowiednich narzędzi, a w tym przypadku – architektury przechowywania danych.

To nie znaczy, że w ogóle nie informowaliśmy go o naszych decyzjach. Mieliśmy warsztaty, na których przedstawiliśmy naszą wizję wraz z uzasadnieniem.

Marek Koncewicz
Marek KoncewiczFullstack Engineer

Ewangelista Laravela. Zwolennik produktowego podejścia do rozwoju aplikacji webowych.