Skip to content

Azums 🦀⚡

Crates.ioDocs.rs📊 Live BenchmarksDurable ExecutionCI StatusLicense

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.


Why Azums Exists

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.


⚡ Quickstart

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_inspect

Then read Developer Experience & Integration.


🧠 The Execution Model

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.


🛡️ Durable Execution, Not Just Queueing

Azums combines several execution primitives under one runtime.

Durable Jobs

  • 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

Worker Runtime

  • 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

Failure Handling

  • retryable failures
  • permanent failures
  • timeouts
  • panic isolation
  • system failures
  • cancellation
  • configurable retry budgets
  • exponential backoff
  • jitter
  • dead-letter queue
  • DLQ inspection
  • replay

Execution History

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.


🔁 Durable Event Streams

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.


🗄️ Use the Storage You Already Need

Azums does not require a dedicated Azums database or a separate broker.

Memory

Rust application
|
Azums
|
Memory

Best for:

  • tests
  • local development
  • short-lived workloads

Memory is process-local and non-durable.

SQLite

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.

PostgreSQL

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.

Redis

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.


🔐 Transactional Enqueue

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.


♻️ Idempotency and At-Least-Once Execution

Two different duplicate problems exist.

Duplicate submission

100 enqueue calls
same idempotency key
|
v
one logical job

Duplicate execution

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.


🏗️ Backend Capabilities

One API does not mean every backend provides the same operational guarantees.

CapabilityMemorySQLitePostgreSQLRedis
Portable job API
Durable jobsConfiguration-dependent
Idempotent enqueueProcess-local
Same-database transactional enqueue
Streams
Consumer offsetsProcess-local
Distributed workers
NotificationsIn-processIn-process + pollingLISTEN/NOTIFY + polling fallbackPub/Sub + polling fallback
RetentionProcess lifetimeExplicit maintenanceExplicit maintenanceBackend-dependent
Execution-rate policiesBacklogBacklogBacklog

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.


🌍 One Execution Model Across Rust Applications

Azums is not tied to web servers.

The same execution primitive can be used for:

Web Backends

HTTP request
|
+--> database mutation
|
+--> Azums job
|
+--> email
+--> webhook
+--> billing
+--> indexing

AI Systems

AI request
|
+--> inference job
+--> tool workflow
+--> agent task
+--> durable event
+--> retry / timeout / recovery

Embedded and Edge Systems

device
|
Azums
|
SQLite
|
telemetry / sync / deferred work

Gaming

game backend
|
Azums
|
+--> asset processing
+--> asynchronous world tasks
+--> notifications
+--> durable state workflows

CLI and Desktop Applications

application
|
Azums
|
SQLite

No external broker process is required.


🌐 Rust Framework Integrations

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.


📜 What Azums 1.0 Promises

The most important source of truth is Execution Semantics.

Every important behavior is classified as:

Guaranteed
Backend-dependent
Unspecified

Portable guarantees include

  • 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

Backend-dependent behavior includes

  • 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

Azums explicitly does not guarantee

  • 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.


🧪 Tested for Failure, Not Only the Happy Path

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.


⚡ Performance

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 azums

Performance 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.


📊 Observability

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?


🚀 Start Small, Scale Without Relearning the Execution Model

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.


📚 Documentation

Start Here

Reliability

Streams and Operations

Architecture and Release Evidence

API documentation is available on docs.rs.


🎯 The Mental Model

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.


🤝 Contributing

Contributions are welcome.

Please read:


📚 Documentation & Book


💬 Community & Support

  • 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.

🤝 Contributing & License

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.

About

The durable execution layer for Rust. Run important async Rust functions outside the immediate request path without introducing a separate message broker.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages