active_replicas 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Appraisals +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/LICENSE +28 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/active_replicas.gemspec +26 -0
- data/bin/console +5 -0
- data/bin/setup +8 -0
- data/example/Gemfile +6 -0
- data/example/Gemfile.lock +117 -0
- data/example/README.md +19 -0
- data/example/app.rb +55 -0
- data/example/config.ru +2 -0
- data/example/setup_database.sh +9 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +134 -0
- data/lib/active_replicas/log_subscriber.rb +56 -0
- data/lib/active_replicas/proxying_connection.rb +64 -0
- data/lib/active_replicas/proxying_connection_pool.rb +146 -0
- data/lib/active_replicas/rails4/connection_handler.rb +52 -0
- data/lib/active_replicas/railtie.rb +41 -0
- data/lib/active_replicas/version.rb +3 -0
- data/lib/active_replicas.rb +16 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a1293906120bdb5cb8986952874432459e1595f
|
4
|
+
data.tar.gz: 36bfcfcddeeb3eafb1c867780c5fec272784c608
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc09554ca185d85c1e13198594e24ea0cb5231fefa8cfd50250bbcf0b4f9d62488980be816bde797c3f3ffee1244819691226d103a8a5699a387bd8ed60fbb09
|
7
|
+
data.tar.gz: 5aeed1edb7fcef59b66da87ba322e3d54291e9b7380cdb999da7b04251ac5e3d27bf3a3fb0337b90326494d2d033a8faba87ac7e9f61db91f2c2230a87d10cf3
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Appraisals
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at dirk@esherido.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Copyright (c) 2016, Dirk Gadsden
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
15
|
+
may be used to endorse or promote products derived from this software
|
16
|
+
without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
22
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
28
|
+
THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# ActiveReplicas
|
2
|
+
|
3
|
+
Allows you to automatically send read-only queries to replica databases; writes will automatically go to the primary and "stick" the request into using the primary for any further queries.
|
4
|
+
|
5
|
+
This is heavily inspired by [Kickstarter's `replica_pools`](https://github.com/kickstarter/replica_pools) gem. It seeks to improve on that gem by better interfacing with ActiveRecord's connection pools.
|
6
|
+
|
7
|
+
## Installation & usage
|
8
|
+
|
9
|
+
ActiveReplicas injects itself into ActiveRecord. To start you'll want to add it to your application's `Gemfile`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'active_replicas'
|
13
|
+
```
|
14
|
+
|
15
|
+
You then need to instruct it as to which connection to use for the primary and which connection(s) to use for the read replicas:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# config/initializers/active_replicas.rb
|
19
|
+
ActiveReplicas::Railtie.hijack_active_record primary: { url: 'mysql2://user@primary/my_app' },
|
20
|
+
replicas: {
|
21
|
+
replica0: { url: 'mysql2://user@replica/my_app' }
|
22
|
+
}
|
23
|
+
```
|
24
|
+
|
25
|
+
**Note**: ActiveReplicas does not do anything automatically. It only injects itself into ActiveRecord when you tell it do so (see above).
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
Bug reports and pull requests are welcome on [GitHub][]. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
30
|
+
|
31
|
+
[GitHub]: https://github.com/dirk/active_replicas
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
Licensed under the 3-clause BSD license. See [LICENSE](LICENSE) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_replicas/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'active_replicas'
|
8
|
+
spec.version = ActiveReplicas::VERSION
|
9
|
+
spec.authors = [ 'Dirk Gadsden' ]
|
10
|
+
spec.email = [ 'dirk@esherido.com' ]
|
11
|
+
|
12
|
+
spec.summary = 'Smart read replicas in ActiveRecord.'
|
13
|
+
spec.description = 'Hooks into ActiveRecord to automatically sends reads to read replicas and writes to primary database.'
|
14
|
+
spec.homepage = 'https://github.com/dirk/active_replicas'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
|
18
|
+
spec.require_paths = [ 'lib' ]
|
19
|
+
|
20
|
+
spec.add_dependency 'concurrent-ruby', '~> 1.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
25
|
+
spec.add_development_dependency 'appraisal', '~> 2.1'
|
26
|
+
end
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/example/Gemfile
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionmailer (4.2.7.1)
|
5
|
+
actionpack (= 4.2.7.1)
|
6
|
+
actionview (= 4.2.7.1)
|
7
|
+
activejob (= 4.2.7.1)
|
8
|
+
mail (~> 2.5, >= 2.5.4)
|
9
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
10
|
+
actionpack (4.2.7.1)
|
11
|
+
actionview (= 4.2.7.1)
|
12
|
+
activesupport (= 4.2.7.1)
|
13
|
+
rack (~> 1.6)
|
14
|
+
rack-test (~> 0.6.2)
|
15
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
17
|
+
actionview (4.2.7.1)
|
18
|
+
activesupport (= 4.2.7.1)
|
19
|
+
builder (~> 3.1)
|
20
|
+
erubis (~> 2.7.0)
|
21
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
23
|
+
activejob (4.2.7.1)
|
24
|
+
activesupport (= 4.2.7.1)
|
25
|
+
globalid (>= 0.3.0)
|
26
|
+
activemodel (4.2.7.1)
|
27
|
+
activesupport (= 4.2.7.1)
|
28
|
+
builder (~> 3.1)
|
29
|
+
activerecord (4.2.7.1)
|
30
|
+
activemodel (= 4.2.7.1)
|
31
|
+
activesupport (= 4.2.7.1)
|
32
|
+
arel (~> 6.0)
|
33
|
+
activesupport (4.2.7.1)
|
34
|
+
i18n (~> 0.7)
|
35
|
+
json (~> 1.7, >= 1.7.7)
|
36
|
+
minitest (~> 5.1)
|
37
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
38
|
+
tzinfo (~> 1.1)
|
39
|
+
arel (6.0.3)
|
40
|
+
builder (3.2.2)
|
41
|
+
coderay (1.1.1)
|
42
|
+
concurrent-ruby (1.0.2)
|
43
|
+
erubis (2.7.0)
|
44
|
+
globalid (0.3.7)
|
45
|
+
activesupport (>= 4.1.0)
|
46
|
+
i18n (0.7.0)
|
47
|
+
json (1.8.3)
|
48
|
+
loofah (2.0.3)
|
49
|
+
nokogiri (>= 1.5.9)
|
50
|
+
mail (2.6.4)
|
51
|
+
mime-types (>= 1.16, < 4)
|
52
|
+
method_source (0.8.2)
|
53
|
+
mime-types (3.1)
|
54
|
+
mime-types-data (~> 3.2015)
|
55
|
+
mime-types-data (3.2016.0521)
|
56
|
+
mini_portile2 (2.1.0)
|
57
|
+
minitest (5.9.1)
|
58
|
+
mysql2 (0.4.5)
|
59
|
+
nokogiri (1.6.8.1)
|
60
|
+
mini_portile2 (~> 2.1.0)
|
61
|
+
pry (0.10.4)
|
62
|
+
coderay (~> 1.1.0)
|
63
|
+
method_source (~> 0.8.1)
|
64
|
+
slop (~> 3.4)
|
65
|
+
rack (1.6.5)
|
66
|
+
rack-test (0.6.3)
|
67
|
+
rack (>= 1.0)
|
68
|
+
rails (4.2.7.1)
|
69
|
+
actionmailer (= 4.2.7.1)
|
70
|
+
actionpack (= 4.2.7.1)
|
71
|
+
actionview (= 4.2.7.1)
|
72
|
+
activejob (= 4.2.7.1)
|
73
|
+
activemodel (= 4.2.7.1)
|
74
|
+
activerecord (= 4.2.7.1)
|
75
|
+
activesupport (= 4.2.7.1)
|
76
|
+
bundler (>= 1.3.0, < 2.0)
|
77
|
+
railties (= 4.2.7.1)
|
78
|
+
sprockets-rails
|
79
|
+
rails-deprecated_sanitizer (1.0.3)
|
80
|
+
activesupport (>= 4.2.0.alpha)
|
81
|
+
rails-dom-testing (1.0.7)
|
82
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
83
|
+
nokogiri (~> 1.6.0)
|
84
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
85
|
+
rails-html-sanitizer (1.0.3)
|
86
|
+
loofah (~> 2.0)
|
87
|
+
railties (4.2.7.1)
|
88
|
+
actionpack (= 4.2.7.1)
|
89
|
+
activesupport (= 4.2.7.1)
|
90
|
+
rake (>= 0.8.7)
|
91
|
+
thor (>= 0.18.1, < 2.0)
|
92
|
+
rake (11.3.0)
|
93
|
+
slop (3.6.0)
|
94
|
+
sprockets (3.7.0)
|
95
|
+
concurrent-ruby (~> 1.0)
|
96
|
+
rack (> 1, < 3)
|
97
|
+
sprockets-rails (3.2.0)
|
98
|
+
actionpack (>= 4.0)
|
99
|
+
activesupport (>= 4.0)
|
100
|
+
sprockets (>= 3.0.0)
|
101
|
+
sqlite3 (1.3.12)
|
102
|
+
thor (0.19.1)
|
103
|
+
thread_safe (0.3.5)
|
104
|
+
tzinfo (1.2.2)
|
105
|
+
thread_safe (~> 0.1)
|
106
|
+
|
107
|
+
PLATFORMS
|
108
|
+
ruby
|
109
|
+
|
110
|
+
DEPENDENCIES
|
111
|
+
mysql2
|
112
|
+
pry
|
113
|
+
rails (~> 4.2.7.1)
|
114
|
+
sqlite3
|
115
|
+
|
116
|
+
BUNDLED WITH
|
117
|
+
1.13.6
|
data/example/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Sample app
|
2
|
+
|
3
|
+
This is for development and testing purposes.
|
4
|
+
|
5
|
+
## Getting started
|
6
|
+
|
7
|
+
The sample app uses Bundler and MySQL to power a trivial single-file Rails application:
|
8
|
+
|
9
|
+
```sh
|
10
|
+
# cd example
|
11
|
+
bundle install
|
12
|
+
./setup_database.sh
|
13
|
+
|
14
|
+
# To launch a console:
|
15
|
+
bundle exec pry -r ./app.rb
|
16
|
+
|
17
|
+
# To start a server:
|
18
|
+
bundle exec rackup config.ru
|
19
|
+
```
|
data/example/app.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Based off an example single-file Rails application:
|
2
|
+
# https://gist.github.com/shawndrost/1629835
|
3
|
+
|
4
|
+
require 'rails/all'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
7
|
+
require 'active_replicas'
|
8
|
+
|
9
|
+
require 'logger'
|
10
|
+
Rails.logger = ActiveRecord::Base.logger = Logger.new STDOUT
|
11
|
+
|
12
|
+
class Tester < Rails::Application
|
13
|
+
config.secret_key_base = 'abc123'
|
14
|
+
end
|
15
|
+
|
16
|
+
ActiveReplicas::Railtie.hijack_active_record primary: { url: 'mysql2://root@localhost/active_replicas' },
|
17
|
+
replicas: {
|
18
|
+
replica0: { url: 'mysql2://root@localhost/active_replicas' }
|
19
|
+
}
|
20
|
+
|
21
|
+
# require 'sqlite3'
|
22
|
+
# database_file = File.join(File.dirname(__FILE__), 'test.sqlite3')
|
23
|
+
# SQLite3::Database.new(database_file).tap do |database|
|
24
|
+
# database.execute("drop table users;") rescue nil
|
25
|
+
# database.execute("create table users (id integer primary key autoincrement, email varchar(100), server_id integer);")
|
26
|
+
# end
|
27
|
+
# ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: database_file
|
28
|
+
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
end
|
31
|
+
|
32
|
+
class UsersController < ActionController::Base
|
33
|
+
def index
|
34
|
+
# Will go to the primary but will let subsequent queries use replicas.
|
35
|
+
count = User.connection.with_primary { User.count }
|
36
|
+
|
37
|
+
# Will go to a replica.
|
38
|
+
users = User.all.map(&:email)
|
39
|
+
|
40
|
+
User.transaction do
|
41
|
+
# This will go to the primary.
|
42
|
+
User.create! email: Time.now.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# Will also go to the primary since we didn't explicitly use the
|
46
|
+
# `with_primary` wrapper.
|
47
|
+
last_user = User.last&.email
|
48
|
+
|
49
|
+
render text: { count: count, users: users, last_user: last_user }.inspect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Tester.routes.draw do
|
54
|
+
root to: 'users#index'
|
55
|
+
end
|
data/example/config.ru
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
active_replicas (0.1.0)
|
5
|
+
concurrent-ruby (~> 1.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (4.2.7.1)
|
11
|
+
actionpack (= 4.2.7.1)
|
12
|
+
actionview (= 4.2.7.1)
|
13
|
+
activejob (= 4.2.7.1)
|
14
|
+
mail (~> 2.5, >= 2.5.4)
|
15
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
16
|
+
actionpack (4.2.7.1)
|
17
|
+
actionview (= 4.2.7.1)
|
18
|
+
activesupport (= 4.2.7.1)
|
19
|
+
rack (~> 1.6)
|
20
|
+
rack-test (~> 0.6.2)
|
21
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
23
|
+
actionview (4.2.7.1)
|
24
|
+
activesupport (= 4.2.7.1)
|
25
|
+
builder (~> 3.1)
|
26
|
+
erubis (~> 2.7.0)
|
27
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
28
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
29
|
+
activejob (4.2.7.1)
|
30
|
+
activesupport (= 4.2.7.1)
|
31
|
+
globalid (>= 0.3.0)
|
32
|
+
activemodel (4.2.7.1)
|
33
|
+
activesupport (= 4.2.7.1)
|
34
|
+
builder (~> 3.1)
|
35
|
+
activerecord (4.2.7.1)
|
36
|
+
activemodel (= 4.2.7.1)
|
37
|
+
activesupport (= 4.2.7.1)
|
38
|
+
arel (~> 6.0)
|
39
|
+
activesupport (4.2.7.1)
|
40
|
+
i18n (~> 0.7)
|
41
|
+
json (~> 1.7, >= 1.7.7)
|
42
|
+
minitest (~> 5.1)
|
43
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
44
|
+
tzinfo (~> 1.1)
|
45
|
+
appraisal (2.1.0)
|
46
|
+
bundler
|
47
|
+
rake
|
48
|
+
thor (>= 0.14.0)
|
49
|
+
arel (6.0.3)
|
50
|
+
builder (3.2.2)
|
51
|
+
concurrent-ruby (1.0.2)
|
52
|
+
diff-lcs (1.2.5)
|
53
|
+
erubis (2.7.0)
|
54
|
+
globalid (0.3.7)
|
55
|
+
activesupport (>= 4.1.0)
|
56
|
+
i18n (0.7.0)
|
57
|
+
json (1.8.3)
|
58
|
+
loofah (2.0.3)
|
59
|
+
nokogiri (>= 1.5.9)
|
60
|
+
mail (2.6.4)
|
61
|
+
mime-types (>= 1.16, < 4)
|
62
|
+
mime-types (3.1)
|
63
|
+
mime-types-data (~> 3.2015)
|
64
|
+
mime-types-data (3.2016.0521)
|
65
|
+
mini_portile2 (2.1.0)
|
66
|
+
minitest (5.9.1)
|
67
|
+
nokogiri (1.6.8.1)
|
68
|
+
mini_portile2 (~> 2.1.0)
|
69
|
+
rack (1.6.5)
|
70
|
+
rack-test (0.6.3)
|
71
|
+
rack (>= 1.0)
|
72
|
+
rails (4.2.7.1)
|
73
|
+
actionmailer (= 4.2.7.1)
|
74
|
+
actionpack (= 4.2.7.1)
|
75
|
+
actionview (= 4.2.7.1)
|
76
|
+
activejob (= 4.2.7.1)
|
77
|
+
activemodel (= 4.2.7.1)
|
78
|
+
activerecord (= 4.2.7.1)
|
79
|
+
activesupport (= 4.2.7.1)
|
80
|
+
bundler (>= 1.3.0, < 2.0)
|
81
|
+
railties (= 4.2.7.1)
|
82
|
+
sprockets-rails
|
83
|
+
rails-deprecated_sanitizer (1.0.3)
|
84
|
+
activesupport (>= 4.2.0.alpha)
|
85
|
+
rails-dom-testing (1.0.7)
|
86
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
87
|
+
nokogiri (~> 1.6.0)
|
88
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
89
|
+
rails-html-sanitizer (1.0.3)
|
90
|
+
loofah (~> 2.0)
|
91
|
+
railties (4.2.7.1)
|
92
|
+
actionpack (= 4.2.7.1)
|
93
|
+
activesupport (= 4.2.7.1)
|
94
|
+
rake (>= 0.8.7)
|
95
|
+
thor (>= 0.18.1, < 2.0)
|
96
|
+
rake (10.5.0)
|
97
|
+
rspec (3.5.0)
|
98
|
+
rspec-core (~> 3.5.0)
|
99
|
+
rspec-expectations (~> 3.5.0)
|
100
|
+
rspec-mocks (~> 3.5.0)
|
101
|
+
rspec-core (3.5.4)
|
102
|
+
rspec-support (~> 3.5.0)
|
103
|
+
rspec-expectations (3.5.0)
|
104
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
105
|
+
rspec-support (~> 3.5.0)
|
106
|
+
rspec-mocks (3.5.0)
|
107
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
108
|
+
rspec-support (~> 3.5.0)
|
109
|
+
rspec-support (3.5.0)
|
110
|
+
sprockets (3.7.0)
|
111
|
+
concurrent-ruby (~> 1.0)
|
112
|
+
rack (> 1, < 3)
|
113
|
+
sprockets-rails (3.2.0)
|
114
|
+
actionpack (>= 4.0)
|
115
|
+
activesupport (>= 4.0)
|
116
|
+
sprockets (>= 3.0.0)
|
117
|
+
thor (0.19.1)
|
118
|
+
thread_safe (0.3.5)
|
119
|
+
tzinfo (1.2.2)
|
120
|
+
thread_safe (~> 0.1)
|
121
|
+
|
122
|
+
PLATFORMS
|
123
|
+
ruby
|
124
|
+
|
125
|
+
DEPENDENCIES
|
126
|
+
active_replicas!
|
127
|
+
appraisal (~> 2.1)
|
128
|
+
bundler (~> 1.13)
|
129
|
+
rails (~> 4.0)
|
130
|
+
rake (~> 10.0)
|
131
|
+
rspec (~> 3.0)
|
132
|
+
|
133
|
+
BUNDLED WITH
|
134
|
+
1.13.6
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_record/log_subscriber'
|
2
|
+
|
3
|
+
module ActiveReplicas
|
4
|
+
class LogSubscriber < ActiveRecord::LogSubscriber
|
5
|
+
def sql(event)
|
6
|
+
self.class.runtime += event.duration
|
7
|
+
return unless logger.debug?
|
8
|
+
|
9
|
+
payload = event.payload
|
10
|
+
|
11
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
12
|
+
|
13
|
+
pool = nil
|
14
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
15
|
+
sql = payload[:sql]
|
16
|
+
binds = nil
|
17
|
+
|
18
|
+
proxy = ActiveRecord::Base.connection_handler.proxying_connection_pool
|
19
|
+
connection_pool = proxy.pool_which_owns_connection payload[:connection_id]
|
20
|
+
if connection_pool
|
21
|
+
role =
|
22
|
+
if proxy.primary_pool? connection_pool
|
23
|
+
'primary'
|
24
|
+
elsif proxy.replica_pool? connection_pool
|
25
|
+
pool_name = proxy.replica_pools.key connection_pool
|
26
|
+
"replica=#{pool_name}"
|
27
|
+
else
|
28
|
+
'unknown'
|
29
|
+
end
|
30
|
+
|
31
|
+
pool = "[#{role}] "
|
32
|
+
end
|
33
|
+
|
34
|
+
unless (payload[:binds] || []).empty?
|
35
|
+
binds = ' ' + payload[:binds].map { |column, value| render_bind(column, value) }.inspect
|
36
|
+
end
|
37
|
+
|
38
|
+
debug "#{pool}#{name} #{sql}#{binds}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger
|
42
|
+
ActiveRecord::Base.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
# Take over logging duties from `ActiveRecord::LogSubscriber`.
|
46
|
+
def self.hijack_active_record
|
47
|
+
self.attach_to :active_record
|
48
|
+
|
49
|
+
subscriber = ActiveSupport::Notifications.notifier.listeners_for('sql.active_record').find do |subscriber|
|
50
|
+
ActiveRecord::LogSubscriber === subscriber.instance_eval { @delegate }
|
51
|
+
end
|
52
|
+
|
53
|
+
ActiveSupport::Notifications.notifier.unsubscribe(subscriber) if subscriber
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActiveReplicas
|
2
|
+
class ProxyingConnection
|
3
|
+
def initialize(connection:, is_primary:, proxy:)
|
4
|
+
@connection = connection
|
5
|
+
@is_primary = is_primary
|
6
|
+
@proxy = proxy
|
7
|
+
end
|
8
|
+
|
9
|
+
# Forwards a call to its own connection or back up to the proxy pool's
|
10
|
+
# primary connection.
|
11
|
+
#
|
12
|
+
# role - Either `:primary` or `:replica`: indicates which database role
|
13
|
+
# is necessary to execute this command.
|
14
|
+
# method - Symbol of method to send to a connection.
|
15
|
+
def delegate_to(role, method, *args, &block)
|
16
|
+
if @is_primary
|
17
|
+
@connection.send method, *args, &block
|
18
|
+
else
|
19
|
+
if role == :primary
|
20
|
+
# Need to get a primary connection from the proxy pool.
|
21
|
+
@proxy.primary_connection.send method, *args, &block
|
22
|
+
else
|
23
|
+
@connection.send method, *args, &block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Helper method to easily use the primary pool from any model:
|
29
|
+
#
|
30
|
+
# MyModel.connection.with_primary { ... }
|
31
|
+
def with_primary
|
32
|
+
@proxy.with_primary { yield }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the underlying proxied database connection.
|
36
|
+
def proxied_connection
|
37
|
+
@connection
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
# Partially cribbed from:
|
42
|
+
# https://github.com/kickstarter/replica_pools/blob/master/lib/replica_pools/connection_proxy.rb#L20
|
43
|
+
def generate_replica_delegations
|
44
|
+
Railtie.replica_delegated_methods.each do |method|
|
45
|
+
generate_delegation method, :replica
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_primary_delegations
|
50
|
+
Railtie.primary_delegated_methods.each do |method|
|
51
|
+
generate_delegation method, :primary
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_delegation(method_name, role)
|
56
|
+
class_eval <<-END, __FILE__, __LINE__ + 1
|
57
|
+
def #{method_name}(*args, &block)
|
58
|
+
delegate_to(:#{role}, :#{method_name}, *args, &block)
|
59
|
+
end
|
60
|
+
END
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module ActiveReplicas
|
2
|
+
# Manages connection pools to the primary and replica databases. Returns
|
3
|
+
# proxy connection instances from those pools on request.
|
4
|
+
#
|
5
|
+
# Also hanldes the internal state of switching back and forth from replica
|
6
|
+
# to primary connections based on heuristics or overrides.
|
7
|
+
class ProxyingConnectionPool
|
8
|
+
attr_reader :primary_pool, :replica_pools
|
9
|
+
|
10
|
+
def initialize(proxy_configuration)
|
11
|
+
@primary_pool = ProxyingConnectionPool.connection_pool_for_spec proxy_configuration[:primary]
|
12
|
+
|
13
|
+
@replica_pools = (proxy_configuration[:replicas] || {}).map do |name, config_spec|
|
14
|
+
[ name, ProxyingConnectionPool.connection_pool_for_spec(config_spec) ]
|
15
|
+
end.to_h
|
16
|
+
|
17
|
+
# Calls to `with_primary` will increment and decrement this.
|
18
|
+
@primary_depth = 0
|
19
|
+
# Current connection pool.
|
20
|
+
@current_pool = nil
|
21
|
+
# Thread-safe map of the connections from each pool. Cleared in tandem
|
22
|
+
# with the connection pools.
|
23
|
+
@connections = Concurrent::Map.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an instance of `ActiveRecord::ConnectionAdapters::ConnectionPool`
|
27
|
+
# configured with the given specification.
|
28
|
+
def self.connection_pool_for_spec(spec)
|
29
|
+
@@resolver ||= ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
30
|
+
|
31
|
+
# Turns a hash configuration into a `ConnectionSpecification` that can
|
32
|
+
# be passed to a `ConnectionPool`.
|
33
|
+
spec = @@resolver.spec config_spec.with_indifferent_access
|
34
|
+
|
35
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new spec
|
36
|
+
end
|
37
|
+
|
38
|
+
# ConnectionPool interface methods
|
39
|
+
# ================================
|
40
|
+
|
41
|
+
def connection
|
42
|
+
pool = current_pool
|
43
|
+
|
44
|
+
@connections[pool] ||= begin
|
45
|
+
conn = pool.connection
|
46
|
+
return unless conn
|
47
|
+
|
48
|
+
ProxyingConnection.new connection: conn,
|
49
|
+
is_primary: pool == @primary_pool,
|
50
|
+
proxy: self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def release_connection
|
55
|
+
each_pool &:release_connection
|
56
|
+
|
57
|
+
@primary_depth = 0
|
58
|
+
@current_pool = nil
|
59
|
+
@connections.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
def connected?
|
63
|
+
current_pool.connected?
|
64
|
+
end
|
65
|
+
|
66
|
+
def disconnect!
|
67
|
+
each_pool &:disconnect!
|
68
|
+
end
|
69
|
+
|
70
|
+
def current_pool
|
71
|
+
if @current_pool == nil
|
72
|
+
@current_pool = next_pool
|
73
|
+
end
|
74
|
+
|
75
|
+
@current_pool
|
76
|
+
end
|
77
|
+
|
78
|
+
# Additional methods
|
79
|
+
# ==================
|
80
|
+
|
81
|
+
def with_primary
|
82
|
+
previous_pool = @current_pool
|
83
|
+
|
84
|
+
@primary_depth += 1
|
85
|
+
@current_pool = @primary_pool
|
86
|
+
|
87
|
+
yield connection
|
88
|
+
ensure
|
89
|
+
@primary_depth = [@primary_depth - 1, 0].max
|
90
|
+
@current_pool = previous_pool
|
91
|
+
end
|
92
|
+
|
93
|
+
# Quick accessor to a primary connection.
|
94
|
+
#
|
95
|
+
# NOTE: If this is not already in a `with_primary` block then calling this
|
96
|
+
# will irreversably place the proxying pool in the primary state until
|
97
|
+
# `clear_active_connections!` is called! If you want to *temporarily*
|
98
|
+
# use the primary then explicitly do so using `with_primary`.
|
99
|
+
def primary_connection
|
100
|
+
if @primary_depth == 0
|
101
|
+
@primary_depth += 1
|
102
|
+
@current_pool = @primary_pool
|
103
|
+
end
|
104
|
+
|
105
|
+
connection
|
106
|
+
end
|
107
|
+
|
108
|
+
def each_pool
|
109
|
+
yield @primary_pool
|
110
|
+
|
111
|
+
@replica_pools.each do |_name, pool|
|
112
|
+
yield pool
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def pool_which_owns_connection(object_id)
|
117
|
+
return @primary_pool if @primary_pool.connections.any? { |c| c.object_id == object_id }
|
118
|
+
|
119
|
+
@replica_pools.values.each do |pool|
|
120
|
+
return pool if pool.connections.any? { |c| c.object_id == object_id }
|
121
|
+
end
|
122
|
+
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def primary_pool?(pool)
|
127
|
+
pool == @primary_pool
|
128
|
+
end
|
129
|
+
|
130
|
+
def replica_pool?(pool)
|
131
|
+
@replica_pools.values.include? pool
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def next_pool
|
137
|
+
replicas = @replica_pools.values
|
138
|
+
|
139
|
+
if replicas.empty?
|
140
|
+
@primary_pool
|
141
|
+
else
|
142
|
+
replicas.sample
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'concurrent/map'
|
2
|
+
|
3
|
+
module ActiveReplicas
|
4
|
+
module Rails4
|
5
|
+
# Wraps around Rails' `ActiveRecord::ConnectionAdapters::ConnectionHandler`
|
6
|
+
# to provide proxy wrappers around requested connections.
|
7
|
+
class ConnectionHandler
|
8
|
+
def initialize(proxy_configuration:, delegate: nil, overrides: nil)
|
9
|
+
@proxy_configuration = proxy_configuration
|
10
|
+
@delegate = delegate
|
11
|
+
@overrides = Set.new(overrides || [])
|
12
|
+
|
13
|
+
# Each process will get its own map of connection keys to database
|
14
|
+
# connection instances.
|
15
|
+
@process_to_connection_pool = Concurrent::Map.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def establish_connection(owner, _spec)
|
19
|
+
raise "ActiveReplicas cannot establish connection for #{owner.name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_active_connections!
|
23
|
+
proxying_connection_pool.release_connection
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cribbed from:
|
27
|
+
# https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L568
|
28
|
+
def retrieve_connection(klass)
|
29
|
+
pool = retrieve_connection_pool klass
|
30
|
+
raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
|
31
|
+
conn = pool.connection
|
32
|
+
raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
|
33
|
+
conn
|
34
|
+
end
|
35
|
+
|
36
|
+
def retrieve_connection_pool(klass)
|
37
|
+
proxying_connection_pool
|
38
|
+
end
|
39
|
+
|
40
|
+
def connected?(klass)
|
41
|
+
pool = retrieve_connection_pool klass
|
42
|
+
pool && pool.connected?
|
43
|
+
end
|
44
|
+
|
45
|
+
def proxying_connection_pool
|
46
|
+
@process_to_connection_pool[Process.pid] ||= ProxyingConnectionPool.new(@proxy_configuration)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ConnectionHandler = Rails4::ConnectionHandler
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveReplicas
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
cattr_reader :connection_handler
|
4
|
+
|
5
|
+
cattr_accessor :replica_delegated_methods
|
6
|
+
cattr_accessor :primary_delegated_methods
|
7
|
+
|
8
|
+
@@replica_delegated_methods = [
|
9
|
+
:active?, :clear_query_cache, :column_name_for_operation, :columns,
|
10
|
+
:disable_query_cache!, :disconnect!, :enable_query_cache!,
|
11
|
+
:query_cache_enabled, :quote_column_name, :quote_table_name,
|
12
|
+
:raw_connection, :reconnect!, :sanitize_limit, :schema_cache, :select,
|
13
|
+
:select_all, :select_one, :select_rows, :select_value, :select_values,
|
14
|
+
:substitute_at, :to_sql, :verify!
|
15
|
+
]
|
16
|
+
|
17
|
+
@@primary_delegated_methods = [
|
18
|
+
:insert, :next_sequence_value, :prefetch_primary_key?,
|
19
|
+
:transaction, :transaction_state, :update
|
20
|
+
]
|
21
|
+
|
22
|
+
def self.hijack_active_record(proxy_configuration, overrides: [])
|
23
|
+
ProxyingConnection.generate_replica_delegations
|
24
|
+
ProxyingConnection.generate_primary_delegations
|
25
|
+
|
26
|
+
@@connection_handler =
|
27
|
+
ConnectionHandler.new proxy_configuration: proxy_configuration,
|
28
|
+
delegate: ActiveRecord::Base.connection_handler,
|
29
|
+
overrides: overrides
|
30
|
+
|
31
|
+
ActiveRecord::Base.class_eval do
|
32
|
+
def self.connection_handler
|
33
|
+
ActiveReplicas::Railtie.connection_handler
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Take over logging duties now that we're the main connection handler.
|
38
|
+
LogSubscriber.hijack_active_record
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_replicas/version'
|
2
|
+
require 'active_replicas/log_subscriber'
|
3
|
+
require 'active_replicas/proxying_connection'
|
4
|
+
require 'active_replicas/proxying_connection_pool'
|
5
|
+
|
6
|
+
if defined? ActiveRecord
|
7
|
+
version = ActiveRecord::VERSION::MAJOR
|
8
|
+
|
9
|
+
if version == 4
|
10
|
+
require 'active_replicas/rails4/connection_handler'
|
11
|
+
else
|
12
|
+
raise "Unsupported ActiveRecord version: #{version}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'active_replicas/railtie' if defined? Rails
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_replicas
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dirk Gadsden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: appraisal
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.1'
|
83
|
+
description: Hooks into ActiveRecord to automatically sends reads to read replicas
|
84
|
+
and writes to primary database.
|
85
|
+
email:
|
86
|
+
- dirk@esherido.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Appraisals
|
95
|
+
- CODE_OF_CONDUCT.md
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- active_replicas.gemspec
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- example/Gemfile
|
104
|
+
- example/Gemfile.lock
|
105
|
+
- example/README.md
|
106
|
+
- example/app.rb
|
107
|
+
- example/config.ru
|
108
|
+
- example/setup_database.sh
|
109
|
+
- gemfiles/rails_4.gemfile
|
110
|
+
- gemfiles/rails_4.gemfile.lock
|
111
|
+
- lib/active_replicas.rb
|
112
|
+
- lib/active_replicas/log_subscriber.rb
|
113
|
+
- lib/active_replicas/proxying_connection.rb
|
114
|
+
- lib/active_replicas/proxying_connection_pool.rb
|
115
|
+
- lib/active_replicas/rails4/connection_handler.rb
|
116
|
+
- lib/active_replicas/railtie.rb
|
117
|
+
- lib/active_replicas/version.rb
|
118
|
+
homepage: https://github.com/dirk/active_replicas
|
119
|
+
licenses: []
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.5.2
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Smart read replicas in ActiveRecord.
|
141
|
+
test_files: []
|