rails-pg-adapter 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/CODE_OF_CONDUCT.md +11 -11
- data/README.md +30 -12
- data/lib/rails_pg_adapter/configuration.rb +8 -1
- data/lib/rails_pg_adapter/patch.rb +55 -3
- data/lib/rails_pg_adapter/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 432740419d2edda4a33caf34ba5efa2af778456763db8864c632b3d813f1953e
|
4
|
+
data.tar.gz: 60a026760c20847ace00bf181878c0039eface04fa30ff51227e677be88f95c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fb1aac2ec7330a599cc9005d339fab13613b409cf37152a003826b760c02b8b4060b0134d50799be4db7d77e6db4165b6ef99d0b1df0399133749f4746c5a25
|
7
|
+
data.tar.gz: 0450d73b3e3330da51c01d64a922828da6b32fe1d500d2c719ee40bd5dbbc2ac2c40d87cc93bc3a8a53676c3476f61180684a6bf05cf301d876f393eab6b5bee
|
data/CHANGELOG.md
CHANGED
data/CODE_OF_CONDUCT.md
CHANGED
@@ -10,21 +10,21 @@ We pledge to act and interact in ways that contribute to an open, welcoming, div
|
|
10
10
|
|
11
11
|
Examples of behavior that contributes to a positive environment for our community include:
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
- Demonstrating empathy and kindness toward other people
|
14
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
- Giving and gracefully accepting constructive feedback
|
16
|
+
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
- Focusing on what is best not just for us as individuals, but for the overall community
|
18
18
|
|
19
19
|
Examples of unacceptable behavior include:
|
20
20
|
|
21
|
-
|
21
|
+
- The use of sexualized language or imagery, and sexual attention or
|
22
22
|
advances of any kind
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
- Public or private harassment
|
25
|
+
- Publishing others' private information, such as a physical or email
|
26
26
|
address, without their explicit permission
|
27
|
-
|
27
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
28
28
|
professional setting
|
29
29
|
|
30
30
|
## Enforcement Responsibilities
|
@@ -67,7 +67,7 @@ Community leaders will follow these Community Impact Guidelines in determining t
|
|
67
67
|
|
68
68
|
### 4. Permanent Ban
|
69
69
|
|
70
|
-
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior,
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
71
|
|
72
72
|
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
73
|
|
data/README.md
CHANGED
@@ -2,16 +2,6 @@
|
|
2
2
|
|
3
3
|
This project allows you to monkey patch `ActiveRecord` (PostgreSQL) and auto-heal applications in production when PostgreSQL database fails over or when a cached column (in `ActiveRecord` schema cache) is removed from the database from a migration in another process.
|
4
4
|
|
5
|
-
## How does it work
|
6
|
-
|
7
|
-
During a database failover in production, the `ActiveRecord` connection pool can become exhausted as queries are made against the database during the failover process. This can leave the `ActiveRecord` connection pools with stale or bad connections, even after the database has successfully recovered. Recovering from this issue usually requires a rolling restart of the application processes or containers.
|
8
|
-
|
9
|
-
`RailsPgAdapter` addresses this problem by resetting the connection pool and re-raises the original exception from an `ActiveRecord` monkey patch. This allows the application to auto-heal from stale connections on its own (after database recovery) when performing queries for a new request, without requiring manual intervention.
|
10
|
-
|
11
|
-
Another issue with `ActiveRecord` queries is `PG::UndefinedColumn`, which occurs when an `ActiveRecord` model includes a `SELECT` query with the name of a column that has been dropped from a Rails migration. This can happen even if the column isn't being referenced anywhere in the code. It occurs when a model is using `ignored_columns`, which prompts `ActiveRecord` to perform a dedicated lookup of the allowed columns in a select, such as `SELECT "users".name, "users".template_id...."`, instead of `SELECT "users".*`. When a column like `template_id` is dropped, PostgreSQL throws an undefined column error, which is bubbled up by `ActiveRecord` into `PG::UndefinedColumn`. Recovering from this issue also usually requires a rolling restart of the application processes or containers.
|
12
|
-
|
13
|
-
`RailsPgAdapter` solves this second issue by resetting the `ActiveRecord` schema cache and memoized model column information when it detects a `PG::UndefinedColumn` raised from a monkey patch. Resetting the column information forces `ActiveRecord` to refresh its schema cache by loading the table information from the database and no longer reference the dropped column for new queries, without requiring manual intervention.
|
14
|
-
|
15
5
|
## Installation
|
16
6
|
|
17
7
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -27,7 +17,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
27
17
|
### Auto healing connections when PostgreSQL database fails over
|
28
18
|
|
29
19
|
```ruby
|
30
|
-
# config/
|
20
|
+
# config/initializers/rails_pg_adapter.rb
|
31
21
|
|
32
22
|
RailsPgAdapter.configure do |c|
|
33
23
|
c.add_failover_patch = true
|
@@ -36,10 +26,28 @@ end
|
|
36
26
|
|
37
27
|
This will add the monkey patch which resets the `ActiveRecord` connections in the connection pool when the database fails over. The patch will reset the connection and re-raise the error each time it detects that an exception related to a database failover is detected.
|
38
28
|
|
29
|
+
### Retrying queries
|
30
|
+
|
31
|
+
When the database is failing you can retry queries that are not in a transaction. The gem will perform a back off retry in establishing the connection.
|
32
|
+
Once the back off is reached and no connection is found, it will bubble up the exception. Otherwise, the query will be retried with a new connection.
|
33
|
+
|
34
|
+
It is an opt-in functionality. You can supply your own back off figures for retries (in seconds) as following:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# config/initializers/rails_pg_adapter.rb
|
38
|
+
|
39
|
+
RailsPgAdapter.configure do |c|
|
40
|
+
c.add_failover_patch = true
|
41
|
+
c.reconnect_with_backoff = [0.5, 1, 2, 4, 8, 16] # seconds
|
42
|
+
...
|
43
|
+
end
|
44
|
+
|
45
|
+
```
|
46
|
+
|
39
47
|
### Refresh model column information on the fly after an existing column is dropped
|
40
48
|
|
41
49
|
```ruby
|
42
|
-
# config/
|
50
|
+
# config/initializers/rails_pg_adapter.rb
|
43
51
|
|
44
52
|
RailsPgAdapter.configure do |c|
|
45
53
|
c.add_reset_column_information_patch = true
|
@@ -48,6 +56,16 @@ end
|
|
48
56
|
|
49
57
|
This will clear the `ActiveRecord` schema cache and reset the `ActiveRecord` column information memoized on the model. The patch will reset the relevant information and re-raise the error each time it detects that an exception related to a dropped column is raised.
|
50
58
|
|
59
|
+
## How does it work
|
60
|
+
|
61
|
+
During a database failover in production, the `ActiveRecord` connection pool can become exhausted as queries are made against the database during the failover process. This can leave the `ActiveRecord` connection pools with stale or bad connections, even after the database has successfully recovered. Recovering from this issue usually requires a rolling restart of the application processes or containers.
|
62
|
+
|
63
|
+
`RailsPgAdapter` addresses this problem by resetting the connection pool and re-raises the original exception from an `ActiveRecord` monkey patch. This allows the application to auto-heal from stale connections on its own (after database recovery) when performing queries for a new request, without requiring manual intervention.
|
64
|
+
|
65
|
+
Another issue with `ActiveRecord` queries is `PG::UndefinedColumn`, which occurs when an `ActiveRecord` model includes a `SELECT` query with the name of a column that has been dropped from a Rails migration. This can happen even if the column isn't being referenced anywhere in the code. It occurs when a model is using `ignored_columns`, which prompts `ActiveRecord` to perform a dedicated lookup of the allowed columns in a select, such as `SELECT "users".name, "users".template_id...."`, instead of `SELECT "users".*`. When a column like `template_id` is dropped, PostgreSQL throws an undefined column error, which is bubbled up by `ActiveRecord` into `PG::UndefinedColumn`. Recovering from this issue also usually requires a rolling restart of the application processes or containers.
|
66
|
+
|
67
|
+
`RailsPgAdapter` solves this second issue by resetting the `ActiveRecord` schema cache and memoized model column information when it detects a `PG::UndefinedColumn` raised from a monkey patch. Resetting the column information forces `ActiveRecord` to refresh its schema cache by loading the table information from the database and no longer reference the dropped column for new queries, without requiring manual intervention.
|
68
|
+
|
51
69
|
## Development
|
52
70
|
|
53
71
|
- Install ruby 3.0
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module RailsPgAdapter
|
4
4
|
class Configuration
|
5
|
-
attr_accessor :add_failover_patch, :add_reset_column_information_patch
|
5
|
+
attr_accessor :add_failover_patch, :add_reset_column_information_patch, :reconnect_with_backoff
|
6
6
|
|
7
7
|
def initialize(attrs)
|
8
8
|
self.add_failover_patch = attrs[:add_failover_patch]
|
9
9
|
self.add_reset_column_information_patch = attrs[:add_reset_column_information_patch]
|
10
|
+
self.reconnect_with_backoff = attrs[:reconnect_with_backoff]
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -14,6 +15,7 @@ module RailsPgAdapter
|
|
14
15
|
@configuration ||= Configuration.new({
|
15
16
|
add_failover_patch: false,
|
16
17
|
add_reset_column_information_patch: false,
|
18
|
+
reconnect_with_backoff: [],
|
17
19
|
})
|
18
20
|
end
|
19
21
|
|
@@ -25,6 +27,10 @@ module RailsPgAdapter
|
|
25
27
|
RailsPgAdapter.configuration.add_failover_patch || false
|
26
28
|
end
|
27
29
|
|
30
|
+
def self.reconnect_with_backoff?
|
31
|
+
!RailsPgAdapter.configuration.reconnect_with_backoff.empty?
|
32
|
+
end
|
33
|
+
|
28
34
|
def self.reset_column_information_patch?
|
29
35
|
RailsPgAdapter.configuration.add_reset_column_information_patch || false
|
30
36
|
end
|
@@ -33,6 +39,7 @@ module RailsPgAdapter
|
|
33
39
|
@configuration = Configuration.new({
|
34
40
|
add_failover_patch: false,
|
35
41
|
add_reset_column_information_patch: false,
|
42
|
+
reconnect_with_backoff: [],
|
36
43
|
})
|
37
44
|
end
|
38
45
|
end
|
@@ -12,6 +12,7 @@ module RailsPgAdapter
|
|
12
12
|
"PG::ConnectionBad",
|
13
13
|
"the database system is starting up",
|
14
14
|
"connection is closed",
|
15
|
+
"could not connect",
|
15
16
|
].freeze
|
16
17
|
CONNECTION_ERROR_RE = /#{CONNECTION_ERROR.map { |w| Regexp.escape(w) }.join("|")}/.freeze
|
17
18
|
|
@@ -23,20 +24,37 @@ module RailsPgAdapter
|
|
23
24
|
def exec_cache(*args)
|
24
25
|
super(*args)
|
25
26
|
rescue ::ActiveRecord::StatementInvalid, ::ActiveRecord::ConnectionNotEstablished => e
|
26
|
-
|
27
|
+
raise unless supported_errors?(e)
|
28
|
+
|
29
|
+
try_reconnect?(e) ? retry : handle_error(e)
|
27
30
|
end
|
28
31
|
|
29
32
|
def exec_no_cache(*args)
|
30
33
|
super(*args)
|
31
34
|
rescue ::ActiveRecord::StatementInvalid, ::ActiveRecord::ConnectionNotEstablished => e
|
32
|
-
|
35
|
+
raise unless supported_errors?(e)
|
36
|
+
|
37
|
+
try_reconnect?(e) ? retry : handle_error(e)
|
38
|
+
end
|
39
|
+
|
40
|
+
def try_reconnect?(e)
|
41
|
+
return false if in_transaction?
|
42
|
+
return false unless failover_error?(e.message)
|
43
|
+
return false unless RailsPgAdapter.reconnect_with_backoff?
|
44
|
+
|
45
|
+
begin
|
46
|
+
reconnect!
|
47
|
+
true
|
48
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
49
|
+
false
|
50
|
+
end
|
33
51
|
end
|
34
52
|
|
35
53
|
def handle_error(e)
|
36
54
|
if failover_error?(e.message) && RailsPgAdapter.failover_patch?
|
37
55
|
warn("clearing connections due to #{e} - #{e.message}")
|
38
56
|
disconnect_and_remove_conn!
|
39
|
-
raise
|
57
|
+
raise(e)
|
40
58
|
end
|
41
59
|
|
42
60
|
return unless missing_column_error?(e.message) && RailsPgAdapter.reset_column_information_patch?
|
@@ -70,7 +88,41 @@ module RailsPgAdapter
|
|
70
88
|
return if Rails.logger.nil?
|
71
89
|
::Rails.logger.warn("[RailsPgAdapter::Patch] #{msg}")
|
72
90
|
end
|
91
|
+
|
92
|
+
def supported_errors?(e)
|
93
|
+
return true if failover_error?(e.message) && RailsPgAdapter.failover_patch?
|
94
|
+
if missing_column_error?(e.message) && RailsPgAdapter.reset_column_information_patch?
|
95
|
+
return true
|
96
|
+
end
|
97
|
+
false
|
98
|
+
end
|
73
99
|
end
|
74
100
|
end
|
75
101
|
|
76
102
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(RailsPgAdapter::Patch)
|
103
|
+
|
104
|
+
# Override new client connection to bake in retries
|
105
|
+
module ActiveRecord
|
106
|
+
module ConnectionAdapters
|
107
|
+
class PostgreSQLAdapter
|
108
|
+
class << self
|
109
|
+
old_new_client_method = instance_method(:new_client)
|
110
|
+
|
111
|
+
define_method(:new_client) do |args|
|
112
|
+
sleep_times = RailsPgAdapter.configuration.reconnect_with_backoff.dup
|
113
|
+
begin
|
114
|
+
old_new_client_method.bind(self).call(args)
|
115
|
+
rescue ::ActiveRecord::ConnectionNotEstablished => e
|
116
|
+
raise(e) unless RailsPgAdapter.failover_patch? && RailsPgAdapter.reconnect_with_backoff?
|
117
|
+
|
118
|
+
sleep_time = sleep_times.shift
|
119
|
+
raise unless sleep_time
|
120
|
+
warn( "Could not establish a connection from new_client, retrying again in #{sleep_time} sec.")
|
121
|
+
sleep(sleep_time)
|
122
|
+
retry
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-pg-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tines Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|