active_replicas 0.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/.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: []
|