sql >> Databáze >  >> RDS >> Mysql

Přepínání mezi více databázemi v Rails bez přerušení transakcí

Toto je ošemetný problém kvůli těsnému propojení uvnitř ActiveRecord , ale podařilo se mi vytvořit nějaký důkaz konceptu, který funguje. Nebo to alespoň vypadá, že to funguje.

Nějaké pozadí

ActiveRecord používá ActiveRecord::ConnectionAdapters::ConnectionHandler třída, která je zodpovědná za ukládání fondů připojení na model. Ve výchozím nastavení je pro všechny modely pouze jeden fond připojení, protože obvyklá aplikace Rails je připojena k jedné databázi.

Po provedení establish_connection pro jinou databázi v konkrétním modelu se pro tento model vytvoří nový fond připojení. A také pro všechny modely, které od něj mohou dědit.

Před provedením jakéhokoli dotazu ActiveRecord nejprve načte fond připojení pro příslušný model a poté načte připojení z fondu.

Upozorňujeme, že výše uvedené vysvětlení nemusí být 100% přesné, ale mělo by být blízko.

Řešení

Cílem je tedy nahradit výchozí obslužnou rutinu připojení vlastním obslužným nástrojem, který bude vracet fond připojení na základě poskytnutého popisu fragmentu.

To lze implementovat mnoha různými způsoby. Udělal jsem to vytvořením objektu proxy, který předává názvy fragmentů jako maskované ActiveRecord třídy. Obslužný program připojení očekává, že získá model AR a podívá se na name vlastnost a také v superclass procházet hierarchickým řetězcem modelu. Implementoval jsem DatabaseModel třída, což je v podstatě název fragmentu, ale chová se jako model AR.

Implementace

Zde je příklad implementace. Pro jednoduchost jsem použil databázi sqlite, tento soubor můžete spustit bez jakéhokoli nastavení. Můžete se také podívat na tuto podstatu

# Define some required dependencies
require "bundler/inline"
gemfile(false) do
  source "https://rubygems.org"
  gem "activerecord", "~> 4.2.8"
  gem "sqlite3"
end

require "active_record"

class User < ActiveRecord::Base
end

DatabaseModel = Struct.new(:name) do
  def superclass
    ActiveRecord::Base
  end
end

# Setup database connections and create databases if not present
connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({
  "users_shard_1" => { adapter: "sqlite3", database: "users_shard_1.sqlite3" },
  "users_shard_2" => { adapter: "sqlite3", database: "users_shard_2.sqlite3" }
})

databases = %w{users_shard_1 users_shard_2}
databases.each do |database|
  filename = "#{database}.sqlite3"

  ActiveRecord::Base.establish_connection({
    adapter: "sqlite3",
    database: filename
  })

  spec = resolver.spec(database.to_sym)
  connection_handler.establish_connection(DatabaseModel.new(database), spec)

  next if File.exists?(filename)

  ActiveRecord::Schema.define(version: 1) do
    create_table :users do |t|
      t.string :name
      t.string :email
    end
  end
end

# Create custom connection handler
class ShardHandler
  def initialize(original_handler)
    @original_handler = original_handler
  end

  def use_database(name)
    @model= DatabaseModel.new(name)
  end

  def retrieve_connection_pool(klass)
    @original_handler.retrieve_connection_pool(@model)
  end

  def retrieve_connection(klass)
    pool = retrieve_connection_pool(klass)
    raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
    conn = pool.connection
    raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
    puts "Using database \"#{conn.instance_variable_get("@config")[:database]}\" (##{conn.object_id})"
    conn
  end
end

User.connection_handler = ShardHandler.new(connection_handler)

User.connection_handler.use_database("users_shard_1")
User.create(name: "John Doe", email: "[email protected]")
puts User.count

User.connection_handler.use_database("users_shard_2")
User.create(name: "Jane Doe", email: "[email protected]")
puts User.count

User.connection_handler.use_database("users_shard_1")
puts User.count

Myslím, že by to mělo poskytnout představu, jak implementovat řešení připravené na výrobu. Doufám, že jsem zde nepřehlédl nic zjevného. Mohu navrhnout několik různých přístupů:

  1. Podtřída ActiveRecord::ConnectionAdapters::ConnectionHandler a přepište metody odpovědné za načítání fondů připojení
  2. Vytvořte zcela novou třídu implementující stejné rozhraní API jako ConnectionHandler
  3. Myslím, že je také možné jednoduše přepsat retrieve_connection metoda. Nepamatuji si, kde je definován, ale myslím, že je v ActiveRecord::Core .

Myslím, že přístupy 1 a 2 jsou správnou cestou a měly by pokrývat všechny případy při práci s databázemi.




  1. Odešlete formulář CodeIgniter a AJAX

  2. Použití Javy k vytvoření zabezpečeného připojení k MySQL Amazon RDS (SSL/TLS)

  3. Zlepšení údržby oddílu pomocí přírůstkové statistiky

  4. Jaké je výchozí pořadí seznamu vráceného z volání filtru Django?