Czy byłoby możliwe posiadanie wielu pul połączeń bazy danych w szynach, aby przełączać się między nimi?

12

Trochę tła

Od lat używam klejnotu Apartamentu do prowadzenia aplikacji wielodostępnej. Niedawno pojawiła się potrzeba skalowania bazy danych na osobne hosty, serwer db po prostu nie może już dłużej nadążać (zarówno odczyty, jak i zapisy stają się zbyt duże) - i tak, skalowałem sprzęt do maksimum (dedykowany sprzęt, 64 rdzenie, 12 napędów Nvm-e w RAID 10, 384 Gb RAM itp.).

Zastanawiałem się nad zrobieniem tego na dzierżawę (1 dzierżawca = 1 konfiguracja / pula połączenia z bazą danych), ponieważ byłby to „prosty” i skuteczny sposób na uzyskanie do- number-of-tenantsrazy większej pojemności bez wprowadzania dużych zmian kodu aplikacji.

Teraz używam szyn 4.2 atm., Wkrótce aktualizuję do 5.2. Widzę, że szyny 6 dodają obsługę definicji połączeń dla poszczególnych modeli, ale to nie jest tak naprawdę potrzebne, ponieważ mam całkowicie dublowany schemat bazy danych dla każdego z moich 20 dzierżawców. Zazwyczaj przełączam „bazę danych” na żądanie (w oprogramowaniu pośrednim) lub na zadanie w tle (oprogramowanie pośrednie sidekiq), jednak jest to obecnie trywialne i obsługiwane jako klejnot mieszkania, ponieważ ustawia tylko search_pathPostgresql i tak naprawdę nie zmienia faktycznego połączenia. Podczas przełączania się na strategię hostingową dla każdego najemcy będę musiał zmienić całe połączenie na żądanie.

Pytania:

  1. Rozumiem, że mógłbym wykonać ActiveRecord::Base.establish_connection(config)zadanie na żądanie / w tle - jednak, jak rozumiem, to powoduje zupełnie nowy uścisk połączenia z bazą danych i nową pulę db odradza się w szynach - prawda? Sądzę, że byłoby to samobójstwem związanym z wydajnością, które spowodowałoby taki narzut przy każdym pojedynczym żądaniu mojej aplikacji.
  2. Zastanawiam się więc, czy ktoś może zobaczyć opcję z szynami np. Wstępnego ustanawiania wielu (łącznie 20) połączeń / pul bazy danych od samego początku (np. Podczas uruchamiania aplikacji), a następnie po prostu przełączać się między tymi pulami na żądanie? Tak więc połączenia db są już wykonane i gotowe do użycia.
  3. Czy to wszystko jest kiepskim złym pomysłem i czy zamiast tego powinienem szukać innego podejścia? Np. 1 instancja aplikacji = jedno określone połączenie z jednym konkretnym najemcą. Albo coś innego.
Niels Kristian
źródło
2
guide.rubyonrails.org/active_record_multiple_databases.html Myślę, że może ci to pomóc
Alex Golubenko
1
Ten PR może Cię zainteresować w repozytorium GitHub w Rails, które niedawno dodało dokładnie tę funkcję, której potrzebujesz do bieżącej mastergałęzi Rails . Czy uruchomienie Rails Egde byłoby opcją lub działaniem wstecznym dla tej wersji Rails?
spickermann
@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endoznacza, że ​​pula zostanie (ponownie) użyta, zamiast tworzyć nowe połączenie za każdym razem?
Ben

Odpowiedzi:

4

Jak rozumiem, istnieją 4 wzorce aplikacji dla wielu najemców:

1. Model dedykowany / wiele środowisk produkcyjnych

Każda instancja lub instancja bazy danych całkowicie obsługuje inną aplikację dzierżawcy i nic nie jest współużytkowane przez dzierżawców.

Jest to aplikacja 1 instancja i 1 baza danych dla 1 najemcy. Rozwój byłby łatwy, jakbyś obsługiwał tylko 1 najemcę. Ale będzie koszmarem dla deweloperów, jeśli masz, powiedzmy, 100 lokatorów.

2. Fizyczna segregacja najemców

1 aplikacja instancji dla wszystkich najemców, ale 1 baza danych dla 1 najemcy. To jest to, czego szukasz. Możesz używać ActiveRecord::Base.establish_connection(config)lub używać klejnotów, lub aktualizować do Rails 6, jak sugerują inne. Zobacz odpowiedź na (2) poniżej.

3. Model izolowanego schematu / Segregacje logiczne

W schemacie izolowanym tabele dzierżawy lub komponenty bazy danych są grupowane w ramach schematu logicznego lub przestrzeni nazw i oddzielone od innych schematów dzierżawy, jednak schemat jest hostowany w tej samej instancji bazy danych.

1 instancja aplikacji i 1 baza danych dla wszystkich najemców, tak jak w przypadku klejnotu do mieszkania.

4. Częściowo izolowany komponent

W tym modelu komponenty o wspólnych funkcjach są współużytkowane przez dzierżawców, a komponenty o unikalnych lub niepowiązanych funkcjach są izolowane. W warstwie danych wspólne dane, takie jak dane identyfikujące najemców, są grupowane lub przechowywane w pojedynczej tabeli, podczas gdy dane specyficzne dla najemcy są izolowane w warstwie tabeli lub instancji.


Jeśli chodzi o (1), ActiveRecord::Base.establish_connection(config)nie uzgadnianie z db na żądanie, jeśli używasz go poprawnie. Możesz sprawdzić tutaj i przeczytać cały komentarz tutaj .

Jeśli chodzi o (2), jeśli nie chcesz używać establish_connection, możesz użyć klejnotu multiverse (działa dla szyn 4.2) lub innych klejnotów. Lub, jak sugerują inni, możesz zaktualizować do wersji Rails 6.

Edycja: używa klejnotu Multiverse establish_connection. Dołącza database.ymli utworzy klasę podstawową, tak aby każda podklasa dzieliła to samo połączenie / pulę. Zasadniczo zmniejsza to nasz wysiłek w użyciu establish_connection.

Jeśli chodzi o (3), odpowiedź:

Jeśli nie masz tak wielu dzierżawców, a Twoja aplikacja jest dość złożona, sugeruję użycie wzorca modelu dedykowanego. Tak więc wybierasz 1 instancję aplikacji = jedno określone połączenie z jednym konkretnym najemcą. Nie musisz komplikować aplikacji, dodając wiele połączeń z bazą danych.

Ale jeśli masz wielu najemców, sugeruję, abyś stosował fizyczną segregację najemców lub częściowo izolowany komponent, w zależności od procesu biznesowego.

Tak czy inaczej, musisz zaktualizować / przepisać aplikację, aby była zgodna z nową architekturą.

KSD Putra
źródło
Cześć dzięki za odpowiedź. Potrzebuję trochę czasu, aby faktycznie przetestować sugestię, zanim będę mógł nagrodzić jedną z nagród, jeśli są to dobre rozwiązania.
Niels Kristian
Mam kilka pytań dotyczących 1 i 2. 1: Nie jestem pewien, czy rozumiem twoje odniesienia. Czy to, co mówisz, że mogę wywołać .establish_connection (config) bez wykonywania uzgadniania db / odtwarzania sondowania db? W takim razie nie jestem pewien, w jaki sposób oba linki to wyjaśniają? 2: Czy dla multiwersum nie jest to przełączanie bazy danych dla poszczególnych modeli, a nie cały przełącznik db dla całej aplikacji? Wydaje mi się, że ich dokumentacja jest dość niejasna
Niels Kristian
Myślę, że mam nieporozumienie. Czy masz ochotę opracować te zdania? Rozumiem, że mógłbym wykonać ActiveRecord :: Base.establish_connection (config) dla zadania żądania / tła - jednak, jak rozumiem, wyzwala to zupełnie nowy uścisk dłoni połączenia z bazą danych i nową pulę db odradza się w szynach It sugerować, że jedno żądanie tworzy jedną pulę db?
KSD Putra
Mam na myśli: (1) Martwię się o wydajność / obciążenie sieci, gdy muszę wywoływać ActiveRecord :: Base.establish_connection (config) na każde żądanie, aby przełączać się między różnymi bazami danych / krajami
Niels Kristian
Nie musisz się martwić o koszty ogólne. Teraz, jeśli używasz pojedynczego DB, masz jedną pulę połączeń (możesz sprawdzić link o połączeniu w odpowiedzi (1) powyżej). Jeśli użyjesz establish_connectionw modelu takim jak ten: class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); endi powiesz, że masz 5 modeli, utworzysz 5 puli połączeń z DB_SECOND_TENANT. I każda pula jest traktowana jednakowo. Tak więc nie tworzysz puli na żądanie, ale na establish_connection.
KSD Putra
3

Z tego co rozumiem (2) powinno być możliwe ręczne przełączanie połączenia w Railsach 6.

claasz
źródło
Dzięki jednak wydaje się to dość dalekie od mojego przypadku użycia. Oznaczałoby to przepisanie całej aplikacji do korzystania z tej procedury wszędzie.
Niels Kristian
3

Zaledwie kilka dni temu do Ruby on Rails na GitHub dodano poziomy shardingmaster . Obecnie ta funkcja nie jest oficjalnie wydana, ale w zależności od wersji Railsowej aplikacji możesz rozważyć użycie Railsów master, dodając to do Gemfile:

gem "rails", github: "rails/rails", branch: "master"

Dzięki tej nowej funkcji możesz skorzystać z puli połączeń z bazą danych Rails i przełączać bazę danych w zależności od warunków.

Nie korzystałem z tej nowej funkcji, ale wydaje się dość prosta:

# in your config/database.yml
production:
  primary:
    database: my_database
    # other config: user, password, etc
  primary_tenant_1:
    database: tenant_1_database
    # other config: user, password, etc

# in your controller for example when updating a tenant
ActiveRecord::Base.connected_to(shard: "primary_tenant_#{tenant.database_shard_number}") do
  tenant.save
end

Nie dodałeś zbyt wielu szczegółów na temat sposobu określania numeru najemcy ani sposobu przeprowadzania autoryzacji w aplikacji. Ale postaram się ustalić numer najemcy jak najszybciej application_controllerw around_action. Coś takiego może być punktem wyjścia:

around_filter :determine_database_connection

private

def determine_database_connection
  # assuming you have a method to determine the current_tenant and that tenant
  # has a method that returns the number of the shard to use or even the 
  # full shard identifier
  shard = current_tenant.database_shard # returns for example `:primary_tenant_1` 

  ActiveRecord::Base.connected_to(shard: shard) do
    yield
  end
end
spickermann
źródło
Czy w takim przypadku miałoby to również taki sam sens powrót do domyślnego połączenia? github.com/influitive/apartment#middleware-considerations
Ben
1
Po opuszczeniu ActiveRecord::Base.connected_to ... dobloku ponownie użyje domyślnego połączenia.
spickermann
@spickermann czytałem ab ten klejnot, nie tylko dla rails6?
7urkm3n
@ 7urkm3n Jest zawarty w bieżącym masteroddziale Rails .
spickermann
Cześć dzięki za odpowiedź. Potrzebuję trochę czasu, aby faktycznie przetestować sugestię, zanim będę mógł nagrodzić jedną z nagród, jeśli są to dobre rozwiązania.
Niels Kristian