rails_failover 1.0.0 → 2.0.1
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/.github/workflows/ci.yml +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +7 -1
- 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 +19 -53
- 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: 22d00b2fbfa1a5c8fc28cae9244e93700885a41da8f1a824bf0c381d4606fafa
|
4
|
+
data.tar.gz: 6e9763206550dfa0101ca5680f740aae32ec9d8d452b469dc7d14c033848c56c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e9fafd722150cda45594b10ff642b4296c29e14bd604a8c0c8cd1fbc89706bbcd3b339c5043478056d32302c13d59b510fb77eaa0c248eb363527cf90d74715
|
7
|
+
data.tar.gz: 55dd5bde9d208dbf9103a73474af639ef03447e4aba5c6012937670b6f627cc5119bcd09cc8559ec6ded4b7fc895e2190d55662acf5b79e5592e8f548fb4e32d
|
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
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [2.0.1] - 2023-05-30
|
9
|
+
|
10
|
+
- FIX: Use `next` instead of `break` to avoid a local jump error
|
11
|
+
|
12
|
+
## [2.0.0] - 2023-05-16
|
13
|
+
|
14
|
+
- DEV: Compatibility with Rails 7.1+ (drop support for Rails 6.0 & ruby 2.7)
|
9
15
|
|
10
16
|
## [1.0.0] - 2023-04-07
|
11
17
|
|
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,70 +4,36 @@ 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
|
+
next 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
|
55
22
|
|
56
23
|
initializer "rails_failover.insert_middleware" do |app|
|
57
|
-
|
58
|
-
ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
|
59
|
-
RailsFailover::ActiveRecord::Interceptor.handle(request, exception)
|
60
|
-
end
|
24
|
+
next unless app.config.active_record_rails_failover
|
61
25
|
|
62
|
-
|
63
|
-
|
64
|
-
|
26
|
+
ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
|
27
|
+
RailsFailover::ActiveRecord::Interceptor.handle(request, exception)
|
28
|
+
end
|
29
|
+
|
30
|
+
if !skip_middleware?(app.config)
|
31
|
+
app.middleware.unshift(RailsFailover::ActiveRecord::Middleware)
|
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.1
|
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
|