Giter Club home page Giter Club logo

active_record_replica's Introduction

Active Record Replica

Gem Version Build Status License

Redirect ActiveRecord (Rails) reads to replica databases while ensuring all writes go to the primary database.

Status

This is a slight modification of Rocket Job's original library, simply renaming it from active_record_slave to active_record_replica.

In order to more clearly distinguish the library from active_record_slave, we also incremented the major version – it is, however, functionally equivalent.

Introduction

active_record_replica redirects all database reads to replica instances while ensuring that all writes go to the primary database. active_record_replica ensures that any reads that are performed within a database transaction are by default directed to the primary database to ensure data consistency.

Status

Production Ready. Actively used in large production environments.

Features

  • Redirecting reads to a single replica database.
  • Works with any database driver that works with ActiveRecord.
  • Supports all Rails 3, 4, or 5 read apis.
    • Including dynamic finders, AREL, and ActiveRecord::Base.select.
    • NOTE: In Rails 3 and 4, QueryCache is only enabled for BaseConnection by default. In Rails 5, it's enabled for all connections. (PR)
  • Transaction aware
    • Detects when a query is inside of a transaction and sends those reads to the primary by default.
    • Can be configured to send reads in a transaction to replica databases.
  • Lightweight footprint.
  • No overhead whatsoever when a replica is not configured.
  • Negligible overhead when redirecting reads to the replica.
  • Connection Pools to both databases are retained and maintained independently by ActiveRecord.
  • The primary and replica databases do not have to be of the same type.
    • For example Oracle could be the primary with MySQL as the replica database.
  • Debug logs include a prefix of Replica: to indicate which SQL statements are going to the replica database.

Example showing Replica redirected read

# Read from the replica database
r = Role.where(name: 'manager').first
r.description = 'Manager'

# Save changes back to the primary database
r.save!

Log file output:

03-13-12 05:56:05 pm,[2608],b[0],[0],  Replica: Role Load (3.0ms)  SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1
03-13-12 05:56:22 pm,[2608],b[0],[0],  AREL (12.0ms)  UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 5

Example showing how reads within a transaction go to the primary

Role.transaction do
  r = Role.where(name: 'manager').first
  r.description = 'Manager'
  r.save!
end

Log file output:

03-13-12 06:02:09 pm,[2608],b[0],[0],  Role Load (2.0ms)  SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'manager' LIMIT 1
03-13-12 06:02:09 pm,[2608],b[0],[0],  AREL (2.0ms)  UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 4

Forcing a read against the primary

Sometimes it is necessary to read from the primary:

ActiveRecordReplica.read_from_primary do
  r = Role.where(name: 'manager').first
end

Usage Notes

delete_all

Delete all executes against the primary database since it is only a delete:

D, [2012-11-06T19:47:29.125932 #89772] DEBUG -- :   SQL (1.0ms)  DELETE FROM "users"

destroy_all

First performs a read against the replica database and then deletes the corresponding data from the primary

D, [2012-11-06T19:43:26.890674 #89002] DEBUG -- :   Replica: User Load (0.1ms)  SELECT "users".* FROM "users"
D, [2012-11-06T19:43:26.890972 #89002] DEBUG -- :    (0.0ms)  begin transaction
D, [2012-11-06T19:43:26.891667 #89002] DEBUG -- :   SQL (0.4ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 3]]
D, [2012-11-06T19:43:26.892697 #89002] DEBUG -- :    (0.9ms)  commit transaction

Transactions

By default ActiveRecordReplica detects when a call is inside a transaction and will send all reads to the primary when a transaction is active.

It is now possible to send reads to database replicas and ignore whether currently inside a transaction:

In file config/application.rb:

# Read from replica even when in an active transaction
config.active_record_replica.ignore_transactions = true

It is important to identify any code in the application that depends on being able to read any changes already part of the transaction, but not yet committed and wrap those reads with ActiveRecordReplica.read_from_primary

Inquiry.transaction do
  # Create a new inquiry
  Inquiry.create

  # The above inquiry is not visible yet if already in a Rails transaction.
  # Use `read_from_primary` to ensure it is included in the count below:
  ActiveRecordReplica.read_from_primary do
    count = Inquiry.count
  end

end

Note

active_record_replica is a very simple layer that inserts itself into the call chain whenever a replica is configured. By observation we noticed that all reads are made to a select set of methods and all writes are made directly to one method: execute.

Using this observation active_record_replica only needs to intercept calls to the known select apis:

  • select_all
  • select_one
  • select_rows
  • select_value
  • select_values

Calls to the above methods are redirected to the replica active record model ActiveRecordReplica::Replica. This model is 100% managed by the regular Active Record mechanisms such as connection pools etc.

This lightweight approach ensures that all calls to the above API's are redirected to the replica without impacting:

  • Transactions
  • Writes
  • Any SQL calls directly to execute

One of the limitations with this approach is that any code that performs a query by calling execute direct will not be redirected to the replica instance. In this case replace the use of execute with one of the the above select methods.

Note when using dependent: destroy

When performing in-memory only model assignments Active Record will create a transaction against the primary even though the transaction may never be used.

Even though the transaction is unused it sends the following messages to the primary database: ~~ SET autocommit=0 commit SET autocommit=1 ~~

This will impact the primary database if sufficient calls are made, such as in batch workers.

For Example:

~~ruby class Parent < ActiveRecord::Base has_one :child, dependent: :destroy end

class Child < ActiveRecord::Base belongs_to :parent end

The following code will create an unused transaction against the primary, even when reads are going to replicas:

parent = Parent.new parent.child = Child.new ~~

If the dependent: :destroy is removed it no longer creates a transaction, but it also means dependents are not destroyed when a parent is destroyed.

For this scenario when we are 100% confident no writes are being performed the following can be performed to ignore any attempt Active Record makes at creating the transaction:

~~ruby ActiveRecordReplica.skip_transactions do parent = Parent.new parent.child = Child.new end ~~

To help identify any code within a block that is creating transactions, wrap the code with ActiveRecordReplica.block_transactions to make it raise an exception anytime a transaction is attempted:

~~ruby ActiveRecordReplica.block_transactions do parent = Parent.new parent.child = Child.new end ~~

Install

Add to Gemfile

gem 'active_record_replica'

Run bundler to install:

bundle

Or, without Bundler:

gem install active_record_replica

Configuration

To enable replica reads for any environment just add a replica: entry to database.yml along with all the usual ActiveRecord database configuration options.

For Example:

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     primary1
  pool:     50
  replica:
    database: production
    username: username
    password: password
    encoding: utf8
    adapter:  mysql
    host:     replica1
    pool:     50

Sometimes it is useful to turn on replica reads per host, for example to activate replica reads only on the linux host 'batch':

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     primary1
  pool:     50
<% if `hostname`.strip == 'batch' %>
  replica:
    database: production
    username: username
    password: password
    encoding: utf8
    adapter:  mysql
    host:     replica1
    pool:     50
<% end %>

If there are multiple replicas, it is possible to randomly select a replica on startup to balance the load across the replicas:

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     primary1
  pool:     50
  replica:
    database: production
    username: username
    password: password
    encoding: utf8
    adapter:  mysql
    host:     <%= %w(replica1 replica2 replica3).sample %>
    pool:     50

Replicas can also be assigned to specific hosts by using the hostname:

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     primary1
  pool:     50
  replica:
    database: production
    username: username
    password: password
    encoding: utf8
    adapter:  mysql
    host:     <%= `hostname`.strip == 'app1' ? 'replica1' : 'replica2' %>
    pool:     50

Set primary as default for Read

The default behavior can also set to read/write operations against primary database.

Create an initializer file config/initializer/active_record_replica.rb to force read from primary:

    ActiveRecordReplica.read_from_primary!

Then use this method and supply block to read from the replica database:

ActiveRecordReplica.read_from_replica do
   User.count
end

Dependencies

See .travis.yml for the list of tested Ruby platforms

Versioning

This project uses Semantic Versioning.

Contributing

  1. Fork repository in Github.

  2. Checkout your forked repository:

    git clone https://github.com/your_github_username/active_record_replica.git
    cd active_record_replica
  3. Create branch for your contribution:

    git co -b your_new_branch_name
  4. Make code changes.

  5. Ensure tests pass.

  6. Push to your fork origin.

    git push origin
  7. Submit PR from the branch on your fork in Github.

Author

Reid Morrison :: @reidmorrison

active_record_replica's People

Contributors

abelorian avatar ali-ehmed avatar goodgravy avatar joshuapinter avatar mattprivman avatar reidmorrison avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

active_record_replica's Issues

Read from Master as default doesn't work in Puma

Rails: 4.2.11.1
Puma: 3.6.2
Active_record_slave: 1.5.0

I think Thread.current.thread_variable_get(:active_record_slave) fail in puma cluster.

I set ActiveRecordSlave.read_from_master! in an initializer but 'reads' go to slave instead by default

AR Slave forces a DB connection on precompile

Hi,
After I added AR Slave gem to my project and database.yml it fails due to this gem now requiring DB connection during RAILS_ENV=production bundle exec rake assets:precompile.

It wasn't required before.

Any ideas?

Is it possible to by default read/write from master db and on specific condition it reads from slave?

I have a big code base in which every query read/writes is executed against Master DB. Now I want (On some specific areas) to read only from Slave DB. This gem seems very useful for my case but by defaults it reads from slave and writes in master. I want this default behavior turn off and force a read against slave on specific active record query.

may be something like

ActiveRecordSlave.read_from_slave do
@users = User.count
end

Is this possible?

Default to master configuration unless specified in slave.

To keep things DRY, what do you think about defaulting to the master's database configuration unless specified in the slave configuration. For the example in your README:

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     master1
  pool:     50
  slave:
    database: production
    username: username
    password: password
    encoding: utf8
    adapter:  mysql
    host:     slave1
    pool:     50

Typically with slave databases, most of the settings are actually the exact same as the master, except for host (usually).

Wouldn't it be nice to write this instead:

production:
  database: production
  username: username
  password: password
  encoding: utf8
  adapter:  mysql
  host:     master1
  pool:     50
  slave:
    host: slave1

And have the slave configuration just inherit from the master configuration?

Let me know what you think and I'll look at doing a PR.

Thanks!

Environment

Provide at least:

  • Ruby Version.
$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
  • Active Record Slave Version.
    1.4.0

  • Application/framework names and versions (e.g. Rails, Sinatra, Puma, etc.).
    Rails

  • Full Stack Trace, if an exception is being raised.

Traceback (most recent call last):
  43: from bin/rails:4:in `<main>'
  42: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:274:in `require'
  41: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:240:in `load_dependency'
  40: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:274:in `block in require'
  39: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
  38: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
  37: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
  36: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
  35: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
  34: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/commands.rb:17:in `<main>'
  33: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
  32: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/commands/commands_tasks.rb:67:in `console'
  31: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/commands/commands_tasks.rb:142:in `require_application_and_environment!'
  30: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/application.rb:328:in `require_environment!'
  29: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:274:in `require'
  28: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:240:in `load_dependency'
  27: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-4.2.11/lib/active_support/dependencies.rb:274:in `block in require'
  26: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/skylight-core-3.1.2/lib/skylight/core/probes.rb:119:in `require'
  25: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
  24: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
  23: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
  22: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
  21: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
  20: from /Users/me/Development/cntral/config/environment.rb:5:in `<main>'
  19: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/railtie.rb:194:in `method_missing'
  18: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/railtie.rb:194:in `public_send'
  17: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/application.rb:352:in `initialize!'
  16: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/initializable.rb:54:in `run_initializers'
  15: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
  14: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
  13: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
  12: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:347:in `call'
  11: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:347:in `each'
  10: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
   9: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
   8: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
   7: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
   6: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/initializable.rb:55:in `block in run_initializers'
   5: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/initializable.rb:30:in `run'
   4: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-4.2.11/lib/rails/initializable.rb:30:in `instance_exec'
   3: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/active_record_slave-1.4.0/lib/active_record_slave/railtie.rb:21:in `block in <class:Railtie>'
   2: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/active_record_slave-1.4.0/lib/active_record_slave/active_record_slave.rb:24:in `install!'
   1: from /Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-4.2.11/lib/active_record/connection_handling.rb:50:in `establish_connection'
/Users/me/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-4.2.11/lib/active_record/connection_adapters/connection_specification.rb:171:in `spec': database configuration does not specify adapter (ActiveRecord::AdapterNotSpecified)

In an environment without a slave the an error is raised

on a Rails 3 app the following is raised when running an environment without a slave defined:

/root/.rbenv/versions/2.2.7/lib/ruby/gems/2.2.0/gems/active_record_slave-1.3.0/lib/active_record_slave/active_record_slave.rb:20:in `install!': undefined method `[]' for nil:NilClass (NoMethodError)
	from /root/.rbenv/versions/2.2.7/lib/ruby/gems/2.2.0/gems/active_record_slave-1.3.0/lib/active_record_slave/railtie.rb:21:in `block in <class:Railtie>'

Can we use a better metaphor?

The Master/Slave terminology used by this gem is unnecessarily harsh, as well as insensitive to the human suffering brought about by slavery throughout the world (past and present). I think we can do better. 🙂

Would you be open to renaming the gem using different, less brutal terms? Perhaps Primary/Replica, or Primary/Reader, etc.?

I'm happy to submit a PR and help in any other way.

Here's a fork exploring the idea: https://github.com/jbarrieault/active_record_reader

Not having been the one to invent the verbiage is not a good reason to perpetuate it.

db:create fails in Rails 5.2

Environment

Ruby Version:
Active Record Slave Version: 1.4.0
Rails: 5.2
Puma 3.12.0

Expected Behavior

The following command should create the database.
bundle exec rake db:create

Actual Behavior

Instead it errors out. It looks to be that the initializer is being called before the database is created.

Joshs-MacBook-Pro-3:my-proj jchappelle$ RAILS_ENV=test bundle exec rake db:create --trace > slave_error.txt
** Invoke db:create (first_time)
** Invoke db:load_config (first_time)
** Invoke environment (first_time)
** Execute environment
rake aborted!
ActiveRecord::NoDatabaseError: FATAL:  database "my-proj_test" does not exist
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:688:in `rescue in connect'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:684:in `connect'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:215:in `initialize'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `new'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `postgresql_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:809:in `new_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `checkout_new_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:832:in `try_to_checkout_new_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:793:in `acquire_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:521:in `checkout'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:380:in `connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:1008:in `retrieve_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activerecord-5.2.0/lib/active_record/connection_handling.rb:90:in `connection'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/active_record_slave-1.4.0/lib/active_record_slave/active_record_slave.rb:17:in `install!'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/active_record_slave-1.4.0/lib/active_record_slave/railtie.rb:21:in `block in <class:Railtie>'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:in `instance_exec'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:in `run'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/initializable.rb:61:in `block in run_initializers'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:228:in `block in tsort_each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:431:in `each_strongly_connected_component_from'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:349:in `block in each_strongly_connected_component'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:347:in `each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:347:in `call'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:347:in `each_strongly_connected_component'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:226:in `tsort_each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/tsort.rb:205:in `tsort_each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/initializable.rb:60:in `run_initializers'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/application.rb:361:in `initialize!'
/Users/jchappelle/Code/my-proj/config/environment.rb:5:in `<main>'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bootsnap-1.3.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bootsnap-1.3.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bootsnap-1.3.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bootsnap-1.3.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bootsnap-1.3.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/application.rb:337:in `require_environment!'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/railties-5.2.0/lib/rails/application.rb:520:in `block in run_tasks_blocks'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:271:in `block in execute'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:271:in `each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:271:in `execute'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:213:in `block in invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:193:in `invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:237:in `block in invoke_prerequisites'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:235:in `each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:235:in `invoke_prerequisites'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:212:in `block in invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:193:in `invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:237:in `block in invoke_prerequisites'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:235:in `each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:235:in `invoke_prerequisites'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:212:in `block in invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:193:in `invoke_with_call_chain'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task.rb:182:in `invoke'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:160:in `invoke_task'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `block (2 levels) in top_level'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `each'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `block in top_level'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:125:in `run_with_threads'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:110:in `top_level'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:83:in `block in run'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:80:in `run'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.1/exe/rake:27:in `<top (required)>'
/Users/jchappelle/.rbenv/versions/2.4.2/bin/rake:23:in `load'
/Users/jchappelle/.rbenv/versions/2.4.2/bin/rake:23:in `<top (required)>'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:75:in `load'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:75:in `kernel_load'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:28:in `run'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli.rb:424:in `exec'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli.rb:27:in `dispatch'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/cli.rb:18:in `start'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/exe/bundle:30:in `block in <top (required)>'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/lib/bundler/friendly_errors.rb:122:in `with_friendly_errors'
/Users/jchappelle/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/bundler-1.16.1/exe/bundle:22:in `<top (required)>'
/Users/jchappelle/.rbenv/versions/2.4.2/bin/bundle:23:in `load'
/Users/jchappelle/.rbenv/versions/2.4.2/bin/bundle:23:in `<main>'

possible to allow salves via a url?

Hi,
Is it possible to connect to slaves via

  slave:
    adapter:   mysql2
    url: <%= ENV['DATABASE_SLAVE_URL'] %>

Im seeing this error NoMethodError: undefined method []' for nil:NilClass active_record_slave-1.3.0/lib/active_record_slave/active_record_slave.rb:20:in install!'

Sometimes sending request to salve during update_attributes

Hello,

I'm seeing errors like the following occasionally:

ActiveRecord::StatementInvalid: Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement: INSERT INTO ...

When calling update_attributes

Support for multiple slaves?

The readme refers to the slave database in plural form in many places, but looking at the code and example it seems it only supports one slave.

Does this currently support multiple slaves?

Multiple databases

Consider there is a model like this:

class DifferentStorage::Base < ActiveRecord::Base
  establish_connection(YAML.load(File.read('config/database.yml')))["different_storage_#{Rails.env}"])

  self.abstract_class = true
end

Expected Behavior

I expect that read queries for that model will be executed against different database (not master or slave). As well as for all child models.

Actual Behavior

Read queries for that model are executed on slave database, which does not contain these models.
Update/delete queries are executed against right database, which constains these models.


Do you have a plan to support models which are stored in different database?

Does this support failover?

Hello, everyone,

Sorry for not following the format but this is a simple question that doesn't really fit into it.

Basically, what happens if one of the slave databases is unreachable for any reason? It will surely not fallback to another slave since you can only have one slave per instance, but would it fallback to master?

I think this information should be added to README.md.

LMK, thank you

Sorry if I created an issue for something that might not be one.

Reads can go to wrong place in multi-threaded environment

Environment

  • Ruby 2.4.5p335
  • Active Record Slave 1.6.0

Expected Behavior

The current state of Active Record Slave (e.g. whether we should be reading from master or the slave) is transferred from one thread to another when a new thread is created. For example, in a multi-threaded Rails server where only one thread runs the initializer code, all the child threads should behave as the initializer specifies.

Actual Behaviour

I found this limitation in Sidekiq, and I suspect #19 is related, however the behaviour can be seen in a plain irb session. Specifically, the Active Record Slave state is thread local:

[1] pry(main)> ActiveRecordSlave.read_from_master!
=> :master
[2] pry(main)> puts "In main thread: #{Thread.current.thread_variable_get(:active_record_slave).inspect}"
In main thread: :master
=> nil
[3] pry(main)> Thread.new { puts "In child thread: #{Thread.current.thread_variable_get(:active_record_slave).inspect}" }.join
In child thread: nil

As this is a fairly fundamental design decision, I don't know if there's going to be a solution here… Do you have any suggestions for how we can use Active Record Slave with e.g. Puma and Sidekiq?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.