Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Beralih di antara beberapa database di Rails tanpa memutus transaksi

Ini adalah masalah yang rumit, karena kopling ketat di dalam ActiveRecord , tetapi saya telah berhasil membuat beberapa bukti konsep yang berfungsi. Atau setidaknya sepertinya berhasil.

Beberapa latar belakang

ActiveRecord menggunakan ActiveRecord::ConnectionAdapters::ConnectionHandler kelas yang bertanggung jawab untuk menyimpan kumpulan koneksi per model. Secara default hanya ada satu kumpulan koneksi untuk semua model, karena aplikasi Rails biasa terhubung ke satu database.

Setelah menjalankan establish_connection untuk database yang berbeda dalam model tertentu, kumpulan koneksi baru dibuat untuk model itu. Dan juga untuk semua model yang mungkin mewarisinya.

Sebelum menjalankan kueri apa pun, ActiveRecord pertama-tama mengambil kumpulan koneksi untuk model yang relevan dan kemudian mengambil koneksi dari kumpulan.

Perhatikan bahwa penjelasan di atas mungkin tidak 100% akurat, tetapi harus mendekati.

Solusi

Jadi idenya adalah mengganti pengendali koneksi default dengan yang khusus yang akan mengembalikan kumpulan koneksi berdasarkan deskripsi pecahan yang disediakan.

Ini dapat diimplementasikan dengan berbagai cara. Saya melakukannya dengan membuat objek proxy yang meneruskan nama pecahan sebagai ActiveRecord yang disamarkan kelas. Pengendali koneksi mengharapkan untuk mendapatkan model AR dan melihat name properti dan juga di superclass untuk berjalan dalam rantai hierarki model. Saya telah menerapkan DatabaseModel class yang pada dasarnya adalah nama shard, tetapi berperilaku seperti model AR.

Implementasi

Berikut adalah contoh implementasi. Saya telah menggunakan database sqlite untuk kesederhanaan, Anda bisa menjalankan file ini tanpa pengaturan apa pun. Anda juga dapat melihat inti ini

# 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

Saya pikir ini harus memberikan ide bagaimana menerapkan solusi siap produksi. Saya harap saya tidak melewatkan sesuatu yang jelas di sini. Saya dapat menyarankan beberapa pendekatan berbeda:

  1. Subkelas ActiveRecord::ConnectionAdapters::ConnectionHandler dan timpa metode yang bertanggung jawab untuk mengambil kumpulan koneksi
  2. Buat kelas yang benar-benar baru dengan mengimplementasikan api yang sama dengan ConnectionHandler
  3. Saya rasa mungkin juga untuk menimpa retrieve_connection metode. Saya tidak ingat di mana itu didefinisikan, tapi saya pikir itu di ActiveRecord::Core .

Saya pikir pendekatan 1 dan 2 adalah cara yang harus dilakukan dan harus mencakup semua kasus saat bekerja dengan database.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Contoh Koneksi MySQL R ODBC

  2. Tidak dapat memasukkan nilai kunci asing ke dalam tabel penautan

  3. GALAT 1067 (42000):Nilai default tidak valid untuk 'created_at'

  4. Akses ke tabel tertentu dalam tag html

  5. Zend_Db:Bagaimana cara menghubungkan ke database MySQL melalui terowongan SSH?