ruby 49 lines · 4 tabs

Recurring Cleanup with a Rake Task and an Idempotent Active Job in Rails

Shared by codesnips Jul 2026
4 tabs
namespace :cleanup do
  desc "Enqueue a job to purge expired sessions"
  task expired_sessions: :environment do
    job = ExpiredSessionCleanupJob.perform_later
    Rails.logger.info("[cleanup:expired_sessions] enqueued job #{job.job_id}")
  end
end
4 files · ruby Explain with highlit

This snippet shows the common Rails pattern for expiring stale records on a schedule: a thin rake task that the system cron (or a scheduler like whenever/cron in a container) invokes, which in turn enqueues an Active Job that does the real work. Separating the two layers matters because rake tasks run in the foreground of the invoking process, hold a full Rails boot, and offer no retries; the job, by contrast, runs on the queue with retry, timeout, and observability that the rest of the app already relies on.

In sessions.rake, the cleanup:expired_sessions task does almost nothing itself — it calls ExpiredSessionCleanupJob.perform_later and logs. Keeping the task trivial means the cron line stays stable and all business logic lives in one testable place. The task is namespaced under cleanup so related housekeeping tasks can be grouped and discovered with rake -T cleanup.

The real work is in ExpiredSessionCleanupJob. It runs on a dedicated :maintenance queue so a flood of cleanup work never starves latency-sensitive queues. Deletion is done in bounded batches via in_batches with an explicit of: size, which caps how many rows a single DELETE touches and keeps transactions short — important on Postgres where long-running deletes hold locks and bloat the table. The job counts affected rows through delete_all, which issues a set-based SQL DELETE and skips Active Record callbacks; that is the right call for pure purging where per-row callbacks would be wasteful.

Idempotency comes for free here: the query only matches rows already past expires_at, so running the job twice simply finds nothing the second time. That safety is what makes the cron-plus-retry combination reliable — a duplicated trigger or a retried job cannot corrupt state.

The scope logic lives on the model in Session, where expired and cleanup_batch_size centralize the definition of "stale" so both the job and any admin tooling agree. The schedule.rb tab shows how the rake task is wired into cron with the whenever gem, translating a Ruby DSL into a crontab entry. A key pitfall to note: because the job deletes in batches inside its own transactions, it should be safe to interrupt mid-run, which this design guarantees since each batch commits independently.


Related snips

Share this code

Here's the card — post it anywhere.

Recurring Cleanup with a Rake Task and an Idempotent Active Job in Rails — share card
Link copied