rails_failover 0.2.0 → 0.3.0

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: 6b47ce7eef70576e83fe137949d50e280677f6f5e2444790a9bdf7cc1708f540
4
- data.tar.gz: eea78a86a311a24926c83ab4450eae6ed59b0f39a60faafdf9affe050074aa7d
3
+ metadata.gz: 307117d6a7083a17a2bf2799737f6f9be3afa77a58bace4f867bedf38fa05a5c
4
+ data.tar.gz: 9d9d54a7a46785f259ba3f19aad402534de7b5305bd57993cb9c92fd14efad3b
5
5
  SHA512:
6
- metadata.gz: 46aea75592369b8a0af7a48ee1898d2fdfecb8a80ebc777ba386052709678258d719a7e2e2d4d00f1c6c3ca7d158747eecb0a329373ff3a8f6423c6f70824f27
7
- data.tar.gz: aa006f95128b6ec9fe600652c62d20c32b948aeff049cf2155d5366b97a57d851631e8de6c128f2f0587c0e4d97a797252b5c2f196a98d7d245e3b0a539d8b53
6
+ metadata.gz: 7c45a722a9d8bbe1d0ea1410630252c5485496a49b6affe5461615779102d25bfafbf0d990bce920d8e52f164ebe1b92e94a1ccd1787d526b98cba54fc039d45
7
+ data.tar.gz: 3dcd9668ef0266b91c35cb3764533fab3624025a3d4dd6db86ce1adafb8168c9d72dde14bbd57fbcb73f7f0049941e6b077eaba519b70b3fa1b03eb92d730b64
data/Gemfile CHANGED
@@ -9,5 +9,11 @@ gem "rspec", "~> 3.0"
9
9
 
10
10
  group :development do
11
11
  gem 'rubocop-discourse'
12
+ end
13
+
14
+ group :development, :test do
12
15
  gem 'byebug'
16
+ gem 'redis', '~> 4.1'
17
+ gem 'pg', '~> 1.2'
18
+ gem 'activerecord', '~> 6.0'
13
19
  end
@@ -1,20 +1,82 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_failover (0.1.0)
5
- redis (~> 4)
4
+ rails_failover (0.3.0)
5
+ activerecord (~> 6.0)
6
+ listen (~> 3.2)
7
+ railties (~> 6.0)
6
8
 
7
9
  GEM
8
10
  remote: https://rubygems.org/
9
11
  specs:
12
+ actionpack (6.0.3.1)
13
+ actionview (= 6.0.3.1)
14
+ activesupport (= 6.0.3.1)
15
+ rack (~> 2.0, >= 2.0.8)
16
+ rack-test (>= 0.6.3)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
19
+ actionview (6.0.3.1)
20
+ activesupport (= 6.0.3.1)
21
+ builder (~> 3.1)
22
+ erubi (~> 1.4)
23
+ rails-dom-testing (~> 2.0)
24
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
25
+ activemodel (6.0.3.1)
26
+ activesupport (= 6.0.3.1)
27
+ activerecord (6.0.3.1)
28
+ activemodel (= 6.0.3.1)
29
+ activesupport (= 6.0.3.1)
30
+ activesupport (6.0.3.1)
31
+ concurrent-ruby (~> 1.0, >= 1.0.2)
32
+ i18n (>= 0.7, < 2)
33
+ minitest (~> 5.1)
34
+ tzinfo (~> 1.1)
35
+ zeitwerk (~> 2.2, >= 2.2.2)
10
36
  ast (2.4.0)
37
+ builder (3.2.4)
11
38
  byebug (11.1.3)
39
+ concurrent-ruby (1.1.6)
40
+ crass (1.0.6)
12
41
  diff-lcs (1.3)
42
+ erubi (1.9.0)
43
+ ffi (1.12.2)
44
+ i18n (1.8.2)
45
+ concurrent-ruby (~> 1.0)
46
+ listen (3.2.1)
47
+ rb-fsevent (~> 0.10, >= 0.10.3)
48
+ rb-inotify (~> 0.9, >= 0.9.10)
49
+ loofah (2.5.0)
50
+ crass (~> 1.0.2)
51
+ nokogiri (>= 1.5.9)
52
+ method_source (1.0.0)
53
+ mini_portile2 (2.4.0)
54
+ minitest (5.14.1)
55
+ nokogiri (1.10.9)
56
+ mini_portile2 (~> 2.4.0)
13
57
  parallel (1.19.1)
14
58
  parser (2.7.1.2)
15
59
  ast (~> 2.4.0)
60
+ pg (1.2.3)
61
+ rack (2.2.2)
62
+ rack-test (1.1.0)
63
+ rack (>= 1.0, < 3)
64
+ rails-dom-testing (2.0.3)
65
+ activesupport (>= 4.2.0)
66
+ nokogiri (>= 1.6)
67
+ rails-html-sanitizer (1.3.0)
68
+ loofah (~> 2.3)
69
+ railties (6.0.3.1)
70
+ actionpack (= 6.0.3.1)
71
+ activesupport (= 6.0.3.1)
72
+ method_source
73
+ rake (>= 0.8.7)
74
+ thor (>= 0.20.3, < 2.0)
16
75
  rainbow (3.0.0)
17
76
  rake (12.3.3)
77
+ rb-fsevent (0.10.4)
78
+ rb-inotify (0.10.1)
79
+ ffi (~> 1.0)
18
80
  redis (4.1.4)
19
81
  rexml (3.2.4)
20
82
  rspec (3.9.0)
@@ -43,15 +105,23 @@ GEM
43
105
  rubocop-rspec (1.39.0)
44
106
  rubocop (>= 0.68.1)
45
107
  ruby-progressbar (1.10.1)
108
+ thor (1.0.1)
109
+ thread_safe (0.3.6)
110
+ tzinfo (1.2.7)
111
+ thread_safe (~> 0.1)
46
112
  unicode-display_width (1.7.0)
113
+ zeitwerk (2.3.0)
47
114
 
48
115
  PLATFORMS
49
116
  ruby
50
117
 
51
118
  DEPENDENCIES
119
+ activerecord (~> 6.0)
52
120
  byebug
121
+ pg (~> 1.2)
53
122
  rails_failover!
54
123
  rake (~> 12.0)
124
+ redis (~> 4.1)
55
125
  rspec (~> 3.0)
56
126
  rubocop-discourse
57
127
 
data/README.md CHANGED
@@ -20,6 +20,22 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
+ ### ActiveRecord
24
+
25
+ In `config/application.rb` add `require 'rails_failover/active_record'` after `require "active_record/railtie"`.
26
+
27
+ In your database configuration, simply add `replica_host` and `replica_port` to your database configuration.
28
+
29
+ ```
30
+ production:
31
+ host: <primary db server host>
32
+ port: <primary db server port>
33
+ replica_host: <replica db server host>
34
+ replica_port: <replica db server port>
35
+ ```
36
+
37
+ The gem will automatically create an `ActiveRecord::ConnectionAdapters::ConnectionHandler` with the `ActiveRecord::Base.reading_role` as the `handler_key`.
38
+
23
39
  ### Redis
24
40
 
25
41
  ```
@@ -41,13 +57,27 @@ end
41
57
 
42
58
  ## Development
43
59
 
44
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
60
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
45
61
 
46
62
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
47
63
 
64
+ ### Testing
65
+
66
+ #### ActiveRecord
67
+
68
+ The ActiveRecord failover tests are run against a dummy Rails server. Run the following commands to run the test:
69
+
70
+ 1. `make setup_pg`
71
+ 1. `make start_pg`
72
+ 1. `bin/rspec active_record`. You may also run the tests with more unicorn workers by adding the `UNICORN_WORKERS` env variable.
73
+
74
+ #### Redis
75
+
76
+ `bin/rspec redis`
77
+
48
78
  ## Contributing
49
79
 
50
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rails_failover. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/rails_failover/blob/master/CODE_OF_CONDUCT.md).
80
+ Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/rails_failover. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/discourse/rails_failover/blob/master/CODE_OF_CONDUCT.md).
51
81
 
52
82
 
53
83
  ## License
@@ -56,4 +86,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
56
86
 
57
87
  ## Code of Conduct
58
88
 
59
- Everyone interacting in the RailsFailover project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/rails_failover/blob/master/CODE_OF_CONDUCT.md).
89
+ Everyone interacting in the RailsFailover project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/discourse/rails_failover/blob/master/CODE_OF_CONDUCT.md).
@@ -3,7 +3,8 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "rails_failover"
6
- require 'redis'
6
+ require "rails_failover/redis"
7
+ require "rails_failover/active_record"
7
8
 
8
9
  # You can add fixtures and/or initialization code here to make experimenting
9
10
  # with your gem easier. You can also use a different console, if you like.
data/bin/rspec CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env bash
2
- make RSPEC_PATH=$1
2
+ RSPEC_PATH=$2 make -s $1
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails_failover/version"
4
- require "redis/patches/client"
5
- require "rails_failover/redis"
6
4
 
7
5
  module RailsFailover
8
6
  class Error < StandardError; end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require_relative 'active_record/railtie'
5
+ require_relative 'active_record/middleware'
6
+ require_relative 'active_record/handler'
7
+
8
+ module RailsFailover
9
+ module ActiveRecord
10
+ def self.logger=(logger)
11
+ @logger = logger
12
+ end
13
+
14
+ def self.logger
15
+ @logger || Rails.logger
16
+ end
17
+
18
+ def self.verify_primary_frequency_seconds=(seconds)
19
+ @verify_primary_frequency_seconds = seconds
20
+ end
21
+
22
+ def self.verify_primary_frequency_seconds
23
+ @verify_primary_frequency_seconds || 5
24
+ end
25
+
26
+ def self.establish_reading_connection(connection_spec)
27
+ config = connection_spec.config
28
+
29
+ if config[:replica_host] && config[:replica_port]
30
+ replica_config = config.dup
31
+
32
+ replica_config[:host] = replica_config.delete(:replica_host)
33
+ replica_config[:port] = replica_config.delete(:replica_port)
34
+ replica_config[:replica] = true
35
+
36
+ handler = ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.reading_role]
37
+ handler.establish_connection(replica_config)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+ require 'singleton'
3
+ require 'monitor'
4
+ require 'listen'
5
+ require 'fileutils'
6
+
7
+ module RailsFailover
8
+ module ActiveRecord
9
+ class Handler
10
+ include Singleton
11
+ include MonitorMixin
12
+
13
+ SEPERATOR = "__RAILS_FAILOVER__"
14
+ VERIFY_FREQUENCY_BUFFER_PRECENT = 20
15
+
16
+ def initialize
17
+ @primaries_down = {}
18
+ @ancestor_pid = Process.pid
19
+
20
+ @dir = '/tmp/rails_failover'
21
+ FileUtils.remove_dir(@dir) if Dir.exists?(@dir)
22
+ FileUtils.mkdir_p(@dir)
23
+
24
+ @listener = Listen.to(@dir) do |modified, added, removed|
25
+ if added.length > 0
26
+ added.each do |f|
27
+ pid, handler_key = File.basename(f).split(SEPERATOR)
28
+
29
+ if Process.pid != pid
30
+ verify_primary(handler_key.to_sym, publish: false)
31
+ end
32
+ end
33
+ end
34
+
35
+ if removed.length > 0
36
+ removed.each do |f|
37
+ pid, handler_key = File.basename(f).split(SEPERATOR)
38
+
39
+ if Process.pid != pid
40
+ primary_up(handler_key.to_sym)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ super() # Monitor#initialize
47
+ end
48
+
49
+ def start_listener
50
+ @listener.start
51
+ end
52
+
53
+ def verify_primary(handler_key, publish: true)
54
+ mon_synchronize do
55
+ primary_down(handler_key)
56
+ publish_primary_down(handler_key) if publish
57
+ return if @thread&.alive? && @thread["pid"] == Process.pid
58
+
59
+ @thread = Thread.new do
60
+ loop do
61
+ initiate_fallback_to_primary
62
+ break if all_primaries_up
63
+ end
64
+ end
65
+
66
+ @thread["pid"] = Process.pid
67
+ @thread
68
+ end
69
+ end
70
+
71
+ def initiate_fallback_to_primary
72
+ frequency = RailsFailover::ActiveRecord.verify_primary_frequency_seconds
73
+ sleep(frequency * ((rand(VERIFY_FREQUENCY_BUFFER_PRECENT) + 100) / 100.0))
74
+
75
+ active_handler_keys = []
76
+
77
+ primaries_down.keys.each do |handler_key|
78
+ connection_handler = ::ActiveRecord::Base.connection_handlers[handler_key]
79
+ spec = connection_handler.retrieve_connection_pool(spec_name).spec
80
+ config = spec.config
81
+ logger.warn "#{Process.pid} Checking server for '#{handler_key} #{spec_name}'..."
82
+ connection_active = false
83
+
84
+ begin
85
+ connection = ::ActiveRecord::Base.public_send(spec.adapter_method, config)
86
+ connection_active = connection.active?
87
+ rescue => e
88
+ logger.warn "#{Process.pid} Connection to server for '#{handler_key} #{spec_name}' failed with '#{e.message}'"
89
+ ensure
90
+ connection.disconnect! if connection
91
+ end
92
+
93
+ if connection_active
94
+ logger.warn "#{Process.pid} Server for '#{handler_key} #{spec_name}' is active."
95
+ active_handler_keys << handler_key
96
+ end
97
+ end
98
+
99
+ active_handler_keys.each do |handler_key|
100
+ primary_up(handler_key)
101
+ publish_primary_up(handler_key)
102
+ end
103
+ end
104
+
105
+ def primary_down?(handler_key)
106
+ primaries_down[handler_key]
107
+ end
108
+
109
+ private
110
+
111
+ def all_primaries_up
112
+ mon_synchronize do
113
+ primaries_down.empty?
114
+ end
115
+ end
116
+
117
+
118
+ def primary_down(handler_key)
119
+ mon_synchronize do
120
+ primaries_down[handler_key] = true
121
+ end
122
+ end
123
+
124
+ def publish_primary_down(handler_key)
125
+ FileUtils.touch("#{@dir}/#{Process.pid}#{SEPERATOR}#{handler_key}")
126
+ end
127
+
128
+ def publish_primary_up(handler_key)
129
+ path = "#{@dir}/#{Process.pid}#{SEPERATOR}#{handler_key}"
130
+
131
+ if File.exists?(path)
132
+ FileUtils.rm("#{@dir}/#{Process.pid}#{SEPERATOR}#{handler_key}")
133
+ end
134
+ end
135
+
136
+ def primary_up(handler_key)
137
+ mon_synchronize do
138
+ primaries_down.delete(handler_key)
139
+ end
140
+ end
141
+
142
+ def spec_name
143
+ ::ActiveRecord::Base.connection_specification_name
144
+ end
145
+
146
+ def primaries_down
147
+ process_pid = Process.pid
148
+ return @primaries_down[process_pid] if @primaries_down[process_pid]
149
+
150
+ mon_synchronize do
151
+ if !@primaries_down[process_pid]
152
+ @primaries_down[process_pid] = @primaries_down[@ancestor_pid] || {}
153
+
154
+ if process_pid != @ancestor_pid
155
+ @primaries_down.delete(@ancestor_pid)
156
+
157
+ @primaries_down[process_pid].each_key do |handler_key|
158
+ verify_primary(handler_key)
159
+ end
160
+ end
161
+ end
162
+
163
+ @primaries_down[process_pid]
164
+ end
165
+ end
166
+
167
+ def logger
168
+ Rails.logger
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsFailover
4
+ module ActiveRecord
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ writing_role = ::ActiveRecord::Base.writing_role
12
+
13
+ role =
14
+ if primary_down = Handler.instance.primary_down?(writing_role)
15
+ ::ActiveRecord::Base.reading_role
16
+ else
17
+ ::ActiveRecord::Base.writing_role
18
+ end
19
+
20
+ ::ActiveRecord::Base.connected_to(role: role) do
21
+ env["rails_failover.role"] = role
22
+ @app.call(env)
23
+ end
24
+ rescue Exception => e
25
+ if (resolve_cause(e).is_a?(::PG::Error))
26
+ Handler.instance.verify_primary(writing_role)
27
+ raise
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def resolve_cause(error)
34
+ if error.cause
35
+ resolve_cause(error.cause)
36
+ else
37
+ error
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ module RailsFailover
2
+ module ActiveRecord
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "rails_failover.init", after: "active_record.initialize_database" do
5
+ ::ActiveSupport.on_load(:active_record) do
6
+ Handler.instance
7
+
8
+ # We are doing this manually for now since we're awaiting Rails 6.1 to be released which will
9
+ # have more stable ActiveRecord APIs for handling multiple databases with different roles.
10
+ ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.reading_role] =
11
+ ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
12
+
13
+ ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.writing_role].connection_pools.each do |connection_pool|
14
+ RailsFailover::ActiveRecord.establish_reading_connection(connection_pool.spec)
15
+ end
16
+
17
+ begin
18
+ ::ActiveRecord::Base.connection
19
+ rescue ::ActiveRecord::NoDatabaseError
20
+ # Do nothing since database hasn't been created
21
+ rescue ::PG::Error => e
22
+ Handler.instance.verify_primary(::ActiveRecord::Base.writing_role)
23
+ ::ActiveRecord::Base.connection_handler = ::ActiveRecord::Base.lookup_connection_handler(:reading)
24
+ end
25
+ end
26
+ end
27
+
28
+ initializer "rails_failover.insert_middleware" do |app|
29
+ app.middleware.insert_after(
30
+ ::ActionDispatch::ActionableExceptions,
31
+ ::RailsFailover::ActiveRecord::Middleware
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'redis'
4
+
5
+ supported_version = '4'
6
+
7
+ if Gem::Version.new(Redis::VERSION) < Gem::Version.new(supported_version)
8
+ raise "redis #{Redis::VERSION} is not supported. Please upgrade to Redis #{supported_version}."
9
+ end
10
+
11
+ require_relative "../redis/patches/client"
3
12
  require_relative 'redis/connector'
4
13
 
5
14
  module RailsFailover
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'failover_handler'
3
+ require_relative 'handler'
4
4
 
5
5
  module RailsFailover
6
6
  class Redis
7
- class Connector
7
+ class Connector < ::Redis::Client::Connector
8
8
  def initialize(options)
9
9
  options[:original_driver] = options[:driver]
10
10
 
@@ -22,7 +22,7 @@ module RailsFailover
22
22
  Errno::ETIMEDOUT,
23
23
  Errno::EINVAL => e
24
24
 
25
- FailoverHandler.instance.verify_master(options.dup)
25
+ Handler.instance.verify_master(options.dup)
26
26
  raise e
27
27
  end
28
28
  end
@@ -33,15 +33,15 @@ module RailsFailover
33
33
  end
34
34
 
35
35
  def resolve
36
- FailoverHandler.instance.master ? @options : @replica_options
36
+ Handler.instance.master ? @options : @replica_options
37
37
  end
38
38
 
39
39
  def check(client)
40
- FailoverHandler.instance.register_client(client)
40
+ Handler.instance.register_client(client)
41
41
  end
42
42
 
43
43
  def on_disconnect(client)
44
- FailoverHandler.instance.deregister_client(client)
44
+ Handler.instance.deregister_client(client)
45
45
  end
46
46
 
47
47
  private
@@ -5,7 +5,7 @@ require 'singleton'
5
5
 
6
6
  module RailsFailover
7
7
  class Redis
8
- class FailoverHandler
8
+ class Handler
9
9
  include Singleton
10
10
  include MonitorMixin
11
11
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsFailover
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/makefile CHANGED
@@ -1,31 +1,21 @@
1
- PORT := 6381
2
- PID_PATH := /tmp/redis.pid
3
- SOCKET_PATH := /tmp/redis.sock
4
- DBFILENAME := master.rdb
5
- replica_port := 6382
6
- REPLICA_PID_PATH := /tmp/redis_replica.pid
7
- REPLICA_SOCKET_PATH := /tmp/redis_replica.sock
8
- REPLICA_DBFILENAME := replica.rdb
1
+ include postgresql.mk
2
+ include redis.mk
9
3
 
10
- default:
11
- @make -s all
4
+ all: redis
12
5
 
13
- all: start test stop
6
+ active_record: teardown_dummy_rails_server setup_dummy_rails_server test_active_record
14
7
 
15
- test:
16
- bundle exec rspec ${RSPEC_PATH}
8
+ test_active_record:
9
+ @ACTIVE_RECORD=1 bundle exec rspec --tag type:active_record ${RSPEC_PATH}
17
10
 
18
- start: start_master start_replica
19
- stop: stop_replica stop_master
11
+ setup_dummy_rails_server:
12
+ @cd spec/support/dummy_app && bundle install --quiet --without test --without development && yarn install && RAILS_ENV=production $(BUNDLER_BIN) exec rails db:create db:migrate db:seed
20
13
 
21
- stop_master:
22
- @redis-cli -p ${PORT} shutdown
14
+ start_dummy_rails_server:
15
+ @cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile bundle exec unicorn -c config/unicorn.conf.rb -D -E production
23
16
 
24
- start_master:
25
- @redis-server --daemonize yes --pidfile ${PID_PATH} --port ${PORT} --unixsocket ${SOCKET_PATH} --dbfilename ${DBFILENAME} --logfile /dev/null
17
+ stop_dummy_rails_server:
18
+ @kill -TERM $(shell cat spec/support/dummy_app/tmp/pids/unicorn.pid)
26
19
 
27
- stop_replica:
28
- @redis-cli -p ${replica_port} shutdown
29
-
30
- start_replica:
31
- @redis-server --daemonize yes --pidfile ${REPLICA_PID_PATH} --port ${replica_port} --unixsocket ${REPLICA_SOCKET_PATH} --replicaof 127.0.0.1 ${PORT} --dbfilename ${REPLICA_DBFILENAME} --logfile /dev/null
20
+ teardown_dummy_rails_server:
21
+ @cd spec/support/dummy_app && DISABLE_DATABASE_ENVIRONMENT_CHECK=1 RAILS_ENV=production $(BUNDLER_BIN) exec rails db:drop
@@ -0,0 +1,54 @@
1
+ PG_BIN_DIR := $(shell pg_config --bindir)
2
+ PWD := $(shell pwd)
3
+ PG_PRIMARY_DIR := $(PWD)/tmp/primary
4
+ PG_PRIMARY_DATA_DIR := $(PG_PRIMARY_DIR)/data
5
+ PG_PRIMARY_RUN_DIR := $(PG_PRIMARY_DIR)/run
6
+ PG_REPLICA_DIR := $(PWD)/tmp/replica
7
+ PG_REPLICA_DATA_DIR := $(PG_REPLICA_DIR)/data
8
+ PG_REPLICA_RUN_DIR := $(PG_REPLICA_DIR)/run
9
+ PG_PRIMARY_PORT := 5434
10
+ PG_REPLICA_PORT := 5435
11
+ PG_REPLICATION_USER := replication
12
+ PG_REPLICATION_PASSWORD := password
13
+ PG_REPLICATION_SLOT_NAME := replication
14
+
15
+ setup_pg: init_primary start_pg_primary setup_primary init_replica stop_pg_primary
16
+
17
+ setup_primary:
18
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE USER $(PG_REPLICATION_USER) WITH REPLICATION ENCRYPTED PASSWORD '$(PG_REPLICATION_PASSWORD)';"
19
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "SELECT * FROM pg_create_physical_replication_slot('$(PG_REPLICATION_SLOT_NAME)');"
20
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE USER test;"
21
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE DATABASE test;"
22
+
23
+ start_pg: start_pg_primary start_pg_replica
24
+
25
+ stop_pg: stop_pg_replica stop_pg_primary
26
+
27
+ init_primary:
28
+ @mkdir -p $(PG_PRIMARY_DATA_DIR)
29
+ @mkdir -p $(PG_PRIMARY_RUN_DIR)
30
+ @$(PG_BIN_DIR)/initdb -E UTF8 -D $(PG_PRIMARY_DATA_DIR)
31
+
32
+ init_replica:
33
+ @mkdir -p $(PG_REPLICA_DATA_DIR)
34
+ @mkdir -p $(PG_REPLICA_RUN_DIR)
35
+ @PGPASSWORD=$(PG_REPLICATION_PASSWORD) $(PG_BIN_DIR)/pg_basebackup -D $(PG_REPLICA_DATA_DIR) -X stream -h $(PG_PRIMARY_RUN_DIR) -p $(PG_PRIMARY_PORT) -U $(PG_REPLICATION_USER) -w -R
36
+ @chmod 0700 $(PG_REPLICA_DATA_DIR)
37
+
38
+ start_pg_primary:
39
+ @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" start
40
+
41
+ start_pg_replica:
42
+ @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" start
43
+
44
+ restart_pg_primary:
45
+ @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" restart
46
+
47
+ stop_pg_primary:
48
+ @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" stop
49
+
50
+ stop_pg_replica:
51
+ @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" stop
52
+
53
+ cleanup_pg:
54
+ @rm -rf $(PG_PRIMARY_DIR) $(PG_REPLICA_DIR)
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Alan Tan"]
9
9
  spec.email = ["tgx@discourse.org"]
10
10
 
11
- spec.summary = %q{Failover for PostgreSQL and Redis}
11
+ spec.summary = %q{Failover for ActiveRecord and Redis}
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
14
 
@@ -21,5 +21,9 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency('redis', '~> 4')
24
+ spec.add_dependency 'listen', "~> 3.2"
25
+
26
+ ["activerecord", "railties"].each do |gem_name|
27
+ spec.add_dependency gem_name, "~> 6.0"
28
+ end
25
29
  end
@@ -0,0 +1,28 @@
1
+ REDIS_PORT := 6381
2
+ REDIS_PID_PATH := /tmp/redis.pid
3
+ REDIS_SOCKET_PATH := /tmp/redis.sock
4
+ REDIS_DBFILENAME := master.rdb
5
+ REDIS_REPLICA_PORT := 6382
6
+ REDIS_REPLICA_PID_PATH := /tmp/redis_replica.pid
7
+ REDIS_REPLICA_SOCKET_PATH := /tmp/redis_replica.sock
8
+ REDIS_REPLICA_DBFILENAME := replica.rdb
9
+
10
+ redis: start_redis test_redis stop_redis
11
+
12
+ test_redis:
13
+ @REDIS=1 bundle exec rspec --tag type:redis ${RSPEC_PATH}
14
+
15
+ start_redis: start_redis_master start_redis_replica
16
+ stop_redis: stop_redis_replica stop_redis_master
17
+
18
+ stop_redis_master:
19
+ @redis-cli -p ${REDIS_PORT} shutdown
20
+
21
+ start_redis_master:
22
+ @redis-server --daemonize yes --pidfile ${REDIS_PID_PATH} --port ${REDIS_PORT} --unixsocket ${REDIS_SOCKET_PATH} --dbfilename ${REDIS_DBFILENAME} --logfile /dev/null
23
+
24
+ stop_redis_replica:
25
+ @redis-cli -p ${REDIS_REPLICA_PORT} shutdown
26
+
27
+ start_redis_replica:
28
+ @redis-server --daemonize yes --pidfile ${REDIS_REPLICA_PID_PATH} --port ${REDIS_REPLICA_PORT} --unixsocket ${REDIS_REPLICA_SOCKET_PATH} --replicaof 127.0.0.1 ${REDIS_PORT} --dbfilename ${REDIS_REPLICA_DBFILENAME} --logfile /dev/null
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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: 2020-05-21 00:00:00.000000000 Z
11
+ date: 2020-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: redis
14
+ name: listen
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4'
19
+ version: '3.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4'
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: railties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '6.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '6.0'
27
55
  description:
28
56
  email:
29
57
  - tgx@discourse.org
@@ -45,13 +73,19 @@ files:
45
73
  - bin/rspec
46
74
  - bin/setup
47
75
  - lib/rails_failover.rb
76
+ - lib/rails_failover/active_record.rb
77
+ - lib/rails_failover/active_record/handler.rb
78
+ - lib/rails_failover/active_record/middleware.rb
79
+ - lib/rails_failover/active_record/railtie.rb
48
80
  - lib/rails_failover/redis.rb
49
81
  - lib/rails_failover/redis/connector.rb
50
- - lib/rails_failover/redis/failover_handler.rb
82
+ - lib/rails_failover/redis/handler.rb
51
83
  - lib/rails_failover/version.rb
52
84
  - lib/redis/patches/client.rb
53
85
  - makefile
86
+ - postgresql.mk
54
87
  - rails_failover.gemspec
88
+ - redis.mk
55
89
  homepage:
56
90
  licenses:
57
91
  - MIT
@@ -74,5 +108,5 @@ requirements: []
74
108
  rubygems_version: 3.0.3
75
109
  signing_key:
76
110
  specification_version: 4
77
- summary: Failover for PostgreSQL and Redis
111
+ summary: Failover for ActiveRecord and Redis
78
112
  test_files: []