Production readiness checks and runtime guards for Rails Solid Queue.
Rails 8 ships with Solid Queue. Redis is optional. Production config is not.
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.
solid_queue_guard catches that before it becomes a 3am incident.
Mission Control shows what is happening.
solid_queue_guard warns what is dangerous.
Solid Queue is excellent. Operating it blindly is not.
| Symptom | What actually happened |
|---|---|
| "Site is up, emails stopped" | Workers dead, heartbeats stale |
| "Only 3 jobs in the queue, why the panic?" | Oldest job waiting 40 minutes — lag, not depth |
| "Recurring billing just… stopped" | Scheduler not running, nobody noticed for a week |
| "Jobs hang after deploy" | Thread count > DB pool — connection starvation |
| "Health check passes, jobs don't" | /up doesn't know Solid Queue exists |
You don't need another dashboard. You need a doctor.
bundle add solid_queue_guard
bin/rails solid_queue_guard:install
bin/rails solid_queue_guard:doctorSolidQueueGuard Report
Status: DEGRADED
Checks:
✅ Active Job adapter is :solid_queue
✅ Queue database configured in database.yml
✅ Solid Queue connects_to queue database (pool: 10)
✅ Solid Queue schema tables present
❌ Worker threads: 10, queue DB pool: 5
⚠️ No workers configured for "mailers" queue
⚠️ recurring.yml exists but scheduler may not run
✅ Solid Queue is not co-located with Puma
Suggested fixes:
- Increase queue DB pool to at least 12 or reduce worker threads
- Add a worker for the mailers queue
- Verify scheduler process is running in production
One command. Actionable output. No Datadog required to get started.
Runs locally, in CI, or pre-deploy. No extra infrastructure.
| Check | Catches |
|---|---|
| Adapter | Active Job not set to :solid_queue |
| Queue database | Missing queue entry in database.yml |
| connects_to | Solid Queue pointing at the wrong DB / pool |
| Queue schema | Missing Solid Queue tables in schema files or queue database |
| Thread pool | threads > available queue DB connections |
| Worker coverage | Queues with no worker assigned |
| Scheduler | recurring.yml tasks without a scheduler |
| Env flags | SOLID_QUEUE_SKIP_RECURRING=true in production |
| Heartbeats | Default thresholds that may not fit your deploy |
| Puma plugin | Solid Queue co-located with web in production |
| Async supervisor | async mode with thread/pool sizing risks |
| Topology | queue.yml worker and pool recommendations |
| Check | Catches |
|---|---|
| Queue lag | Oldest ready job waiting too long |
| Stale processes | Dead workers, dispatchers, schedulers |
| Dispatcher health | Scheduled jobs never becoming ready |
| Blocked jobs | Concurrency control silently holding jobs |
| Recurring staleness | Cron tasks that stopped firing |
| Puma plugin runtime | Plugin enabled but no active Solid Queue processes |
HTTP /health |
Kamal, ECS, K8s, UptimeRobot integration |
bundle add solid_queue_guard
bin/rails solid_queue_guard:install
bin/rails generate solid_queue_guard:install:ci # optional GitHub Actions workflow# Human-readable report
bin/rails solid_queue_guard:doctor
# JSON for scripts / CI
bin/rails "solid_queue_guard:doctor[json]"
SOLID_QUEUE_GUARD_FORMAT=json bin/rails solid_queue_guard:doctor
# Fail CI on warnings too
SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor
# Full report (config + runtime when available)
bin/rails solid_queue_guard:report| Code | Meaning |
|---|---|
0 |
Healthy or degraded |
1 |
Unhealthy (or degraded in --strict / SOLID_QUEUE_GUARD_STRICT=1) |
Perfect for deploy pipelines:
# GitHub Actions
- name: Solid Queue production readiness
run: SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctorOr generate a workflow:
bin/rails generate solid_queue_guard:install:ci# config/routes.rb
mount SolidQueueGuard::Engine, at: "/solid_queue_guard"curl localhost:3000/solid_queue_guard/health
# => { "status": "degraded", "queue_lag_seconds": 245, "warnings": [...] }Optional token protection:
config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
# curl -H "X-Solid-Queue-Guard-Token: $TOKEN" ...HTTP status policy (for Kamal, ECS, load balancers):
config.degraded_http_status = 207 # or :ok (200), 503, etc.
config.unhealthy_http_status = 503 # defaultWorks with Kamal, Heroku, Fly.io, ECS/Fargate, Kubernetes, Better Stack, UptimeRobot.
Add a Guard tab to Mission Control — Jobs — same Bulma UI, same auth, same navigation:
# Gemfile
gem "mission_control-jobs"
gem "solid_queue_guard"
# config/initializers/solid_queue_guard.rb
SolidQueueGuard.configure do |config|
config.integrate_mission_control = true
end
# config/routes.rb
mount MissionControl::Jobs::Engine, at: "/jobs"
mount SolidQueueGuard::Engine, at: "/solid_queue_guard" # keep /health for probesOpen /jobs/guard (or click the Guard tab inside Mission Control). The gem adds a new tab to Mission Control navigation — same layout, auth, and Bulma UI as Queues, Workers, and Recurring tasks:
The page shows:
| Section | Content |
|---|---|
| Overall status | healthy / degraded / unhealthy badge |
| Metrics | Queue lag, failed jobs (1h), dead processes, check count |
| Warnings & suggestions | Same messages as doctor / /health |
| Checks table | Every check with status tag, message, and suggestion |
Authentication: the Guard tab inherits Mission Control auth — HTTP basic (default) or your admin controller via MissionControl::Jobs.base_controller_class. No separate login.
Load balancers: keep using GET /solid_queue_guard/health with optional health_token. Probes do not go through Mission Control.
Requirements: an asset pipeline (Propshaft or Sprockets), same as Mission Control. mission_control-jobs is optional — only needed when integrate_mission_control is enabled.
Solid Queue does not show up in Rails /up. solid_queue_guard gives you four operational surfaces:
| Surface | Command / URL | Best for |
|---|---|---|
| Doctor | bin/rails solid_queue_guard:doctor |
Local pre-deploy, config review |
| CI gate | SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor |
Block merges with broken queue config |
| HTTP health | GET /solid_queue_guard/health |
Production uptime monitors (Kamal, ECS, UptimeRobot) |
| Guard tab | GET /jobs/guard (with integrate_mission_control) |
Human-readable checks inside Mission Control |
Mission Control shows what is happening. solid_queue_guard warns what is dangerous. Use both.
Run before changing queue.yml, database.yml, or recurring tasks:
bin/rails solid_queue_guard:doctor # config checks (default scope)
bin/rails solid_queue_guard:report # config + runtime when the queue DB is availableBy default the install generator sets config.enabled = %w[production staging].include?(Rails.env), so in development and other non-deployed environments checks are skipped unless you enable them:
SolidQueueGuard.configure { |c| c.enabled = true }Or run with production config locally when validating deploy readiness.
Validate configuration before deploy. Process/runtime checks usually skip in CI because there is no bin/jobs supervisor on the runner.
- name: Solid Queue production readiness
run: SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctorSTRICT=1 turns warnings into exit code 1, so the pipeline fails on misconfigured pools, missing worker coverage, or missing schema tables — not only hard failures.
Generate a starter workflow:
bin/rails generate solid_queue_guard:install:ciMount the engine and point your load balancer or Kamal health check at /solid_queue_guard/health:
# config/routes.rb
mount SolidQueueGuard::Engine, at: "/solid_queue_guard"Runtime checks matter here: queue lag, stale heartbeats, dispatcher health, and process topology reflect live Solid Queue state.
Optional hardening:
config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
config.health_cache_ttl = 15.seconds
config.degraded_http_status = 207 # or :ok (200), 503, etc.
config.notify_with = [:rails_logger, :slack]Developer CI Production
│ │ │
│ doctor │ doctor --strict │ GET /health (every 30–60s)
▼ ▼ ▼
"pool wrong?" block bad deploy alert: worker dead
"schema ok?" before merge alert: queue lag
Runtime checks query the queue database (via SolidQueue::Record). If connects_to is missing or the DB is unreachable, they skip — they do not pass silently as healthy.
Reads distinct kind values from solid_queue_processes:
| Kind | Role |
|---|---|
Supervisor |
Parent process (bin/jobs) |
Worker |
Consumes ready jobs |
Dispatcher |
Moves scheduled jobs to ready |
Scheduler |
Runs recurring tasks |
| Result | Meaning |
|---|---|
| Nothing registered — supervisor not running | |
| Jobs will not be processed | |
| Scheduled work may stall (when recurring/scheduled jobs exist) | |
| ✅ Pass | Expected kinds are present |
This check looks at presence of records, not heartbeat freshness.
Finds processes where last_heartbeat_at is older than stale_process_threshold (default 5 minutes):
| Result | Meaning |
|---|---|
| ✅ Pass | All processes reported recently |
| ❌ Fail | One or more workers/dispatchers look dead or stuck |
Use this in production health to catch workers that died after deploy.
When tmp/pids/solid_queue.pid (or SOLID_QUEUE_PIDFILE) exists, verifies the PID is alive. Often warns in development where bin/jobs is not running. Disable if you do not use pidfiles:
config.disabled_checks = [:pidfile]When plugin :solid_queue is in config/puma.rb, verifies active processes with recent heartbeats exist. Skips when the Puma plugin is not enabled.
QueueSchemaCheck does not require db/queue_schema.rb. It validates that all tables for your installed solid_queue version exist in any of:
db/queue_schema.rbdb/schema.rbdb/structure.sql- the connected queue database
Apps that keep Solid Queue tables only in structure.sql (common in Revelo-style repos) pass correctly.
| Context | Config checks | Process / runtime checks |
|---|---|---|
Local doctor |
✅ Primary value | bin/jobs |
CI --strict |
✅ Primary value | ⏭️ Usually skip |
Production /health |
✅ When DB up | ✅ Primary value |
# config/initializers/solid_queue_guard.rb
SolidQueueGuard.configure do |config|
config.enabled = %w[production staging].include?(Rails.env)
config.queue_lag_thresholds = {
critical: 30.seconds,
default: 5.minutes,
mailers: 15.minutes
}
config.failed_jobs_threshold = 20
config.stale_process_threshold = 5.minutes
# Per-check overrides (v0.6+)
config.disabled_checks = [:pidfile]
config.checks.queue_lag = { threshold: 10.minutes }
config.checks.failed_jobs = { threshold: 5, enabled: true }
# HTTP status policy (v0.8+)
# config.degraded_http_status = 207
# config.unhealthy_http_status = 503
# config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
# config.integrate_rails_health = true
# config.integrate_mission_control = true # Guard tab in Mission Control (requires mission_control-jobs)
# config.notify_with = [:rails_logger, :slack, :datadog, :webhook]
# config.metrics_backends = [:statsd, :prometheus, :opentelemetry]
endThe following surface is stable until 2.0 and follows semantic versioning:
| API | Description |
|---|---|
SolidQueueGuard.configure |
Block-style configuration |
SolidQueueGuard.config |
Current configuration object |
SolidQueueGuard.enabled? |
Whether checks run |
solid_queue_guard:doctor |
Config readiness report |
solid_queue_guard:health |
Runtime health report |
solid_queue_guard:report |
Full diagnostic report |
solid_queue_guard:install |
Initializer generator |
solid_queue_guard:install:ci |
GitHub Actions workflow generator |
mount SolidQueueGuard::Engine |
HTTP health endpoint |
config.integrate_mission_control |
Guard tab in Mission Control (opt-in, requires mission_control-jobs) |
GET /jobs/guard |
Human-readable health dashboard (when integration enabled) |
Configuration attributes, rake tasks, health JSON shape, and Mission Control integration are public. Internal check classes and registry are @api private.
Breaking changes ship only in major versions (2.0+). Deprecations warn one minor version ahead.
| Mission Control — Jobs | solid_queue_guard | |
|---|---|---|
| Purpose | Inspect & manage jobs | Detect production risk |
| UI | Dashboard | CLI + JSON + health endpoint + optional Guard tab in Mission Control |
| Retry / discard | Yes | No (use Mission Control) |
| Config doctor | No | Yes |
| Queue lag alerts | No | Yes |
| Pre-deploy checks | No | Yes |
| Recurring job guard | Manual inspection | Automatic |
Use both. They solve different problems.
| Version | Ships | Status |
|---|---|---|
| v0.1 | doctor — config checks, CI integration, install generator |
✅ Released |
| v0.2 | Runtime health, queue lag, dispatcher/blocked jobs, HTTP endpoint | ✅ Released |
| v0.3 | Slack, Datadog, webhook notifications | ✅ Released |
| v0.4 | StatsD, Prometheus, OpenTelemetry metrics | ✅ Released |
| v0.5 | Auto-recommendations for queue.yml topology |
✅ Released |
| v0.6 | Per-check configuration (disabled_checks, config.checks) |
✅ Released |
| v0.7 | Puma plugin runtime check, async supervisor awareness | ✅ Released |
| v0.8 | HTTP status policy, install:ci generator |
✅ Released |
| v1.0 | Stable public API, strict semver | ✅ Released |
| v1.1 | Mission Control Guard tab (opt-in) | ✅ Released |
| Gem version | Ruby | Rails |
|---|---|---|
| 1.1.x | 3.1+ | 7.1, 7.2, 8.0 |
| 1.0.x | 3.1+ | 7.1, 7.2, 8.0 |
| 0.5.x | 3.1+ | 7.1, 7.2, 8.0 |
| 0.1.x | 3.1+ | 7.1, 7.2, 8.0 |
- Ruby >= 3.1
- Rails >= 7.1, < 9.0
- solid_queue >= 1.0, < 2.0
- mission_control-jobs >= 1.0 — optional, only for
integrate_mission_control
git clone https://github.com/rafael-pissardo/solid_queue_guard.git
cd solid_queue_guard
bundle install
cd test/dummy && bin/setup
cd ../.. && bundle exec rake test
bundle exec rubocop
bundle exec appraisal install
bundle exec appraisal rake testRelease a new version after CI passes:
# Tag must match lib/solid_queue_guard/version.rb (currently 1.1.3)
git tag v1.1.3
git push origin v1.1.3Trusted Publishing on RubyGems publishes automatically when the tag is pushed.
Issues and PRs welcome. Keep it focused: production safety, not another job dashboard.
- Fork it
- Create your branch (
git checkout -b my-new-check) - Write a test first
- Make the check pass
- Open a PR
MIT — see MIT-LICENSE.
Run the doctor before you run the deploy.
Built for Rails teams who chose Solid Queue and still sleep at night.
