This article introduces Azums, a Rust-native transactional background job engine designed to solve common distributed system challenges like the dual-write problem and inefficient idle CPU usage. It achieves this by integrating job enqueuing directly within database transactions and leveraging native database notification systems for zero-cost idle execution. The solution provides a unified API across various storage backends, offering a robust approach to asynchronous task processing in distributed architectures.
Read original on Dev.to #systemdesignWhen designing backend services that rely on asynchronous processing, such as sending emails, processing data, or managing event streams, a robust background job queue is essential. Traditional approaches often introduce architectural challenges, particularly in distributed environments. This article explores Azums, a Rust framework that tackles these issues head-on by re-thinking how job queues interact with primary data stores and manage worker resources.
Azums addresses the dual-write problem by integrating job enqueuing directly into the primary database transaction. This means that job creation becomes an atomic operation with the main application data changes. If the transaction commits, both the data and the job are persisted. If it rolls back, neither is. This guarantees strong consistency between application state and queued tasks, eliminating the need for complex distributed transaction protocols or compensating actions.
use azums::Job;
let mut tx = pool.begin().await?;
// 1. Mutate application data
sqlx::query!("INSERT INTO users (id, email) VALUES ($1, $2)", user.id, user.email)
.execute(&mut *tx)
.await?;
// 2. Enqueue job inside the SAME database transaction
client.enqueue_tx(&mut *tx, Job::new("welcome_email", serde_json::json!({ "user_id": user.id }))).await?;
// 3. Atomically commit both user data AND job queue state
tx.commit().await?;To combat idle CPU waste, Azums leverages database-native notification systems instead of continuous polling. This event-driven approach ensures that workers only activate when a new job is available, achieving 0.0% idle CPU usage and sub-millisecond dispatch latencies. This is a critical design choice for cost-efficiency and responsiveness in large-scale deployments.