Use a single database for primary, cache, queue, cable#24
Merged
Conversation
Rails 8 ships database.yml with four separately-configured roles in production (primary, cache, queue, cable) so Solid Cache, Solid Queue, and Solid Cable can each live in their own database. The default already points all four roles at the same DATABASE_URL with separate migration paths — sharing one Render Postgres saves money but keeps the conceptual overhead of four config blocks, three empty stub schema files, and three nonexistent migration directories (db/cache_migrate, db/queue_migrate, db/cable_migrate). The migrations actually live in db/migrate/ and db/schema.rb already contains all the SC/SQ/SCable tables, so the multi-role config was purely cosmetic. Collapse to one role for student projects on Render's free tier: - config/database.yml: replace the four-role production block with a single `<<: *default` + `url:` pair. - config/cache.yml: drop `database: cache` so SolidCache::Record inherits ApplicationRecord's connection. - config/cable.yml: drop the `connects_to` block so SolidCable inherits the primary connection. - Delete db/cable_schema.rb, db/cache_schema.rb, db/queue_schema.rb; they were empty (`version: 0` stubs) and only matter when each role has its own database. Verified with `RAILS_ENV=production bin/rails runner` that ApplicationRecord, SolidQueue::Record, SolidCache::Record, and SolidCable::Record all bind to the `primary` config. The same single-DB pattern is already in production on aplace.app and has been stable since the async-mode rollout.
raghubetina
added a commit
that referenced
this pull request
May 18, 2026
This branch's puma.rb adds `solid_queue_mode :async if ENV["SOLID_QUEUE_IN_PUMA"]`, but the lockfile resolved to solid_queue 1.1.4, which has no such DSL method. Booting in production crashes immediately with: config/puma.rb:47:in 'Puma::DSL#_load_from': undefined method 'solid_queue_mode' for an instance of Puma::DSL (NoMethodError) Async supervisor mode was re-introduced in solid_queue 1.3.0 (rails/solid_queue#644); the earlier 0.4 implementation was removed in 0.7. Pinning to ~> 1.3 documents the minimum that matches this branch's puma.rb and updates the lockfile to 1.4.0. Caught by deploying a marketplace smoke-test app built from this branch + #22 + #24 to Render free tier.
raghubetina
added a commit
that referenced
this pull request
May 18, 2026
* Run Solid Queue in async mode on Render free tier Companion to #22 (WEB_CONCURRENCY=0). Free tier (512MB) can't fit SQ's default fork mode, which spawns supervisor + worker + dispatcher + scheduler subprocesses for ~460MB of RSS overhead on top of Puma. The container OOM-cycles every ~7-15 min — the deploy still looks "live" but HTTP returns 502s during each restart window. `solid_queue_mode :async` (documented switch in lib/puma/plugin/solid_queue.rb of the gem) tells the plugin to run worker/dispatcher/scheduler as threads inside the Puma master process. The 4 SQ subprocesses collapse into ~50MB of thread overhead in Puma. Container RSS drops from ~530MB (cycling into OOM) to a flat ~300MB. Trade-off per SQ's own docs ("Only use async if you know what you're doing and have strong reasons to"): - Less isolation between SQ and Puma. A leaky/hung SQ thread affects request serving. For low-traffic student projects this is the right call; revisit if upgrading to a paid plan. - No inter-process parallelism for jobs. Free tier is already 0.1 vCPU, so this isn't real lost throughput. - Recurring tasks and concurrency controls still work unchanged. The DB pool bump (5 → 8) is non-optional: previously the pool was sized for Puma's 3 request threads only; now the same process also runs 3 SQ worker threads + dispatcher + scheduler. Under any load the old pool=5 would throw ConnectionTimeoutError. The 5 -> 8 default still respects DB_POOL env override for projects that need finer control. Diagnosed and validated on raghubetina/aplace: raghubetina/aplace#58 — RSS metric showed the predicted plateau at ~300MB with zero growth after deploy. * Pin solid_queue ~> 1.3 for solid_queue_mode :async support This branch's puma.rb adds `solid_queue_mode :async if ENV["SOLID_QUEUE_IN_PUMA"]`, but the lockfile resolved to solid_queue 1.1.4, which has no such DSL method. Booting in production crashes immediately with: config/puma.rb:47:in 'Puma::DSL#_load_from': undefined method 'solid_queue_mode' for an instance of Puma::DSL (NoMethodError) Async supervisor mode was re-introduced in solid_queue 1.3.0 (rails/solid_queue#644); the earlier 0.4 implementation was removed in 0.7. Pinning to ~> 1.3 documents the minimum that matches this branch's puma.rb and updates the lockfile to 1.4.0. Caught by deploying a marketplace smoke-test app built from this branch + #22 + #24 to Render free tier.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rails 8 ships
config/database.ymlwith four separately-configured roles in production (primary, cache, queue, cable) so Solid Cache, Solid Queue, and Solid Cable can each live in their own database. The default already points all four roles at the sameDATABASE_URLwith separate migration paths — sharing one Render Postgres saves money but keeps the conceptual overhead of four config blocks, three empty stub schema files, and three nonexistent migration directories.The migrations actually live in
db/migrate/anddb/schema.rbalready contains all the SC/SQ/SCable tables, so the multi-role config was purely cosmetic. This PR collapses it to a single role.Changes:
config/database.yml: replace the four-role production block with a single<<: *default+url:pair.config/cache.yml: dropdatabase: cachesoSolidCache::RecordinheritsApplicationRecord's connection.config/cable.yml: drop theconnects_toblock so Solid Cable inherits the primary connection.db/cable_schema.rb,db/cache_schema.rb,db/queue_schema.rb— empty (version: 0) stubs only meaningful when each role has its own database.The same single-DB pattern is already in production on aplace.app and has been stable since the async-mode rollout.
Test plan
RAILS_ENV=production bin/rails runnerconfirmsApplicationRecord,SolidQueue::Record,SolidCache::Record, andSolidCable::Recordall reportconnection_db_config.name == "primary".connects_to,database_selector,database_resolver,shard_selector, or per-DB schema cache files anywhere inconfig/orapp/.cache_migrate,queue_migrate, orcable_migratedirectories (which never existed on disk).db/schema.rb.