Zwischen mehreren Datenbanken in Rails wechseln, ohne Transaktionen zu unterbrechen

8

Ich richte eine Rails-App mit mehreren Datenbanken ein. Es verwendet ActiveRecord::Base.establish_connection db_config , um zwischen den Datenbanken zu wechseln (die alle in database.yml konfiguriert sind).

establish_connection unterbricht anscheinend ausstehende Transaktionen bei jedem Aufruf. Eine negative Konsequenz ist das Testen, wobei use_transactional_tests deaktiviert werden muss (was zu unerwünscht langsamen Tests führt).

Also ... wie kann eine Rails-App mehrere Transaktionen in verschiedenen Datenbanken gleichzeitig verwalten? (Um das klarzustellen, suche ich keine schicke datenbankübergreifende Transaktion. Nur eine Möglichkeit für den Datenbank-Client, dh die Rails-App, mehrere Transaktionen gleichzeitig zu verwalten, einen pro Datenbank.)

Die einzige Lösung, die ich gesehen habe, ist % code_% direkt in die Klassendefinition einfügen , aber das setzt voraus, dass Sie eine Datenbank haben, die bestimmten Klassen gewidmet ist. Ich verwende eine benutzerbasierte Sharding-Strategie, bei der ein einzelner Datensatztyp auf mehrere Datenbanken verteilt ist, sodass die Datenbank dynamisch im Code geschaltet werden muss.

    
mahemoff 29.04.2017, 04:39
quelle

1 Antwort

4

Dies ist ein kniffliges Problem, wegen der engen Kopplung in ActiveRecord , aber ich habe es geschafft, einen funktionierenden Proof of Concept zu erstellen. Oder zumindest sieht es so aus, als ob es funktioniert.

Ein Hintergrund

ActiveRecord verwendet eine ActiveRecord::ConnectionAdapters::ConnectionHandler -Klasse, die für das Speichern von Verbindungspools pro Modell verantwortlich ist. Standardmäßig gibt es nur einen Verbindungspool für alle Modelle, da die normale Rails-App mit einer Datenbank verbunden ist.

Nach der Ausführung von establish_connection für eine andere Datenbank in einem bestimmten Modell wird ein neuer Verbindungspool für dieses Modell erstellt. Und auch für alle Modelle, die davon erben können.

Bevor eine Abfrage ausgeführt wird, ruft ActiveRecord zuerst den Verbindungspool für das relevante Modell ab und ruft dann die Verbindung aus dem Pool ab.

Beachten Sie, dass die obige Erklärung möglicherweise nicht 100% genau ist, aber es sollte nahe sein.

Lösung

Die Idee ist also, den Standard-Verbindungs-Handler durch einen benutzerdefinierten zu ersetzen, der den Verbindungspool basierend auf der bereitgestellten Shard-Beschreibung zurückgibt.

Dies kann auf viele verschiedene Arten implementiert werden. Ich habe es geschafft, indem ich das Proxy-Objekt erstellt habe, das Shard-Namen als getarnte ActiveRecord -Klassen übergibt. Der Connection-Handler erwartet ein AR-Modell und betrachtet die name -Eigenschaft und auch superclass , um die Hierarchiekette des Modells zu durchlaufen. Ich habe DatabaseModel -Klasse implementiert, die im Grunde Shard-Name ist, aber es verhält sich wie AR-Modell.

Implementierung

Hier ist eine Beispielimplementierung. Ich habe die SQLite-Datenbank der Einfachheit halber verwendet, Sie können diese Datei einfach ohne Setup ausführen. Sie können auch einen Blick auf diese Liste

werfen %Vor%

Ich denke, das sollte eine Idee geben, wie man eine produktionsfertige Lösung implementiert. Ich hoffe, ich habe hier nichts übersehen. Ich kann einige verschiedene Ansätze vorschlagen:

  1. Unterklasse ActiveRecord::ConnectionAdapters::ConnectionHandler und überschreibt diejenigen Methoden, die für das Abrufen von Verbindungspools
  2. verantwortlich sind
  3. Erstellen Sie eine komplett neue Klasse, die dieselbe API wie ConnectionHandler implementiert.
  4. Ich denke, es ist auch möglich, retrieve_connection method einfach zu überschreiben. Ich erinnere mich nicht, wo es definiert ist, aber ich denke, es ist in ActiveRecord::Core .

Ich denke, die Ansätze 1 und 2 sind der richtige Weg und sollten alle Fälle abdecken, in denen mit Datenbanken gearbeitet wird.

    
Michał Młoźniak 06.05.2017, 11:03
quelle