Wäre es möglich, mehrere Datenbankverbindungspools in Rails zu haben, um zwischen diesen zu wechseln?

12

Ein kleiner Hintergrund

Ich verwende das Apartment-Juwel seit Jahren, um eine Mandantenfähigkeits-App auszuführen. Vor kurzem ist die Notwendigkeit angekommen, die Datenbank auf separate Hosts zu skalieren. Der Datenbankserver kann einfach nicht mehr mithalten (sowohl Lese- als auch Schreibvorgänge werden zu viel) - und ja, ich habe die Hardware maximal skaliert (dediziert) Hardware, 64 Kerne, 12 Nvm-e-Laufwerke in RAID 10, 384 GB RAM usw.).

Ich habe überlegt, dies pro Mandant (1 Mandant = 1 Datenbankverbindungskonfiguration / number-of-tenants-pool) durchzuführen , da dies eine "einfache" und effiziente Möglichkeit wäre, bis zu- mal mehr Kapazität zu erreichen, ohne viele Änderungen am Anwendungscode vorzunehmen.

Jetzt laufe ich Schienen mit 4,2 atm und rüste bald auf 5,2 auf. Ich kann sehen, dass Rails 6 die Unterstützung für Verbindungsdefinitionen pro Modell hinzufügt, aber das ist nicht wirklich das, was ich brauche, da ich für jeden meiner 20 Mandanten ein vollständig gespiegeltes Datenbankschema habe. Normalerweise wechsle ich "Datenbank" pro Anfrage (in Middleware) oder pro Hintergrundjob (Sidekiq Middleware). Dies ist jedoch derzeit trivial und wird vom Apartment-Juwel behandelt, da es nur die search_pathin Postgresql festlegt und die tatsächliche Verbindung nicht wirklich ändert. Wenn ich zu einer Mandanten-Hosting-Strategie wechsle, muss ich die gesamte Verbindung pro Anfrage wechseln.

Fragen:

  1. Ich verstehe, dass ich einen ActiveRecord::Base.establish_connection(config)Job pro Anfrage / Hintergrund ausführen könnte - aber wie ich auch verstehe, löst dies einen völlig neuen Handshake für die Datenbankverbindung und einen neuen Datenbankpool aus, der in Rails erzeugt wird - richtig? Ich denke, das wäre ein Leistungsselbstmord, um diese Art von Overhead bei jeder einzelnen Anfrage an meine Bewerbung zu verursachen.
  2. Ich frage mich daher, ob jemand die Option mit Schienen sehen kann, z. B. mehrere (insgesamt 20) Datenbankverbindungen / -pools von Anfang an (z. B. beim Booten der Anwendung) vorab einzurichten und dann einfach pro Anforderung zwischen diesen Pools zu wechseln. Damit sind die DB-Verbindungen bereits hergestellt und einsatzbereit.
  3. Ist das alles nur eine schlechte Idee, und sollte ich stattdessen nach einem anderen Ansatz suchen? ZB 1 App-Instanz = eine bestimmte Verbindung zu einem bestimmten Mandanten. Oder etwas anderes.
Niels Kristian
quelle
2
guides.rubyonrails.org/active_record_multiple_databases.html Ich denke, es könnte Ihnen helfen
Alex Golubenko
1
Möglicherweise interessiert Sie diese PR im GitHub-Repository von Rails, das kürzlich genau die Funktion hinzugefügt hat, die Sie zum aktuellen Rails- masterZweig benötigen . Wäre es eine Option, Rails Egde auszuführen oder diese Funktion auf Ihre aktuelle Rails-Version zurückzusetzen?
Spickermann
@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endbedeutet, dass der Pool (wieder) verwendet wird, anstatt jedes Mal eine ganz neue Verbindung herzustellen?
Ben

Antworten:

4

Soweit ich weiß, gibt es 4 Muster für die Mandantenfähigkeits-App:

1. Spezielles Modell / mehrere Produktionsumgebungen

Jede Instanz oder Datenbankinstanz hostet vollständig eine andere Mandantenanwendung, und die Mandanten teilen nichts.

Dies ist 1 Instanz-App und 1 Datenbank für 1 Mandanten. Die Entwicklung wäre einfach, als würden Sie nur 1 Mieter bedienen. Aber es wird ein Albtraum für Devops, wenn Sie beispielsweise 100 Mieter haben.

2. Physische Trennung der Mieter

1 Instanz-App für alle Mandanten, aber 1 Datenbank für 1 Mandanten. Dies ist, wonach Sie suchen. Sie können ActiveRecord::Base.establish_connection(config)Edelsteine verwenden oder verwenden oder auf Rails 6 aktualisieren, wie andere vorschlagen. Siehe die Antwort für (2) unten.

3. Isoliertes Schemamodell / Logische Segregationen

In einem isolierten Schema werden die Mandantentabellen oder Datenbankkomponenten unter einem logischen Schema oder Namensraum gruppiert und von anderen Mandantenschemata getrennt. Das Schema wird jedoch in derselben Datenbankinstanz gehostet.

1 Instanz-App und 1 Datenbank für alle Mieter, wie Sie es mit Apartment Gem tun.

4. Teilweise isolierte Komponente

In diesem Modell werden Komponenten mit gemeinsamen Funktionen von Mandanten gemeinsam genutzt, während Komponenten mit eindeutigen oder nicht verwandten Funktionen isoliert werden. Auf der Datenschicht werden allgemeine Daten wie Daten, die Mandanten identifizieren, gruppiert oder in einer einzigen Tabelle gespeichert, während mandantenspezifische Daten auf Tabellen- oder Instanzschicht isoliert werden.


Was (1) ActiveRecord::Base.establish_connection(config)betrifft , kein Handshake an db pro Anfrage, wenn Sie es richtig verwenden. Sie können hier überprüfen und den gesamten Kommentar hier lesen .

Wie für (2), wenn Sie nicht verwenden möchten establish_connection, können Sie Edelstein- Multiversum (es funktioniert für Schienen 4.2) oder andere Edelsteine ​​verwenden. Wie andere vorschlagen, können Sie auch auf Rails 6 aktualisieren.

Bearbeiten: Multiverse Edelstein verwendet establish_connection. Es wird die angehängt database.ymlund eine Basisklasse erstellt, sodass jede Unterklasse dieselbe Verbindung / denselben Pool verwendet. Grundsätzlich reduziert es unseren Aufwand zu verwenden establish_connection.

Zu (3) lautet die Antwort:

Wenn Sie nicht so viele Mandanten haben und Ihre Anwendung ziemlich komplex ist, empfehlen wir Ihnen, das Muster "Dedicated Model" zu verwenden. Sie wählen also 1 App-Instanz = eine bestimmte Verbindung zu einem bestimmten Mandanten. Sie müssen Ihre Apps nicht komplexer gestalten, indem Sie mehrere Datenbankverbindungen hinzufügen.

Wenn Sie jedoch viele Mandanten haben, empfehlen wir Ihnen, die physische Trennung von Mandanten oder teilweise isolierten Komponenten abhängig von Ihrem Geschäftsprozess zu verwenden.

In beiden Fällen müssen Sie Ihre Anwendung aktualisieren / neu schreiben, um der neuen Architektur zu entsprechen.

KSD Putra
quelle
Hallo danke für die Antwort. Ich werde ein wenig Zeit brauchen, um den Vorschlag tatsächlich zu testen, bevor ich eine der Antworten belohnen kann, wenn es sich um gute Lösungen handelt.
Niels Kristian
Ich habe einige Fragen zu 1 und 2. 1: Ich bin nicht sicher, ob ich Ihre Referenzen verstehe. Was sagen Sie dazu, dass ich .establish_connection (config) aufrufen kann, ohne db handshake / db poll neu zu erstellen? In diesem Fall bin ich mir nicht sicher, wie die beiden Links das erklären? 2: Ist das für Multiverse nicht eher ein Datenbankwechsel pro Modell als ein vollständiger Datenbankwechsel für die gesamte App? Ich finde ihre Dokumentation ziemlich vage
Niels Kristian
Ich glaube ich habe Missverständnisse. Haben Sie etwas dagegen, diese Sätze auszuarbeiten? Ich verstehe, dass ich pro Anfrage / Hintergrundjob einen ActiveRecord :: Base.establish_connection (config) ausführen könnte - aber wie ich auch verstehe, löst dies einen völlig neuen Datenbankverbindungs-Handshake aus und einen neuen Datenbankpool, der in Rails It erzeugt wird Schlagen Sie vor, dass eine Anfrage einen Datenbankpool erstellt?
KSD Putra
Ich meine: (1) Ich mache mir Sorgen um die Leistung / den Netzwerk-Overhead, wenn ich bei jeder Anfrage ActiveRecord :: Base.establish_connection (config) aufrufen muss, nur um zwischen den verschiedenen Datenbanken / Ländern zu wechseln
Niels Kristian
Sie müssen sich keine Sorgen um den Overhead machen. Wenn Sie jetzt eine einzelne Datenbank verwenden, haben Sie einen Verbindungspool (Sie können den Link zur Verbindung in der Antwort von (1) oben überprüfen). Wenn Sie establish_connectionin diesem Modell class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); endFolgendes verwenden : und sagen, Sie haben 5 Modelle, erstellen Sie 5 Verbindungspools zum DB_SECOND_TENANT. Und jeder Pool wird gleich behandelt. Sie erstellen also keinen Pool pro Anforderung, sondern pro establish_connection.
KSD Putra
3

Soweit ich weiß, sollte (2) mit manueller Verbindungsumschaltung in Rails 6 möglich sein.

claasz
quelle
Vielen Dank, aber dies scheint ziemlich weit von meinem Anwendungsfall entfernt zu sein. Es würde bedeuten, die gesamte App neu zu schreiben, um dieses Verfahren überall zu verwenden.
Niels Kristian
3

Vor ein paar Tagen wurde Ruby on Rails ' Zweig auf GitHub um horizontales Sharding erweitert master. Derzeit ist diese Funktion nicht offiziell freigegeben. Abhängig von der Rails-Version Ihrer Anwendung möchten Sie möglicherweise die Verwendung von Rails in Betracht ziehen, indem Sie Folgendes masterzu Ihrem Gemfile:

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

Mit dieser neuen Funktion können Sie den Datenbankverbindungspool von Rails nutzen und die Datenbank basierend auf den Bedingungen wechseln.

Ich habe diese neue Funktion nicht verwendet, aber sie scheint ziemlich einfach zu sein:

# 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

Sie haben nicht viele Details hinzugefügt, wie Sie die Mandantennummer bestimmen oder wie die Autorisierung in Ihrer Anwendung erfolgt. Aber ich würde versuchen, die Mieternummer so schnell wie möglich in der application_controllerin einem zu ermitteln around_action. So etwas könnte ein Ausgangspunkt sein:

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
quelle
Wäre es genauso sinnvoll, auch in diesem Fall zur Standardverbindung zurückzukehren? github.com/influitive/apartment#middleware-considerations
Ben
1
Sobald Sie den ActiveRecord::Base.connected_to ... doBlock verlassen , wird die Standardverbindung wieder verwendet.
Spickermann
@spickermann ich habe ab dieses Juwel gelesen, nicht nur für Rails6?
7urkm3n
@ 7urkm3n Es ist in der aktuellen Rails- masterFiliale enthalten.
Spickermann
Hallo danke für die Antwort. Ich werde ein wenig Zeit brauchen, um den Vorschlag tatsächlich zu testen, bevor ich eine der Antworten belohnen kann, wenn es sich um gute Lösungen handelt.
Niels Kristian