rails-pg-adapter 0.1.4 → 0.1.5
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/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
|