rails_failover 0.2.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsFailover
4
- VERSION = "0.2.0"
4
+ VERSION = "0.5.2"
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 UNICORN_WORKERS=5 SECRET_KEY_BASE=somekey 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,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
- spec.add_dependency('redis', '~> 4')
24
+ ["activerecord", "railties"].each do |gem_name|
25
+ spec.add_dependency gem_name, "~> 6.0"
26
+ end
25
27
  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 := 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.0
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-05-21 00:00:00.000000000 Z
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: redis
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4'
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: '4'
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/failover_handler.rb
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.0.3
95
+ rubygems_version: 3.1.2
75
96
  signing_key:
76
97
  specification_version: 4
77
- summary: Failover for PostgreSQL and Redis
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