distribute_reads 0.3.0 → 0.3.4

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: 4fb973f4af2e8828fc62a2c921605d595ee89bf521ba9f2b65f4c4f695225a76
4
- data.tar.gz: 26ac05c9a814401ed03e56dfd7ceafa637e3ac0dcdb8aba79e12f9d664b5bbac
3
+ metadata.gz: 0e52cc02b09f1027e932869c4acacff1d979bbe1c4deb34c952e81bd14c97fc9
4
+ data.tar.gz: 783d432ebe59418e717cd52e2b09ae54174a21000f4cdef96be6e0ebf6651c22
5
5
  SHA512:
6
- metadata.gz: f40892ea166bb86ea89809d7b0e1cb74616ba55251f3208272319dab0d71322eb2c4efca3aaa2c134a9e0ae6adc1c20ea2cae16451d3c2fd6205e4f344cd27a8
7
- data.tar.gz: 415fa474c9e3185f9d39b5410747f3d0a9feaf775e9a7e92d1595bc28d94e8ce69caa06d1c36eb646a98c94f963bc16ae27e54780260172689792066cf06ada2
6
+ metadata.gz: 728d62fd84f564e7e35981ae7bd37177c02ab69304b0a9c36e60ec2045af27a43fb284db4f12f5ca9094cbe9fd8108c3f4b6bed0421f4613bee9fb4a50ca8169
7
+ data.tar.gz: 3698ec3ba5c50277a638f013293be1c28a0404baf477a0473e135bf1512641ac9afc6f89060a031f9b256bce162e3585b51640a8672e7c8b8b7ddbb8aa2059ee
data/CHANGELOG.md CHANGED
@@ -1,29 +1,46 @@
1
- ## 0.3.0
1
+ ## 0.3.4 (2021-08-22)
2
+
3
+ - Added support for Aurora Postgres replication lag
4
+
5
+ ## 0.3.3 (2020-05-06)
6
+
7
+ - Fixed deprecation warning with MySQL
8
+
9
+ ## 0.3.2 (2020-01-02)
10
+
11
+ - Added `eager_load` option
12
+ - Removed warning when relation is loaded
13
+
14
+ ## 0.3.1 (2019-10-28)
15
+
16
+ - Added source location to logging
17
+
18
+ ## 0.3.0 (2019-06-14)
2
19
 
3
20
  - Use logger instead of stderr
4
21
  - Handle `NULL` replication lag for MySQL
5
22
  - Fixed replication lag check running on primary when replicas blacklisted
6
23
 
7
- ## 0.2.4
24
+ ## 0.2.4 (2018-11-14)
8
25
 
9
26
  - Added support for Aurora MySQL replication lag
10
27
  - Added more logging
11
28
 
12
- ## 0.2.3
29
+ ## 0.2.3 (2018-05-24)
13
30
 
14
31
  - Added support for Makara 0.4
15
32
 
16
- ## 0.2.2
33
+ ## 0.2.2 (2018-03-29)
17
34
 
18
35
  - Added support for MySQL replication lag
19
36
  - Added `replica` option
20
37
 
21
- ## 0.2.1
38
+ ## 0.2.1 (2017-12-14)
22
39
 
23
40
  - Fixed lag check for Postgres 10
24
41
  - Added `replication_lag` method
25
42
 
26
- ## 0.2.0
43
+ ## 0.2.0 (2017-10-02)
27
44
 
28
45
  Breaking
29
46
 
@@ -40,16 +57,16 @@ Other
40
57
  - Added default options
41
58
  - Improved lag query
42
59
 
43
- ## 0.1.2
60
+ ## 0.1.2 (2017-09-20)
44
61
 
45
62
  - Raise `ArgumentError` when missing block
46
63
  - Improved lag query
47
64
  - Warn if returning `ActiveRecord::Relation`
48
65
 
49
- ## 0.1.1
66
+ ## 0.1.1 (2017-05-14)
50
67
 
51
68
  - Added method for jobs
52
69
 
53
- ## 0.1.0
70
+ ## 0.1.0 (2017-03-26)
54
71
 
55
72
  - First release
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017-2019 Andrew Kane
1
+ Copyright (c) 2017-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,7 +4,7 @@ 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
 
@@ -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
  ```
@@ -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 = 'aurora runtime'").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
@@ -97,7 +117,24 @@ module DistributeReads
97
117
  end
98
118
 
99
119
  def self.log(message)
100
- logger.info("[distribute_reads] #{message}") if logger
120
+ if logger
121
+ logger.info { "[distribute_reads] #{message}" }
122
+
123
+ # show location like Active Record
124
+ source = backtrace_cleaner.clean(caller.lazy).first
125
+ logger.info { " ↳ #{source}" } if source
126
+ end
127
+ end
128
+
129
+ # private
130
+ def self.backtrace_cleaner
131
+ @backtrace_cleaner ||= begin
132
+ bc = ActiveSupport::BacktraceCleaner.new
133
+ bc.add_silencer { |line| line.include?("lib/distribute_reads") }
134
+ bc.add_silencer { |line| line.include?("lib/makara") }
135
+ bc.add_silencer { |line| line.include?("lib/active_record") }
136
+ bc
137
+ end
101
138
  end
102
139
 
103
140
  # private
@@ -132,6 +169,8 @@ end
132
169
 
133
170
  Makara::Proxy.prepend DistributeReads::AppropriatePool
134
171
  Object.include DistributeReads::GlobalMethods
172
+ # TODO uncomment in 0.4.0
173
+ # Object.send :private, :distribute_reads
135
174
 
136
175
  ActiveSupport.on_load(:active_job) do
137
176
  require "distribute_reads/job_methods"
@@ -23,7 +23,7 @@ module DistributeReads
23
23
  current_lag =
24
24
  begin
25
25
  DistributeReads.replication_lag(connection: base_model.connection)
26
- rescue DistributeReads::NoReplicasAvailable => e
26
+ rescue DistributeReads::NoReplicasAvailable
27
27
  # TODO rescue more exceptions?
28
28
  false
29
29
  end
@@ -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.0"
2
+ VERSION = "0.3.4"
3
3
  end
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.0
4
+ version: 0.3.4
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-06-14 00:00:00.000000000 Z
11
+ date: 2021-08-22 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.2.22
62
+ signing_key:
147
63
  specification_version: 4
148
64
  summary: Scale database reads with replicas in Rails
149
65
  test_files: []