Skip to content
Queue & Background Jobs

Queue & Background Jobs

Overview

Lemmego includes a built-in background job system (tasker) wrapped as a first-class service provider (queue). It supports multiple queues, worker pools, retries with backoff, scheduled jobs, batch operations, multi-node clustering, and a real-time web dashboard.

Quick Start

The queue provider is already registered in new projects:

// bootstrap/providers.go
&queue.Provider{}

With no config, it auto-reads the database connection from your existing configs/database.go. Jobs are stored in the same database as your app.

Defining a Job

Jobs implement the Job interface:

package jobs

import (
    "context"
    "fmt"
    "github.com/lemmego/queue"
)

type SendWelcomeEmail struct {
    UserID int    `json:"user_id"`
    Email  string `json:"email"`
}

func (j *SendWelcomeEmail) Handle(ctx context.Context) error {
    fmt.Printf("Sending email to %s\n", j.Email)
    return nil
}

func init() {
    queue.RegisterJob("*jobs.SendWelcomeEmail", func() queue.Job {
        return &SendWelcomeEmail{}
    })
}

Dispatching Jobs

queue.Dispatch(ctx, &SendWelcomeEmail{
    UserID: 42,
    Email:  "user@test.com",
})

// With options
queue.Dispatch(ctx, &SendWelcomeEmail{UserID: 43},
    queue.OnQueue("email"),
    queue.WithDelay(5*time.Minute),
    queue.WithTags("priority"),
)

Running Workers

lemmego run tasker:work --queue=default,email --workers=3

Workers claim jobs from the database atomically and process them. Run multiple worker processes across different machines — they coordinate through the shared database.

Optional Job Interfaces

Customize job behavior by implementing optional interfaces:

InterfacePurpose
ShouldQueueSet a custom queue name
ShouldDelayDelay job execution
ShouldRetryMax retry attempts
ShouldFailCalled when all retries exhausted
ShouldTagTags for dashboard filtering
ShouldBatchGroup jobs into batches
ShouldTimeoutPer-job timeout
BeforeHandleHookRun before job execution
AfterHandleHookRun after successful execution
BeforeRetryHookRun before each retry

Retry & Backoff

By default, jobs use exponential backoff: 2^attempt * 2s, max 24h. Max 3 attempts. Configure per job:

func (j *MyJob) MaxAttempts() int { return 5 }

Failed jobs appear on the dashboard with full error stack traces. Click “Retry” to re-queue them.

Worker Pools

Configure pools per queue:

&queue.Provider{
    Config: &queue.Config{
        Workers: map[string]int{
            "default": 3,
            "email":   5,
            "images":  10,
        },
    },
}

Enable auto-scaling to dynamically adjust workers based on queue backlog:

Config: &queue.Config{
    EnableAutoscale: true,
}

Scheduling Recurring Jobs

scheduler.New(mgr).Register(scheduler.ScheduledJob{
    ID:       "daily-report",
    Schedule: "0 6 * * *",  // every day at 6 AM
    Job:      &GenerateReport{},
    Queue:    "default",
})

Web Dashboard

Visit http://localhost:8080/tasker/ for the real-time dashboard:

FeatureDescription
Stat cardsStatus, jobs/min, processes, failed count
Queues tableAvailable/running/completed/failed per queue, pause/resume
Jobs tableFilterable by state (failed, retryable, running, etc.)
Job detailPayload, errors, execution timeline, output
ActionsRetry, cancel, batch retry, batch cancel
WorkersConnected nodes with uptime
MetricsPer-job-type throughput, avg runtime, fail rate
Live updatesAuto-refresh every 10 seconds + SSE real-time stats

API Endpoints

All endpoints are mounted under /tasker/:

MethodPathDescription
GET/tasker/Dashboard HTML
GET/tasker/api/statsGlobal stats
GET/tasker/api/jobsPaginated job list
GET/tasker/api/jobs/{id}Job detail
POST/tasker/api/jobs/{id}/retryRetry a job
POST/tasker/api/jobs/{id}/cancelCancel a job
POST/tasker/api/jobs/batch/retryBatch retry
POST/tasker/api/jobs/batch/cancelBatch cancel
GET/tasker/api/queuesQueue metrics
POST/tasker/api/queues/{queue}/pausePause a queue
POST/tasker/api/queues/{queue}/resumeResume a queue
GET/tasker/api/workersConnected nodes
GET/tasker/api/metrics/jobsPer-job-type metrics
GET/tasker/api/eventsSSE real-time stream
POST/tasker/api/prunePrune old jobs

Configuration

Via provider options

&queue.Provider{
    Config: &queue.Config{
        Driver:             "sql",         // or "redis"
        DSN:                "postgres://...",
        DriverName:         "postgres",
        TablePrefix:        "tasker_",
        RoutePrefix:        "/tasker",
        DefaultQueue:       "default",
        DefaultMaxAttempts: 3,
        Workers:            map[string]int{"default": 3},
    },
}

Via .env

TASKER_ROUTE_PREFIX=/jobs
TASKER_TABLE_PREFIX=myapp_
TASKER_QUEUE=default
TASKER_MAX_ATTEMPTS=3
TASKER_HEARTBEAT_SEC=5
TASKER_REQUEUE_SEC=60
TASKER_PRUNE_HOURS=168
TASKER_AUTOSCALE=false

Driver Support

SQL (PostgreSQL / MySQL / SQLite)

Default driver. Stores jobs in a tasker_jobs table with atomic claiming using SELECT ... FOR UPDATE SKIP LOCKED. Supports all SQL databases.

Redis

Alternative driver for node coordination, leader election, and distributed locking (job queue implementation uses database).

Architecture

   ┌──────────────┐     ┌──────────────┐
   │   Web App    │     │  Worker 1    │
   │  Dispatch    │     │  Supervisor  │
   │  Dashboard   │     │  └─Pool (3)  │
   └──────┬───────┘     └──────┬───────┘
          │                    │
          └────────┬───────────┘
                   │
          ┌────────▼────────┐
          │   PostgreSQL    │
          │  (jobs table)   │
          │  SELECT FOR     │
          │  UPDATE SKIP    │
          │  LOCKED         │
          └─────────────────┘

Multiple workers share the same database — they claim available jobs atomically. No direct inter-node communication needed. Scale by adding more worker processes.

CLI Commands

lemmego run tasker:work            # Start worker
  --queue=default,email            #   Queues to listen on
  --workers=3                      #   Workers per queue

lemmego run tasker:dispatch        # Dispatch test jobs
  --count=10
  --queue=default
  --fail-rate=30                   # % chance of failure
  --delay=500                      # Simulated processing delay (ms)

lemmego run tasker:job retry 5     # Retry job #5
lemmego run tasker:job cancel 5    # Cancel job #5

lemmego run tasker:queue list      # List queue stats