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 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: []