Need a lightweight job queue that runs across multiple machines without Redis, RabbitMQ, or cloud services? In this post, we’ll build a surprisingly capable distributed queue using SQLite, Python, and a little file-locking magic. This pattern works best when you need a shared queue across local or networked disks — think cron clusters, render farms, or batch processors — without spinning up infrastructure.
Why Use SQLite as a Queue?
- No external services to maintain
- Great for jobs with infrequent updates
- Works well on shared network storage (NFS, SMB)
Step 1: Create the Queue Table
We’ll use a simple table with a claimed flag and a timestamp:
import sqlite3
def init_db():
conn = sqlite3.connect("queue.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY,
task TEXT,
claimed_by TEXT,
claimed_at DATETIME
)
""")
conn.commit()
conn.close()
Step 2: Enqueue Jobs
Add a job by inserting a row:
def enqueue(task):
conn = sqlite3.connect("queue.db")
conn.execute("INSERT INTO jobs (task) VALUES (?)", (task,))
conn.commit()
conn.close()
Step 3: Claim a Job with Locking
To safely claim jobs across machines, we’ll use a conditional update:
import datetime, socket
def claim_job():
conn = sqlite3.connect("queue.db", isolation_level="IMMEDIATE")
conn.row_factory = sqlite3.Row
hostname = socket.gethostname()
now = datetime.datetime.utcnow().isoformat()
cur = conn.execute("""
UPDATE jobs
SET claimed_by = ?, claimed_at = ?
WHERE id = (
SELECT id FROM jobs WHERE claimed_by IS NULL LIMIT 1
)
RETURNING *
""", (hostname, now))
job = cur.fetchone()
conn.commit()
conn.close()
return job
Step 4: Process and Delete the Job
def process(job):
print(f"Processing: {job['task']}")
conn = sqlite3.connect("queue.db")
conn.execute("DELETE FROM jobs WHERE id = ?", (job["id"],))
conn.commit()
conn.close()
Step 5: Worker Loop
This can run in a cron or systemd timer on multiple machines:
if name == "main":
init_db()
job = claim_job()
if job:
process(job)
Pros and Cons
✅ Pros
- Zero external dependencies
- Easy to inspect/debug
- Works across NFS or shared volumes
⚠️ Cons
- Not suitable for high-throughput workloads
- Concurrent writes are serialized
- SQLite file locking can behave differently per OS
🚀 Alternatives
-
Redis + RQ: Good for scalable Python job queues
-
Beanstalkd: Lightweight and dedicated for queuing
-
Celery: Overkill for local jobs, but full-featured
Summary
For scenarios where installing or maintaining queue infrastructure isn’t ideal, a SQLite-powered distributed queue offers a minimalist alternative. It’s perfect for home labs, edge nodes, or anything that needs durable, shared-state task processing with no server overhead.
If this was useful, you can support me here: buymeacoffee.com/hexshift