SwitchConnection

Gem Version Build Status Coverage Status Code Climate

Switching database connection between slave one and writable one. Fork from switch_point gem. Original Version: https://github.com/eagletmt/switch_point.

Installation

Add this line to your application's Gemfile:

gem 'switch_connection'

And then execute:

$ bundle

Or install it yourself as:

$ gem install switch_connection

Usage

Suppose you have 4 databases: db-blog-master, db-blog-slave, db-comment-master and db-comment-slave. Article model and Category model are stored in db-blog-master,slave and Comment model is stored in db-comment-master,slave.

Configuration

In database.yml:

production_blog_master:
  adapter: mysql2
  username: blog_writable
  host: db-blog-master
production_blog_slave:
  adapter: mysql2
  username: blog_slave
  host: db-blog-slave
production_comment_master:
    ...

In initializer:

SwitchConnection.configure do |config|
  config.define_switch_point :blog,
    slaves: [:"#{Rails.env}_blog_slave1",:"#{Rails.env}_blog_slave2"]
    master: :"#{Rails.env}_blog_master"
  config.define_switch_point :comment,
    slaves: [:"#{Rails.env}_comment_slave"]
    master: :"#{Rails.env}_comment_master"
end

In models:

class Article < ActiveRecord::Base
  use_switch_point :blog
end

class Category < ActiveRecord::Base
  use_switch_point :blog
end

class Comment < ActiveRecord::Base
  use_switch_point :comment
end

Switching connections

Article.with_slave { Article.first } # Read from db-blog-slave
Category.with_slave { Category.first } # Also read from db-blog-slave
Comment.with_slave { Comment.first } # Read from db-comment-slave

Article.with_slave do
  article = Article.first  # Read from db-blog-slave
  article.title = 'new title'
  Article.with_master do
    article.save!  # Write to db-blog-master
    article.reload  # Read from db-blog-master
    Category.first  # Read from db-blog-master
  end
end
  • with_switch_point ruby Book.with_switch_point(:main) { Book.count }

Note that Article and Category shares their connections.

Notes

auto_master

auto_master by default.

When auto_master is enabled, destructive queries is sent to writable connection even in slave mode. But it does NOT work well on transactions.

Suppose after_save callback is set to User model. When User.create is called, it proceeds as follows.

  1. BEGIN TRANSACTION is sent to READONLY connection.
  2. switch_point switches the connection to WRITABLE.
  3. INSERT statement is sent to WRITABLE connection.
  4. switch_point reset the connection to READONLY.
  5. after_save callback is called.
    • At this point, the connection is READONLY and in a transaction.
  6. COMMIT TRANSACTION is sent to READONLY connection.

Model has several connection-related methods: connection_handler, connection_pool, connected? and so on. Since only connection method is monkey-patched, other connection-related methods doesn't work properly. If you'd like to use those methods, send it to Model.switch_point_proxy.model_for_connection.

Internals

There's a proxy which holds two connections: slave one and writable one. A proxy has a thread-local state indicating the current mode: slave or writable.

Each ActiveRecord model refers to a proxy. ActiveRecord::Base.connection is hooked and delegated to the referred proxy.

When the writable connection is requested to execute destructive query, the slave connection clears its query cache.

switch_point

Special case: ActiveRecord::Base.connection

Basically, each connection managed by a proxy isn't shared between proxies. But there's one exception: ActiveRecord::Base.

Contributing

  1. Fork it ( https://github.com/phamvanmhung2e123/switch_point/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request