Serait-il possible d'avoir plusieurs pools de connexions de base de données dans les rails pour basculer entre?

12

Un peu d'histoire

J'utilise le Gem Appartement pour exécuter une application multi-locataire depuis des années. Récemment, le besoin de faire évoluer la base de données sur des hôtes séparés est arrivé, le serveur db ne peut tout simplement plus suivre (les lectures et les écritures deviennent trop) - et oui, j'ai dimensionné le matériel au maximum (dédié matériel, 64 cœurs, 12 disques Nvm-e en raid 10, 384 Go de RAM, etc.).

number-of-tenantsJ'envisageais de faire cela par locataire (1 locataire = 1 configuration / pool de connexion à la base de données) car ce serait un moyen "simple" et efficace pour obtenir jusqu'à -toujours plus de capacité sans effectuer de charges de changements de code d'application.

Maintenant, j'utilise des rails 4.2 atm., Je passerai bientôt à 5.2. Je peux voir que rails 6 ajoute la prise en charge des définitions de connexion par modèle, mais ce n'est pas vraiment ce dont j'ai besoin, car j'ai un schéma de base de données complètement en miroir pour chacun de mes 20 locataires. En général, je change de "base de données" par demande (dans le middleware) ou par tâche d'arrière-plan (middlekiq middleware), mais cela est actuellement trivial et géré par la gemme Appartement, car il définit simplement le search_pathdans Postgresql et ne change pas vraiment la connexion réelle. Lors du passage à une stratégie d'hébergement par locataire, je devrai changer la connexion entière par demande.

Des questions:

  1. Je comprends que je pourrais faire un travail ActiveRecord::Base.establish_connection(config)par demande / en arrière-plan - cependant, comme je le comprends aussi, cela déclenche une nouvelle négociation de connexion à la base de données et un nouveau pool de bases de données à apparaître dans les rails - non? Je suppose que ce serait un suicide de performance de faire ce genre de frais généraux sur chaque demande unique à ma demande.
  2. Je me demande donc si quelqu'un peut voir l'option avec des rails par exemple de préétablir plusieurs (totaux 20) connexions / pools de base de données depuis le début (par exemple au démarrage de l'application), puis basculer entre ces pools par demande? Pour que les connexions DB soient déjà établies et prêtes à être utilisées.
  3. Tout cela n'est-il qu'une mauvaise mauvaise idée, et devrais-je plutôt rechercher une approche différente? Par exemple, 1 instance d'application = une connexion spécifique à un locataire spécifique. Ou autre chose.
Niels Kristian
la source
2
guides.rubyonrails.org/active_record_multiple_databases.html Je pense que cela pourrait vous aider
Alex Golubenko
1
Vous pourriez être intéressé par ce PR dans le référentiel GitHub de Rails qui a récemment ajouté exactement la fonctionnalité dont vous avez besoin à la masterbranche Rails actuelle . L'exécution de Rails Egde serait-elle une option ou une sauvegarde de cette fonctionnalité dans votre version actuelle de Rails?
Spickermann
@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endsignifie que le pool sera (ré) utilisé, au lieu de créer une nouvelle connexion à chaque fois?
Ben

Réponses:

4

Si je comprends bien, il existe 4 modèles pour une application multi-locataire:

1. Modèle dédié / environnements de production multiples

Chaque instance ou instance de base de données héberge entièrement une application de locataire différente et rien n'est partagé entre les locataires.

Il s'agit d'une application d'instance et d'une base de données pour 1 locataire. Le développement serait facile comme si vous ne serviez qu'un seul locataire. Mais ce sera un cauchemar pour les devops si vous avez, disons, 100 locataires.

2. Séparation physique des locataires

1 application d'instance pour tous les locataires, mais 1 base de données pour 1 locataire. C'est ce que vous recherchez. Vous pouvez utiliser ActiveRecord::Base.establish_connection(config), ou utiliser des gemmes, ou mettre à jour vers Rails 6 comme d'autres suggèrent. Voir la réponse pour (2) ci-dessous.

3. Modèle de schéma isolé / ségrégations logiques

Dans un schéma isolé, les tables de locataires ou les composants de base de données sont regroupés sous un schéma logique ou un espace de noms et séparés des autres schémas de locataires, mais le schéma est hébergé dans la même instance de base de données.

1 application d'instance et 1 base de données pour tous les locataires, comme vous le faites avec gem d'appartement.

4. Composant partiellement isolé

Dans ce modèle, les composants qui ont des fonctionnalités communes sont partagés entre les locataires tandis que les composants avec des fonctions uniques ou non liées sont isolés. Au niveau de la couche de données, les données courantes telles que les données qui identifient les locataires sont regroupées ou conservées dans une seule table tandis que les données spécifiques au locataire sont isolées au niveau de la table ou de la couche d'instance.


Quant à (1), ActiveRecord::Base.establish_connection(config)ne pas établir de liaison avec db par requête si vous l'utilisez correctement. Vous pouvez vérifier ici et lire tous les commentaires ici .

Quant à (2), si vous ne voulez pas utiliser establish_connection, vous pouvez utiliser le multivers gem (il fonctionne pour les rails 4.2), ou d'autres gemmes. Ou, comme d'autres le suggèrent, vous pouvez mettre à jour vers Rails 6.

Edit: la gemme multivers utilise establish_connection. Il ajoutera le database.ymlet créera une classe de base afin que chaque sous-classe partage la même connexion / pool. Fondamentalement, cela réduit nos efforts d'utilisation establish_connection.

Quant à (3), la réponse:

Si vous n'avez pas autant de locataires et que votre application est assez complexe, je vous suggère d'utiliser le modèle de modèle dédié. Donc, vous optez pour 1 instance d'application = une connexion spécifique à un locataire spécifique. Vous n'avez pas à rendre vos applications plus complexes en ajoutant plusieurs connexions à la base de données.

Mais si vous avez plusieurs locataires, je vous suggère d'utiliser la séparation physique des locataires ou le composant partiellement isolé en fonction de votre processus métier.

Dans tous les cas, vous devez mettre à jour / réécrire votre application pour vous conformer à la nouvelle architecture.

KSD Putra
la source
Salut merci pour la réponse. J'aurai besoin d'un peu de temps pour tester réellement la suggestion avant de pouvoir récompenser l'une des réponses si elles sont de bonnes solutions.
Niels Kristian
J'ai quelques questions concernant 1 et 2. 1: Je ne suis pas sûr de comprendre vos références. Est-ce que vous dites que je peux appeler .establish_connection (config) sans faire de poignée de main db / recréer le sondage db? Dans ce cas, je ne sais pas comment les deux liens expliquent cela? 2: Pour le multivers, n'est-ce pas une commutation de base de données par modèle plutôt qu'un commutateur db entier pour l'application entière? Je pense que leur documentation est assez vague
Niels Kristian
Je pense que j'ai un malentendu. Pourriez-vous élaborer ces phrases? Je comprends que je pouvais faire une ActiveRecord :: Base.establish_connection (config) par demande / tâche de fond - mais, si je comprends aussi, qui déclenche une poignée de main de connexion de base de données entièrement nouvelle à réaliser et un pool db pour frayer dans les rails Il suggérer qu'une demande crée un pool de bases de données?
KSD Putra
Je veux dire: (1) Je m'inquiète des performances / surcharge du réseau lorsque je dois appeler ActiveRecord :: Base.establish_connection (config) à chaque demande, juste pour basculer entre les différentes bases de données / pays
Niels Kristian
Vous n'avez pas à vous soucier des frais généraux. Maintenant, si vous utilisez une seule base de données, vous avez un pool de connexions (vous pouvez vérifier le lien sur la connexion dans la réponse de (1) ci-dessus). Si vous utilisez establish_connectiondans le modèle comme celui-ci class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); end:, et que vous avez 5 modèles, vous créez 5 pools de connexions au DB_SECOND_TENANT. Et chaque piscine est traitée de manière égale. Donc, vous ne créez pas de pool par demande, mais par establish_connection.
KSD Putra
3

D'après ce que je comprends, (2) devrait être possible avec une commutation de connexion manuelle dans Rails 6.

claasz
la source
Merci cependant, cela semble assez loin de mon cas d'utilisation. Cela impliquerait de réécrire l'application entière pour utiliser cette procédure partout.
Niels Kristian
3

Il y a quelques jours à peine, un sharding horizontal a été ajouté à la masterbranche Ruby on Rails sur GitHub. Actuellement, cette fonctionnalité n'est pas officiellement publiée, mais en fonction de la version de Rails de votre application, vous pouvez envisager d'utiliser Rails masteren l'ajoutant à votre Gemfile:

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

Avec cette nouvelle fonctionnalité, vous pouvez profiter du pool de connexions à la base de données de Rails et basculer la base de données en fonction des conditions.

Je n'ai pas utilisé cette nouvelle fonctionnalité, mais cela semble assez simple:

# 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

Vous n'avez pas ajouté beaucoup de détails sur la façon dont vous déterminez le numéro de locataire ou sur la façon dont l'autorisation est effectuée dans votre demande. Mais je voudrais essayer de déterminer le nombre de locataires le plus tôt possible dans le application_controllerdans un around_action. Quelque chose comme ça pourrait être un point de départ:

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
la source
Est-ce que cela aurait le même sens de revenir à la connexion par défaut dans ce cas également? github.com/influitive/apartment#middleware-considerations
Ben
1
Une fois que vous quittez le ActiveRecord::Base.connected_to ... dobloc, il utilise à nouveau la connexion par défaut.
Spickermann
@spickermann je lisais ce bijou, n'est-ce pas seulement pour rails6?
7urkm3n
@ 7urkm3n Il est inclus dans la masterbranche Rails actuelle .
Spickermann
Salut merci pour la réponse. J'aurai besoin d'un peu de temps pour tester réellement la suggestion avant de pouvoir récompenser l'une des réponses si elles sont de bonnes solutions.
Niels Kristian