active_record_slave 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +69 -65
- data/Rakefile +13 -21
- data/lib/active_record_slave/active_record_slave.rb +43 -8
- data/lib/active_record_slave/instance_methods.rb +1 -1
- data/lib/active_record_slave/version.rb +1 -1
- metadata +48 -25
- data/Gemfile +0 -15
- data/Gemfile.lock +0 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85a394f332c93d140dda4319bd811a50332bbb3a
|
4
|
+
data.tar.gz: 9eb811c94d7d43de483569245d587530a7a4725f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 00eb5c81081b4c205843f7a65aa7fef94f4e6614f35dfb662b0832bb2ec79813dd25fc50edb7aaa8cd612d545bf0eeb9053383d6653ee021d22fe702bce13402
|
7
|
+
data.tar.gz: 3e9ffeb4eb9cbe4bfea45ad2450af2c420a49b4095680a45baaa9e607d1fd49fed600d57f9b24be673da6698518eb75c36df0eed497d98251071605e3762f47f
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ active_record_slave
|
|
3
3
|
|
4
4
|
ActiveRecord drop-in solution to efficiently redirect reads to slave databases
|
5
5
|
|
6
|
-
* http://github.com/
|
6
|
+
* http://github.com/reidmorrison/active_record_slave
|
7
7
|
|
8
8
|
## Introduction
|
9
9
|
|
@@ -20,18 +20,24 @@ database to ensure data consistency.
|
|
20
20
|
* Transaction aware. Detects when a query is inside of a transaction and sends
|
21
21
|
those reads to the master
|
22
22
|
* Lightweight footprint
|
23
|
-
* No overhead when a slave is not configured
|
24
|
-
*
|
23
|
+
* No overhead whatsoever when a slave is not configured
|
24
|
+
* Negligible overhead when redirecting reads to the slave
|
25
25
|
* Connection Pools to both databases are retained and maintained independently by ActiveRecord
|
26
26
|
* The master and slave databases do not have to be of the same type.
|
27
27
|
For example one can be MySQL and the other Oracle if required.
|
28
|
-
* Debug logs include 'Slave: '
|
28
|
+
* Debug logs include a prefix of 'Slave: ' to indicate which SQL statements are going
|
29
29
|
to the slave database
|
30
30
|
|
31
31
|
### Example showing Slave redirected read
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# Read from the slave database
|
35
|
+
r = Role.where(:name => "manager").first
|
36
|
+
r.description = 'Manager'
|
37
|
+
|
38
|
+
# Save changes back to the master database
|
39
|
+
r.save!
|
40
|
+
```
|
35
41
|
|
36
42
|
Log file output:
|
37
43
|
|
@@ -39,11 +45,14 @@ Log file output:
|
|
39
45
|
03-13-12 05:56:22 pm,[2608],b[0],[0], AREL (12.0ms) UPDATE `roles` SET `description` = 'Manager' WHERE `roles`.`id` = 5
|
40
46
|
|
41
47
|
### Example showing how reads within a transaction go to the master
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
Role.transaction do
|
51
|
+
r = Role.where(:name => "manager").first
|
52
|
+
r.description = 'Manager'
|
53
|
+
r.save!
|
54
|
+
end
|
55
|
+
```
|
47
56
|
|
48
57
|
Log file output:
|
49
58
|
|
@@ -82,18 +91,16 @@ D, [2012-11-06T19:43:26.891667 #89002] DEBUG -- : SQL (0.4ms) DELETE FROM "us
|
|
82
91
|
D, [2012-11-06T19:43:26.892697 #89002] DEBUG -- : (0.9ms) commit transaction
|
83
92
|
```
|
84
93
|
|
94
|
+
## Dependencies
|
85
95
|
|
86
|
-
|
96
|
+
* Tested on Rails 3 and Rails 4
|
87
97
|
|
88
|
-
|
89
|
-
|
90
|
-
May also work with Rails 2. Anyone want to give it a try and let me know?
|
91
|
-
Happy to make it work with Rails 2 if anyone needs it
|
98
|
+
See [.travis.yml](https://github.com/reidmorrison/active_record_slave/.travis.yml) for the list of tested Ruby platforms
|
92
99
|
|
93
100
|
## Note
|
94
101
|
|
95
|
-
ActiveRecord::Base.execute is
|
96
|
-
the database
|
102
|
+
ActiveRecord::Base.execute is sometimes used to perform custom SQL calls against
|
103
|
+
the database to bypass ActiveRecord. It is necessary to replace these calls
|
97
104
|
with the standard ActiveRecord::Base.select call for them to be picked up by
|
98
105
|
active_record_slave and redirected to the slave.
|
99
106
|
|
@@ -111,62 +118,59 @@ along with all the usual ActiveRecord database configuration options.
|
|
111
118
|
|
112
119
|
For Example:
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
121
|
+
```yaml
|
122
|
+
development:
|
123
|
+
database: myapp_development
|
124
|
+
username: root
|
125
|
+
password:
|
126
|
+
encoding: utf8
|
127
|
+
adapter: mysql
|
128
|
+
host: 127.0.0.1
|
129
|
+
pool: 20
|
130
|
+
slave:
|
131
|
+
database: myapp_development_replica
|
132
|
+
username: root
|
133
|
+
password:
|
134
|
+
encoding: utf8
|
135
|
+
adapter: mysql
|
136
|
+
host: 127.0.0.1
|
137
|
+
pool: 20
|
138
|
+
```
|
130
139
|
|
131
140
|
Sometimes it is useful to turn on slave reads per host, for example to activate
|
132
141
|
slave reads only on the linux host 'batch':
|
133
142
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
* active_record_slave is running in a large production system and was tested
|
156
|
-
directly as part of that solution
|
157
|
-
* I welcome anyone that can submit tests to verify this gem in a standalone outside
|
158
|
-
environment
|
143
|
+
```yaml
|
144
|
+
development:
|
145
|
+
database: myapp_development
|
146
|
+
username: root
|
147
|
+
password:
|
148
|
+
encoding: utf8
|
149
|
+
adapter: mysql
|
150
|
+
host: 127.0.0.1
|
151
|
+
pool: 20
|
152
|
+
<% if `hostname`.strip == 'batch' %>
|
153
|
+
slave:
|
154
|
+
database: myapp_development_replica
|
155
|
+
username: root
|
156
|
+
password:
|
157
|
+
encoding: utf8
|
158
|
+
adapter: mysql
|
159
|
+
host: 127.0.0.1
|
160
|
+
pool: 20
|
161
|
+
<% end %>
|
162
|
+
```
|
159
163
|
|
160
164
|
## Possible Future Enhancements
|
161
165
|
|
162
|
-
* Support for multiple slaves (ask for it by submitting
|
166
|
+
* Support for multiple slaves (ask for it by submitting an issue)
|
163
167
|
|
164
168
|
Meta
|
165
169
|
----
|
166
170
|
|
167
|
-
* Code: `git clone git://github.com/
|
168
|
-
* Home: <https://github.com/
|
169
|
-
* Bugs: <https://github.com/
|
171
|
+
* Code: `git clone git://github.com/reidmorrison/active_record_slave.git`
|
172
|
+
* Home: <https://github.com/reidmorrison/active_record_slave>
|
173
|
+
* Bugs: <https://github.com/reidmorrison/active_record_slave/issues>
|
170
174
|
* Gems: <http://rubygems.org/gems/active_record_slave>
|
171
175
|
|
172
176
|
This project uses [Semantic Versioning](http://semver.org/).
|
@@ -179,7 +183,7 @@ Reid Morrison :: reidmo@gmail.com :: @reidmorrison
|
|
179
183
|
License
|
180
184
|
-------
|
181
185
|
|
182
|
-
Copyright 2012
|
186
|
+
Copyright 2012, 2013, 2014 Reid Morrison
|
183
187
|
|
184
188
|
Licensed under the Apache License, Version 2.0 (the "License");
|
185
189
|
you may not use this file except in compliance with the License.
|
data/Rakefile
CHANGED
@@ -1,28 +1,18 @@
|
|
1
|
-
lib = File.expand_path('../lib/', __FILE__)
|
2
|
-
$:.unshift lib unless $:.include?(lib)
|
3
|
-
|
4
1
|
require 'rake/clean'
|
5
2
|
require 'rake/testtask'
|
6
|
-
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
7
5
|
require 'active_record_slave/version'
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
s.date = Date.today.to_s
|
19
|
-
s.summary = "ActiveRecord read from slave"
|
20
|
-
s.description = "ActiveRecordSlave is a library to seamlessly enable reading from database slaves in a Rails 3 project, written in Ruby."
|
21
|
-
s.files = FileList["./**/*"].exclude(/.gem$/, /.log$/,/nbproject/,/sqlite3$/).map{|f| f.sub(/^\.\//, '')}
|
22
|
-
s.has_rdoc = true
|
23
|
-
#s.add_dependency 'activerecord', '>= 3.0.0'
|
24
|
-
end
|
25
|
-
Gem::Builder.new(gemspec).build
|
7
|
+
task :gem do
|
8
|
+
system "gem build active_record_slave.gemspec"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :publish => :gem do
|
12
|
+
system "git tag -a v#{ActiveRecordSlave::VERSION} -m 'Tagging #{ActiveRecordSlave::VERSION}'"
|
13
|
+
system "git push --tags"
|
14
|
+
system "gem push active_record_slave-#{ActiveRecordSlave::VERSION}.gem"
|
15
|
+
system "rm active_record_slave-#{ActiveRecordSlave::VERSION}.gem"
|
26
16
|
end
|
27
17
|
|
28
18
|
desc "Run Test Suite"
|
@@ -34,3 +24,5 @@ task :test do
|
|
34
24
|
|
35
25
|
Rake::Task['functional'].invoke
|
36
26
|
end
|
27
|
+
|
28
|
+
task :default => :test
|
@@ -34,14 +34,49 @@ module ActiveRecordSlave
|
|
34
34
|
# Force reads for the supplied block to read from the master database
|
35
35
|
# Only applies to calls made within the current thread
|
36
36
|
def self.read_from_master
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
return yield if read_from_master?
|
38
|
+
begin
|
39
|
+
# Set :master indicator in thread local storage so that it is visible
|
40
|
+
# during the select call
|
41
|
+
read_from_master!
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
read_from_slave!
|
45
|
+
end
|
44
46
|
end
|
45
47
|
|
46
|
-
|
48
|
+
if RUBY_VERSION.to_i >= 2
|
49
|
+
# Fibers have their own thread local variables so use thread_variable_get
|
50
|
+
|
51
|
+
# Whether this thread is currently forcing all reads to go against the master database
|
52
|
+
def self.read_from_master?
|
53
|
+
Thread.current.thread_variable_get(:active_record_slave) == :master
|
54
|
+
end
|
47
55
|
|
56
|
+
# Force all subsequent reads on this thread and any fibers called by this thread to go the master
|
57
|
+
def self.read_from_master!
|
58
|
+
Thread.current.thread_variable_set(:active_record_slave, :master)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Subsequent reads on this thread and any fibers called by this thread can go to a slave
|
62
|
+
def self.read_from_slave!
|
63
|
+
Thread.current.thread_variable_set(:active_record_slave, nil)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
# Whether this thread is currently forcing all reads to go against the master database
|
67
|
+
def self.read_from_master?
|
68
|
+
Thread.current[:active_record_slave] == :master
|
69
|
+
end
|
70
|
+
|
71
|
+
# Force all subsequent reads on this thread and any fibers called by this thread to go the master
|
72
|
+
def self.read_from_master!
|
73
|
+
Thread.current[:active_record_slave] = :master
|
74
|
+
end
|
75
|
+
|
76
|
+
# Subsequent reads on this thread and any fibers called by this thread can go to a slave
|
77
|
+
def self.read_from_slave!
|
78
|
+
Thread.current[:active_record_slave] = nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -5,7 +5,7 @@ module ActiveRecordSlave
|
|
5
5
|
# Replace #select with one that calls the slave connection instead
|
6
6
|
def select_with_slave_reader(sql, name = nil, *args)
|
7
7
|
# Only read from slave when not in a transaction and when this is not already the slave connection
|
8
|
-
if (open_transactions == 0) &&
|
8
|
+
if (open_transactions == 0) && !ActiveRecordSlave.read_from_master?
|
9
9
|
ActiveRecordSlave.read_from_master do
|
10
10
|
Slave.connection.select(sql, "Slave: #{name || 'SQL'}", *args)
|
11
11
|
end
|
metadata
CHANGED
@@ -1,62 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_slave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Reid Morrison
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
11
|
+
date: 2014-04-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sync_attr
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thread_safe
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
description:
|
16
42
|
email:
|
17
43
|
- reidmo@gmail.com
|
18
44
|
executables: []
|
19
45
|
extensions: []
|
20
46
|
extra_rdoc_files: []
|
21
47
|
files:
|
22
|
-
-
|
23
|
-
-
|
48
|
+
- LICENSE.txt
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/active_record_slave.rb
|
24
52
|
- lib/active_record_slave/active_record_slave.rb
|
25
53
|
- lib/active_record_slave/instance_methods.rb
|
26
54
|
- lib/active_record_slave/railtie.rb
|
27
55
|
- lib/active_record_slave/slave.rb
|
28
56
|
- lib/active_record_slave/version.rb
|
29
|
-
- lib/active_record_slave.rb
|
30
|
-
- LICENSE.txt
|
31
|
-
- Rakefile
|
32
|
-
- README.md
|
33
57
|
- test/active_record_slave_test.rb
|
34
58
|
- test/database.yml
|
35
|
-
homepage: https://github.com/
|
36
|
-
licenses:
|
59
|
+
homepage: https://github.com/reidmorrison/active_record_slave
|
60
|
+
licenses:
|
61
|
+
- Apache License V2.0
|
62
|
+
metadata: {}
|
37
63
|
post_install_message:
|
38
64
|
rdoc_options: []
|
39
65
|
require_paths:
|
40
66
|
- lib
|
41
67
|
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
68
|
requirements:
|
44
|
-
- -
|
69
|
+
- - ">="
|
45
70
|
- !ruby/object:Gem::Version
|
46
71
|
version: '0'
|
47
|
-
segments:
|
48
|
-
- 0
|
49
|
-
hash: -2259159571471524106
|
50
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
-
none: false
|
52
73
|
requirements:
|
53
|
-
- -
|
74
|
+
- - ">="
|
54
75
|
- !ruby/object:Gem::Version
|
55
76
|
version: '0'
|
56
77
|
requirements: []
|
57
78
|
rubyforge_project:
|
58
|
-
rubygems_version:
|
79
|
+
rubygems_version: 2.2.2
|
59
80
|
signing_key:
|
60
|
-
specification_version:
|
61
|
-
summary: ActiveRecord
|
62
|
-
test_files:
|
81
|
+
specification_version: 4
|
82
|
+
summary: ActiveRecord drop-in solution to efficiently redirect reads to slave databases
|
83
|
+
test_files:
|
84
|
+
- test/active_record_slave_test.rb
|
85
|
+
- test/database.yml
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: http://rubygems.org/
|
3
|
-
specs:
|
4
|
-
activemodel (3.2.8)
|
5
|
-
activesupport (= 3.2.8)
|
6
|
-
builder (~> 3.0.0)
|
7
|
-
activerecord (3.2.8)
|
8
|
-
activemodel (= 3.2.8)
|
9
|
-
activesupport (= 3.2.8)
|
10
|
-
arel (~> 3.0.2)
|
11
|
-
tzinfo (~> 0.3.29)
|
12
|
-
activesupport (3.2.8)
|
13
|
-
i18n (~> 0.6)
|
14
|
-
multi_json (~> 1.0)
|
15
|
-
arel (3.0.2)
|
16
|
-
builder (3.0.4)
|
17
|
-
i18n (0.6.1)
|
18
|
-
multi_json (1.3.7)
|
19
|
-
shoulda (3.3.2)
|
20
|
-
shoulda-context (~> 1.0.1)
|
21
|
-
shoulda-matchers (~> 1.4.1)
|
22
|
-
shoulda-context (1.0.1)
|
23
|
-
shoulda-matchers (1.4.1)
|
24
|
-
activesupport (>= 3.0.0)
|
25
|
-
sqlite3 (1.3.6)
|
26
|
-
tzinfo (0.3.35)
|
27
|
-
|
28
|
-
PLATFORMS
|
29
|
-
ruby
|
30
|
-
|
31
|
-
DEPENDENCIES
|
32
|
-
activerecord
|
33
|
-
shoulda
|
34
|
-
sqlite3
|