Run Puma in cluster mode, and give the nightly purge container a deadline - #133
Conversation
MRI executes Ruby on one core per process, so a single Puma process with sixteen threads still serves every request from one core while the rest of the machine idles. Two workers in production, overridable through WEB_CONCURRENCY; development stays in single mode. No preload_app!, because phased restarts (SIGUSR1) — the way to recycle workers without dropping requests — need workers that boot the app themselves.
The rails runner that performs PurgeImagesJob can deadlock at process exit while draining the :async adapter's thread pool — ActiveStorage still enqueues PurgeJobs during destroy despite the inline purge. When that happens the container never exits, --rm never fires, one hung container accumulates per night holding a MySQL connection open, and set -e keeps every later step of the script (download retention, orphan reap, shard-directory cleanup) from running at all. The purge work itself finishes in seconds, before the hang, so bounding the container's lifetime at ten minutes loses nothing.
PR Summary by QodoEnable Puma clustering and bound nightly purge runtime
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
The purge container carries a fixed name and the cleanup after it removes whatever currently holds that name, so two overlapping runs -- the cron one plus a manual invocation -- would have the newcomer kill the running purge's container and then skip its own purge while still executing the later cleanup steps. flock on a lock file makes the second invocation bow out instead.
|
/review |
|
Code review by qodo was updated up to the latest commit 1401ca7 |
A container left behind by an interrupted run -- host reboot, script killed after the timeout fired but before its own cleanup -- would make the next night's docker run fail on the name collision, and that night's purge would be skipped with only a warning. Under the flock nothing legitimate can hold the name, so remove whatever does before starting. Raised by review on #133.
Two pieces of housekeeping:
config/puma.rb— MRI executes Ruby on one core per process, so a single Puma process with sixteen threads still serves every request from one core while the rest of the machine idles. Production now boots two workers (overridable throughWEB_CONCURRENCY); development stays in single mode. Nopreload_app!: phased restarts (SIGUSR1) replace workers one at a time with the listener kept open — a restart without dropped requests — and they only work when each worker boots the app itself.deploy/purge-snapshots.sh— the rails runner performingPurgeImagesJobcan deadlock at process exit while draining the:asyncadapter's thread pool (ActiveStorage still enqueuesPurgeJobs during destroy despite the inline purge). When that happens the container never exits,--rmnever fires, one hung container accumulates per night holding a MySQL connection open, andset -ekeeps the rest of the nightly script (download retention, orphan reap, shard-directory cleanup) from running at all. The purge work itself finishes in seconds, before the hang, so the container now gets a ten-minute deadline and a forced cleanup, and a failure logs a warning instead of aborting the script.Verified: full test suite green (359 runs, 0 failures),
ruby -cclean, rubocop adds no offenses to the touched files.