rails-sharding 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbec123e1537bac8bf7f3b6d2e711cfefa156f41
4
- data.tar.gz: 4fae6ab76f8b687746436585a74d618c5f4a2308
3
+ metadata.gz: 58f0c90d4f03a5f3aa3d4ff363cdac165c948f8f
4
+ data.tar.gz: d927face94cf2c001534c1353b322294ab1984b8
5
5
  SHA512:
6
- metadata.gz: f8488d56f2e5eb38df46f138d4ad7ac28bc12008ee08383f984268fd3201629bb3eebeb54c96120bddf846e2e1f39f171e38fc4ab52457bbcdbcafa805b0e72c
7
- data.tar.gz: 95a78672f724efa645c911d0d824b57be1dcf43d96257be54aab15e92ba544b81ce370e3a9131ee5abdd27f4605a8a49a5e45dd66dd00f4e4f58b3c7dfc48f28
6
+ metadata.gz: aa192b584a7ef8e58be570798c8d35ae04f17134791ed325a272f32dc5c32d3d847e60d06894da35093736d7c29f52bba32414b4d27b6d5107e2390e86c43221
7
+ data.tar.gz: cd329c1e3473f0f8a11f93bfc15bdd5278ebb041b8fccf6d5e2b7b496fa229919914b4080e4d97913e61ad090c6b67dfdf2a9c22e729cace4129e4f63e92bf95
data/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
  [![Code Climate](https://codeclimate.com/github/hsgubert/rails-sharding/badges/gpa.svg)](https://codeclimate.com/github/hsgubert/rails-sharding)
5
5
  [![Test Coverage](https://codeclimate.com/github/hsgubert/rails-sharding/badges/coverage.svg)](https://codeclimate.com/github/hsgubert/rails-sharding/coverage)
6
6
  [![Gem Version](https://badge.fury.io/rb/rails-sharding.svg)](https://badge.fury.io/rb/rails-sharding)
7
+ [![Dependency Status](https://gemnasium.com/badges/github.com/hsgubert/rails-sharding.svg)](https://gemnasium.com/github.com/hsgubert/rails-sharding)
7
8
 
8
- Simple and robust sharding for Rails, including Migrations and ActiveRecord extensions
9
+ Simple and robust sharding gem for Rails, including Migrations and ActiveRecord extensions
9
10
 
10
11
  This gems allows you to easily create extra databases to your rails application, and freely allocate ActiveRecord instances to any of the databases. It also provides rake tasks and migrations to help you manage the schema by shard groups.
11
12
 
@@ -27,8 +28,10 @@ You can also use the block syntax, where all your queries inside will be directe
27
28
  You can also pick and choose which models will be shardable, so that all the models that are not shardable will still be retrieved from the master database, even if inside a using_shard block.
28
29
 
29
30
  ## Compatibility
31
+ Gem version 1.x.x -> compatible with Rails 5.0
32
+
30
33
  Gem version 0.1.1 -> compatible with Rails 4.2
31
- Gem version 1.0.0 -> compatible with Rails 5.0
34
+
32
35
 
33
36
  ## Installation
34
37
 
@@ -141,6 +144,29 @@ rake shards:migrate SHARD_GROUP=shard_group_1
141
144
  rake shards:migrate SHARD_GROUP=shard_group_1 SHARD=shard1
142
145
  ```
143
146
 
147
+ ## Gem Options
148
+ Running the `rails g rails_sharding:scaffold` will create an initializer at `config/initializers/rails-sharding.rb`. You can pass additional configurations on this initializer to control the gem behavior. You can see below all available options and their default values:
149
+ ```ruby
150
+ # config/initializers/rails-sharding.rb
151
+
152
+ Rails::Sharding.setup do |config|
153
+ # If true one connection will be established per shard (in every shard group) on startup.
154
+ # If false the user must call Shards::ConnectionHandler.establish_connection(shard_group, shard_name) manually at least once before using each shard.
155
+ config.establish_all_connections_on_setup = true
156
+
157
+ # If true the method #using_shard will be mixed in ActiveRecord scopes. Put this to false if you don't want the gem to modify ActiveRecord
158
+ config.extend_active_record_scope = true,
159
+
160
+ # Specifies where to find the definition of the shards configurations
161
+ config.shards_config_file = 'config/shards.yml',
162
+
163
+ # Specifies where to find the migrations for each shard group
164
+ config.shards_migrations_dir = 'db/shards_migrations',
165
+
166
+ # Specifies where to find the schemas for each shard group
167
+ config.shards_schemas_dir = 'db/shards_schemas'
168
+ end
169
+ ```
144
170
 
145
171
  ## Development and Contributing
146
172
 
data/Rakefile CHANGED
@@ -6,7 +6,8 @@ RSpec::Core::RakeTask.new(:spec)
6
6
  task :default => :spec
7
7
 
8
8
  # defines an environment task so we can run rake tasks from lib/tasks/rails-sharding.rake.
9
- # They require the :environment task, which is defined by rails, but we don need anything
9
+ # The tasks on rails-sharding.rake depend on the :environment task, which is usuallu defined
10
+ # by rails. In our case, we just stub it so the rake tasks run
10
11
  task :environment do
11
12
  # do nothing
12
13
  end
@@ -16,12 +17,7 @@ namespace :db do
16
17
 
17
18
  desc 'Loads gem test environment and rake tasks from gem'
18
19
  task :load_env do
19
- require 'rspec'
20
-
21
- # requires spec helper but ensures no test coverage is reported to codeclimate
22
- ENV['CODECLIMATE_REPO_TOKEN'] = nil
23
- require './spec/spec_helper'
24
-
20
+ require './spec/load_gem_test_env'
25
21
  load 'lib/tasks/rails-sharding.rake'
26
22
  end
27
23
 
@@ -1,7 +1,7 @@
1
1
  default: &default
2
2
  adapter: mysql2
3
3
  encoding: utf8
4
- reconnect: false
4
+ reconnect: true
5
5
  pool: 5
6
6
  username: ___
7
7
  password: ___
@@ -13,6 +13,16 @@ module Rails::Sharding
13
13
  # this to false if you don't want the gem to modify ActiveRecord
14
14
  extend_active_record_scope: true,
15
15
 
16
+ # If true the query logs of ActiveRecord will be tagged with the corresponding
17
+ # shard you're querying
18
+ add_shard_tag_to_query_logs: true,
19
+
20
+ # If true a warning will be printed everytime a using_shard block ends without
21
+ # the shard connection being retrieved at least once inside the block. This warning
22
+ # is helpful to remember the developer to include ShardableModel module on the
23
+ # sharded models, otherwise they will always connect to the master database.
24
+ no_connection_retrieved_warning: true,
25
+
16
26
  # Specifies where to find the definition of the shards configurations
17
27
  shards_config_file: 'config/shards.yml',
18
28
 
@@ -27,6 +37,5 @@ module Rails::Sharding
27
37
  self.cattr_accessor config_name
28
38
  self.send(config_name.to_s + '=', default_value)
29
39
  end
30
-
31
40
  end
32
41
  end
@@ -49,7 +49,14 @@ module Rails::Sharding
49
49
  end
50
50
 
51
51
  def self.retrieve_connection(shard_group, shard_name)
52
- connection_handler.retrieve_connection(connection_name(shard_group, shard_name))
52
+ connection_name = connection_name(shard_group, shard_name)
53
+ connection = connection_handler.retrieve_connection(connection_name)
54
+
55
+ if connection && Config.add_shard_tag_to_query_logs
56
+ add_shard_tag_to_connection_log(connection, connection_name)
57
+ else
58
+ connection
59
+ end
53
60
  end
54
61
 
55
62
  def self.connected?(shard_group, shard_name)
@@ -57,7 +64,13 @@ module Rails::Sharding
57
64
  end
58
65
 
59
66
  def self.with_connection(shard_group, shard_name, &block)
60
- connection_pool(shard_group, shard_name).with_connection(&block)
67
+ connection_pool(shard_group, shard_name).with_connection do |connection|
68
+ if connection && Config.add_shard_tag_to_query_logs
69
+ connection_name = connection_name(shard_group, shard_name)
70
+ add_shard_tag_to_connection_log(connection, connection_name)
71
+ end
72
+ block.call(connection)
73
+ end
61
74
  end
62
75
 
63
76
  def self.remove_connection(shard_group, shard_name)
@@ -79,5 +92,31 @@ module Rails::Sharding
79
92
  def self.connection_name(shard_group, shard_name)
80
93
  shard_group.to_s + ':' + shard_name.to_s
81
94
  end
95
+
96
+ # Adds a shard tag to the log of all queries executed through this connection
97
+ def self.add_shard_tag_to_connection_log(connection, shard_tag)
98
+ # avoids modifing connection twice
99
+ if connection.respond_to? :shard_tag
100
+ connection.shard_tag = shard_tag
101
+ return connection
102
+ end
103
+
104
+ # creates #shard_tag attribute in connection
105
+ connection.singleton_class.send(:attr_accessor, :shard_tag)
106
+ connection.shard_tag = shard_tag
107
+
108
+ # create an alias #original_execute, as a copy of the #execute for this connection
109
+ connection.singleton_class.send(:alias_method, :original_execute, :execute)
110
+
111
+ # defines a new #execute that adds a tag to the log
112
+ class << connection
113
+ def execute(sql, name=nil)
114
+ name = (name.to_s + " (#{shard_tag})").strip
115
+ self.original_execute(sql, name)
116
+ end
117
+ end
118
+
119
+ connection
120
+ end
82
121
  end
83
122
  end
@@ -10,28 +10,29 @@ module Rails::Sharding
10
10
 
11
11
  # Opens a block where all queries will be directed to the selected shard
12
12
  def self.using_shard(shard_group, shard_name)
13
- raise 'Cannot nest using_shard blocks' if ShardThreadRegistry.connecting_to_shard?
14
-
15
- ShardThreadRegistry.current_shard_group = shard_group
16
- ShardThreadRegistry.current_shard_name = shard_name
17
- ShardThreadRegistry.shard_connection_used = false
13
+ ShardThreadRegistry.push_current_shard(shard_group, shard_name)
18
14
  yield
19
15
  ensure
16
+ shard_group, shard_name, connection_used = ShardThreadRegistry.pop_current_shard
17
+
20
18
  # shows warning to user
21
- if !ShardThreadRegistry.shard_connection_used
22
- puts "Warning: no connection to shard '#{ShardThreadRegistry.current_shard_group}:#{ShardThreadRegistry.current_shard_name}' was made inside the using_shard block. Make sure you don't forget to include Rails::Sharding::ShardableModel to the models you want to be sharded"
19
+ if Config.no_connection_retrieved_warning && !connection_used
20
+ puts "Warning: no connection to shard '#{shard_group}:#{shard_name}' was retrieved inside the using_shard block. Make sure you don't forget to include Rails::Sharding::ShardableModel to the models you want to be sharded. Disable this warning with Rails::Sharding::Config.no_connection_retrieved_warning = false."
23
21
  end
24
22
 
25
23
  # Releases connections in case user left some connection in the reserved state
26
24
  # (by calling retrieve_connection instead of with_connection). Also, using
27
25
  # normal activerecord queries leaves a connection in the reserved state
28
- ConnectionHandler.connection_pool(*ShardThreadRegistry.current_shard_group_and_name).release_connection
29
- ShardThreadRegistry.connect_back_to_master!
26
+ # Obs: don't do this with a master database connection
27
+ ConnectionHandler.connection_pool(shard_group, shard_name).release_connection if shard_group && shard_name
30
28
  end
31
29
 
32
30
  def self.configurations(environment=Rails.env)
33
31
  @@db_configs ||= YAML.load_file(Config.shards_config_file)
34
- @@db_configs[environment]
32
+ environment_config = @@db_configs[environment]
33
+ return environment_config if environment_config
34
+
35
+ raise Errors::ConfigNotFoundError, 'Found no shard configurations for enviroment "' + environment + '" in ' + Config.shards_config_file.to_s + ' file was not found'
35
36
  rescue Errno::ENOENT
36
37
  raise Errors::ConfigNotFoundError, Config.shards_config_file.to_s + ' file was not found'
37
38
  end
@@ -1,13 +1,21 @@
1
1
 
2
2
  module Rails::Sharding
3
3
  class ShardThreadRegistry
4
- # creates two thread-specific variables to store the current shard of connection
5
- thread_mattr_accessor :_current_shard_group
6
- thread_mattr_accessor :_current_shard_name
4
+ # Creates two thread-specific stacks to store the shard of connection
5
+ # The top of the stack indicates the current connection
6
+ # This allows us to have nested blocks of #using_shard and keep track of the
7
+ # connections as we open/close those blocks
8
+ thread_mattr_accessor :_shard_group_stack
9
+ thread_mattr_accessor :_shard_name_stack
7
10
 
8
- # auxiliary variable used to check if shard connectio was used inside an
9
- # using_shard block (so we can print an alert if not)
10
- thread_mattr_accessor :shard_connection_used
11
+ # auxiliary stack that keeps track of wether each shard connection was used
12
+ # inside its respective using_shard block (so we can print an alert if not)
13
+ thread_mattr_accessor :_shard_connection_used_stack
14
+
15
+ # accessors that initialize stacks if necessary
16
+ def self.shard_group_stack; self._shard_group_stack ||= [] end;
17
+ def self.shard_name_stack; self._shard_name_stack ||= [] end;
18
+ def self.shard_connection_used_stack; self._shard_connection_used_stack ||= [] end;
11
19
 
12
20
  def self.connecting_to_master?
13
21
  current_shard_group.nil? || current_shard_name.nil?
@@ -17,30 +25,48 @@ module Rails::Sharding
17
25
  !connecting_to_master?
18
26
  end
19
27
 
28
+ # Clears the connection stack and goes back to connecting to master
20
29
  def self.connect_back_to_master!
21
- self.current_shard_group = nil
22
- self.current_shard_name = nil
23
- self.shard_connection_used = false
30
+ shard_group_stack.clear
31
+ shard_name_stack.clear
32
+ shard_connection_used_stack.clear
24
33
  end
25
34
 
26
35
  # Returns the current shard group (for the current Thread)
27
36
  def self.current_shard_group
28
- _current_shard_group
29
- end
30
-
31
- # Sets the current shard group (for the current Thread)
32
- def self.current_shard_group=(group)
33
- self._current_shard_group = group.blank? ? nil : group.to_sym
37
+ shard_group_stack.last
34
38
  end
35
39
 
36
40
  # Returns the current shard name (for the current Thread)
37
41
  def self.current_shard_name
38
- _current_shard_name
42
+ shard_name_stack.last
43
+ end
44
+
45
+ def self.current_connection_used?
46
+ shard_connection_used_stack.last
47
+ end
48
+
49
+ # adds shard connection to the stack
50
+ def self.push_current_shard(group, name)
51
+ # this line supresses the unused connection warning when there are nested
52
+ # using_shard blocks. We suppress the warning because we view nested using_shard
53
+ # blocks as a override
54
+ notify_connection_retrieved
55
+
56
+ shard_group_stack.push(group.blank? ? nil : group.to_sym)
57
+ shard_name_stack.push(name.blank? ? nil : name.to_sym)
58
+ shard_connection_used_stack.push(false)
59
+ end
60
+
61
+ # notifies the current connection was used (wee keep track of this to warn
62
+ # the user in case the connection is not used)
63
+ def self.notify_connection_retrieved
64
+ shard_connection_used_stack[-1] = true if shard_connection_used_stack.present?
39
65
  end
40
66
 
41
- # Sets the current shard name (for the current Thread)
42
- def self.current_shard_name=(name)
43
- self._current_shard_name = name.blank? ? nil : name.to_sym
67
+ # removes shard connection to the stack
68
+ def self.pop_current_shard
69
+ [shard_group_stack.pop, shard_name_stack.pop, shard_connection_used_stack.pop]
44
70
  end
45
71
 
46
72
  def self.current_shard_group_and_name
@@ -32,20 +32,22 @@ module Rails::Sharding
32
32
 
33
33
  # @overrides ActiveRecord::ConnectionHandling#connection_pool
34
34
  def sharded_connection_pool
35
+ ShardThreadRegistry.notify_connection_retrieved
36
+
35
37
  if ShardThreadRegistry.connecting_to_master?
36
38
  return original_connection_pool
37
39
  else
38
- ShardThreadRegistry.shard_connection_used = true # records that shard connection was used at least once
39
40
  return ConnectionHandler.connection_pool(*ShardThreadRegistry.current_shard_group_and_name)
40
41
  end
41
42
  end
42
43
 
43
44
  # @overrides ActiveRecord::ConnectionHandling#retrieve_connection
44
45
  def sharded_retrieve_connection
46
+ ShardThreadRegistry.notify_connection_retrieved
47
+
45
48
  if ShardThreadRegistry.connecting_to_master?
46
49
  return original_retrieve_connection
47
50
  else
48
- ShardThreadRegistry.shard_connection_used = true # records that shard connection was used at least once
49
51
  return ConnectionHandler.retrieve_connection(*ShardThreadRegistry.current_shard_group_and_name)
50
52
  end
51
53
  end
@@ -1,5 +1,5 @@
1
1
  module Rails
2
2
  module Sharding
3
- VERSION = "1.0.0"
3
+ VERSION = "1.0.1"
4
4
  end
5
5
  end
@@ -26,9 +26,9 @@ Gem::Specification.new do |spec|
26
26
  spec.add_runtime_dependency 'rails', '~> 5.0'
27
27
 
28
28
  spec.add_development_dependency "bundler", "~> 1.12"
29
- spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rake", "~> 11.0"
30
30
  spec.add_development_dependency "rspec", "~> 3.0"
31
- spec.add_development_dependency "byebug", '~> 0'
31
+ spec.add_development_dependency "byebug", '~> 9'
32
32
  spec.add_development_dependency "mysql2", '~> 0'
33
33
  spec.add_development_dependency "codeclimate-test-reporter", '~> 0'
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-sharding
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henrique Gubert
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-10 00:00:00.000000000 Z
11
+ date: 2016-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '11.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '11.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '9'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: mysql2
85
85
  requirement: !ruby/object:Gem::Requirement