Menu
Dev.to #systemdesign·August 11, 2026

Transactional Background Job Queues in Rust: Addressing Dual-Write and Idle CPU

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 #systemdesign

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

Core Problems with Traditional Job Queues

  • The Dual-Write Problem: Many job queues require an external broker (e.g., Redis). If a database transaction commits successfully but the job enqueue operation to the external broker fails, the system state can become inconsistent. This leads to lost jobs or orphaned data.
  • Idle CPU Waste: SQL-based job queues often rely on polling mechanisms (e.g., `SELECT ... LIMIT X` every few seconds). When the queue is empty, this constant polling consumes unnecessary CPU cycles and incurs cloud costs, especially with many worker instances.
  • Engine/Driver Lock-in: Switching storage backends (e.g., from SQLite to PostgreSQL or Redis) typically necessitates significant code changes due to different APIs and abstractions, hindering portability and flexibility.

Azums' Architectural Solutions

Eliminating the Dual-Write Problem with Transactional Enqueueing

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.

rust
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?;

Zero-Cost Idle Execution with Native Notifications

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.

  • PostgreSQL: Utilizes `LISTEN / NOTIFY` for instant signaling to waiting workers, combined with `FOR UPDATE SKIP LOCKED` for efficient job claiming.
  • Redis: Employs native Pub/Sub channels and atomic Lua scripts for reliable event delivery and job processing.
  • SQLite: Uses optimized WAL (Write-Ahead Logging) mode and event channels, making it suitable for embedded and edge environments.
job queuebackground jobstransactionaldual-write problemRustPostgreSQLRedisdistributed systems

Comments

Loading comments...