distribute_reads 0.3.1 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 044c145f24198d69d3f221698f3452ee301e947cefe7f675eb8e7b704effc353
4
- data.tar.gz: 1f7c381f92ae63fcd8dc0ced50fa317591d319fbb1473af87dab01ac2e77a855
3
+ metadata.gz: a478f3bce157af7ae245b8fd090c48dac8054feca0a5100142bf653aa8e594d3
4
+ data.tar.gz: 48980648523710bb24cb043883cc4e76851b0f0637e02ca07fbc831777d6a339
5
5
  SHA512:
6
- metadata.gz: 88019e465c3f305b16eca8bdf05a6b75fb9eec4b5d8b598167cadd904b86e403af9a460d36865286ece8224218f4e93a7249cb81a26a90a8d63c7a6f65fb5f03
7
- data.tar.gz: 1c336a5dbd584864aae1a422300e1468b456521a170754b6f2c138c6f4a10997401db9ae729eb799c3ffad64255b227e3d02cf54ea60f7e39e1ee759128f29bb
6
+ metadata.gz: 0a8ce6d4ed8f87fabb9ccb1fe9008b425990aec97fd1ad07a316c7fd80ce5ad09b72ec0acd52c3f7521fbb5e7a30b0b8e40a03c51a4a49653a3f226ef4a96228
7
+ data.tar.gz: 2c2e1482d173f7c844aa04b33db649161ea4aa62121ad1211949dec4d0cafd63ad8a668ea5bf30328384cbb7b9b010a5abb8dbdb30f20d6f8c802a44eb4e1516
data/CHANGELOG.md CHANGED
@@ -1,33 +1,50 @@
1
- ## 0.3.1
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017-2019 Andrew Kane
1
+ Copyright (c) 2017-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
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
- [![Build Status](https://travis-ci.org/ankane/distribute_reads.svg?branch=master)](https://travis-ci.org/ankane/distribute_reads)
7
+ [![Build Status](https://github.com/ankane/distribute_reads/workflows/build/badge.svg?branch=master)](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 'distribute_reads'
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://edgeguides.rubyonrails.org/active_record_multiple_databases.html) :tada:
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 test, run:
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
- DistributeReads.log "Call `to_a` inside block to execute query on replica" if value.is_a?(ActiveRecord::Relation) && !previous_value
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
@@ -1,3 +1,3 @@
1
1
  module DistributeReads
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -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
- @server_version_num ||= {}
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
- lag_condition =
51
- if @server_version_num[cache_key] >= 100000
52
- "pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()"
53
- else
54
- "pg_last_xlog_receive_location() = pg_last_xlog_replay_location()"
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
- connection.execute(
58
- "SELECT CASE
59
- WHEN NOT pg_is_in_recovery() OR #{lag_condition} THEN 0
60
- ELSE EXTRACT (EPOCH FROM NOW() - pg_last_xact_replay_timestamp())
61
- END AS lag".squish
62
- ).first["lag"].to_f
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.exec_query("SHOW VARIABLES LIKE 'aurora_version'").to_hash.any?
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.exec_query("SELECT Replica_lag_in_msec FROM mysql.ro_replica_status WHERE Server_id = @@aurora_server_id").to_hash.first
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.exec_query("SHOW SLAVE STATUS").to_hash.first
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("[distribute_reads] #{message}")
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(" ↳ #{source}") if source
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.1
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: 2019-10-28 00:00:00.000000000 Z
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
- - !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
- - !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.0.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: []