A production-grade CLI-based background job queue system with worker processes, retry logic with exponential backoff, and Dead Letter Queue (DLQ) support.
- β Job Queue Management - Enqueue and manage background jobs
- β Multiple Workers - Run concurrent worker processes
- β Automatic Retries - Failed jobs retry with exponential backoff
- β Dead Letter Queue - Permanently failed jobs moved to DLQ
- β Persistent Storage - SQLite-based storage survives restarts
- β Safety Timeout - Automatic recovery of jobs from crashed workers
- β Job Output Logging - Capture stdout, stderr, and exit codes
- β Configurable - Runtime configuration via CLI
- β Clean CLI Interface - Intuitive command structure
- Python 3.8+
- click library
git clone https://github.com/IamHarriiii/Queuectl.git
cd queuectlpip install -r requirements.txtpip install -e .This makes the queuectl command available system-wide.
queuectl --help# Simple job
queuectl enqueue '{"id":"job1","command":"echo Hello World"}'
# Job with custom retry count
queuectl enqueue '{"id":"job2","command":"sleep 5","max_retries":5}'
# Job without explicit ID (auto-generated)
queuectl enqueue '{"command":"ls -la"}'# Start single worker
queuectl worker start
# Start multiple workers
queuectl worker start --count 3Workers will run in foreground. Press Ctrl+C to stop them gracefully.
queuectl statusOutput:
==================================================
QUEUE STATUS
==================================================
Jobs:
Pending: 3
Processing: 1
Completed: 10
Failed: 0
Dead (DLQ): 2
--------------------
Total: 16
Active Workers: 1
Configuration:
backoff_base: 2
job_timeout: 300
max_retries: 3
worker_poll_interval: 1
==================================================
# List all jobs
queuectl list
# List jobs by state
queuectl list --state pending
queuectl list --state completed
queuectl list --state failed
# Limit results
queuectl list --limit 50# List jobs in DLQ
queuectl dlq list
# Retry a job from DLQ
queuectl dlq retry job1# View all configuration
queuectl config list
# Get specific config value
queuectl config get max-retries
# Set configuration
queuectl config set max-retries 5
queuectl config set backoff-base 3
queuectl config set job-timeout 600βββββββββββββββ
β CLI β User interface
ββββββββ¬βββββββ
β
ββββββββΌβββββββ
β Queue β Job management
ββββββββ¬βββββββ
β
ββββββββΌβββββββ
β Storage β SQLite persistence
βββββββββββββββ
βββββββββββββββ
β Workers β Job execution (multi-process)
ββββββββ¬βββββββ
β
ββββββββΌβββββββ
β Executor β Command execution
βββββββββββββββ
[ENQUEUE]
β
PENDING βββ PROCESSING βββ COMPLETED β
β β
β FAILED (attempts < max_retries)
β β
ββββββ (exponential backoff wait)
β
DEAD (DLQ) β
- Job Creation: User enqueues job via CLI
- Job Claiming: Worker atomically claims pending job from queue
- Execution: Worker executes command via subprocess
- Outcome Handling:
- Success (exit code 0) β Mark as
completed - Failure (non-zero exit code):
- If retries remaining β Schedule retry with backoff β Mark as
pending - If no retries left β Move to DLQ β Mark as
dead
- If retries remaining β Schedule retry with backoff β Mark as
- Success (exit code 0) β Mark as
Race Condition Prevention:
- Uses atomic SQL UPDATE with WHERE clause
- Only one worker can claim a job
- Includes safety timeout for crashed workers (5 minutes)
SQL Query:
UPDATE jobs
SET state='processing', worker_id=?, locked_at=CURRENT_TIMESTAMP
WHERE id IN (
SELECT id FROM jobs
WHERE (state='pending' OR (state='processing' AND locked_at < datetime('now', '-5 minutes')))
AND (run_at IS NULL OR run_at <= CURRENT_TIMESTAMP)
ORDER BY created_at ASC
LIMIT 1
)Exponential Backoff Formula:
delay = base ^ attempts (seconds)
Example (base=2, max_retries=3):
- Attempt 1 fails β Wait 2ΒΉ = 2 seconds
- Attempt 2 fails β Wait 2Β² = 4 seconds
- Attempt 3 fails β Wait 2Β³ = 8 seconds
- After attempt 3 β Move to DLQ
Storage: SQLite database at ~/.queuectl/queuectl.db
Schema:
jobs (
id, command, state, attempts, max_retries,
worker_id, locked_at, run_at,
stdout, stderr, exit_code,
created_at, updated_at
)
config (
key, value
)| Key | Default | Description |
|---|---|---|
max_retries |
3 | Maximum retry attempts before DLQ |
backoff_base |
2 | Base for exponential backoff calculation |
job_timeout |
300 | Job execution timeout in seconds |
worker_poll_interval |
1 | Worker polling interval in seconds |
- Trusted Environment: Commands are provided by trusted users via CLI (not external API)
- Single Machine: System runs on a single machine (not distributed)
- Moderate Load: Designed for moderate job volumes (thousands, not millions)
Decision: Use shell=True for command execution
Rationale:
- Allows compound commands like
echo "Hi" && sleep 2 - Enables shell features (pipes, redirects, environment variables)
- Assignment assumes simple shell commands
Risk: Shell injection if untrusted input
- Mitigation: Commands only come from trusted CLI interface
- Documentation: Clearly documented in code and README
Production Alternative: For untrusted input, use shell=False with command parsing and validation
Decision: Foreground worker processes (multiprocessing)
Rationale:
- Simpler implementation and debugging
- Easier graceful shutdown (signal handling)
- Better for demonstration and testing
Alternative: Daemon workers with PID files
- More complex but better for production deployment
- Could be added as enhancement
Decision: SQLite
Rationale:
- ACID compliance for atomicity
- Built-in locking mechanisms
- Better query performance
- Indexed lookups
Trade-off: Slightly heavier than JSON files, but worth it for reliability
Run the comprehensive test suite:
python tests/test_scenarios.pyTests Included:
- Basic job completes successfully
- Failed job retries with backoff and moves to DLQ
- Multiple workers process jobs without overlap
- Invalid commands fail gracefully
- Job data survives restart
- DLQ retry functionality
Test 1: Basic Success Flow
queuectl enqueue '{"id":"test1","command":"echo Success"}'
queuectl worker start --count 1 &
sleep 3
queuectl list --state completedTest 2: Retry and DLQ
queuectl config set max-retries 2
queuectl enqueue '{"id":"test2","command":"exit 1","max_retries":2}'
queuectl worker start --count 1 &
sleep 5
queuectl dlq listTest 3: Multiple Workers
for i in {1..5}; do
queuectl enqueue "{\"id\":\"job$i\",\"command\":\"sleep 2\"}"
done
queuectl worker start --count 3 &
sleep 5
queuectl statusqueuectl/
βββ queuectl/
β βββ __init__.py # Package initialization
β βββ cli.py # CLI commands (Click)
β βββ queue.py # Queue operations
β βββ worker.py # Worker logic & job execution
β βββ storage.py # SQLite database layer
β βββ config.py # Configuration management
β βββ models.py # Job data model
βββ tests/
β βββ test_scenarios.py # Integration tests
βββ README.md # This file
βββ requirements.txt # Python dependencies
βββ setup.py # Package setup
- β Job timeout handling - Configurable timeout with subprocess
- β
Scheduled/delayed jobs -
run_atfield for delayed execution - β Job output logging - Captures stdout, stderr, exit_code
- β Safety timeout - Auto-recovery of jobs from crashed workers
- β DLQ retry - Move jobs from DLQ back to queue
https://drive.google.com/file/d/1xGuwrG4USCyO1zYnsmwxv8DplZ3bvC3A/view?usp=sharing
- Database location:
~/.queuectl/queuectl.db - Workers finish current job before shutdown (graceful)
- Job output is truncated to 2000 characters to prevent database bloat
- Atomic operations prevent race conditions between workers
- Single Machine Only: Not designed for distributed deployment
- No Job Priority: All jobs processed FIFO
- Limited Monitoring: No built-in dashboard (terminal output only)
- No Authentication: Assumes trusted local environment
- Job priority queues
- Web dashboard for monitoring
- Job dependencies and workflows
- Metrics and statistics tracking
- Docker deployment support
- API endpoint for job submission
This project is created for educational purposes as part of a backend developer internship assignment.
HARINARAYANAN U [email protected] https://github.com/IamHarriiii