Qrush
A lightweight, production-ready job queue and task scheduler for Rust applications built on Redis and Tokio. The core is web-framework agnostic, and the optional built-in dashboard works with either Actix Web or Axum. Qrush provides both integrated and separate process modes, making it suitable for everything from simple background tasks to large-scale distributed systems.
Features
- π Dual Deployment Modes: Integrated (single process) or separate worker process
- π§© Framework Choice: Optional dashboard for Actix Web or Axum; the queue/worker core needs neither
- β‘ High Performance: Built on Redis and Tokio for maximum throughput
- π Cron Scheduling: Full cron expression support for recurring tasks
- β±οΈ Delayed Jobs: Schedule jobs to run after a specified delay
- π Built-in Metrics UI: Real-time dashboard for monitoring queues, jobs, and workers
- π Security: Optional Basic Auth for metrics endpoints
- π― Type-Safe: Leverages Rust's type system for safe job handling
- π Graceful Shutdown: Clean worker shutdown with configurable grace periods
- π Scalable: Support for multiple queues with different priorities and concurrency levels
Feature Flags
The built-in dashboard is optional and works with either Actix or Axum β pick the one that matches your app.
| Feature | Default | Description |
|---|---|---|
dashboard-actix |
β | Metrics dashboard served with Actix Web (qrush::routes::metrics_route). Pulls in Actix Web, Tera, and the web stack. |
dashboard-axum |
β | Metrics dashboard served with Axum (qrush::routes::axum_route). Pulls in Axum, Tera, and the web stack. |
dashboard |
β | Back-compat alias for dashboard-actix. |
Library-only usage (default). No dashboard framework is enabled by default,
so a plain dependency gives you enqueue + workers with no web stack:
[]
= "2.0.0"
To mount the dashboard, opt into one framework:
# Actix
= { = "2.0.0", = ["dashboard-actix"] }
# Axum
= { = "2.0.0", = ["dashboard-axum"] }
Migrating from 1.x to 2.0
In 1.x the dashboard was Actix-only and enabled by default. In 2.0 it is framework-selectable and off by default. Nothing else changed β the route-wiring function and all queue/worker/cron APIs are the same.
| 1.x | 2.0 | |
|---|---|---|
| Dashboard default | on (Actix) | off |
| Enable Actix dashboard | (default) | features = ["dashboard-actix"] |
| Enable Axum dashboard | not available | features = ["dashboard-axum"] |
# 1.x
= "1.0.1"
# 2.0 β Actix (equivalent to the old default; no code changes needed)
= { = "2.0.0", = ["dashboard-actix"] }
If you only used enqueue + workers (no dashboard), a plain qrush = "2.0.0"
now pulls in less β the web stack is no longer compiled by default. See the
CHANGELOG for the full list of changes.
Quick Start
Installation
Add to your Cargo.toml:
[]
= "2.0.0"
= { = "1", = ["rt-multi-thread", "macros"] }
= { = "1", = ["derive"] }
= "0.1"
= "1"
= "0.3"
qrushbundles its own Redis client (with cluster support), so you don't need to depend onredisdirectly unless you use it yourself.
Basic Usage (Integrated Mode)
use Job;
use ;
use QueueConfig;
use register_job;
use async_trait;
use ;
use BoxFuture;
use Result;
async
Architecture
QRush supports two deployment modes:
Integrated Mode
Workers run in the same process as your application. Perfect for small to medium applications.
βββββββββββββββββββββββ
β Application β
β (Single Process) β
β β
β β’ HTTP Server β
β β’ Enqueue Jobs β
β β’ Process Jobs β β Workers here
βββββββββββββββββββββββ
Separate Process Mode
Workers run in a dedicated process. Recommended for production environments.
βββββββββββββββββββββββ βββββββββββββββββββββββ
β Web Server β β qrush-engine β
β (cargo run) β β (separate process) β
β β β β
β β’ HTTP Server β β β’ Worker Pools β
β β’ Enqueue Jobs ββββββΌββRedisβββΌββΆ Process Jobs β
β β’ Serve Routes β β β’ Cron Scheduler β
βββββββββββββββββββββββ βββββββββββββββββββββββ
Documentation
Integrated Mode
See Part 1: Integrated Mode below for complete setup instructions.
Separate Process Mode
See Part 2: Separate Process Mode below for production deployment.
API Reference
Core Traits
Job: Implement this trait for your job typesCronJob: Implement for recurring scheduled jobs
Core Functions
enqueue(job) -> QrushResult<String>: Enqueue a job immediately; returns the job IDenqueue_in(job, delay_secs) -> QrushResult<String>: Enqueue a job with a delay; returns the job IDregister_job(name, handler): Register a job handlerQueueConfig::initialize(redis_url, queues): Start worker pools and the cron scheduler
Cron Scheduling
All under qrush::cron::cron_scheduler::CronScheduler (see Cron Jobs):
register_cron_job(job) -> Result<()>: Persist a schedule to Redislist_cron_jobs() -> Result<Vec<CronJobMeta>>: List registered cron jobsrun_now(cron_id) -> Result<String>: Enqueue a cron job immediatelytoggle_cron_job(cron_id, enabled) -> Result<()>: Pause / resume a scheduledelete_cron_job(cron_id) -> Result<()>: Remove a schedule
Errors
The public API returns QrushResult<T> (Result<T, QrushError>). QrushError
distinguishes Redis, Serialization, and Config failures, and implements
std::error::Error, so it still propagates through ? in anyhow-based code.
Engine Runtime
qrush::engine::run_engine(redis_url, queues, shutdown_grace_secs): Run worker processqrush::engine::parse_queues(spec): Parse queue specification string
Command-Line Interface
The crate also ships reference binaries β qrush (a management CLI with
start/stop/status/stats/queues/jobs subcommands) and qrush-engine
(the worker process) β that you can adapt for your own app. See
src/bin/cli.md for the full CLI guide.
Examples
Runnable dashboard examples
The repo ships a complete, runnable dashboard example for each framework. With a
Redis instance available (REDIS_URL, defaults to redis://127.0.0.1:6379):
# Actix β serves http://127.0.0.1:8080/qrush/metrics
# Axum β serves http://127.0.0.1:8080/qrush/metrics
Cron Jobs
A cron job is a regular Job that runs on a schedule instead of
being enqueued by hand. The work still lives in Job::perform; CronJob only
adds when to run it. Follow these three steps.
Step 1 β Define the job and its perform()
This is identical to any other QRush job: implement Job (the work + a handler
so a worker can rebuild it from Redis).
use Job;
use register_job;
use async_trait;
use ;
use BoxFuture;
use Result;
Step 2 β Add the schedule (CronJob)
Attach a cron expression and a unique id to the same type.
use CronJob;
Step 3 β Register and start it in main
The job above is framework-agnostic; only main differs. In both frameworks
the order is the same:
set_redis_url(...)β required before any Redis call.register_job(...)β so a worker can run the job.CronScheduler::register_cron_job(...)β saves the schedule to Redis.QueueConfig::initialize(...)β starts the workers and the cron scheduler.
β οΈ The cron scheduler only runs where
QueueConfig::initializeis called. In Separate Process Mode that's the engine binary, not the web server β put steps 2β4 there.
Actix (features = ["dashboard-actix"]):
use ;
use register_job;
use CronScheduler;
use qrush_metrics_routes;
use ;
async
Axum (features = ["dashboard-axum"]):
use ;
use register_job;
use CronScheduler;
use qrush_metrics_router;
use Router;
async
That's it. When the schedule fires, QRush enqueues the job onto its queue()
and a worker runs perform(). Watch it (and manage schedules) on the dashboard
at /qrush/metrics/extras/cron.
On restart:
register_cron_joberrors if a job with the samecron_idalready exists in Redis, so the?above would abort a second boot. The schedule already survives restarts, so either skip re-registering, treat the duplicate as non-fatal (log and continue instead of?), or callCronScheduler::delete_cron_job("hourly_email")first to re-seed it.
Managing cron jobs
Manage schedules from the dashboard at /qrush/metrics/extras/cron, or
programmatically via CronScheduler:
use CronScheduler;
list_cron_jobs.await?; // -> Vec<CronJobMeta>
run_now.await?; // enqueue once, right now
toggle_cron_job.await?; // pause
toggle_cron_job.await?; // resume
delete_cron_job.await?; // remove entirely
To register a job that starts paused, override enabled() on the CronJob
impl (it defaults to true); enable it later from the dashboard or with
toggle_cron_job:
A disabled job stays registered but is skipped and removed from the run schedule until re-enabled.
Multiple Queues
let queues = vec!;
Metrics UI
Requires a dashboard feature β
dashboard-actixordashboard-axum(not enabled by default). See Feature Flags.
Access the built-in metrics dashboard at /qrush/metrics:
- Queue statistics and job counts
- Worker status and health
- Cron job management
- Job retry and deletion
- CSV export
Requirements
- Rust 1.89.0 or later
- Redis 6.0 or later
- Tokio runtime (multi-threaded)
Environment Variables
# Required
REDIS_URL=redis://127.0.0.1:6379
# Optional
QRUSH_BASIC_AUTH=admin:password # Basic auth for metrics
RUST_LOG=info,qrush=info # Logging level
Detailed Documentation
Integrated Mode (Detailed)
Use this mode when: You want a simple setup with workers running in the same process as your web server.
1. Add Dependencies
[]
# Pick the dashboard framework you use: "dashboard-actix" or "dashboard-axum"
= { = "2.0.0", = ["dashboard-actix"] }
= "4" # or: axum = "0.8"
= { = "1", = ["rt-multi-thread", "macros"] }
= { = "1", = ["derive"] }
= "0.1"
= "1"
= "0.3"
2. Define a Job
use Job;
use async_trait;
use ;
use BoxFuture;
use Result;
3. Initialize QRush
The queue/worker setup is identical for both frameworks β only the dashboard
wiring differs. The dashboard mounts at /qrush/metrics/... in both cases.
Actix (features = ["dashboard-actix"]):
use ;
use register_job;
use qrush_metrics_routes;
use ;
async
Axum (features = ["dashboard-axum"]):
use ;
use register_job;
use qrush_metrics_router;
use Router;
async
4. Enqueue Jobs
use ;
// Immediate
enqueue.await?;
// Delayed (300 seconds)
enqueue_in.await?;
Separate Process Mode (Detailed)
Use this mode when: You want production-ready separation with workers in a dedicated process.
1. Create Engine Binary
Create src/bin/qrush_engine.rs:
use ;
use set_redis_url;
use register_job;
use CronScheduler;
// Your job types
use NotifyUser;
async
2. Update Cargo.toml
The engine binary above uses tracing_subscriber for logging and dotenvy
to load .env, so add them alongside the [[bin]] entry:
[]
= { = "0.3", = ["env-filter"] }
= "0.15"
[[]]
= "qrush_engine"
= "src/bin/qrush_engine.rs"
3. Web Server (No Workers)
In your web server main.rs, only register jobs for enqueueing β do not
call QueueConfig::initialize (that starts workers; the engine process owns
them here). Use whichever framework you enabled. To also expose the dashboard,
mount it exactly as shown in Integrated Mode β Initialize QRush.
Actix (features = ["dashboard-actix"]):
use set_redis_url;
use register_job;
use qrush_metrics_routes;
use ;
async
Axum (features = ["dashboard-axum"]):
use set_redis_url;
use register_job;
use qrush_metrics_router;
use Router;
async
4. Run Both Processes
Terminal 1 - Web Server:
Terminal 2 - Worker Engine:
Cron Expressions
QRush accepts both 6-field (sec min hour day month weekday) and 5-field
(min hour day month weekday) expressions. A 5-field expression defaults seconds
to 0, so */5 * * * * and 0 */5 * * * * are equivalent.
Common examples:
| Expression | Meaning |
|---|---|
"* * * * *" |
Every minute (5-field) |
"0 * * * * *" |
Every minute (6-field) |
"0 */5 * * * *" |
Every 5 minutes |
"0 0 * * * *" |
Every hour |
"0 0 0 * * *" |
Daily at midnight |
"0 30 9 * * *" |
Daily at 09:30 |
"0 0 0 * * 1" |
Every Monday at midnight |
"0 0 9 * * MON-FRI" |
Weekdays at 09:00 |
"0 0 0 1 * *" |
First day of every month |
"0 0 12 1 JAN *" |
Jan 1st at noon |
Each field supports the usual operators:
*β any valueaβ an exact valuea,b,cβ a lista-bβ an inclusive range*/nβ a step over the whole range (e.g.*/15in minutes)a-b/nβ a step within a range- Names: months
JANβDEC, weekdaysSUNβSAT(case-insensitive). For the weekday field, both0and7mean Sunday.
Timezone. Expressions evaluate in UTC by default. Override per job with
fn timezone(&self) -> &'static str on the CronJob impl, returning any IANA
name (e.g. "Asia/Kolkata", "America/New_York") β so "0 0 9 * * *" fires at
09:00 in that zone, DST included.
Precision & missed runs. The scheduler ticks every ~5 seconds, so a job fires within a few seconds of its scheduled time (don't rely on sub-5s precision). If the scheduler was down when a run was due, that run fires once on the next tick and is then re-anchored to its next future slot β missed cycles are not backfilled one-per-cycle. Claiming is atomic in Redis, so running multiple engine processes will not double-fire the same job.
Metrics Endpoints
GET /qrush/metrics- Dashboard overviewGET /qrush/metrics/queues/{queue}- Queue detailsGET /qrush/metrics/extras/cron- Cron job managementGET /qrush/metrics/extras/workers- Worker statusGET /qrush/metrics/health- Health checkPOST /qrush/metrics/jobs/action- Job actions (retry/delete)
Production Tips
- Use separate process mode for production
- Set
QRUSH_BASIC_AUTHto protect metrics endpoints - Configure appropriate queue concurrency based on your workload
- Monitor Redis memory usage
- Use graceful shutdown for zero-downtime deployments
- Scale workers horizontally by running multiple engine processes
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
- Documentation: docs.rs/qrush
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with β€οΈ by Srotas Space