active_record_slave 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|