distribute_reads 0.3.1 → 0.3.5
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 +4 -4
- data/CHANGELOG.md +27 -10
- data/LICENSE.txt +1 -1
- data/README.md +13 -7
- data/lib/distribute_reads/global_methods.rb +7 -1
- data/lib/distribute_reads/version.rb +1 -1
- data/lib/distribute_reads.rb +42 -20
- metadata +8 -92
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a478f3bce157af7ae245b8fd090c48dac8054feca0a5100142bf653aa8e594d3
|
|
4
|
+
data.tar.gz: 48980648523710bb24cb043883cc4e76851b0f0637e02ca07fbc831777d6a339
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a8ce6d4ed8f87fabb9ccb1fe9008b425990aec97fd1ad07a316c7fd80ce5ad09b72ec0acd52c3f7521fbb5e7a30b0b8e40a03c51a4a49653a3f226ef4a96228
|
|
7
|
+
data.tar.gz: 2c2e1482d173f7c844aa04b33db649161ea4aa62121ad1211949dec4d0cafd63ad8a668ea5bf30328384cbb7b9b010a5abb8dbdb30f20d6f8c802a44eb4e1516
|
data/CHANGELOG.md
CHANGED
|
@@ -1,33 +1,50 @@
|
|
|
1
|
-
## 0.3.
|
|
1
|
+
## 0.3.5 (2022-01-30)
|
|
2
|
+
|
|
3
|
+
- Added support for Aurora Postgres 13 replication lag
|
|
4
|
+
|
|
5
|
+
## 0.3.4 (2021-08-22)
|
|
6
|
+
|
|
7
|
+
- Added support for Aurora Postgres replication lag
|
|
8
|
+
|
|
9
|
+
## 0.3.3 (2020-05-06)
|
|
10
|
+
|
|
11
|
+
- Fixed deprecation warning with MySQL
|
|
12
|
+
|
|
13
|
+
## 0.3.2 (2020-01-02)
|
|
14
|
+
|
|
15
|
+
- Added `eager_load` option
|
|
16
|
+
- Removed warning when relation is loaded
|
|
17
|
+
|
|
18
|
+
## 0.3.1 (2019-10-28)
|
|
2
19
|
|
|
3
20
|
- Added source location to logging
|
|
4
21
|
|
|
5
|
-
## 0.3.0
|
|
22
|
+
## 0.3.0 (2019-06-14)
|
|
6
23
|
|
|
7
24
|
- Use logger instead of stderr
|
|
8
25
|
- Handle `NULL` replication lag for MySQL
|
|
9
26
|
- Fixed replication lag check running on primary when replicas blacklisted
|
|
10
27
|
|
|
11
|
-
## 0.2.4
|
|
28
|
+
## 0.2.4 (2018-11-14)
|
|
12
29
|
|
|
13
30
|
- Added support for Aurora MySQL replication lag
|
|
14
31
|
- Added more logging
|
|
15
32
|
|
|
16
|
-
## 0.2.3
|
|
33
|
+
## 0.2.3 (2018-05-24)
|
|
17
34
|
|
|
18
35
|
- Added support for Makara 0.4
|
|
19
36
|
|
|
20
|
-
## 0.2.2
|
|
37
|
+
## 0.2.2 (2018-03-29)
|
|
21
38
|
|
|
22
39
|
- Added support for MySQL replication lag
|
|
23
40
|
- Added `replica` option
|
|
24
41
|
|
|
25
|
-
## 0.2.1
|
|
42
|
+
## 0.2.1 (2017-12-14)
|
|
26
43
|
|
|
27
44
|
- Fixed lag check for Postgres 10
|
|
28
45
|
- Added `replication_lag` method
|
|
29
46
|
|
|
30
|
-
## 0.2.0
|
|
47
|
+
## 0.2.0 (2017-10-02)
|
|
31
48
|
|
|
32
49
|
Breaking
|
|
33
50
|
|
|
@@ -44,16 +61,16 @@ Other
|
|
|
44
61
|
- Added default options
|
|
45
62
|
- Improved lag query
|
|
46
63
|
|
|
47
|
-
## 0.1.2
|
|
64
|
+
## 0.1.2 (2017-09-20)
|
|
48
65
|
|
|
49
66
|
- Raise `ArgumentError` when missing block
|
|
50
67
|
- Improved lag query
|
|
51
68
|
- Warn if returning `ActiveRecord::Relation`
|
|
52
69
|
|
|
53
|
-
## 0.1.1
|
|
70
|
+
## 0.1.1 (2017-05-14)
|
|
54
71
|
|
|
55
72
|
- Added method for jobs
|
|
56
73
|
|
|
57
|
-
## 0.1.0
|
|
74
|
+
## 0.1.0 (2017-03-26)
|
|
58
75
|
|
|
59
76
|
- First release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -4,14 +4,14 @@ Scale database reads to replicas in Rails
|
|
|
4
4
|
|
|
5
5
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
|
6
6
|
|
|
7
|
-
[](https://github.com/ankane/distribute_reads/actions)
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
Add this line to your application’s Gemfile:
|
|
12
12
|
|
|
13
13
|
```ruby
|
|
14
|
-
gem
|
|
14
|
+
gem "distribute_reads"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## How to Use
|
|
@@ -80,12 +80,18 @@ ActiveRecord uses [lazy evaluation](https://www.theodinproject.com/courses/ruby-
|
|
|
80
80
|
users = distribute_reads { User.where(orders_count: 1) } # not executed yet
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Call `to_a` inside the block ensure the query runs on a replica.
|
|
83
|
+
Call `to_a` or `load` inside the block to ensure the query runs on a replica.
|
|
84
84
|
|
|
85
85
|
```ruby
|
|
86
86
|
users = distribute_reads { User.where(orders_count: 1).to_a }
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
You can automatically load relations returned from `distribute_reads` blocks by creating an initializer with:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
DistributeReads.eager_load = true
|
|
93
|
+
```
|
|
94
|
+
|
|
89
95
|
## Options
|
|
90
96
|
|
|
91
97
|
### Replica Lag
|
|
@@ -181,7 +187,7 @@ end
|
|
|
181
187
|
|
|
182
188
|
## Rails 6
|
|
183
189
|
|
|
184
|
-
Rails 6 has [native support for replicas](https://
|
|
190
|
+
Rails 6 has [native support for replicas](https://guides.rubyonrails.org/active_record_multiple_databases.html) :tada:
|
|
185
191
|
|
|
186
192
|
```ruby
|
|
187
193
|
ActiveRecord::Base.connected_to(role: :reading) do
|
|
@@ -208,13 +214,13 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
|
208
214
|
- Write, clarify, or fix documentation
|
|
209
215
|
- Suggest or add new features
|
|
210
216
|
|
|
211
|
-
To
|
|
217
|
+
To get started with development and testing:
|
|
212
218
|
|
|
213
219
|
```sh
|
|
214
220
|
git clone https://github.com/ankane/distribute_reads.git
|
|
215
221
|
cd distribute_reads
|
|
216
222
|
createdb distribute_reads_test_primary
|
|
217
223
|
createdb distribute_reads_test_replica
|
|
218
|
-
bundle
|
|
219
|
-
bundle exec rake
|
|
224
|
+
bundle install
|
|
225
|
+
bundle exec rake test
|
|
220
226
|
```
|
|
@@ -54,7 +54,13 @@ module DistributeReads
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
value = yield
|
|
57
|
-
|
|
57
|
+
if value.is_a?(ActiveRecord::Relation) && !previous_value && !value.loaded?
|
|
58
|
+
if DistributeReads.eager_load
|
|
59
|
+
value = value.load
|
|
60
|
+
else
|
|
61
|
+
DistributeReads.log "Call `to_a` inside block to execute query on replica"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
58
64
|
value
|
|
59
65
|
ensure
|
|
60
66
|
Thread.current[:distribute_reads] = previous_value
|
data/lib/distribute_reads.rb
CHANGED
|
@@ -14,8 +14,7 @@ module DistributeReads
|
|
|
14
14
|
class NoReplicasAvailable < Error; end
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
attr_accessor :by_default
|
|
18
|
-
attr_accessor :default_options
|
|
17
|
+
attr_accessor :by_default, :default_options, :eager_load
|
|
19
18
|
attr_writer :logger
|
|
20
19
|
end
|
|
21
20
|
self.by_default = false
|
|
@@ -23,6 +22,7 @@ module DistributeReads
|
|
|
23
22
|
failover: true,
|
|
24
23
|
lag_failover: false
|
|
25
24
|
}
|
|
25
|
+
self.eager_load = false
|
|
26
26
|
|
|
27
27
|
def self.logger
|
|
28
28
|
unless defined?(@logger)
|
|
@@ -43,37 +43,57 @@ module DistributeReads
|
|
|
43
43
|
case connection.adapter_name
|
|
44
44
|
when "PostgreSQL", "PostGIS"
|
|
45
45
|
# cache the version number
|
|
46
|
-
@
|
|
46
|
+
@aurora_postgres ||= {}
|
|
47
47
|
cache_key = connection.pool.object_id
|
|
48
|
-
@server_version_num[cache_key] ||= connection.execute("SHOW server_version_num").first["server_version_num"].to_i
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
unless @aurora_postgres.key?(cache_key)
|
|
50
|
+
@aurora_postgres[cache_key] = connection.select_all("SELECT 1 FROM pg_stat_activity WHERE backend_type IN ('aurora runtime', 'aurora runtime process')").any?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@server_version_num ||= {}
|
|
54
|
+
@server_version_num[cache_key] ||= connection.select_all("SHOW server_version_num").first["server_version_num"].to_i
|
|
55
|
+
|
|
56
|
+
if @aurora_postgres[cache_key]
|
|
57
|
+
# no way to get session_id at the moment
|
|
58
|
+
# also, pg_is_in_recovery() is always false
|
|
59
|
+
# and pg_settings are the same for writer and readers
|
|
60
|
+
# this means we can't tell:
|
|
61
|
+
# 1. if this is the primary or replica
|
|
62
|
+
# 2. if replica, which one
|
|
63
|
+
status = connection.select_all("SELECT MAX(replica_lag_in_msec) AS replica_lag_in_msec, COUNT(*) AS replica_count FROM aurora_replica_status() WHERE session_id != 'MASTER_SESSION_ID'").first
|
|
64
|
+
if status && status["replica_count"].to_i > 1
|
|
65
|
+
log "Multiple readers available, taking max lag of all of them"
|
|
55
66
|
end
|
|
67
|
+
status ? status["replica_lag_in_msec"].to_f / 1000.0 : 0.0
|
|
68
|
+
else
|
|
69
|
+
lag_condition =
|
|
70
|
+
if @server_version_num[cache_key] >= 100000
|
|
71
|
+
"pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()"
|
|
72
|
+
else
|
|
73
|
+
"pg_last_xlog_receive_location() = pg_last_xlog_replay_location()"
|
|
74
|
+
end
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
connection.select_all(
|
|
77
|
+
"SELECT CASE
|
|
78
|
+
WHEN NOT pg_is_in_recovery() OR #{lag_condition} THEN 0
|
|
79
|
+
ELSE EXTRACT (EPOCH FROM NOW() - pg_last_xact_replay_timestamp())
|
|
80
|
+
END AS lag".squish
|
|
81
|
+
).first["lag"].to_f
|
|
82
|
+
end
|
|
63
83
|
when "MySQL", "Mysql2", "Mysql2Spatial", "Mysql2Rgeo"
|
|
64
84
|
@aurora_mysql ||= {}
|
|
65
85
|
cache_key = connection.pool.object_id
|
|
66
86
|
|
|
67
87
|
unless @aurora_mysql.key?(cache_key)
|
|
68
88
|
# makara doesn't send SHOW queries to replica by default
|
|
69
|
-
@aurora_mysql[cache_key] = connection.
|
|
89
|
+
@aurora_mysql[cache_key] = connection.select_all("SHOW VARIABLES LIKE 'aurora_version'").any?
|
|
70
90
|
end
|
|
71
91
|
|
|
72
92
|
if @aurora_mysql[cache_key]
|
|
73
|
-
status = connection.
|
|
93
|
+
status = connection.select_all("SELECT Replica_lag_in_msec FROM mysql.ro_replica_status WHERE Server_id = @@aurora_server_id").first
|
|
74
94
|
status ? status["Replica_lag_in_msec"].to_f / 1000.0 : 0.0
|
|
75
95
|
else
|
|
76
|
-
status = connection.
|
|
96
|
+
status = connection.select_all("SHOW SLAVE STATUS").first
|
|
77
97
|
if status
|
|
78
98
|
if status["Seconds_Behind_Master"].nil?
|
|
79
99
|
# replication stopped
|
|
@@ -98,11 +118,11 @@ module DistributeReads
|
|
|
98
118
|
|
|
99
119
|
def self.log(message)
|
|
100
120
|
if logger
|
|
101
|
-
logger.info
|
|
121
|
+
logger.info { "[distribute_reads] #{message}" }
|
|
102
122
|
|
|
103
123
|
# show location like Active Record
|
|
104
124
|
source = backtrace_cleaner.clean(caller.lazy).first
|
|
105
|
-
logger.info
|
|
125
|
+
logger.info { " ↳ #{source}" } if source
|
|
106
126
|
end
|
|
107
127
|
end
|
|
108
128
|
|
|
@@ -149,6 +169,8 @@ end
|
|
|
149
169
|
|
|
150
170
|
Makara::Proxy.prepend DistributeReads::AppropriatePool
|
|
151
171
|
Object.include DistributeReads::GlobalMethods
|
|
172
|
+
# TODO uncomment in 0.4.0
|
|
173
|
+
# Object.send :private, :distribute_reads
|
|
152
174
|
|
|
153
175
|
ActiveSupport.on_load(:active_job) do
|
|
154
176
|
require "distribute_reads/job_methods"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: distribute_reads
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-01-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: makara
|
|
@@ -24,92 +24,8 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0.3'
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: mysql2
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: activejob
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
111
|
-
description:
|
|
112
|
-
email: andrew@chartkick.com
|
|
27
|
+
description:
|
|
28
|
+
email: andrew@ankane.org
|
|
113
29
|
executables: []
|
|
114
30
|
extensions: []
|
|
115
31
|
extra_rdoc_files: []
|
|
@@ -127,7 +43,7 @@ homepage: https://github.com/ankane/distribute_reads
|
|
|
127
43
|
licenses:
|
|
128
44
|
- MIT
|
|
129
45
|
metadata: {}
|
|
130
|
-
post_install_message:
|
|
46
|
+
post_install_message:
|
|
131
47
|
rdoc_options: []
|
|
132
48
|
require_paths:
|
|
133
49
|
- lib
|
|
@@ -142,8 +58,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
142
58
|
- !ruby/object:Gem::Version
|
|
143
59
|
version: '0'
|
|
144
60
|
requirements: []
|
|
145
|
-
rubygems_version: 3.
|
|
146
|
-
signing_key:
|
|
61
|
+
rubygems_version: 3.3.3
|
|
62
|
+
signing_key:
|
|
147
63
|
specification_version: 4
|
|
148
64
|
summary: Scale database reads with replicas in Rails
|
|
149
65
|
test_files: []
|