The Problem: Do You Really Need Redis or RabbitMQ?
Let me paint you a picture.
You're building a Python application that needs to process background tasks. Maybe you're sending emails, generating reports, or processing images.
Option 1: You deploy Redis or RabbitMQ. Now you're managing another service, dealing with persistence concerns, and increasing your infrastructure complexity.
Option 2: You use Celery with Redis or RabbitMQ. It works, but you now have a sprawling configuration just to send a few emails.
The Hidden Cost: For many projects, a separate message broker is overkill. You're spending more time managing infrastructure than building features.
I faced this exact situation. So I built Conductor.
Introducing Conductor
Conductor is a PostgreSQL-backed async task queue for Python teams that don't want to manage a separate message broker.
What Makes Conductor Different?
1. PostgreSQL as the Backend
- No new infrastructure to deploy
- Leverages PostgreSQL's durability and transaction guarantees
- Your existing database now powers your task queue
2. Exactly-Once Semantics
- Tasks run exactly once, no duplicates
- Idempotency is built into the design
- PostgreSQL's
ON CONFLICTensures atomic operations
3. Built for Async Python
- Fully
asynciocompatible - Async workers that don't block your event loop
- FastAPI integration ready
4. Production-Ready Features
- Exponential backoff with jitter for retries
- Dead letter queue for failed tasks
- Prometheus metrics for observability
- Structured logging with request tracing
- Health checks for your workers
5. Impressive Performance
- 400+ tasks per second throughput per worker
- Scales horizontally with multiple workers
- Low latency for task scheduling
Quick Example: How It Works
from conductor import Conductor
# Initialize with your existing PostgreSQL connection
conductor = Conductor("postgresql://user:pass@localhost/db")
# Define a task
@conductor.task
async def send_email(to: str, subject: str, body: str):
# Your email sending logic here
print(f"Sending '{subject}' to {to}")
return True
# Schedule the task
await send_email.schedule(
to="user@example.com",
subject="Welcome!",
body="Thanks for signing up."
)
# Run the worker
await conductor.worker.start()
That's it. No Redis. No RabbitMQ. Just your PostgreSQL database.
The Features You Actually Need
1. Exactly-Once Semantics
In production, duplicate task execution can be catastrophic. Imagine sending two welcome emails or processing a payment twice.
Conductor uses PostgreSQL's transaction isolation and ON CONFLICT to guarantee each task runs exactly once.
-- Atomic task insertion with deduplication
INSERT INTO tasks (id, name, payload, status, created_at)
VALUES ('unique_id_123', 'send_email', '{"to":"user@example.com"}', 'pending', NOW())
ON CONFLICT (id) DO NOTHING;
2. Exponential Backoff with Jitter
When a task fails, you want to retry - but not all at once.
Conductor implements:
- Configurable retry policies
- Exponential backoff:
attempt^2seconds - Jitter: adding random variance to prevent thundering herds
- Max retry limits to prevent infinite loops
@conductor.task(
max_retries=5,
retry_delay_base=2, # Seconds: 2, 4, 8, 16, 32
retry_with_jitter=True
)
async def flaky_api_call():
# This will retry with smart delays
pass
3. Dead Letter Queue
Sometimes tasks just can't be processed. Maybe the data is corrupt, or an external API is permanently down.
Conductor moves failed tasks to a dead letter queue for manual inspection:
- Track why tasks failed
- Reprocess them after fixing the issue
- Prevent invalid tasks from clogging your system
4. Observability Built-In
A task queue is a black box unless you can see inside it.
Conductor provides:
- Prometheus metrics for task rates, success/failure counts, and latency
- Structured JSON logging with trace IDs for debugging
- Grafana dashboards for real-time monitoring
- Health check endpoints for load balancers
5. Deployment Guides
I've included guides for:
- Docker Compose: Local development with one command
- Kubernetes: Production deployment with rolling updates
- systemd: Traditional server deployment
Benchmarks: Conductor vs. Redis
| Metric | Conductor (PostgreSQL) | Redis RQ |
|---|---|---|
| Tasks/sec per worker | 400+ | ~500 |
| Message persistence | ✅ Yes | ❌ No (optional) |
| Transaction support | ✅ Yes | ❌ Limited |
| Exactly-once semantics | ✅ Yes | ❌ No |
| Operational complexity | ✅ Low | ⚠️ Medium |
The Verdict: Redis RQ is faster by ~20%, but Conductor offers stronger guarantees with less operational overhead.
Who Should Use Conductor?
✅ Use Conductor if:
- You already use PostgreSQL
- You don't want to manage Redis or RabbitMQ
- You need exactly-once task execution
- You're building a FastAPI / async Python application
- You're in the early stages of your project and want to keep it simple
❌ Don't use Conductor if:
- You need sub-millisecond task latency
- You're processing millions of tasks per second
- You need complex routing or priority queues
- Your team is already expert in Celery/Redis
Getting Started in 3 Minutes
1. Install Conductor
pip install conductor-task-queue
2. Create Your PostgreSQL Database
CREATE DATABASE conductor_db;
3. Write Your First Task
# tasks.py
from conductor import Conductor
conductor = Conductor(
"postgresql://user:pass@localhost/conductor_db"
)
@conductor.task
async def add_numbers(a: int, b: int):
return a + b
# Schedule it
await add_numbers.schedule(5, 7)
4. Start Your Worker
# worker.py
from tasks import conductor
if __name__ == "__main__":
conductor.worker.run()
5. Monitor Your Tasks
# monitor.py
status = await conductor.get_task_status("task_id_123")
print(f"Status: {status.state}")
print(f"Result: {status.result}")
What's Next for Conductor?
I'm actively developing Conductor and have big plans:
- [ ] Dashboard UI for task management
- [ ] Cron-like scheduled tasks with interval support
- [ ] Batch processing for parallel execution
- [ ] WebSocket support for real-time updates
How You Can Help
Conductor is open-source and MIT licensed. Here's how you can contribute:
⭐ Star the repository: GitHub - Conductor
🐛 Report issues: Found a bug? Open an issue on GitHub.
💡 Suggest features: Tell me what you need from a task queue.
🔧 Contribute code: PRs are welcome for bug fixes and improvements.
📝 Write documentation: Good docs are essential for adoption.
Connect With Me
I'm Panagiotis Panageas, a Python backend engineer building production systems in Europe.
- GitHub: @Archangel-77
- Open to roles: Remote or EU-based backend engineering positions
If you're using Conductor or building similar tools, I'd love to hear about your experience!
Have you tried using PostgreSQL as a task queue? What challenges did you face? Share your experience in the comments below! 👇
Top comments (0)