rails_failover 0.2.0 → 0.3.0

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