sql >> データベース >  >> RDS >> Mysql

トランザクションを中断せずにRailsの複数のデータベースを切り替える

    ActiveRecord内の緊密な結合のため、これはトリッキーな問題です。 、しかし、私はうまくいく概念実証を作成することができました。または、少なくとも機能しているように見えます。

    いくつかの背景

    ActiveRecord ActiveRecord::ConnectionAdapters::ConnectionHandlerを使用します モデルごとの接続プールの保存を担当するクラス。通常のRailsアプリは1つのデータベースに接続されているため、デフォルトでは、すべてのモデルに対して1つの接続プールしかありません。

    establish_connectionを実行した後 特定のモデルの異なるデータベースに対して、そのモデルに対して新しい接続プールが作成されます。また、それを継承する可能性のあるすべてのモデルについても。

    クエリを実行する前に、ActiveRecord 最初に関連するモデルの接続プールを取得し、次にプールから接続を取得します。

    上記の説明は100%正確ではないかもしれませんが、近いはずです。

    解決策

    したがって、デフォルトの接続ハンドラーを、提供されたシャードの説明に基づいて接続プールを返すカスタムハンドラーに置き換えるというアイデアがあります。

    これは、さまざまな方法で実装できます。これは、シャード名を偽装したActiveRecordとして渡すプロキシオブジェクトを作成することで実現しました。 クラス。接続ハンドラーはARモデルを取得することを期待しており、nameを調べます プロパティおよびsuperclass モデルの階層チェーンを歩きます。 DatabaseModelを実装しました 基本的にシャード名ですが、ARモデルのように動作するクラスです。

    実装

    これが実装例です。簡単にするためにsqliteデータベースを使用しましたが、セットアップなしでこのファイルを実行できます。 この要点 もご覧ください。

    # 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
    

    これにより、本番環境に対応したソリューションを実装する方法がわかるはずです。ここで明らかなことを見逃していないことを願っています。いくつかの異なるアプローチを提案できます:

    1. サブクラスActiveRecord::ConnectionAdapters::ConnectionHandler 接続プールの取得を担当するメソッドを上書きします
    2. ConnectionHandlerと同じAPIを実装する完全に新しいクラスを作成します
    3. retrieve_connectionを上書きすることも可能だと思います 方法。どこで定義されているかは覚えていませんが、ActiveRecord::Coreにあると思います 。

    アプローチ1と2が進むべき道であり、データベースを操作する際のすべてのケースをカバーする必要があると思います。




    1. SQL Server(T-SQL)でドイツ語形式で日付を表示する方法

    2. Microsoft Accessとは何ですか?新規ユーザー向けの簡単な紹介

    3. SQLServerで削除されたレコードを復元するノウハウ

    4. django2での移行エラー。 AttributeError:'str'オブジェクトには属性'decode'がありません