solid_queue_guard 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/MIT-LICENSE +21 -0
- data/README.md +258 -0
- data/Rakefile +17 -0
- data/app/controllers/solid_queue_guard/application_controller.rb +6 -0
- data/app/controllers/solid_queue_guard/health_controller.rb +9 -0
- data/config/routes.rb +5 -0
- data/lib/generators/solid_queue_guard/install/USAGE +8 -0
- data/lib/generators/solid_queue_guard/install/install_generator.rb +17 -0
- data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.rb +17 -0
- data/lib/solid_queue_guard/check/result.rb +37 -0
- data/lib/solid_queue_guard/checks/base.rb +52 -0
- data/lib/solid_queue_guard/checks/config/adapter_check.rb +23 -0
- data/lib/solid_queue_guard/checks/config/connects_to_check.rb +38 -0
- data/lib/solid_queue_guard/checks/config/env_flags_check.rb +21 -0
- data/lib/solid_queue_guard/checks/config/process_heartbeat_config_check.rb +30 -0
- data/lib/solid_queue_guard/checks/config/puma_colocated_check.rb +33 -0
- data/lib/solid_queue_guard/checks/config/queue_database_check.rb +24 -0
- data/lib/solid_queue_guard/checks/config/queue_schema_check.rb +23 -0
- data/lib/solid_queue_guard/checks/config/scheduler_config_check.rb +32 -0
- data/lib/solid_queue_guard/checks/config/thread_pool_check.rb +38 -0
- data/lib/solid_queue_guard/checks/config/worker_coverage_check.rb +51 -0
- data/lib/solid_queue_guard/checks/registry.rb +48 -0
- data/lib/solid_queue_guard/checks/runtime/base.rb +21 -0
- data/lib/solid_queue_guard/checks/runtime/blocked_jobs_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/dispatcher_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/failed_jobs_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/finished_jobs_growth_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/orphaned_claims_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/paused_queue_lag_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/pidfile_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/process_topology_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/queue_lag_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/recurring_stale_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/scheduled_backlog_check.rb +9 -0
- data/lib/solid_queue_guard/checks/runtime/stale_process_check.rb +9 -0
- data/lib/solid_queue_guard/cli.rb +55 -0
- data/lib/solid_queue_guard/configuration.rb +33 -0
- data/lib/solid_queue_guard/engine.rb +21 -0
- data/lib/solid_queue_guard/formatters/json.rb +20 -0
- data/lib/solid_queue_guard/formatters/terminal.rb +43 -0
- data/lib/solid_queue_guard/notifiers/base.rb +17 -0
- data/lib/solid_queue_guard/queue_coverage.rb +37 -0
- data/lib/solid_queue_guard/report.rb +44 -0
- data/lib/solid_queue_guard/runner.rb +47 -0
- data/lib/solid_queue_guard/tasks.rb +39 -0
- data/lib/solid_queue_guard/version.rb +5 -0
- data/lib/solid_queue_guard.rb +38 -0
- metadata +346 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bd60b98c25c507b3069f98b6b69282f627a398e96bd43d7a5d0713f0499c8610
|
|
4
|
+
data.tar.gz: c60a8995c311f4935456f58ca950e1d4cdb4b7e601dc1e6290f62f9d06931cb0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ac5fbbb57df0cb133466076f0c4c1b698a13004b3f44e22bc64139e04e8c4bb51973a22e534ff0617329709a154a92a7a93ac9e083ea0087d86dadc73c6e89cf
|
|
7
|
+
data.tar.gz: b16bc3883213b26928b0cfa2c4d0b355ce43bd956a4c3df27e4cad4f8f71534748a15e5f9c892190a018741df8c9f1fbe4aaaa577e960f4e39a2a28c00a823ed
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rafael Pissardo
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# solid_queue_guard
|
|
2
|
+
|
|
3
|
+
**Production readiness checks and runtime guards for Rails Solid Queue.**
|
|
4
|
+
|
|
5
|
+
[](https://rubygems.org/gems/solid_queue_guard)
|
|
6
|
+
[](MIT-LICENSE)
|
|
7
|
+
|
|
8
|
+
Rails 8 ships with [Solid Queue](https://github.com/rails/solid_queue). Redis is optional. Production config is not.
|
|
9
|
+
|
|
10
|
+
Your web tier can be **green** while your jobs are **dead**. Your queue can look **empty** while scheduled work is **stuck**. Your `queue.yml` can declare **10 threads** against a database pool of **5**.
|
|
11
|
+
|
|
12
|
+
**solid_queue_guard** catches that *before* it becomes a 3am incident.
|
|
13
|
+
|
|
14
|
+
> **Mission Control** shows what is happening.
|
|
15
|
+
> **solid_queue_guard** warns what is dangerous.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why this exists
|
|
20
|
+
|
|
21
|
+
Solid Queue is excellent. Operating it blindly is not.
|
|
22
|
+
|
|
23
|
+
| Symptom | What actually happened |
|
|
24
|
+
| ------- | ---------------------- |
|
|
25
|
+
| "Site is up, emails stopped" | Workers dead, heartbeats stale |
|
|
26
|
+
| "Only 3 jobs in the queue, why the panic?" | Oldest job waiting 40 minutes — **lag**, not depth |
|
|
27
|
+
| "Recurring billing just… stopped" | Scheduler not running, nobody noticed for a week |
|
|
28
|
+
| "Jobs hang after deploy" | Thread count > DB pool — connection starvation |
|
|
29
|
+
| "Health check passes, jobs don't" | `/up` doesn't know Solid Queue exists |
|
|
30
|
+
|
|
31
|
+
You don't need another dashboard. You need a **doctor**.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## See it in action
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bundle add solid_queue_guard
|
|
39
|
+
bin/rails solid_queue_guard:install
|
|
40
|
+
bin/rails solid_queue_guard:doctor
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
SolidQueueGuard Report
|
|
45
|
+
|
|
46
|
+
Status: DEGRADED
|
|
47
|
+
|
|
48
|
+
Checks:
|
|
49
|
+
✅ Active Job adapter is :solid_queue
|
|
50
|
+
✅ Queue database configured in database.yml
|
|
51
|
+
✅ Solid Queue connects_to queue database (pool: 10)
|
|
52
|
+
✅ db/queue_schema.rb exists
|
|
53
|
+
❌ Worker threads: 10, queue DB pool: 5
|
|
54
|
+
⚠️ No workers configured for "mailers" queue
|
|
55
|
+
⚠️ recurring.yml exists but scheduler may not run
|
|
56
|
+
✅ Solid Queue is not co-located with Puma
|
|
57
|
+
|
|
58
|
+
Suggested fixes:
|
|
59
|
+
- Increase queue DB pool to at least 12 or reduce worker threads
|
|
60
|
+
- Add a worker for the mailers queue
|
|
61
|
+
- Verify scheduler process is running in production
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
One command. Actionable output. No Datadog required to get started.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## What it checks
|
|
69
|
+
|
|
70
|
+
### v0.1 — Configuration doctor (available now)
|
|
71
|
+
|
|
72
|
+
Runs locally, in CI, or pre-deploy. **No extra infrastructure.**
|
|
73
|
+
|
|
74
|
+
| Check | Catches |
|
|
75
|
+
| ----- | ------- |
|
|
76
|
+
| **Adapter** | Active Job not set to `:solid_queue` |
|
|
77
|
+
| **Queue database** | Missing `queue` entry in `database.yml` |
|
|
78
|
+
| **connects_to** | Solid Queue pointing at the wrong DB / pool |
|
|
79
|
+
| **Queue schema** | Missing `db/queue_schema.rb` |
|
|
80
|
+
| **Thread pool** | `threads` > available queue DB connections |
|
|
81
|
+
| **Worker coverage** | Queues with no worker assigned |
|
|
82
|
+
| **Scheduler** | `recurring.yml` tasks without a scheduler |
|
|
83
|
+
| **Env flags** | `SOLID_QUEUE_SKIP_RECURRING=true` in production |
|
|
84
|
+
| **Heartbeats** | Default thresholds that may not fit your deploy |
|
|
85
|
+
| **Puma plugin** | Solid Queue co-located with web in production |
|
|
86
|
+
|
|
87
|
+
### v0.2+ — Runtime guards (roadmap)
|
|
88
|
+
|
|
89
|
+
| Check | Catches |
|
|
90
|
+
| ----- | ------- |
|
|
91
|
+
| **Queue lag** | Oldest ready job waiting too long |
|
|
92
|
+
| **Stale processes** | Dead workers, dispatchers, schedulers |
|
|
93
|
+
| **Dispatcher health** | Scheduled jobs never becoming ready |
|
|
94
|
+
| **Blocked jobs** | Concurrency control silently holding jobs |
|
|
95
|
+
| **Recurring staleness** | Cron tasks that stopped firing |
|
|
96
|
+
| **HTTP `/health`** | Kamal, ECS, K8s, UptimeRobot integration |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Quick start
|
|
101
|
+
|
|
102
|
+
### Install
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
bundle add solid_queue_guard
|
|
106
|
+
bin/rails solid_queue_guard:install
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Run the doctor
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Human-readable report
|
|
113
|
+
bin/rails solid_queue_guard:doctor
|
|
114
|
+
|
|
115
|
+
# JSON for scripts / CI
|
|
116
|
+
bin/rails "solid_queue_guard:doctor[json]"
|
|
117
|
+
SOLID_QUEUE_GUARD_FORMAT=json bin/rails solid_queue_guard:doctor
|
|
118
|
+
|
|
119
|
+
# Fail CI on warnings too
|
|
120
|
+
SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor
|
|
121
|
+
|
|
122
|
+
# Full report (config + runtime when available)
|
|
123
|
+
bin/rails solid_queue_guard:report
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Exit codes
|
|
127
|
+
|
|
128
|
+
| Code | Meaning |
|
|
129
|
+
| ---- | ------- |
|
|
130
|
+
| `0` | Healthy or degraded |
|
|
131
|
+
| `1` | Unhealthy (or degraded in `--strict` / `SOLID_QUEUE_GUARD_STRICT=1`) |
|
|
132
|
+
|
|
133
|
+
Perfect for deploy pipelines:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
# GitHub Actions
|
|
137
|
+
- name: Solid Queue production readiness
|
|
138
|
+
run: SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### HTTP health (v0.2)
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# config/routes.rb
|
|
145
|
+
mount SolidQueueGuard::Engine, at: "/solid_queue_guard"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
curl localhost:3000/solid_queue_guard/health
|
|
150
|
+
# => { "status": "degraded", "queue_lag_seconds": 245, "warnings": [...] }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Works with **Kamal**, **Heroku**, **Fly.io**, **ECS/Fargate**, **Kubernetes**, **Better Stack**, **UptimeRobot**.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# config/initializers/solid_queue_guard.rb
|
|
161
|
+
SolidQueueGuard.configure do |config|
|
|
162
|
+
config.enabled = Rails.env.production?
|
|
163
|
+
|
|
164
|
+
config.queue_lag_thresholds = {
|
|
165
|
+
critical: 30.seconds,
|
|
166
|
+
default: 5.minutes,
|
|
167
|
+
mailers: 15.minutes
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
config.failed_jobs_threshold = 20
|
|
171
|
+
config.stale_process_threshold = 5.minutes
|
|
172
|
+
|
|
173
|
+
# config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"] # v0.2
|
|
174
|
+
# config.notify_with = [:rails_logger, :slack] # v0.3
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## solid_queue_guard vs Mission Control
|
|
181
|
+
|
|
182
|
+
| | [Mission Control — Jobs](https://github.com/rails/mission_control-jobs) | solid_queue_guard |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| **Purpose** | Inspect & manage jobs | Detect production risk |
|
|
185
|
+
| **UI** | Dashboard | CLI + JSON + health endpoint |
|
|
186
|
+
| **Retry / discard** | Yes | No (use Mission Control) |
|
|
187
|
+
| **Config doctor** | No | Yes |
|
|
188
|
+
| **Queue lag alerts** | No | Yes (v0.2) |
|
|
189
|
+
| **Pre-deploy checks** | No | Yes |
|
|
190
|
+
| **Recurring job guard** | Manual inspection | Automatic (v0.2) |
|
|
191
|
+
|
|
192
|
+
**Use both.** They solve different problems.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Roadmap
|
|
197
|
+
|
|
198
|
+
| Version | Ships |
|
|
199
|
+
| ------- | ----- |
|
|
200
|
+
| **v0.1** | `doctor` — config checks, CI integration, install generator |
|
|
201
|
+
| **v0.2** | Runtime health, queue lag, dispatcher/blocked jobs, HTTP endpoint |
|
|
202
|
+
| **v0.3** | Slack, Datadog, webhook notifications |
|
|
203
|
+
| **v0.4** | StatsD, Prometheus, OpenTelemetry metrics |
|
|
204
|
+
| **v0.5** | Auto-recommendations for `queue.yml` topology |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Compatibility
|
|
209
|
+
|
|
210
|
+
| Gem version | Ruby | Rails |
|
|
211
|
+
| ----------- | ---- | ----- |
|
|
212
|
+
| 0.1.x | 3.1+ | 7.1, 7.2, 8.0 |
|
|
213
|
+
|
|
214
|
+
## Requirements
|
|
215
|
+
|
|
216
|
+
- Ruby >= 3.1
|
|
217
|
+
- Rails >= 7.1, < 9.0
|
|
218
|
+
- [solid_queue](https://github.com/rails/solid_queue) >= 1.0, < 2.0
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git clone https://github.com/rafael-pissardo/solid_queue_guard.git
|
|
226
|
+
cd solid_queue_guard
|
|
227
|
+
bundle install
|
|
228
|
+
cd test/dummy && bin/setup
|
|
229
|
+
cd ../.. && bundle exec rake test
|
|
230
|
+
bundle exec rubocop
|
|
231
|
+
bundle exec appraisal install
|
|
232
|
+
bundle exec appraisal rake test
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Issues and PRs welcome. Keep it focused: **production safety**, not another job dashboard.
|
|
240
|
+
|
|
241
|
+
1. Fork it
|
|
242
|
+
2. Create your branch (`git checkout -b my-new-check`)
|
|
243
|
+
3. Write a test first
|
|
244
|
+
4. Make the check pass
|
|
245
|
+
5. Open a PR
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT — see [MIT-LICENSE](MIT-LICENSE).
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
<p align="center">
|
|
256
|
+
<strong>Run the doctor before you run the deploy.</strong><br>
|
|
257
|
+
<sub>Built for Rails teams who chose Solid Queue and still sleep at night.</sub>
|
|
258
|
+
</p>
|
data/Rakefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
|
6
|
+
load 'rails/tasks/engine.rake'
|
|
7
|
+
|
|
8
|
+
require 'bundler/gem_tasks'
|
|
9
|
+
|
|
10
|
+
require 'rake/testtask'
|
|
11
|
+
|
|
12
|
+
Rake::TestTask.new(:test) do |task|
|
|
13
|
+
task.libs << 'test'
|
|
14
|
+
task.pattern = 'test/**/*_test.rb'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task default: :test
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module SolidQueueGuard
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
|
9
|
+
|
|
10
|
+
desc 'Creates Solid Queue Guard initializer'
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template 'solid_queue_guard.rb', 'config/initializers/solid_queue_guard.rb'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SolidQueueGuard.configure do |config|
|
|
4
|
+
config.enabled = Rails.env.production?
|
|
5
|
+
|
|
6
|
+
config.queue_lag_thresholds = {
|
|
7
|
+
critical: 30.seconds,
|
|
8
|
+
default: 5.minutes,
|
|
9
|
+
mailers: 15.minutes
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
config.failed_jobs_threshold = 20
|
|
13
|
+
config.stale_process_threshold = 5.minutes
|
|
14
|
+
|
|
15
|
+
# config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
|
|
16
|
+
# config.notify_with = [ :rails_logger ]
|
|
17
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Check
|
|
5
|
+
Result = Data.define(:id, :status, :message, :suggestion, :metadata) do
|
|
6
|
+
def initialize(id:, status:, message:, suggestion: nil, metadata: {})
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def pass?
|
|
11
|
+
status == :pass
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def warn?
|
|
15
|
+
status == :warn
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fail?
|
|
19
|
+
status == :fail
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def skip?
|
|
23
|
+
status == :skip
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
id: id,
|
|
29
|
+
status: status.to_s,
|
|
30
|
+
message: message,
|
|
31
|
+
suggestion: suggestion,
|
|
32
|
+
metadata: metadata
|
|
33
|
+
}.compact
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
# @api private
|
|
6
|
+
class Base
|
|
7
|
+
def self.call(**options)
|
|
8
|
+
new(**options).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(**options)
|
|
12
|
+
@options = options
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :options
|
|
22
|
+
|
|
23
|
+
def pass(id, message, **kwargs)
|
|
24
|
+
build_result(id, :pass, message, **kwargs)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warn(id, message, **kwargs)
|
|
28
|
+
build_result(id, :warn, message, **kwargs)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def failure(id, message, **kwargs)
|
|
32
|
+
build_result(id, :fail, message, **kwargs)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def skip(id, message, **kwargs)
|
|
36
|
+
build_result(id, :skip, message, **kwargs)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_result(id, status, message, suggestion: nil, metadata: {})
|
|
40
|
+
Check::Result.new(id: id, status: status, message: message, suggestion: suggestion, metadata: metadata)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def rails_root
|
|
44
|
+
Rails.root
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def solid_queue_configuration
|
|
48
|
+
@solid_queue_configuration ||= SolidQueue::Configuration.new
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class AdapterCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
adapter = Rails.application.config.active_job.queue_adapter
|
|
9
|
+
|
|
10
|
+
if adapter.to_sym == :solid_queue
|
|
11
|
+
pass('adapter', 'Active Job adapter is :solid_queue')
|
|
12
|
+
else
|
|
13
|
+
failure(
|
|
14
|
+
'adapter',
|
|
15
|
+
"Active Job adapter is #{adapter.inspect}, expected :solid_queue",
|
|
16
|
+
suggestion: 'Set config.active_job.queue_adapter = :solid_queue'
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class ConnectsToCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
connects_to = Rails.application.config.solid_queue.connects_to
|
|
9
|
+
|
|
10
|
+
if connects_to.blank?
|
|
11
|
+
return failure(
|
|
12
|
+
'connects_to',
|
|
13
|
+
'config.solid_queue.connects_to is not configured',
|
|
14
|
+
suggestion: 'Set config.solid_queue.connects_to = { database: { writing: :queue } }'
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
writing = connects_to.dig(:database, :writing) || connects_to.dig('database', 'writing')
|
|
19
|
+
|
|
20
|
+
if writing.to_s != 'queue'
|
|
21
|
+
failure(
|
|
22
|
+
'connects_to',
|
|
23
|
+
"Solid Queue connects_to writing target is #{writing.inspect}, expected :queue",
|
|
24
|
+
suggestion: 'Point config.solid_queue.connects_to to the queue database'
|
|
25
|
+
)
|
|
26
|
+
elsif SolidQueue::Record.connection_pool.nil?
|
|
27
|
+
failure('connects_to', 'Solid Queue queue database connection pool is not available')
|
|
28
|
+
else
|
|
29
|
+
pass(
|
|
30
|
+
'connects_to',
|
|
31
|
+
"Solid Queue connects_to queue database (pool: #{SolidQueue::Record.connection_pool.size})"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class EnvFlagsCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
if Rails.env.production? && ActiveModel::Type::Boolean.new.cast(ENV.fetch('SOLID_QUEUE_SKIP_RECURRING', nil))
|
|
9
|
+
warn(
|
|
10
|
+
'env_flags',
|
|
11
|
+
'SOLID_QUEUE_SKIP_RECURRING=true in production',
|
|
12
|
+
suggestion: 'Remove SOLID_QUEUE_SKIP_RECURRING unless recurring jobs are intentionally disabled'
|
|
13
|
+
)
|
|
14
|
+
else
|
|
15
|
+
pass('env_flags', 'No dangerous Solid Queue environment flags detected')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class ProcessHeartbeatConfigCheck < Base
|
|
7
|
+
DEFAULT_HEARTBEAT_INTERVAL = 60.seconds
|
|
8
|
+
DEFAULT_ALIVE_THRESHOLD = 5.minutes
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
heartbeat_interval = SolidQueue.process_heartbeat_interval
|
|
12
|
+
alive_threshold = SolidQueue.process_alive_threshold
|
|
13
|
+
|
|
14
|
+
if heartbeat_interval == DEFAULT_HEARTBEAT_INTERVAL && alive_threshold == DEFAULT_ALIVE_THRESHOLD
|
|
15
|
+
warn(
|
|
16
|
+
'process_heartbeat_config',
|
|
17
|
+
'Process heartbeat thresholds use defaults (interval: 60s, alive: 5m)',
|
|
18
|
+
suggestion: 'Consider customizing process_alive_threshold for your deployment latency requirements'
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
pass(
|
|
22
|
+
'process_heartbeat_config',
|
|
23
|
+
"Process heartbeat interval: #{heartbeat_interval.inspect}, alive threshold: #{alive_threshold.inspect}"
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class PumaColocatedCheck < Base
|
|
7
|
+
PUMA_PLUGIN_PATTERN = /plugin\s+:?solid_queue/
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
puma_config = rails_root.join('config/puma.rb')
|
|
11
|
+
|
|
12
|
+
return pass('puma_colocated', 'No config/puma.rb found') unless puma_config.exist?
|
|
13
|
+
|
|
14
|
+
content = puma_config.read
|
|
15
|
+
|
|
16
|
+
if content.match?(PUMA_PLUGIN_PATTERN)
|
|
17
|
+
if Rails.env.production?
|
|
18
|
+
warn(
|
|
19
|
+
'puma_colocated',
|
|
20
|
+
'Solid Queue Puma plugin is enabled in production',
|
|
21
|
+
suggestion: 'Run Solid Queue in a dedicated job process for better isolation and memory management'
|
|
22
|
+
)
|
|
23
|
+
else
|
|
24
|
+
pass('puma_colocated', 'Solid Queue Puma plugin detected (non-production environment)')
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
pass('puma_colocated', 'Solid Queue is not co-located with Puma')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class QueueDatabaseCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
config = Rails.application.config.database_configuration
|
|
9
|
+
env_config = config[Rails.env]
|
|
10
|
+
|
|
11
|
+
if env_config.is_a?(Hash) && env_config.key?('queue')
|
|
12
|
+
pass('queue_database', 'Queue database configured in database.yml')
|
|
13
|
+
else
|
|
14
|
+
failure(
|
|
15
|
+
'queue_database',
|
|
16
|
+
"No queue database entry in database.yml for #{Rails.env}",
|
|
17
|
+
suggestion: 'Add a queue database configuration for Solid Queue'
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class QueueSchemaCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
schema_path = rails_root.join('db/queue_schema.rb')
|
|
9
|
+
|
|
10
|
+
if schema_path.exist?
|
|
11
|
+
pass('queue_schema', 'db/queue_schema.rb exists')
|
|
12
|
+
else
|
|
13
|
+
warn(
|
|
14
|
+
'queue_schema',
|
|
15
|
+
'db/queue_schema.rb not found',
|
|
16
|
+
suggestion: 'Run bin/rails solid_queue:install to generate the queue schema'
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|