Timer

A Timer spawns a Process on a schedule that is defined using cron syntax.

Timer Definition

A Timer is defined in convox.yml.

services:
  worker:
    build: ./worker
timers:
  cleanup:
    annotations:
      - test.annotation.org/value=foobar
    command: bin/cleanup
    schedule: "0 3 * * *"
    service: worker

Attributes

Name Required Description
annotations no A list of annotation keys and values to populate the metadata for the deployed pods and their serviceaccounts.
command yes The command to execute once the Process starts
schedule yes A cron formatted schedule for spawning the Process. All times are UTC
service yes The name of the Service that will be used to spawn the Process
concurrency no Specifies how to treat concurrent executions of a job created by this cron job. Valid values: Allow, Forbid, Replace. Defaults to Allow if not defined. Values are case-insensitive (Convox normalizes to title case before applying). See the Kubernetes concurrency policy documentation for details.
parallelCount no The number of parallel replicas to run for each timer execution. Defaults to 1. Each replica receives a unique TIMER_INDEX environment variable (0-based). Supported version >= 3.22.4

Cron Expression Format

Cron expressions use the following format. All times are UTC.

.----------------- minute (0 - 59)
|  .-------------- hour (0 - 23)
|  |  .----------- day-of-month (1 - 31)
|  |  |  .-------- month (1 - 12) OR JAN,FEB,MAR,APR ...
|  |  |  |  .----- day-of-week (0 - 6) OR SUN,MON,TUE,WED,THU,FRI,SAT
|  |  |  |  |
*  *  *  *  *

The smallest unit of time is minute.

Using a Template Service

Timers can run against any Service, even one that is scaled to zero. You can use this to create a template Service for your Timers.

services:
  web:
    build: .
    command: bin/web
    port: 5000
  jobs:
    build: ./jobs
    scale:
      count: 0
timers:
  cleanup:
    command: bin/cleanup
    schedule: "*/2 * * * *"
    service: jobs
    concurrency: Forbid

On this App the jobs Service is scaled to zero and not running any durable Processes.

The cleanup Timer will spawn a Process of the jobs Service to run bin/cleanup once every two minutes.

Parallel Timer Execution

Timers support running multiple parallel replicas for increased throughput and distributed processing of scheduled tasks. This is particularly useful for high-volume batch processing, data pipelines, or any scheduled workload that benefits from horizontal scaling.

Configuring Parallel Execution

To enable parallel execution, use the parallelCount attribute:

services:
  worker:
    build: ./worker
timers:
  data-processor:
    command: bin/process-data
    schedule: "0 * * * *"
    service: worker
    parallelCount: 5

In this example, the data-processor timer will spawn 5 parallel replicas every hour, each running the bin/process-data command simultaneously.

Using TIMER_INDEX for Work Distribution

Each parallel replica receives a unique TIMER_INDEX environment variable (starting from 0) that can be used to partition work across instances:

timers:
  cleanup:
    command: ./cleanup.sh
    schedule: "*/10 * * * *"
    service: worker
    parallelCount: 3

Each replica receives:

  • First container: TIMER_INDEX=0
  • Second container: TIMER_INDEX=1
  • Third container: TIMER_INDEX=2

Example: Partitioned Data Processing

Here's an example of using TIMER_INDEX to partition work across timer replicas:

#!/bin/bash
# cleanup.sh - Partition cleanup work across replicas

TOTAL_REPLICAS=3
PARTITION=$TIMER_INDEX

echo "Processing partition $PARTITION of $TOTAL_REPLICAS"

# Process different data segments based on replica index
case $TIMER_INDEX in
  0)
    echo "Processing records where id % 3 = 0"
    psql $DATABASE_URL -c "DELETE FROM logs WHERE created_at < NOW() - INTERVAL '30 days' AND id % 3 = 0"
    ;;
  1)
    echo "Processing records where id % 3 = 1"
    psql $DATABASE_URL -c "DELETE FROM logs WHERE created_at < NOW() - INTERVAL '30 days' AND id % 3 = 1"
    ;;
  2)
    echo "Processing records where id % 3 = 2"
    psql $DATABASE_URL -c "DELETE FROM logs WHERE created_at < NOW() - INTERVAL '30 days' AND id % 3 = 2"
    ;;
esac

Use Cases for Parallel Timers

Parallel timer execution is valuable for:

  • Data Processing Pipelines: Process large datasets by partitioning work across replicas
  • Cleanup Operations: Parallelize deletion or archival tasks across different data segments
  • Report Generation: Generate multiple reports simultaneously for different regions or departments
  • Import/Export Jobs: Handle multiple data sources or destinations concurrently
  • Monitoring Tasks: Check different service endpoints or regions in parallel

Important Considerations

When using parallel timers, keep in mind:

  1. Concurrency Safety: Ensure your timer logic can handle concurrent execution without conflicts
  2. Database Locking: Be aware of potential database locks or race conditions
  3. Resource Limits: Each replica consumes resources - ensure your cluster has sufficient capacity
  4. Idempotency: Design timer operations to be idempotent where possible
  5. Concurrency Policy: The concurrency attribute still applies to the timer as a whole, controlling whether new timer executions can start while previous ones are running

Complete Example with Parallel Execution

services:
  web:
    build: .
    command: bin/web
    port: 5000
  jobs:
    build: ./jobs
    scale:
      count: 0
timers:
  hourly-import:
    command: bin/import-data
    schedule: "0 * * * *"
    service: jobs
    parallelCount: 4
    concurrency: Forbid
  daily-cleanup:
    command: bin/cleanup
    schedule: "0 2 * * *"
    service: jobs
    parallelCount: 10
    annotations:
      - monitoring.example.com/alert=true

In this configuration:

  • hourly-import runs 4 parallel import processes every hour, with concurrency: Forbid ensuring no overlapping executions
  • daily-cleanup runs 10 parallel cleanup processes at 2 AM UTC daily
  • Both timers use the jobs service template which is scaled to zero when not in use