Job

The JoobQ::Job module provides an abstract structure for defining jobs within the JoobQ asynchronous job processing framework. This module includes functionality for managing job statuses, scheduling, retries, timeouts, and more.

Defining a Job

To define a custom job, include the JoobQ::Job module in your job class and implement the perform method. The perform method is where you put the logic that should be executed when the job runs.

class ExampleJob
  include JoobQ::Job

  def perform
    puts "Executing job logic"
  end
end

After defining your job, you can enqueue it, schedule it, or delay its execution using the provided methods.

Job Properties

  • jid: The unique identifier for the job (UUID).

  • queue: The queue to which the job is assigned.

  • retries: The maximum number of retries allowed if the job fails.

  • expires: The expiration time of the job in Unix milliseconds.

  • status: The current status of the job.

  • timeout: The maximum execution time allowed for the job.

Job Statuses

The JoobQ::Job::Status enum defines the possible states for a job:

  • Pending: The job has been created but not scheduled.

  • Scheduled: The job is scheduled to run at a specific time.

  • Running: The job is currently executing.

  • Completed: The job finished successfully.

  • Retrying: The job is retrying after a failure.

  • Failed: The job execution failed after exhausting retries.

  • Expired: The job expired before execution.

Each status has corresponding predicate and setter methods for checking and updating job status.

job = ExampleJob.new
job.running! # Sets the job's status to Running
job.running? # Checks if the job's status is Running

Best Practices for Defining Jobs

When defining jobs in JoobQ, it's important to follow certain best practices to ensure reliability and maintainability. Here are some key recommendations:

Idempotency

Jobs must be idempotent. This means that running the same job multiple times should produce the same result. Idempotency is crucial for ensuring that jobs can be retried safely without causing unintended side effects. To achieve idempotency:

  • Avoid modifying external state directly within the job.

  • Use unique identifiers to track job execution and prevent duplicate processing.

  • Ensure that any side effects (e.g., database updates, API calls) are safe to repeat.

Simple Primitive Types for Arguments

Job arguments must be simple primitive types such as integers, strings, and booleans. This ensures that the job data can be easily serialized and deserialized, and reduces the risk of errors during job execution. Complex objects or data structures should be avoided as job arguments.

Number of Arguments

Keep the number of arguments for jobs to a minimum. Having too many arguments can make the job definition complex and harder to maintain. As a best practice:

  • Limit the number of arguments to 3-5.

  • Group related arguments into a single object if necessary.

  • Use default values for optional arguments to simplify job invocation.

By following these best practices, you can ensure that your jobs are reliable, maintainable, and easy to work with in the JoobQ framework.

Enqueueing and Execution Methods

Batch Enqueue

To enqueue multiple jobs at once, use the batch_enqueue method:

ExampleJob.batch_enqueue([job1, job2, job3])

Enqueue a Single Job

To enqueue a single job for processing:

ExampleJob.enqueue(param: "value")

Perform a Job Immediately

If you want to execute a job immediately without enqueuing it:

ExampleJob.perform(param: "value")

Delay and Scheduling Methods

Delaying a Job

To delay a job for a specific timespan before it is processed:

ExampleJob.delay(2.minutes, param: "value")

Enqueue After a Delay

To enqueue a job to be processed after a specified delay:

ExampleJob.enqueue_at(1.minute, param: "value")

Scheduling a Recurring Job

To schedule a job to run at a specific interval:

ExampleJob.schedule(5.seconds, param: "value")

Timeout Handling

Jobs can have a timeout to ensure they do not run indefinitely. Use with_timeout to enforce a timeout on the job's execution. If the block takes longer than the specified timeout, it raises a Timeout::TimeoutError.

def perform
  with_timeout do
    # Simulate a long-running task
    puts "Starting a task that should timeout..."
    sleep 10.seconds
  rescue Timeout::TimeoutError => e
    puts e.message # => "execution expired after 5 seconds"
  end
end

Example Usage

Here's a complete example demonstrating how to define, enqueue, and execute a job using JoobQ::Job:

require "joobq"

# Define a job
class ExampleJob
  include JoobQ::Job
  property x : Int32

  def initialize(@x : Int32)
  end

  def perform
    puts "Performing job with x = #{x}"
  end
end

# Enqueue the job
ExampleJob.enqueue(x: 42)

# Delay the job
ExampleJob.delay(1.minute, x: 42)

# Schedule a recurring job
ExampleJob.schedule(10.minutes, x: 42)

In this example, ExampleJob is defined with a property x and a perform method. The job is enqueued, delayed, and scheduled for recurring execution.

Best Practices for Defining Jobs

  • Idempotency: Ensure that your jobs are idempotent, meaning that running the job multiple times produces the same result. This is important in case of retries.

  • Handle Failures Gracefully: Use retries to handle transient errors and ensure your perform method handles exceptions properly.

  • Timeouts: Use with_timeout to prevent jobs from running indefinitely and consuming resources.

  • Test Jobs Thoroughly: Write tests for your job logic to ensure that jobs perform as expected and handle edge cases correctly.


The JoobQ::Job module provides a flexible and powerful way to define asynchronous tasks that can be enqueued, delayed, and scheduled. Use these features to create reliable background jobs and streamline your application’s workflow!

Last updated