rails_failover 0.2.0 → 0.5.2
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/.rubocop.yml +4 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +6 -5
- data/Gemfile.lock +65 -2
- data/README.md +79 -10
- data/bin/console +2 -1
- data/bin/rspec +1 -1
- data/lib/rails_failover.rb +0 -2
- data/lib/rails_failover/active_record.rb +62 -0
- data/lib/rails_failover/active_record/handler.rb +140 -0
- data/lib/rails_failover/active_record/middleware.rb +103 -0
- data/lib/rails_failover/active_record/railtie.rb +58 -0
- data/lib/rails_failover/redis.rb +32 -15
- data/lib/rails_failover/redis/connector.rb +14 -9
- data/lib/rails_failover/redis/handler.rb +193 -0
- data/lib/rails_failover/version.rb +1 -1
- data/makefile +14 -24
- data/postgresql.mk +54 -0
- data/rails_failover.gemspec +4 -2
- data/redis.mk +28 -0
- metadata +29 -8
- data/lib/rails_failover/redis/failover_handler.rb +0 -109
data/makefile
CHANGED
@@ -1,31 +1,21 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
11
|
-
@make -s all
|
4
|
+
all: redis
|
12
5
|
|
13
|
-
|
6
|
+
active_record: teardown_dummy_rails_server setup_dummy_rails_server test_active_record
|
14
7
|
|
15
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
22
|
-
@
|
14
|
+
start_dummy_rails_server:
|
15
|
+
@cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile UNICORN_WORKERS=5 SECRET_KEY_BASE=somekey bundle exec unicorn -c config/unicorn.conf.rb -D -E production
|
23
16
|
|
24
|
-
|
25
|
-
@
|
17
|
+
stop_dummy_rails_server:
|
18
|
+
@kill -TERM $(shell cat spec/support/dummy_app/tmp/pids/unicorn.pid)
|
26
19
|
|
27
|
-
|
28
|
-
@
|
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
|
data/postgresql.mk
ADDED
@@ -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)
|
data/rails_failover.gemspec
CHANGED
@@ -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
|
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,7 @@ 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
|
-
|
24
|
+
["activerecord", "railties"].each do |gem_name|
|
25
|
+
spec.add_dependency gem_name, "~> 6.0"
|
26
|
+
end
|
25
27
|
end
|
data/redis.mk
ADDED
@@ -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 := primary.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_primary start_redis_replica
|
16
|
+
stop_redis: stop_redis_replica stop_redis_primary
|
17
|
+
|
18
|
+
stop_redis_primary:
|
19
|
+
@redis-cli -p ${REDIS_PORT} shutdown
|
20
|
+
|
21
|
+
start_redis_primary:
|
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} --slaveof 127.0.0.1 ${REDIS_PORT} --dbfilename ${REDIS_REPLICA_DBFILENAME} --logfile /dev/null
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.5.2
|
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-
|
11
|
+
date: 2020-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.0'
|
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: '
|
26
|
+
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
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'
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
- tgx@discourse.org
|
@@ -35,6 +49,7 @@ files:
|
|
35
49
|
- ".rspec"
|
36
50
|
- ".rubocop.yml"
|
37
51
|
- ".travis.yml"
|
52
|
+
- CHANGELOG.md
|
38
53
|
- CODE_OF_CONDUCT.md
|
39
54
|
- Gemfile
|
40
55
|
- Gemfile.lock
|
@@ -45,13 +60,19 @@ files:
|
|
45
60
|
- bin/rspec
|
46
61
|
- bin/setup
|
47
62
|
- lib/rails_failover.rb
|
63
|
+
- lib/rails_failover/active_record.rb
|
64
|
+
- lib/rails_failover/active_record/handler.rb
|
65
|
+
- lib/rails_failover/active_record/middleware.rb
|
66
|
+
- lib/rails_failover/active_record/railtie.rb
|
48
67
|
- lib/rails_failover/redis.rb
|
49
68
|
- lib/rails_failover/redis/connector.rb
|
50
|
-
- lib/rails_failover/redis/
|
69
|
+
- lib/rails_failover/redis/handler.rb
|
51
70
|
- lib/rails_failover/version.rb
|
52
71
|
- lib/redis/patches/client.rb
|
53
72
|
- makefile
|
73
|
+
- postgresql.mk
|
54
74
|
- rails_failover.gemspec
|
75
|
+
- redis.mk
|
55
76
|
homepage:
|
56
77
|
licenses:
|
57
78
|
- MIT
|
@@ -71,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
92
|
- !ruby/object:Gem::Version
|
72
93
|
version: '0'
|
73
94
|
requirements: []
|
74
|
-
rubygems_version: 3.
|
95
|
+
rubygems_version: 3.1.2
|
75
96
|
signing_key:
|
76
97
|
specification_version: 4
|
77
|
-
summary: Failover for
|
98
|
+
summary: Failover for ActiveRecord and Redis
|
78
99
|
test_files: []
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'monitor'
|
4
|
-
require 'singleton'
|
5
|
-
|
6
|
-
module RailsFailover
|
7
|
-
class Redis
|
8
|
-
class FailoverHandler
|
9
|
-
include Singleton
|
10
|
-
include MonitorMixin
|
11
|
-
|
12
|
-
MASTER_ROLE_STATUS = "role:master"
|
13
|
-
MASTER_LOADED_STATUS = "loading:0"
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
@master = true
|
17
|
-
@clients = []
|
18
|
-
|
19
|
-
super() # Monitor#initialize
|
20
|
-
end
|
21
|
-
|
22
|
-
def verify_master(options)
|
23
|
-
mon_synchronize do
|
24
|
-
return if @thread&.alive?
|
25
|
-
|
26
|
-
self.master = false
|
27
|
-
disconnect_clients
|
28
|
-
RailsFailover::Redis.master_down_callbacks.each { |callback| callback.call }
|
29
|
-
|
30
|
-
@thread = Thread.new do
|
31
|
-
loop do
|
32
|
-
thread = Thread.new { initiate_fallback_to_master(options) }
|
33
|
-
thread.join
|
34
|
-
break if self.master
|
35
|
-
sleep (RailsFailover::Redis.verify_master_frequency_seconds + (Time.now.to_i % RailsFailover::Redis.verify_master_frequency_seconds))
|
36
|
-
ensure
|
37
|
-
thread.kill
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def initiate_fallback_to_master(options)
|
44
|
-
info = nil
|
45
|
-
|
46
|
-
begin
|
47
|
-
master_client = ::Redis::Client.new(options.dup)
|
48
|
-
log "#{log_prefix}: Checking connection to master server..."
|
49
|
-
info = master_client.call([:info])
|
50
|
-
rescue => e
|
51
|
-
log "#{log_prefix}: Connection to Master server failed with '#{e.message}'"
|
52
|
-
ensure
|
53
|
-
master_client&.disconnect
|
54
|
-
end
|
55
|
-
|
56
|
-
if info && info.include?(MASTER_LOADED_STATUS) && info.include?(MASTER_ROLE_STATUS)
|
57
|
-
self.master = true
|
58
|
-
log "#{log_prefix}: Master server is active, disconnecting clients from replica"
|
59
|
-
disconnect_clients
|
60
|
-
RailsFailover::Redis.master_up_callbacks.each { |callback| callback.call }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def register_client(client)
|
65
|
-
mon_synchronize do
|
66
|
-
@clients << client
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def deregister_client(client)
|
71
|
-
mon_synchronize do
|
72
|
-
@clients.delete(client)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def clients
|
77
|
-
mon_synchronize { @clients }
|
78
|
-
end
|
79
|
-
|
80
|
-
def master
|
81
|
-
mon_synchronize { @master }
|
82
|
-
end
|
83
|
-
|
84
|
-
def master=(args)
|
85
|
-
mon_synchronize { @master = args }
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def disconnect_clients
|
91
|
-
mon_synchronize do
|
92
|
-
@clients.dup.each do |c|
|
93
|
-
c.disconnect
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def log(message)
|
99
|
-
if logger = RailsFailover::Redis.logger
|
100
|
-
logger.warn(message)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def log_prefix
|
105
|
-
"#{self.class}"
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|