thiagopradi / octopus

Database Sharding for ActiveRecord
2.53k stars 505 forks source link

Cannot run rake task `db:create` with mysql. #426

Open abMatGit opened 7 years ago

abMatGit commented 7 years ago

Hey guys! There seems to be an interesting issue related to db:create rake tasks that were failing. This is a synopsis of our research (thanks @camertron):

Issue:

When running bundle exec rake db:create rake task on a fresh rails server, using mysql2 adapters, we get the following error:

Unknown database 'octopus_development'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/mysql2_adapter.rb:23:in `rescue in mysql2_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/mysql2_adapter.rb:10:in `mysql2_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:438:in `new_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:448:in `checkout_new_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:422:in `acquire_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:349:in `block in checkout'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/2.1.0/monitor.rb:211:in `mon_synchronize'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:348:in `checkout'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:263:in `block in connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/2.1.0/monitor.rb:211:in `mon_synchronize'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:262:in `connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/bundler/gems/octopus-6b10d719ce0f/lib/octopus/proxy.rb:72:in `safe_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/bundler/gems/octopus-6b10d719ce0f/lib/octopus/proxy.rb:79:in `select_connection'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/bundler/gems/octopus-6b10d719ce0f/lib/octopus/proxy.rb:199:in `legacy_method_missing_logic'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/bundler/gems/octopus-6b10d719ce0f/lib/octopus/proxy.rb:130:in `method_missing'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/tasks/mysql_database_tasks.rb:16:in `create'
/Users/amatuszewski/.rbenv/versions/2.1.9/lib/ruby/gems/2.1.0/gems/activerecord-4.2.7.1/lib/active_record/tasks/database_tasks.rb:93:in `create'
... <Further stack trace>

What seems to be the issue here is that we are calling the create task: here. The connection is an Octopus::Proxy class which when calling the create_database method, hits Octopus::Proxy#method_missing which eventually delegates to: safe_connection's connection

The problem here is two things: Rails db:create rake task establishes the connection with a database configuration { 'database' => nil }: here The Octopus is overriding this connection with connection_pool.connection which uses a database configuration { 'database' => value in yaml file}. This connection raises the Unknown database issue.

Proposed solution:

Alias the establish_connection method to explicitly and return an establish_connection_without_octopus connection.

This will cause establish_connection configuration_without_database to return a MySQL2::Adapter class instead of a Octopus::Proxy class, from which we can successfully call connection.create_database and bypass Octopus::Proxy#method_missing

Replication stack of issue:

Gemfile:

gem 'rails', '4.2.7.1'
...
gem 'ar-octopus', git: 'git@github.com:thiagopradi/octopus'
gem 'mysql2'

Database.yml:

development:
  adapter: mysql2
  encoding: utf8
  database: 'octopus_development'
  username: 'root'
  password: 'password'

Shards.yml:

octopus:
  environments:
    - development

  shards: &shards
    master: &master
      adapter: mysql2
      encoding: utf8
      database: 'octopus_development'
      username: 'root'
      password: 'password'

  development:
    <<: *shards
pboling commented 7 years ago

I am running into a similar issue with rake db:reset in development mode, where we are trying to use Octopus to expose write to read only errors before they hit production.

swordfish444 commented 6 years ago

@abMatGit @pboling What did you guys end up doing to address this in your apps?

Were you able to come up with a work-around? We are also experiencing this issue and it would help to know how you proceeded.

Thanks to both of you in advance!

jughead commented 6 years ago

My current workaround:

  1. Add a rake task to enhance db:create db:drop tasks

    
    namespace :octopus do
    task on: :environment do
    ActiveRecord::Base.clear_all_connections!
    Octopus.enable!
    end
    
    task off: :environment do
    ActiveRecord::Base.clear_all_connections!
    Octopus.disable!
    end
    end

Rake::Task['db:create'].enhance(['octopus:off']) do Rake::Task['octopus:on'].invoke end

Rake::Task['db:drop'].enhance(['octopus:off']) do Rake::Task['octopus:on'].invoke end


2. Patch Octopus module:

module Octopus class << self def disable! @@enabled = false end

def enable!
  @@enabled = true
end

def enabled_with_additional_check?
  enabled_without_additional_check? && @@enabled
end
alias_method :enabled_without_additional_check?, :enabled?
alias_method :enabled?, :enabled_with_additional_check?

end end Octopus.enable!

jvenezia commented 5 years ago

@jughead thanks for the workaround.

Didn't you mean to invoke octopus:off after db:drop (instead of octopus:on) ?

This works better for me, otherwise chaining rake db:drop db:create fails, because db:drop would keep Octopus enabled with no existing database.

evanboho commented 5 years ago

The problem with this approach is that passing in tasks to the arguments of the enhance method adds them as prerequisites. As such, they only get invoked once, so chaining db:create and db:drop may break.

I was able to get this to work more consistently with this:

Rake::Task['db:create'].actions.unshift Proc.new {
  Rake::Task['octopus:patch'].invoke
  Octopus.disable!
}
Rake::Task['db:create'].enhance do
  Rake::Task['octopus:patch'].invoke
  Octopus.enable!
end
Rake::Task['db:drop'].actions.unshift Proc.new {
  Rake::Task['octopus:patch'].invoke
  Octopus.disable!
}
Rake::Task['db:drop'].enhance do
  Rake::Task['octopus:patch'].invoke
  Octopus.enable!
end

Where there's a task octopus:patch that does the monkey patching. As it's a rake task, it will only ever get called once.

jvenezia commented 5 years ago

Thank you @evanboho

Can you share your octopus:patch rake task please?

rajat23 commented 4 years ago

Hello all, is there any update on this issue? thanks :)

kvokka commented 4 years ago

There is simpler solution. You can put

Octopus.environments=[]

in Rakefile and it will do the trick.