Moul

Background Workers & Job Queues

High-performance, SQLite-backed asynchronous background job processor with queue priorities, retries, and dead-letter queue.

Inspired by Elixir's Oban, Moul provides a high-performance background job engine backed directly by SQLite. It eliminates external broker dependencies like Redis or RabbitMQ.


Job Lifecycle & State Transitions

stateDiagram-v2
    [*] --> Available: Enqueued
    Available --> Executing: Worker Pick
    Executing --> Completed: Success
    Executing --> Retryable: Failure (attempts < max)
    Retryable --> Available: Backoff Delay Elapsed
    Executing --> Discarded: Failure (attempts >= max)
    Discarded --> Available: Manual Retry via CLI / Admin
    Completed --> [*]

Job States

  • available: Job is queued and ready for worker pickup.
  • executing: Currently being processed by a worker goroutine.
  • completed: Finished successfully.
  • discarded: Exhausted all retry attempts or cancelled manually (moved to DLQ).

Automatic Retries & Exponential Backoff

delay = (attempt^2 * 10) + 10 seconds + jitter
AttemptMinimum Delay
1~20 seconds
2~50 seconds
3~100 seconds (1.6 min)
4~170 seconds (2.8 min)
5~260 seconds (4.3 min)

Enqueueing Jobs

Via REST API

Enqueue a job by inserting a record into any collection with type: "worker":

curl -X POST "http://localhost:8090/api/moul/tasks_queue/records" \
  -H "X-Admin-Key: test-admin-key-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "worker": "SendEmailNotification",
    "priority": 1,
    "max_attempts": 10,
    "args": {
      "to": "user@example.com",
      "template": "welcome_email",
      "userId": "usr_9921"
    }
  }'
const res = await fetch('http://localhost:8090/api/moul/tasks_queue/records', {
  method: 'POST',
  headers: {
    'X-Admin-Key': 'test-admin-key-1234',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    worker: 'SendEmailNotification',
    priority: 1,
    args: { to: 'user@example.com' },
  }),
});
// Using internal worker package or pkg/app
job, err := workerEngine.Enqueue(ctx, "tasks_queue", map[string]any{
    "worker": "SendEmailNotification",
    "priority": 1,
    "args": map[string]any{
        "to": "user@example.com",
    },
})

Immediate Signal Dispatch

Moul does not rely exclusively on periodic database polling tickers. Enqueueing a job immediately emits an in-memory channel signal, waking up idle worker goroutines with sub-millisecond dispatch latency.


Dead-Letter Queue (DLQ) & CLI Management

When jobs fail repeatedly and exceed max_attempts, they enter the discarded state. You can inspect and retry discarded jobs directly via the CLI:

# 1. List all failed/discarded jobs in a queue
moul worker list-failed tasks_queue

# 2. Retry all failed jobs in the queue
moul worker retry tasks_queue

# 3. Retry a specific job by ID
moul worker retry tasks_queue job_01JXYZ123

On this page