rails_failover 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +4 -0
- data/README.md +3 -4
- data/lib/rails_failover/active_record/handler.rb +5 -11
- data/lib/rails_failover/active_record/middleware.rb +28 -38
- data/lib/rails_failover/active_record/railtie.rb +13 -47
- data/lib/rails_failover/active_record.rb +49 -53
- data/lib/rails_failover/version.rb +1 -1
- data/rails_failover.gemspec +3 -3
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b769d81f278cffd6d960f9a43da2feb15d46e23dd35d2608dbef0fdb04688a70
|
4
|
+
data.tar.gz: a72536269e00455cf0506f68dfbbdf6c98a7b7c8a24064532a7219fa31b4bec6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33f8835985676d80ba1701eda774322db0701040691e3164218bbf931901314a277ad63c926634f7ac6270f43e5899a7a90030b03f7ec53badb618bac0a187b3
|
7
|
+
data.tar.gz: a219d8441c37438bc1ea367fc99920cf3090728a5be482dd59a68f80a5690effa3f2a2a059085675980ed2e816e662ca3c485b12543437bde2801e07d4adfa6b
|
data/.github/workflows/ci.yml
CHANGED
@@ -35,7 +35,7 @@ jobs:
|
|
35
35
|
strategy:
|
36
36
|
fail-fast: false
|
37
37
|
matrix:
|
38
|
-
ruby: ['
|
38
|
+
ruby: ['3.0', '3.1', '3.2']
|
39
39
|
|
40
40
|
steps:
|
41
41
|
- uses: actions/checkout@v3
|
@@ -59,13 +59,13 @@ jobs:
|
|
59
59
|
strategy:
|
60
60
|
fail-fast: false
|
61
61
|
matrix:
|
62
|
-
ruby: ['3.2', '3.1', '3.0'
|
62
|
+
ruby: ['3.2', '3.1', '3.0']
|
63
63
|
rails: ['7.0.0']
|
64
64
|
include:
|
65
65
|
- ruby: '3.2'
|
66
66
|
rails: '6.1.0'
|
67
67
|
- ruby: '3.2'
|
68
|
-
rails: '
|
68
|
+
rails: 'edge'
|
69
69
|
|
70
70
|
steps:
|
71
71
|
- uses: actions/checkout@v3
|
@@ -97,7 +97,7 @@ jobs:
|
|
97
97
|
- uses: actions/checkout@v3
|
98
98
|
|
99
99
|
- name: Release Gem
|
100
|
-
uses: discourse/publish-rubygems-action@
|
100
|
+
uses: discourse/publish-rubygems-action@v3
|
101
101
|
env:
|
102
102
|
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
103
103
|
GIT_EMAIL: team@discourse.org
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [2.0.0] - 2023-05-16
|
11
|
+
|
12
|
+
- DEV: Compatibility with Rails 7.1+ (drop support for Rails 6.0 & ruby 2.7)
|
13
|
+
|
10
14
|
## [1.0.0] - 2023-04-07
|
11
15
|
|
12
16
|
- DEV: Remove the support for Ruby < 2.7
|
data/README.md
CHANGED
@@ -37,7 +37,8 @@ production:
|
|
37
37
|
replica_port: <replica db server port>
|
38
38
|
```
|
39
39
|
|
40
|
-
The gem will automatically create
|
40
|
+
The gem will automatically create a role (using `ActiveRecord.reading_role`) on
|
41
|
+
the default `ActiveRecord` connection handler.
|
41
42
|
|
42
43
|
#### Failover/Fallback Hooks
|
43
44
|
|
@@ -53,8 +54,6 @@ end
|
|
53
54
|
|
54
55
|
#### Multiple connection handlers
|
55
56
|
|
56
|
-
Note: This API is unstable and is likely to change when Rails 6.1 is released with sharding support.
|
57
|
-
|
58
57
|
```yml
|
59
58
|
# config/database.yml
|
60
59
|
|
@@ -72,7 +71,7 @@ production:
|
|
72
71
|
|
73
72
|
# In your ActiveRecord base model or model.
|
74
73
|
|
75
|
-
connects_to database: { writing: :primary, second_database_writing: :second_database_writing
|
74
|
+
connects_to database: { writing: :primary, second_database_writing: :second_database_writing }
|
76
75
|
```
|
77
76
|
|
78
77
|
### Redis
|
@@ -55,21 +55,15 @@ module RailsFailover
|
|
55
55
|
active_handler_keys = []
|
56
56
|
|
57
57
|
primaries_down.keys.each do |handler_key|
|
58
|
-
connection_handler = ::ActiveRecord::Base.connection_handlers[handler_key]
|
59
|
-
|
60
|
-
connection_pool = connection_handler.retrieve_connection_pool(spec_name)
|
61
|
-
if connection_pool.respond_to?(:db_config)
|
62
|
-
config = connection_pool.db_config.configuration_hash
|
63
|
-
adapter_method = connection_pool.db_config.adapter_method
|
64
|
-
else
|
65
|
-
config = connection_pool.spec.config
|
66
|
-
adapter_method = connection_pool.spec.adapter_method
|
67
|
-
end
|
68
58
|
logger.debug "#{Process.pid} Checking server for '#{handler_key} #{spec_name}'..."
|
69
59
|
connection_active = false
|
70
60
|
|
71
61
|
begin
|
72
|
-
connection =
|
62
|
+
connection =
|
63
|
+
::ActiveRecord::Base.connection_handler.retrieve_connection(
|
64
|
+
spec_name,
|
65
|
+
role: handler_key,
|
66
|
+
)
|
73
67
|
connection_active = connection.active?
|
74
68
|
rescue => e
|
75
69
|
logger.debug "#{Process.pid} Connection to server for '#{handler_key} #{spec_name}' failed with '#{e.message}'"
|
@@ -49,9 +49,8 @@ module RailsFailover
|
|
49
49
|
writing_role = resolve_writing_role(current_role, is_writing_role)
|
50
50
|
|
51
51
|
role =
|
52
|
-
if
|
53
|
-
|
54
|
-
Handler.instance.primary_down?(writing_role)
|
52
|
+
if self.class.force_reading_role_callback&.call(env) ||
|
53
|
+
Handler.instance.primary_down?(writing_role)
|
55
54
|
reading_role = resolve_reading_role(current_role, is_writing_role)
|
56
55
|
ensure_reading_connection_established!(
|
57
56
|
writing_role: writing_role,
|
@@ -75,48 +74,39 @@ module RailsFailover
|
|
75
74
|
private
|
76
75
|
|
77
76
|
def ensure_reading_connection_established!(writing_role:, reading_role:)
|
78
|
-
::ActiveRecord::Base.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
config
|
86
|
-
|
87
|
-
|
77
|
+
connection_handler = ::ActiveRecord::Base.connection_handler
|
78
|
+
connection_handler
|
79
|
+
.connection_pools(writing_role)
|
80
|
+
.each do |connection_pool|
|
81
|
+
config = connection_pool.db_config.configuration_hash
|
82
|
+
RailsFailover::ActiveRecord.establish_reading_connection(
|
83
|
+
connection_handler,
|
84
|
+
config,
|
85
|
+
role: reading_role,
|
86
|
+
)
|
88
87
|
end
|
89
|
-
|
90
|
-
handler
|
91
|
-
end
|
92
88
|
end
|
93
89
|
|
94
90
|
def resolve_writing_role(current_role, is_writing_role)
|
95
|
-
if is_writing_role
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
.
|
100
|
-
.
|
101
|
-
|
102
|
-
|
103
|
-
)
|
104
|
-
.to_sym
|
105
|
-
end
|
91
|
+
return current_role if is_writing_role
|
92
|
+
current_role
|
93
|
+
.to_s
|
94
|
+
.sub(
|
95
|
+
/#{RailsFailover::ActiveRecord.reading_role}$/,
|
96
|
+
RailsFailover::ActiveRecord.writing_role.to_s,
|
97
|
+
)
|
98
|
+
.to_sym
|
106
99
|
end
|
107
100
|
|
108
101
|
def resolve_reading_role(current_role, is_writing_role)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
else
|
118
|
-
current_role
|
119
|
-
end
|
102
|
+
return current_role unless is_writing_role
|
103
|
+
current_role
|
104
|
+
.to_s
|
105
|
+
.sub(
|
106
|
+
/#{RailsFailover::ActiveRecord.writing_role}$/,
|
107
|
+
RailsFailover::ActiveRecord.reading_role.to_s,
|
108
|
+
)
|
109
|
+
.to_sym
|
120
110
|
end
|
121
111
|
end
|
122
112
|
end
|
@@ -4,51 +4,18 @@ module RailsFailover
|
|
4
4
|
module ActiveRecord
|
5
5
|
class Railtie < ::Rails::Railtie
|
6
6
|
initializer "rails_failover.init", after: "active_record.initialize_database" do |app|
|
7
|
-
# AR 6.0 / 6.1 compat
|
8
|
-
config =
|
9
|
-
if ::ActiveRecord::Base.respond_to? :connection_db_config
|
10
|
-
::ActiveRecord::Base.connection_db_config.configuration_hash
|
11
|
-
else
|
12
|
-
::ActiveRecord::Base.connection_config
|
13
|
-
end
|
14
|
-
|
15
7
|
app.config.active_record_rails_failover = false
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
] = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
28
|
-
|
29
|
-
::ActiveRecord::Base.connection_handlers[RailsFailover::ActiveRecord.writing_role]
|
30
|
-
.connection_pools
|
31
|
-
.each do |connection_pool|
|
32
|
-
if connection_pool.respond_to?(:db_config)
|
33
|
-
config = connection_pool.db_config.configuration_hash
|
34
|
-
else
|
35
|
-
config = connection_pool.spec.config
|
36
|
-
end
|
37
|
-
RailsFailover::ActiveRecord.establish_reading_connection(
|
38
|
-
::ActiveRecord::Base.connection_handlers[RailsFailover::ActiveRecord.reading_role],
|
39
|
-
config,
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
begin
|
44
|
-
::ActiveRecord::Base.connection
|
45
|
-
rescue ::ActiveRecord::NoDatabaseError
|
46
|
-
# Do nothing since database hasn't been created
|
47
|
-
rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
|
48
|
-
Handler.instance.verify_primary(RailsFailover::ActiveRecord.writing_role)
|
49
|
-
::ActiveRecord::Base.connection_handler =
|
50
|
-
::ActiveRecord::Base.lookup_connection_handler(:reading)
|
51
|
-
end
|
8
|
+
config = RailsFailover::ActiveRecord.config
|
9
|
+
break unless config[:replica_host] && config[:replica_port]
|
10
|
+
|
11
|
+
app.config.active_record_rails_failover = true
|
12
|
+
::ActiveSupport.on_load(:active_record) do
|
13
|
+
begin
|
14
|
+
::ActiveRecord::Base.connection
|
15
|
+
rescue ::ActiveRecord::NoDatabaseError
|
16
|
+
# Do nothing since database hasn't been created
|
17
|
+
rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
|
18
|
+
Handler.instance.verify_primary(RailsFailover::ActiveRecord.writing_role)
|
52
19
|
end
|
53
20
|
end
|
54
21
|
end
|
@@ -60,14 +27,13 @@ module RailsFailover
|
|
60
27
|
end
|
61
28
|
|
62
29
|
if !skip_middleware?(app.config)
|
63
|
-
app.middleware.unshift(
|
30
|
+
app.middleware.unshift(RailsFailover::ActiveRecord::Middleware)
|
64
31
|
end
|
65
32
|
end
|
66
33
|
end
|
67
34
|
|
68
35
|
def skip_middleware?(config)
|
69
|
-
|
70
|
-
config.skip_rails_failover_active_record_middleware
|
36
|
+
config.try(:skip_rails_failover_active_record_middleware)
|
71
37
|
end
|
72
38
|
end
|
73
39
|
end
|
@@ -7,77 +7,73 @@ require_relative "active_record/railtie" if defined?(::Rails)
|
|
7
7
|
require_relative "active_record/middleware"
|
8
8
|
require_relative "active_record/handler"
|
9
9
|
|
10
|
-
AR =
|
11
|
-
(
|
12
|
-
if ::ActiveRecord.respond_to?(:reading_role)
|
13
|
-
::ActiveRecord
|
14
|
-
else
|
15
|
-
::ActiveRecord::Base
|
16
|
-
end
|
17
|
-
)
|
18
|
-
|
19
10
|
module RailsFailover
|
20
11
|
module ActiveRecord
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
class << self
|
13
|
+
def config
|
14
|
+
::ActiveRecord::Base.connection_db_config.configuration_hash
|
15
|
+
end
|
24
16
|
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
def logger=(logger)
|
18
|
+
@logger = logger
|
19
|
+
end
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
def logger
|
22
|
+
@logger || Rails.logger
|
23
|
+
end
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
def verify_primary_frequency_seconds=(seconds)
|
26
|
+
@verify_primary_frequency_seconds = seconds
|
27
|
+
end
|
36
28
|
|
37
|
-
|
38
|
-
|
29
|
+
def verify_primary_frequency_seconds
|
30
|
+
@verify_primary_frequency_seconds || 5
|
31
|
+
end
|
32
|
+
|
33
|
+
def establish_reading_connection(handler, config, role: reading_role)
|
34
|
+
return unless config[:replica_host] && config[:replica_port]
|
39
35
|
replica_config = config.dup
|
40
36
|
replica_config[:host] = replica_config.delete(:replica_host)
|
41
37
|
replica_config[:port] = replica_config.delete(:replica_port)
|
42
38
|
replica_config[:replica] = true
|
43
|
-
handler.establish_connection(replica_config)
|
39
|
+
handler.establish_connection(replica_config, role: role)
|
44
40
|
end
|
45
|
-
end
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
def register_force_reading_role_callback(&block)
|
43
|
+
Middleware.force_reading_role_callback = block
|
44
|
+
end
|
50
45
|
|
51
|
-
|
52
|
-
|
53
|
-
|
46
|
+
def on_failover(&block)
|
47
|
+
@on_failover_callback = block
|
48
|
+
end
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
def on_failover_callback!(key)
|
51
|
+
@on_failover_callback&.call(key)
|
52
|
+
rescue => e
|
53
|
+
logger.warn(
|
54
|
+
"RailsFailover::ActiveRecord.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
|
55
|
+
)
|
56
|
+
end
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
def on_fallback(&block)
|
59
|
+
@on_fallback_callback = block
|
60
|
+
end
|
66
61
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
62
|
+
def on_fallback_callback!(key)
|
63
|
+
@on_fallback_callback&.call(key)
|
64
|
+
rescue => e
|
65
|
+
logger.warn(
|
66
|
+
"RailsFailover::ActiveRecord.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
|
67
|
+
)
|
68
|
+
end
|
74
69
|
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
def reading_role
|
71
|
+
::ActiveRecord.try(:reading_role) || ::ActiveRecord::Base.reading_role
|
72
|
+
end
|
78
73
|
|
79
|
-
|
80
|
-
|
74
|
+
def writing_role
|
75
|
+
::ActiveRecord.try(:writing_role) || ::ActiveRecord::Base.writing_role
|
76
|
+
end
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
data/rails_failover.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Failover for ActiveRecord and Redis"
|
12
12
|
spec.homepage = "https://github.com/discourse/rails_failover"
|
13
13
|
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
15
15
|
|
16
16
|
# Specify which files should be added to the gem when it is released.
|
17
17
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
-
spec.add_dependency "activerecord", "
|
27
|
-
spec.add_dependency "railties", "
|
26
|
+
spec.add_dependency "activerecord", ">= 6.1", "<= 7.1"
|
27
|
+
spec.add_dependency "railties", ">= 6.1", "<= 7.1"
|
28
28
|
spec.add_dependency "concurrent-ruby"
|
29
29
|
|
30
30
|
spec.add_development_dependency "rake", "~> 12.0"
|
metadata
CHANGED
@@ -1,53 +1,53 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alan Tan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6.
|
20
|
-
- - "
|
19
|
+
version: '6.1'
|
20
|
+
- - "<="
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '7.1'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '6.
|
30
|
-
- - "
|
29
|
+
version: '6.1'
|
30
|
+
- - "<="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '7.1'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: railties
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '6.
|
40
|
-
- - "
|
39
|
+
version: '6.1'
|
40
|
+
- - "<="
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: '7.1'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- - "
|
47
|
+
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '6.
|
50
|
-
- - "
|
49
|
+
version: '6.1'
|
50
|
+
- - "<="
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '7.1'
|
53
53
|
- !ruby/object:Gem::Dependency
|
@@ -237,14 +237,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
237
237
|
requirements:
|
238
238
|
- - ">="
|
239
239
|
- !ruby/object:Gem::Version
|
240
|
-
version:
|
240
|
+
version: 3.0.0
|
241
241
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
242
|
requirements:
|
243
243
|
- - ">="
|
244
244
|
- !ruby/object:Gem::Version
|
245
245
|
version: '0'
|
246
246
|
requirements: []
|
247
|
-
rubygems_version: 3.
|
247
|
+
rubygems_version: 3.4.10
|
248
248
|
signing_key:
|
249
249
|
specification_version: 4
|
250
250
|
summary: Failover for ActiveRecord and Redis
|