distribute_reads 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b58c208898bdc32763b35155e78946bd6a2cec3f
4
+ data.tar.gz: 758dbc1bd72a7d2541435f4e1bcca5d795ad230d
5
+ SHA512:
6
+ metadata.gz: 3c46fd083ba3256c568e89f27c77d22a042fcbae6f56ae7a0b58f0f7f81217cff7f2ca1f189b4052a302238a7e96672bce6ff04ac3964db515be6f46c9fc9b7f
7
+ data.tar.gz: 0ba5b6f61fca1d3f9825147cdcead1eb8f267df86a05c61022b7f1e4f41b6c75d6c66510ce35cd91ccccfd12a3793688788f46818d69fc7b292b70f1af607cbf
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in distribute_reads.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Distribute Reads
2
+
3
+ Scale database reads to replicas in Rails
4
+
5
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem 'distribute_reads'
13
+ ```
14
+
15
+ ## How to Use
16
+
17
+ [Makara](https://github.com/taskrabbit/makara) does most of the work. First, update `database.yml` to use it:
18
+
19
+ ```yml
20
+ default: &default
21
+ adapter: postgresql_makara
22
+ makara:
23
+ sticky: true
24
+ connections:
25
+ - role: master
26
+ name: primary
27
+ url: <%= ENV["PRIMARY_DATABASE_URL"] %>
28
+ - name: replica
29
+ url: <%= ENV["REPLICA_DATABASE_URL"] %>
30
+
31
+ development:
32
+ <<: *default
33
+
34
+ production:
35
+ <<: *default
36
+ ```
37
+
38
+ **Note:** You can use the same instance for the primary and replica in development.
39
+
40
+ By default, all reads go to the primary instance. To use the replica, do:
41
+
42
+ ```ruby
43
+ distribute_reads { User.count }
44
+ ```
45
+
46
+ Works with multiple queries as well.
47
+
48
+ ```ruby
49
+ distribute_reads do
50
+ User.find_each do |user| # replica
51
+ user.orders_count = user.orders.count # replica
52
+ user.save! # primary
53
+ end
54
+ end
55
+ ```
56
+
57
+ ## Options
58
+
59
+ Raise an error when replica lag is too high - *PostgreSQL only*
60
+
61
+ ```ruby
62
+ distribute_reads(max_lag: 3) do
63
+ # raises DistributeReads::TooMuchLag
64
+ end
65
+ ```
66
+
67
+ Don’t default to primary (default Makara behavior)
68
+
69
+ ```ruby
70
+ DistributeReads.default_to_primary = false
71
+ ```
72
+
73
+ ## History
74
+
75
+ View the [changelog](https://github.com/ankane/distribute_reads/blob/master/CHANGELOG.md)
76
+
77
+ ## Contributing
78
+
79
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
80
+
81
+ - [Report bugs](https://github.com/ankane/distribute_reads/issues)
82
+ - Fix bugs and [submit pull requests](https://github.com/ankane/distribute_reads/pulls)
83
+ - Write, clarify, or fix documentation
84
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.warning = false
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "distribute_reads/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "distribute_reads"
8
+ spec.version = DistributeReads::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+
12
+ spec.summary = "Scale database reads with replicas in Rails"
13
+ spec.homepage = "https://github.com/ankane/distribute_reads"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "makara"
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "minitest"
27
+ spec.add_development_dependency "pg"
28
+ end
@@ -0,0 +1,17 @@
1
+ module DistributeReads
2
+ module AppropriatePool
3
+ def _appropriate_pool(*args)
4
+ if Thread.current[:distribute_reads]
5
+ if needs_master?(*args) || @slave_pool.completely_blacklisted? || in_transaction?
6
+ @master_pool
7
+ else
8
+ @slave_pool
9
+ end
10
+ elsif DistributeReads.default_to_primary
11
+ @master_pool
12
+ else
13
+ super
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module DistributeReads
2
+ module GlobalMethods
3
+ def distribute_reads(max_lag: nil)
4
+ previous_value = Thread.current[:distribute_reads]
5
+ begin
6
+ if max_lag && DistributeReads.lag > max_lag
7
+ raise DistributeReads::TooMuchLag, "Replica lag over #{max_lag} seconds"
8
+ end
9
+ Thread.current[:distribute_reads] = true
10
+ yield
11
+ ensure
12
+ Thread.current[:distribute_reads] = previous_value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module DistributeReads
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require "makara"
2
+ require "distribute_reads/appropriate_pool"
3
+ require "distribute_reads/global_methods"
4
+ require "distribute_reads/version"
5
+
6
+ module DistributeReads
7
+ class TooMuchLag < StandardError; end
8
+
9
+ class << self
10
+ attr_accessor :default_to_primary
11
+ end
12
+ self.default_to_primary = true
13
+
14
+ def self.lag
15
+ conn = ActiveRecord::Base.connection
16
+ if %w(PostgreSQL PostGIS).include?(conn.adapter_name)
17
+ conn.execute("SELECT EXTRACT(EPOCH FROM NOW() - pg_last_xact_replay_timestamp()) AS lag").first["lag"].to_f
18
+ else
19
+ raise "Option not supported with this adapter"
20
+ end
21
+ end
22
+ end
23
+
24
+ Makara::Proxy.send :prepend, DistributeReads::AppropriatePool
25
+ Object.send :include, DistributeReads::GlobalMethods
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: distribute_reads
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: makara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '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: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - andrew@chartkick.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - CHANGELOG.md
92
+ - Gemfile
93
+ - README.md
94
+ - Rakefile
95
+ - distribute_reads.gemspec
96
+ - lib/distribute_reads.rb
97
+ - lib/distribute_reads/appropriate_pool.rb
98
+ - lib/distribute_reads/global_methods.rb
99
+ - lib/distribute_reads/version.rb
100
+ homepage: https://github.com/ankane/distribute_reads
101
+ licenses: []
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.6.8
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Scale database reads with replicas in Rails
123
+ test_files: []