Published on

System Design: Comparing Background Job Techniques in GCP: App Schedulers, Cloud Scheduler, and Cloud Run Jobs

Authors
  • avatar
    Name
    Loi Tran
    Twitter

Introduction

In modern cloud applications, automating background tasks is essential for maintaining performance, reliability, and scalability. Scheduled jobs are commonly used for a variety of purposes, such as sending email digests, cleaning up old data, syncing with external APIs, or generating reports at regular intervals.

For example, you might want to:

  • Send a weekly summary email to users.
  • Run nightly database backups.
  • Periodically fetch and process data from a third-party service.
  • Clean up expired sessions or logs every hour.

There are several ways to implement scheduled jobs in cloud environments, each with its own strengths and trade-offs. In this post, we'll explore three popular techniques:

  1. App Scheduler
  2. Cron Job/Cloud Scheduler
  3. Cloud Run Job

App Scheduler

A Python library (APScheduler) that allows you to schedule and run background tasks directly within your application code.

Example:

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

scheduler = BackgroundScheduler()

def process_new_dates():
    pass

def start_scheduler():
    # Runs at 3:00am EST/EDT depending on time of the year.
    ny_tz = ZoneInfo("America/New_York")
    scheduler.add_job(
        process_new_dates,
        CronTrigger(hour=3, minute=0, timezone=ny_tz),
        id="process_new_dates",
        replace_existing=True,
    )

start_scheduler()

Pros:

  • Easy to integrate directly into Python applications.
  • Flexible scheduling (interval, cron, date).
  • No external dependencies if running inside your app.

Cons:

  • Runs only as long as your application is running.
  • Not suitable for distributed or cloud-native environments.
  • Limited scalability and reliability for large workloads.
  • Unsuitable when your app has multiple instances.

App Schedulers are defined in application code. It works but it has a few disadvantages. If your application is scaled horizontally for example, then the job may run multiple times unnecessarily.

Cloud Scheduler (Cron Job)

A fully managed Google Cloud service that triggers jobs or HTTP endpoints on a defined schedule, similar to traditional cron jobs.

def stream_orats_data(days: int = 3, max_workers: int = 12) -> Iterator[str]:
    pass

@router.get("/pull-orats-data")
async def pull_orats_data_unified(
    days: int = Query(
        default=3, ge=1, le=90, description="Number of days to pull data for"
    ),
    max_workers: int = Query(
        default=12, ge=1, le=50, description="Maximum number of concurrent workers"
    ),
):
    def generate():
        try:
            yield from stream_orats_data(days, max_workers)
        except Exception as e:
            logger.error(f"❌ Error in pulling data: {str(e)}")

    return StreamingResponse(
        generate(),
        media_type="text/plain",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Type": "text/event-stream",
        },
    )

We define an API endpoint which is called by a cron job such as Cloud Scheduler API.

Pros:

  • Managed by Google Cloud; highly reliable.
  • Can trigger HTTP endpoints, Pub/Sub, or App Engine tasks.
  • Decouples scheduling from application logic.

Cons:

  • Requires cloud setup and permissions.
  • Only triggers jobs; actual job execution must be handled elsewhere.
  • Limited to Google Cloud ecosystem.
  • Performance hit/blocks your server while processing.

Cron jobs which call exposed endpoints also work. However they have the disadvantage of utilizing server resources while running which can potentially produce performance hits on your app.

Cloud Run Job

A serverless solution from Google Cloud that runs containerized batch jobs on demand, ideal for scalable and isolated background processing.

# scripts.scheduled.process_data.py

def run_populations():
    pass

if __name__ == "__main__":
    if run_populations():
        logger.info("🎉 All Scheduled Population scripts completed successfully.")
    else:
        logger.error("‼️ Some Scheduled Population scripts failed.")

We define a script in application code which is run using a container. Since your application is probably already containerized, the overhead is defining a Cloud Run Job which uses the same image built for your Cloud Run Service & scheduling it to be executed with a cron job like Cloud Scheduler.

Pros:

  • Serverless execution; scales automatically.
  • Can run containerized workloads on demand.
  • Good for batch jobs, data processing, and one-off tasks.

Cons:

  • Requires containerization of your job.
  • Cold start latency may affect short jobs.

Conclusion

Choose APScheduler for simple, in-app scheduling. Use Cloud Scheduler for reliable, cloud-managed triggers. Opt for Cloud Run Job for scalable, serverless batch processing.