Date Created: 2025-03-29
By: 16BitMiker
[ BACK.. ]
In todayβs web applications, responsiveness is everything. When users submit forms, upload images, or trigger long-running tasks, blocking the main application thread causes delays and frustration.
Thatβs where πͺ Minion comes in β a robust, database-backed job queue system for Perl that makes it easy to offload slow tasks to background workers, keeping your app snappy and scalable.
Minion is an asynchronous job queue system built for Perl applications. It allows you to queue up tasks (jobs) for background execution, leaving your main application to handle other requests without delay.
π¦ Database-backed β Jobs persist across restarts
πΎ Backend support β SQLite, PostgreSQL, MySQL, and more
π₯ Distributed processing β Run workers on multiple hosts
β± Delayed execution β Schedule tasks for later
π Automatic retries β Handle failures with retry logic
β¬οΈ Priorities and dependencies β Fine-grained control over job execution
π§ Monitoring tools β Track job progress and status
Letβs walk through a full example that highlights how Minion works from task definition to job execution and monitoring.
βx#!/usr/bin/env perl
use ;
use ;
use ;
# Create a new Minion instance using SQLite for storage.
# The SQLite DB file 'jobs.db' will persist all job data.
my $minion = ->new( => 'sqlite:jobs.db');
# Define a task named 'sleep_task'
# This task simulates work by sleeping for a specified number of seconds.
$minion-> ( => sub ($job, $seconds) {
# $job: the job object that represents the current job
# $seconds: parameter passed when the job is enqueued
sleep $seconds; # Simulate time-consuming work
# Mark the job as finished and store a result message
$job-> ("Slept $seconds seconds");
});
# Main logic for submitting and monitoring a job
{
# Enqueue a job to run sleep_task with a parameter of 5 seconds
my $job_id = $minion-> ( => [5]);
print "Started sleep job: $job_id\n";
# Simulate doing other work while the background job runs
my $counter = 0;
while (1) {
# Get the current status of the job by its ID
my $status = $minion-> ($job_id)->->{state};
# Exit the loop if the job has finished
last if $status eq 'finished';
# Simulate foreground work while waiting
$counter++;
print "Did some work (count: $counter) while sleep running...\n";
sleep 1; # Sleep a bit to avoid busy-waiting
}
print "Sleep job finished!\n";
}
# If we run the script with the 'worker' argument, launch a background worker
if (@ARGV && $ARGV[0] eq 'worker') {
# Start a dedicated worker process to fetch and process jobs
$minion->-> ;
}
Youβll need two terminals:
π§βπ» Terminal 1: Start the worker
xxxxxxxxxx
$ perl script.pl worker
π§βπ» Terminal 2: Submit a job
xxxxxxxxxx
$ perl script.pl
Started sleep job: 1
Did some work (count: 1) while sleep running...
...
Sleep job finished!
xxxxxxxxxx
my $minion = ->new( => 'sqlite:jobs.db');
Initializes Minion with SQLite backend.
The jobs.db
file holds all job metadata.
For production, consider PostgreSQL:
xxxxxxxxxx
my $minion = ->new( => 'postgresql://user:pass@localhost/myapp');
xxxxxxxxxx
$minion-> ( => sub ($job, $seconds) {
sleep $seconds;
$job-> ("Slept $seconds seconds");
});
Defines a task named sleep_task
.
The task receives:
$job
: job object with methods like finish, fail, retry.
$seconds
: argument passed when queueing the task.
sleep
simulates a time-consuming operation.
finish
marks the job as completed and stores a result.
xxxxxxxxxx
my $job_id = $minion-> ( => [5]);
Enqueues a job for the sleep_task
with argument 5
.
Returns a job ID to track the job later.
Arguments must be passed as an arrayref.
xxxxxxxxxx
$minion->-> ;
Starts a worker loop that:
Polls the queue for new jobs.
Executes tasks.
Handles retries or failures.
Should be run in a separate process or thread.
xxxxxxxxxx
# High-priority job (processed sooner)
$minion-> ( => [] => { => 50});
# Low-priority job (processed later)
$minion-> ( => [] => { => -20});
Priorities range from -100 to 100.
Higher values run earlier when multiple jobs are available.
xxxxxxxxxx
# Schedule a job to run 1 hour from now
$minion-> ( => [] => { => 3600});
Useful for reminders, notifications, or data syncs.
xxxxxxxxxx
my $parent_id = $minion-> ( => []);
# These jobs will only run after generate_report completes
$minion-> ( => [] => { => [$parent_id]});
$minion-> ( => [] => { => [$parent_id]});
Enables workflows with sequential tasks.
xxxxxxxxxx
$minion-> ( => sub ($job) {
my $result = eval { () };
if ($@) {
# After 3 tries, mark it as failed
return $job-> ("API call failed after 3 attempts: $@")
if $job-> >= 2;
# Retry with exponential backoff
return $job-> ({ => (2 ** $job-> ) * 60});
}
$job-> ({ => 1, => $result});
});
eval
catches runtime errors.
Retry up to 3 times with increasing delay.
Avoids flooding external services after transient failures.
xxxxxxxxxx
$minion-> ( => sub ($job) {
# Allow max 5 concurrent jobs in 60-second window
return $job-> ({ => 30})
unless my $guard = $minion-> ('api_limit', 60, { => 5});
# Perform the API call here
# The guard is automatically released after this block exits
});
Useful for APIs with rate limits.
Prevents overloading third-party services.
xxxxxxxxxx
$minion-> ( => sub ($job, $recipient, $subject, $body) {
my $mailer = ::::->new();
my $email = ::-> (
=> [ => $recipient, => 'no-reply@example.com', => $subject],
=> $body,
);
eval { $mailer->send($email) };
if ($@) {
return $job-> ({ => 300}) if $job-> < 3;
return $job-> ("Failed to send email: $@");
}
$job-> ("Email sent to $recipient");
});
Offloads email sending to avoid blocking web requests.
xxxxxxxxxx
$minion-> ( => sub ($job, $image_path, $dimensions) {
my $img = ::->new();
$img-> ($image_path);
$img-> ( => $dimensions);
my $output_path = $image_path;
$output_path =~ s/(\.\w+)$/_resized$1/;
$img-> ($output_path);
$job-> ({ => $image_path, => $output_path});
});
Handles heavy image tasks asynchronously.
xxxxxxxxxx
$minion-> ( => sub ($job, $user_id, $report_type) {
$job-> ( => 0); # Track progress
my $data = ($user_id, $report_type);
$job-> ( => 50);
my $report_path = ($data);
$job-> ( => 90);
$user_id, $report_path); (
$job-> ({ => $report_path});
});
Background reporting keeps your app fast and scalable.
xxxxxxxxxx
$app-> ('Minion::Admin');
Web UI for monitoring jobs:
View pending, finished, failed jobs
Retry or delete jobs
Inspect job data
xxxxxxxxxx
# List jobs
$ perl -MMojolicious::Plugin::Minion::Admin::Command -e 'jobs'
# Show job details
$ perl -MMojolicious::Plugin::Minion::Admin::Command -e 'job 123'
β
Keep tasks idempotent β jobs can run more than once
β
Use delays + retries for transient errors
β
Monitor queue size and worker utilization
β
Use named queues to separate concerns:
xxxxxxxxxx
$minion-> ( => [] => { => 'email'});
$minion->-> ({ => ['email']});
β Pass only lightweight data β use database IDs instead of full objects
Minion is a powerful tool for building scalable, responsive Perl applications. By moving slow or blocking operations to background jobs, you dramatically improve user experience and system resilience.
From image processing to APIs, email, and reporting β Minion handles it all with clean syntax, solid architecture, and production-ready features.
Happy queuing! πͺ