Background workers and cron jobs as first-class service types
Message Queues & Event-Driven Architecture's own domain covered the competing-consumers pattern conceptually — a worker process pulling jobs off a queue. Railway is where that pattern becomes a real, concrete deployable thing, with no web framework required to bolt it onto.
Intermediate
2 min read
A worker: a service with no HTTP interface at all
# worker.py — no web framework, no routes, no port to listen onimport timefrom queue_client import get_next_jobwhile True: job = get_next_job() if job: process(job) else: time.sleep(1)
A Railway worker service is simply a long-running process with no requirement to expose an HTTP port at all — Railway deploys and keeps it running exactly like any web service, just without the "give this a public domain" step that a frontend or API service needs. This directly implements Message Queues' own competing-consumers pattern: deploy several instances of the same worker service, each independently pulling jobs from the same queue, and Railway's own scaling (manual or automatic, depending on plan) handles running more or fewer instances.
Cron jobs: a service that runs on a schedule, not continuously
Railway service type: "Cron Job"
Schedule: 0 2 * * * (every day at 2 AM)
Command: python cleanup_old_records.py
A cron job service runs its command on a defined schedule, then exits — billed (per lesson 6's metering) only for the actual time it runs, not for sitting idle between scheduled runs the way a continuously-running worker would be. This is the right shape for genuinely periodic work (a nightly cleanup, a daily report generation, a weekly digest email) that has no reason to be a continuously-running process waiting for the next scheduled moment.
Why this distinction between worker and cron matters for cost and correctness
Running genuinely periodic work as an always-on worker (checking "is it 2 AM yet?" in a loop) wastes the metered-billing advantage lesson 6 described — you'd pay for 24 hours of idle compute to do 5 minutes of real work once a day. Running genuinely continuous, reactive work (processing a queue the moment a job arrives) as a cron job instead introduces real latency — jobs sit unprocessed until the next scheduled run, rather than being picked up immediately. Matching the actual shape of the work (continuous-and-reactive vs. periodic-and-scheduled) to the correct service type is a real, concrete decision with real cost and latency consequences, not just a labeling preference.
Scaling a worker horizontally: more instances, same queue
Railway dashboard → worker service → Scaling → Replicas: 3
Increasing a worker's replica count runs multiple independent instances of the exact same process, all pulling from the same shared queue — this is precisely Message Queues' own competing-consumers pattern, made concrete as a single slider in a dashboard rather than something requiring its own separate infrastructure to orchestrate.