The durable execution layer for Rust. Run important async Rust functions outside the immediate request path without introducing a separate message broker.
Azums is an embedded execution runtime for Rust applications.
Applications register ordinary async Rust handlers. Azums takes responsibility for the execution lifecycle around those handlers: persistence, scheduling, claiming, leasing, heartbeats, retries, crash recovery, dead-letter handling, replay, execution history, and observability.
It runs on the storage environment that fits your application:
- Memory for tests and ephemeral workloads
- SQLite for embedded, desktop, CLI, edge, and single-binary applications
- PostgreSQL for durable transactional applications and distributed workers
- Redis for Redis-native distributed deployments
You do not need Redis, Kafka, RabbitMQ, or another broker just to execute background Rust functions reliably.
If your architecture already uses Redis, Azums can use it. If your application already depends on PostgreSQL, Azums can keep execution state there. If you are building an embedded application, SQLite is enough.
The programming model remains the same while the operational capabilities of each backend remain explicit.
Rust applications frequently need to perform work after the operation that requested it has finished:
- send an email after creating an account
- process a payment webhook
- call an unreliable external API
- generate reports or exports
- index documents after a database mutation
- process uploaded media
- execute long-running AI inference or tool workflows
- process telemetry from edge devices
- schedule work for later
- publish durable events
- rebuild projections from event history
You can use tokio::spawn:
request
|
v
tokio::spawn(handler())
but the task belongs to the lifetime of that process.
If the process disappears, so does the in-memory future.
Azums changes the model:
application
|
| submit durable work
v
Azums
|
+--> persist execution intent
|
+--> lease to worker
|
+--> run handler
|
+--> heartbeat ownership
|
+--> record attempt
|
+--> complete
|
+--> retry after failure
|
+--> recover after worker loss
|
+--> DLQ when execution cannot continue
The central idea is:
Failure should not silently erase an important execution.
Once durable work is successfully accepted, Azums retains responsibility for its execution lifecycle until it reaches a defined terminal outcome, subject to the guarantees of the selected backend and worker availability.
Azums provides at-least-once execution, not exactly-once external side effects.
Add Azums:
[dependencies]
azums = "1.0"tokio = { version = "1", features = ["full"] }
serde_json = "1"anyhow = "1"Register a handler, enqueue work, and execute it:
use azums::{quickstart,Job};#[tokio::main]asyncfnmain() -> anyhow::Result<()>{let client = quickstart("memory").await?
.with_queue("default");
client
.register_handler("greet", |job| asyncmove{println!("Hello, {}!", job.payload["name"]);Ok(())}).await;let job_id = client
.enqueue(Job::new("greet",
serde_json::json!({"name":"World"}),).queue("default").max_attempts(5).idempotency_key("greet:world"),).await?;
client.run_until_empty().await?;println!("{:?}", client.explain_job(job_id).await?);Ok(())}The same handler model works across supported storage environments:
let memory = quickstart("memory").await?;let sqlite =
quickstart("sqlite://jobs.db?mode=rwc").await?;let postgres =
quickstart("postgres://user:pass@localhost/app").await?;let redis =
quickstart("redis://127.0.0.1:6379").await?;The API is portable.
The guarantees are not assumed to be identical.
Azums exposes backend differences through BackendCapabilities.
For the progressive install → enqueue → process → retry → inspect path:
cargo run -p azums --example install_enqueue_process_retry_inspectThen read Developer Experience & Integration.
Azums is built around a controlled execution lifecycle:
SCHEDULED
|
v
QUEUED
|
v
RUNNING ---------> COMPLETED
| |
| +-----------> CANCELLED
|
+-------------> DLQ
|
v
RETRY_WAIT
|
+-------------> QUEUED
COMPLETED, CANCELLED, and DLQ are terminal states.
Every transition outside the documented lifecycle is illegal.
While work executes, Azums uses:
CLAIM
|
v
LEASE
|
v
HEARTBEAT
|
v
ATTEMPT
|
v
HANDLER
|
v
ACK
The lease answers:
Which worker currently owns the right to execute and mutate this running job?
The heartbeat answers:
Is that worker still alive?
The attempt records:
What happened during this invocation?
The ACK records successful completion.
If a worker disappears before completion:
worker disappears
|
v
heartbeat stops
|
v
lease expires
|
v
abandoned execution is recorded
|
v
work becomes recoverable
This is why Azums provides at-least-once execution.
Azums combines several execution primitives under one runtime.
- individual and batch enqueue
- named job types
- queues
- arbitrary JSON payloads
- typed payload deserialization
- priorities
- idempotency keys
- delayed execution
- execution deadlines
- recurring execution
- replay lineage
- Tokio-native execution
- handler registration
- worker identities
- batch leasing
- exclusive leases
- heartbeat extension
- handler timeouts
- graceful shutdown
- notification wake-up with polling fallback
- periodic expired-lease recovery
- retryable failures
- permanent failures
- timeouts
- panic isolation
- system failures
- cancellation
- configurable retry budgets
- exponential backoff
- jitter
- dead-letter queue
- DLQ inspection
- replay
Azums separates current job state from execution history.
A durable job can retain information about:
- current lifecycle state
- attempt count
- workers
- execution timings
- failures
- retry history
- DLQ reason
- replay lineage
- trace information
This allows an application to ask:
What happened?
instead of reconstructing execution from unstructured logs.
Jobs answer:
What work must be executed?
Streams answer:
What happened that consumers must be able to observe?
Azums supports durable event streams with:
- append-only events
- monotonic stream-local sequence numbers
- consumer groups
- durable offsets
- monotonic ACK
- independent consumer progress
- replay
- retention-aware pruning
- notification subscriptions
Example:
use azums::{quickstart,NewEvent};use serde_json::json;asyncfnexample() -> anyhow::Result<()>{let client = quickstart("memory").await?;let orders = client.stream("orders");let sequence = orders
.publish("order_created",json!({"order_id":"ord-1001"}),).await?;let events = orders.read_next("billing",100).await?;for event in events {// Process the event.
orders
.ack("billing", event.sequence_no).await?;}println!("Published sequence {sequence}");Ok(())}Reading an event does not automatically advance the consumer offset.
If a consumer crashes before ACK, the event may be delivered again.
Streams therefore use the same durability philosophy as jobs:
Persist responsibility and make recovery explicit.
Azums does not require a dedicated Azums database or a separate broker.
Rust application
|
Azums
|
Memory
Best for:
- tests
- local development
- short-lived workloads
Memory is process-local and non-durable.
Rust application
|
Azums
|
SQLite
Best for:
- embedded systems
- desktop software
- edge applications
- CLI tools
- single-binary deployments
- single-process services
SQLite provides durable local storage without operating another service.
Application instances
|
Azums
|
PostgreSQL
|
Worker instances
Best for:
- production backend services
- multi-host workers
- microservices
- transactional applications
- applications already using PostgreSQL
PostgreSQL can also provide same-database transactional enqueue.
Application
|
Azums
|
Redis
Best for deployments that already want Redis-native distributed execution and have intentionally configured Redis persistence and eviction behavior.
Redis is supported.
Redis is not required.
One of the most important failure boundaries in background execution is:
application mutation succeeds
|
v
enqueue fails
or the inverse:
job becomes visible
|
v
application transaction rolls back
With SQLite and PostgreSQL, application data and Azums work can share the same database transaction:
use azums::{Job,PostgresBackend};use serde_json::json;asyncfncreate_user(pool:&sqlx::PgPool,backend:&PostgresBackend,) -> anyhow::Result<()>{letmut tx = pool.begin().await?;
sqlx::query("INSERT INTO users (id) VALUES ($1)").bind("user-123").execute(&mut*tx).await?;
backend
.enqueue_in_tx(&mut tx,Job::new("send_welcome_email",json!({"user_id":"user-123"}),).into(),).await?;
tx.commit().await?;Ok(())}The boundary is precise:
BEGIN
application mutation
Azums enqueue
COMMIT
Commit preserves both.
Rollback preserves neither.
Azums does not claim transactions across unrelated external systems.
Two different duplicate problems exist.
100 enqueue calls
same idempotency key
|
v
one logical job
handler performs external effect
|
v
worker crashes before ACK
|
v
job is recovered
|
v
handler may execute again
An enqueue idempotency key cannot make an arbitrary external side effect exactly once.
For external systems, use a stable idempotency key:
let external_key =
format!("azums-job:{}", job.id);
payment_api
.charge_with_idempotency_key(
external_key,
amount,).await?;For database effects, record the processed job ID or stream sequence under a unique constraint in the same transaction as the application mutation.
Azums makes duplicate execution visible and controllable.
It does not pretend distributed side effects are magically exactly once.
One API does not mean every backend provides the same operational guarantees.
| Capability | Memory | SQLite | PostgreSQL | Redis |
|---|---|---|---|---|
| Portable job API | ✅ | ✅ | ✅ | ✅ |
| Durable jobs | ❌ | ✅ | ✅ | Configuration-dependent |
| Idempotent enqueue | Process-local | ✅ | ✅ | ✅ |
| Same-database transactional enqueue | ❌ | ✅ | ✅ | ❌ |
| Streams | ✅ | ✅ | ✅ | ✅ |
| Consumer offsets | Process-local | ✅ | ✅ | ✅ |
| Distributed workers | ❌ | ❌ | ✅ | ✅ |
| Notifications | In-process | In-process + polling | LISTEN/NOTIFY + polling fallback | Pub/Sub + polling fallback |
| Retention | Process lifetime | Explicit maintenance | Explicit maintenance | Backend-dependent |
| Execution-rate policies | Backlog | Backlog | ✅ | Backlog |
Applications can inspect these guarantees at runtime:
let client = azums::quickstart(
std::env::var("DATABASE_URL")?
).await?;let capabilities = client.capabilities();
anyhow::ensure!(
capabilities.durable_jobs,"this deployment requires durable jobs");
anyhow::ensure!(
capabilities.distributed_workers,"this deployment runs workers on multiple hosts");The rule is:
Same programming model. Explicit operational differences.
Azums is not tied to web servers.
The same execution primitive can be used for:
HTTP request
|
+--> database mutation
|
+--> Azums job
|
+--> email
+--> webhook
+--> billing
+--> indexing
AI request
|
+--> inference job
+--> tool workflow
+--> agent task
+--> durable event
+--> retry / timeout / recovery
device
|
Azums
|
SQLite
|
telemetry / sync / deferred work
game backend
|
Azums
|
+--> asset processing
+--> asynchronous world tasks
+--> notifications
+--> durable state workflows
application
|
Azums
|
SQLite
No external broker process is required.
Azums ships integration crates for common Rust web frameworks:
Example integration:
asyncfncreate_user(queue:JobQueue,Json(payload):Json<UserPayload>,) -> implIntoResponse{let job_id = queue
.enqueue_now("default","welcome_email",json!(payload),).await?;Json(json!({"status":"queued","id": job_id
}))}Web framework integration is convenience around the same Azums execution model.
The most important source of truth is Execution Semantics.
Every important behavior is classified as:
Guaranteed
Backend-dependent
Unspecified
- at-least-once delivery for retained runnable work while workers are available
- rejection of illegal lifecycle transitions
- terminal states remain terminal
- at most one valid active lease for a job
- expired-lease recovery
- deterministic failure classification
- retry and DLQ behavior
- non-null idempotency keys identify one logical job
- no intentional leasing before scheduling eligibility
- monotonically increasing stream sequences
- monotonically advancing consumer offsets
- replay with preserved lineage and history
- durability through process or machine failure
- transaction scope
- worker distribution
- ordering strength
- notifications
- wake-up latency
- retention
- backpressure
- Redis persistence and eviction behavior
- backend clock behavior
- exactly-once execution
- exactly-once external side effects
- exact wall-clock execution time
- global completion ordering
- worker fairness
- transactions across arbitrary external services
- automatic consumer-group balancing
- permanent retention
- automatic scaling
- cancellation undoing an external effect that already happened
Stable semantics matter more than a benchmark number.
Azums 1.0 was tested against the types of failures its execution contract is designed to survive.
Release evidence includes:
- full workspace test suite
- lifecycle invariant tests
- property-based execution tests
- malformed-input and fuzz-hardening tests
- worker crash and lease-recovery tests
- heartbeat and wrong-owner tests
- transaction commit and rollback boundaries
- connection-loss and failure-boundary tests
- retry and DLQ tests
- idempotency scenarios
- scheduling and timeout tests
- stream replay and consumer-offset tests
- randomized chaos scenarios
- concurrency testing from 1 to 100 workers
- large-scale runs reaching one million jobs per matrix case
- 6.96 million job executions in the final recorded large-scale matrix
- benchmark regression gates
- documentation build
- dependency security audit
- API compatibility checks
The release evidence means:
No known documented Azums 1.0 guarantee was violated by the tested release.
It does not mean arbitrary infrastructure or external side effects can never fail.
See Release Candidate Evidence.
Correctness comes first, but durable execution still needs to be fast.
Azums includes reproducible benchmark tooling:
cargo run -p azums --release --bin azums-perf
cargo bench -p azumsPerformance numbers depend on:
- backend
- hardware
- worker count
- job shape
- persistence configuration
- contention
- benchmark version
For that reason, benchmark results are evidence, not semantic guarantees.
View the live benchmark dashboard.
Azums can explain execution state directly:
ifletSome(explanation) =
client.explain_job(job_id).await?
{println!("{}", explanation.summary);println!("status: {}", explanation.status);println!("retries: {}",
explanation.retry_count
);println!("last worker: {:?}",
explanation.last_worker_id
);println!("last error: {:?}",
explanation.last_error
);}Queue metrics include information such as:
- queue depth
- completions
- failures
- retries
- DLQ count
- execution latency
- claim information
- worker counts where measurable
The goal is not merely to know that something failed.
The goal is to answer:
What happened, why did it happen, who owned the execution, and what can happen next?
A project can begin with:
Tests
|
Azums
|
Memory
move to:
CLI / Desktop / Edge
|
Azums
|
SQLite
and later operate:
Application instances
|
Azums
|
PostgreSQL
|
Worker instances
without changing the fundamental handler model.
That is one of Azums' core design goals:
The execution model should remain understandable as the deployment grows.
- Azums Product and Implementation Handbook — Product model, implementation walkthrough, guarantees, release evidence, and learning path.
- Execution Semantics — Canonical source of truth for Azums guarantees.
- Developer Experience & Integration — Install-to-production adoption path.
- Storage Backend Equivalence — Capability matrix across Memory, SQLite, PostgreSQL, and Redis.
- Job Lifecycle
- Lease Recovery
- Retry, Failure Classification & DLQ
- Idempotency & Duplicate Execution
- Transactional Integrity
- Scheduling & Time Semantics
- Durable Event Streaming
- Concurrency & Backpressure
- Observability
- Production Deployment
- Failure & Recovery Runbook
- Architecture Overview
- Azums Low-Level Design
- Primitive Correctness
- Chaos Engineering
- Property Testing
- Fuzzing & Input Hardening
- Release Candidate Evidence
- API Stability Policy
API documentation is available on docs.rs.
Think of Azums as three related layers:
1. Durable Intent
Job
schedule
priority
idempotency
stream event
|
v
2. Controlled Execution
claim
lease
heartbeat
attempt
handler
ACK
retry
DLQ
|
v
3. Explanation & Recovery
execution history
worker history
consumer offsets
replay
metrics
lifecycle explanation
The durable record is the source of truth.
Notifications improve wake-up latency but do not define correctness.
Leases make abandoned execution recoverable.
Retries make transient failure survivable.
Execution history makes failure explainable.
Backend capabilities keep operational promises honest.
And the application remains responsible for making external side effects safe under at-least-once execution.
Azums turns ordinary async Rust functions into recoverable, observable, durable execution.
Contributions are welcome.
Please read:
- Docs.rs API Guide: Comprehensive module documentation & inline examples.
- Architecture & Technical Design (ARCHITECTURE.md): State machine,
FOR UPDATE SKIP LOCKEDleasing algorithm, phantom recovery, and partitioning. - Azums Low-Level Design (LLD + DSA): Deep-dive architecture specs, data structures, and algorithm complexity.
- Execution Semantics: Canonical guarantee matrix for scheduling, DLQ, idempotency, transactional enqueue, streams, consumer groups, replay, and cancellation.
- Storage Backend Equivalence: Runtime capability model and compatibility matrix for Memory, SQLite, PostgreSQL, and Redis.
- Transactional Integrity: Commit/rollback contract for SQL transactional enqueue.
- Retry, Failure Classification & DLQ: Deterministic failure classes, backoff policy, DLQ inspection, and replay.
- Idempotency & Duplicate Execution: Enqueue dedupe keys and application-side side-effect idempotency.
- Developer Experience & Integration: Install-to-inspect adoption path and integration notes.
- Azums Architecture Book: FOR UPDATE SKIP LOCKED leasing, DLQ sequence diagrams, and table partitioning.
- GitHub Discussions: Have questions, feature requests, or architecture ideas? Join our GitHub Discussions.
- Issue Tracker: Found a bug or issue? Report it on our GitHub Issues tracker.
Contributions are welcome! Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Licensed under either of Apache License, Version 2.0 or MIT License at your option.