activerecord-health 0.1.0
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 +7 -0
- data/CONTRIBUTING.md +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +239 -0
- data/Rakefile +25 -0
- data/docker-compose.yml +27 -0
- data/lib/activerecord/health/adapters/mysql_adapter.rb +60 -0
- data/lib/activerecord/health/adapters/postgresql_adapter.rb +23 -0
- data/lib/activerecord/health/configuration.rb +61 -0
- data/lib/activerecord/health/extensions.rb +44 -0
- data/lib/activerecord/health/railtie.rb +11 -0
- data/lib/activerecord/health/version.rb +7 -0
- data/lib/activerecord/health.rb +134 -0
- data/lib/activerecord-health.rb +3 -0
- data/test/integration/mysql_integration_test.rb +104 -0
- data/test/integration/postgresql_integration_test.rb +104 -0
- data/test/test_helper.rb +74 -0
- data/test/unit/adapters/mysql_adapter_test.rb +94 -0
- data/test/unit/adapters/postgresql_adapter_test.rb +25 -0
- data/test/unit/configuration_test.rb +140 -0
- data/test/unit/extensions_test.rb +113 -0
- data/test/unit/health_test.rb +189 -0
- data/test/unit/sheddable_test.rb +92 -0
- metadata +94 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8ffd824e810c1dc16ebe84ec333bacd29867f80320b278b95ba5f9132e3929e0
|
|
4
|
+
data.tar.gz: 28599edb6d93b0e21b9cfd1c52f938a4394060e6757cf805195f0e410489548b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3c185ce40c985568ad318b5d356cc967e5781a3e5ee98259ebc9edbff3a28f4d409fde79945a00f7b00a8dd31b9df84de3785f66f9993745be08a87861b8c755
|
|
7
|
+
data.tar.gz: c3199b2e98cfdd1059e2e98bdbd1c640b8a0efa3d4de5ceddd1e70efcb9f8502d2a365ccd934158a2b2a01bddc51a4f7ab8821bfd774ce4aaacb092d6e1164b4
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## Development
|
|
2
|
+
|
|
3
|
+
### Requirements
|
|
4
|
+
|
|
5
|
+
- Ruby 3.2+
|
|
6
|
+
- Docker (for integration tests)
|
|
7
|
+
|
|
8
|
+
### Running Tests
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Unit tests only
|
|
12
|
+
bundle exec rake test
|
|
13
|
+
|
|
14
|
+
# Start test databases
|
|
15
|
+
docker-compose up -d
|
|
16
|
+
|
|
17
|
+
# All tests (unit + integration)
|
|
18
|
+
bundle exec rake test_all
|
|
19
|
+
|
|
20
|
+
# Stop test databases
|
|
21
|
+
docker-compose down
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Project Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
lib/
|
|
28
|
+
├── activerecord-health.rb # Main entry point
|
|
29
|
+
└── activerecord/
|
|
30
|
+
└── health/
|
|
31
|
+
├── configuration.rb # Config handling
|
|
32
|
+
├── extensions.rb # Optional model/connection methods
|
|
33
|
+
└── adapters/
|
|
34
|
+
├── postgresql_adapter.rb
|
|
35
|
+
└── mysql_adapter.rb
|
|
36
|
+
test/
|
|
37
|
+
├── unit/ # Fast tests with mocks
|
|
38
|
+
└── integration/ # Tests against real databases
|
|
39
|
+
```
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nate Berkopec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# ActiveRecord::Health
|
|
2
|
+
|
|
3
|
+
Monitor your database's health by tracking active sessions. When load gets too high, shed work to keep your app running.
|
|
4
|
+
|
|
5
|
+
## Why Use This?
|
|
6
|
+
|
|
7
|
+
This gem was inspired by [Simon Eskildsen](https://www.youtube.com/watch?v=N8NWDHgWA28), who described a similar system in place at Shopify.
|
|
8
|
+
|
|
9
|
+
Databases slow down when they have too many active queries. This gem helps you:
|
|
10
|
+
|
|
11
|
+
- **Shed load safely.** Skip low-priority work when the database is busy.
|
|
12
|
+
- **Protect your app.** Return 503 errors instead of timing out, which allows higher-priority work to get through.
|
|
13
|
+
|
|
14
|
+
The gem counts active database sessions. It compares this count to your database's vCPU count. When active sessions exceed a threshold, the database is "unhealthy."
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Add to your Gemfile:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "activerecord-health"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# config/initializers/activerecord_health.rb
|
|
34
|
+
ActiveRecord::Health.configure do |config|
|
|
35
|
+
config.vcpu_count = 16 # Required: your database server's vCPU count
|
|
36
|
+
config.cache = Rails.cache # Required: any ActiveSupport::Cache store
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now check if your database is healthy:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
ActiveRecord::Health.ok?
|
|
44
|
+
# => true
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
ActiveRecord::Health.configure do |config|
|
|
51
|
+
# Required settings
|
|
52
|
+
config.vcpu_count = 16 # Number of vCPUs on your database server
|
|
53
|
+
config.cache = Rails.cache # Cache store for health check results
|
|
54
|
+
|
|
55
|
+
# Optional settings
|
|
56
|
+
config.threshold = 0.75 # Max healthy load (default: 0.75)
|
|
57
|
+
config.cache_ttl = 60 # Cache duration in seconds (default: 60)
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> [!IMPORTANT]
|
|
62
|
+
> You must set `vcpu_count` and `cache`. The gem raises an error without them.
|
|
63
|
+
|
|
64
|
+
### What Does Threshold Mean?
|
|
65
|
+
|
|
66
|
+
The threshold is the maximum healthy load as a ratio of vCPUs.
|
|
67
|
+
|
|
68
|
+
With `vcpu_count = 16` and `threshold = 0.75`:
|
|
69
|
+
- Up to 12 active sessions = healthy (12/16 = 0.75)
|
|
70
|
+
- More than 12 active sessions = unhealthy
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
### Check Health
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# Returns true if database is healthy
|
|
78
|
+
ActiveRecord::Health.ok?
|
|
79
|
+
|
|
80
|
+
# Get current load as a percentage
|
|
81
|
+
ActiveRecord::Health.load_pct
|
|
82
|
+
# => 0.5 (50% of vCPUs in use)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Shed Work
|
|
86
|
+
|
|
87
|
+
Use `sheddable` to skip work when the database is overloaded:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
ActiveRecord::Health.sheddable do
|
|
91
|
+
GenerateReport.perform(user_id: current_user.id)
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `sheddable_pct` for different priority levels:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# High priority: only run below 50% load
|
|
99
|
+
ActiveRecord::Health.sheddable_pct(pct: 0.5) do
|
|
100
|
+
BulkImport.perform(data)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Low priority: only run below 90% load
|
|
104
|
+
ActiveRecord::Health.sheddable_pct(pct: 0.9) do
|
|
105
|
+
SendAnalyticsEmail.perform(user_id: current_user.id)
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Usage Examples
|
|
110
|
+
|
|
111
|
+
### Controller Filter
|
|
112
|
+
|
|
113
|
+
Return 503 when the database is overloaded:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
class ReportsController < ApplicationController
|
|
117
|
+
before_action :check_database_health
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def check_database_health
|
|
122
|
+
return if ActiveRecord::Health.ok?
|
|
123
|
+
render json: { error: "Service temporarily unavailable" },
|
|
124
|
+
status: :service_unavailable
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Sidekiq Middleware
|
|
130
|
+
|
|
131
|
+
Retry jobs when the database is unhealthy:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# config/initializers/sidekiq.rb
|
|
135
|
+
class DatabaseHealthMiddleware
|
|
136
|
+
THROTTLED_QUEUES = %w[reports analytics bulk_import].freeze
|
|
137
|
+
|
|
138
|
+
def call(_worker, job, _queue)
|
|
139
|
+
if THROTTLED_QUEUES.include?(job["queue"]) && !ActiveRecord::Health.ok?
|
|
140
|
+
raise ActiveRecord::Health::Unhealthy
|
|
141
|
+
end
|
|
142
|
+
yield
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Sidekiq.configure_server do |config|
|
|
147
|
+
config.server_middleware do |chain|
|
|
148
|
+
chain.add DatabaseHealthMiddleware
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Multi-Database Support
|
|
154
|
+
|
|
155
|
+
Pass the model class that connects to your database:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# Check the primary database (default)
|
|
159
|
+
ActiveRecord::Health.ok?
|
|
160
|
+
|
|
161
|
+
# Check a specific database
|
|
162
|
+
ActiveRecord::Health.ok?(model: AnimalsRecord)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Configure each database separately:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
ActiveRecord::Health.configure do |config|
|
|
169
|
+
config.vcpu_count = 16 # Default for primary database
|
|
170
|
+
config.cache = Rails.cache
|
|
171
|
+
|
|
172
|
+
config.for_model(AnimalsRecord) do |db|
|
|
173
|
+
db.vcpu_count = 8
|
|
174
|
+
db.threshold = 0.5
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Database Support
|
|
180
|
+
|
|
181
|
+
| Database | Supported |
|
|
182
|
+
|----------|-----------|
|
|
183
|
+
| PostgreSQL 10+ | Yes |
|
|
184
|
+
| MySQL 5.1+ | Yes |
|
|
185
|
+
| MySQL 8.0.22+ | Yes (uses performance_schema) |
|
|
186
|
+
| MariaDB | Yes |
|
|
187
|
+
| SQLite | No |
|
|
188
|
+
|
|
189
|
+
## Observability
|
|
190
|
+
|
|
191
|
+
Send load data to Datadog, StatsD, or other tools. The gem fires an event each time it checks the database:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
ActiveSupport::Notifications.subscribe("health_check.activerecord_health") do |*, payload|
|
|
195
|
+
StatsD.gauge("db.load_pct", payload[:load_pct], tags: ["db:#{payload[:database]}"])
|
|
196
|
+
StatsD.gauge("db.active_sessions", payload[:active_sessions], tags: ["db:#{payload[:database]}"])
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> [!TIP]
|
|
201
|
+
> Start by tracking `load_pct` for a few days. This helps you learn what "normal" looks like before you set thresholds.
|
|
202
|
+
|
|
203
|
+
The event fires only when the gem runs a query. It does not fire when reading from cache.
|
|
204
|
+
|
|
205
|
+
**Event payload:**
|
|
206
|
+
|
|
207
|
+
| Key | Description |
|
|
208
|
+
|-----|-------------|
|
|
209
|
+
| `database` | Connection name |
|
|
210
|
+
| `load_pct` | Load as a ratio (0.0 to 1.0+) |
|
|
211
|
+
| `active_sessions` | Number of active sessions |
|
|
212
|
+
|
|
213
|
+
## Optional Extensions
|
|
214
|
+
|
|
215
|
+
Add convenience methods to connections and models:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
require "activerecord/health/extensions"
|
|
219
|
+
|
|
220
|
+
ActiveRecord::Base.connection.healthy?
|
|
221
|
+
# => true
|
|
222
|
+
|
|
223
|
+
ActiveRecord::Base.connection.load_pct
|
|
224
|
+
# => 0.75
|
|
225
|
+
|
|
226
|
+
ActiveRecord::Base.database_healthy?
|
|
227
|
+
# => true
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Known Issues
|
|
231
|
+
|
|
232
|
+
This gem is simple by design. Keep these limits in mind:
|
|
233
|
+
|
|
234
|
+
- **Errors look like overload.** The health check query can fail for many reasons: network problems, DNS issues, or connection pool limits. When this happens, the gem marks the database as unhealthy. It caches this result for `cache_ttl` seconds. This can cause load shedding even when the database is fine.
|
|
235
|
+
- **Session counts can be wrong.** The gem assumes many active sessions means the CPU is busy. But sessions can be active while waiting on locks, disk reads, or slow clients. The database may have room to spare, but the gem still reports it as unhealthy.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT License. See [LICENSE](LICENSE.txt) for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
require "standard/rake"
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
|
8
|
+
t.libs << "test"
|
|
9
|
+
t.libs << "lib"
|
|
10
|
+
t.test_files = FileList["test/unit/**/*_test.rb"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Rake::TestTask.new(:test_integration) do |t|
|
|
14
|
+
t.libs << "test"
|
|
15
|
+
t.libs << "lib"
|
|
16
|
+
t.test_files = FileList["test/integration/**/*_test.rb"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Rake::TestTask.new(:test_all) do |t|
|
|
20
|
+
t.libs << "test"
|
|
21
|
+
t.libs << "lib"
|
|
22
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
task default: %i[test standard]
|
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:latest
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_USER: postgres
|
|
6
|
+
POSTGRES_PASSWORD: postgres
|
|
7
|
+
POSTGRES_DB: activerecord_health_test
|
|
8
|
+
ports:
|
|
9
|
+
- "5432:5432"
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
12
|
+
interval: 5s
|
|
13
|
+
timeout: 5s
|
|
14
|
+
retries: 5
|
|
15
|
+
|
|
16
|
+
mysql:
|
|
17
|
+
image: mysql:latest
|
|
18
|
+
environment:
|
|
19
|
+
MYSQL_ROOT_PASSWORD: root
|
|
20
|
+
MYSQL_DATABASE: activerecord_health_test
|
|
21
|
+
ports:
|
|
22
|
+
- "3306:3306"
|
|
23
|
+
healthcheck:
|
|
24
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
25
|
+
interval: 5s
|
|
26
|
+
timeout: 5s
|
|
27
|
+
retries: 5
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Health
|
|
5
|
+
module Adapters
|
|
6
|
+
class MySQLAdapter
|
|
7
|
+
PERFORMANCE_SCHEMA_MIN_VERSION = Gem::Version.new("8.0.22")
|
|
8
|
+
|
|
9
|
+
attr_reader :version_string
|
|
10
|
+
|
|
11
|
+
def initialize(version_string)
|
|
12
|
+
@version_string = version_string
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def name
|
|
16
|
+
:mysql
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def active_session_count_query
|
|
20
|
+
uses_performance_schema? ? performance_schema_query : information_schema_query
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def uses_performance_schema?
|
|
24
|
+
!mariadb? && mysql_version >= PERFORMANCE_SCHEMA_MIN_VERSION
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def mariadb?
|
|
30
|
+
version_string.downcase.include?("mariadb")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def mysql_version
|
|
34
|
+
Gem::Version.new(version_string.split("-").first)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def performance_schema_query
|
|
38
|
+
<<~SQL.squish
|
|
39
|
+
SELECT COUNT(*)
|
|
40
|
+
FROM performance_schema.processlist
|
|
41
|
+
WHERE COMMAND != 'Sleep'
|
|
42
|
+
AND ID != CONNECTION_ID()
|
|
43
|
+
AND USER NOT IN ('event_scheduler', 'system user')
|
|
44
|
+
SQL
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def information_schema_query
|
|
48
|
+
<<~SQL.squish
|
|
49
|
+
SELECT COUNT(*)
|
|
50
|
+
FROM information_schema.processlist
|
|
51
|
+
WHERE Command != 'Sleep'
|
|
52
|
+
AND ID != CONNECTION_ID()
|
|
53
|
+
AND User NOT IN ('event_scheduler', 'system user')
|
|
54
|
+
AND Command NOT IN ('Binlog Dump', 'Binlog Dump GTID')
|
|
55
|
+
SQL
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Health
|
|
5
|
+
module Adapters
|
|
6
|
+
class PostgreSQLAdapter
|
|
7
|
+
def name
|
|
8
|
+
:postgresql
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def active_session_count_query
|
|
12
|
+
<<~SQL.squish
|
|
13
|
+
SELECT count(*)
|
|
14
|
+
FROM pg_stat_activity
|
|
15
|
+
WHERE state = 'active'
|
|
16
|
+
AND backend_type = 'client backend'
|
|
17
|
+
AND pid != pg_backend_pid()
|
|
18
|
+
SQL
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Health
|
|
5
|
+
class ConfigurationError < StandardError; end
|
|
6
|
+
|
|
7
|
+
class Configuration
|
|
8
|
+
attr_accessor :vcpu_count, :threshold, :cache, :cache_ttl
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@threshold = 0.75
|
|
12
|
+
@cache_ttl = 60
|
|
13
|
+
@model_configs = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate!
|
|
17
|
+
raise ConfigurationError, "vcpu_count must be configured" if vcpu_count.nil?
|
|
18
|
+
raise ConfigurationError, "cache must be configured" if cache.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def for_model(model, &block)
|
|
22
|
+
if block_given?
|
|
23
|
+
config = ModelConfiguration.new(self)
|
|
24
|
+
block.call(config)
|
|
25
|
+
@model_configs[model] = config
|
|
26
|
+
else
|
|
27
|
+
@model_configs[model] || self
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def max_healthy_sessions
|
|
32
|
+
(vcpu_count * threshold).floor
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class ModelConfiguration
|
|
37
|
+
attr_accessor :vcpu_count
|
|
38
|
+
attr_writer :threshold
|
|
39
|
+
|
|
40
|
+
def initialize(parent)
|
|
41
|
+
@parent = parent
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def cache
|
|
45
|
+
@parent.cache
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cache_ttl
|
|
49
|
+
@parent.cache_ttl
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def threshold
|
|
53
|
+
@threshold || @parent.threshold
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def max_healthy_sessions
|
|
57
|
+
(vcpu_count * threshold).floor
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../health"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Health
|
|
7
|
+
module ConnectionExtension
|
|
8
|
+
def healthy?
|
|
9
|
+
db_config_name = pool.db_config.name
|
|
10
|
+
ActiveRecord::Health.ok?(model: ConnectionModelProxy.new(db_config_name, self))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def load_pct
|
|
14
|
+
db_config_name = pool.db_config.name
|
|
15
|
+
ActiveRecord::Health.load_pct(model: ConnectionModelProxy.new(db_config_name, self))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ModelExtension
|
|
20
|
+
def database_healthy?
|
|
21
|
+
ActiveRecord::Health.ok?(model: self)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class ConnectionModelProxy
|
|
26
|
+
attr_reader :connection
|
|
27
|
+
|
|
28
|
+
def initialize(db_config_name, connection)
|
|
29
|
+
@db_config_name = db_config_name
|
|
30
|
+
@connection = connection
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def connection_db_config
|
|
34
|
+
DbConfigProxy.new(@db_config_name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def class
|
|
38
|
+
ActiveRecord::Base
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
DbConfigProxy = Struct.new(:name)
|
|
43
|
+
end
|
|
44
|
+
end
|