The project is in a healthy, maintained state
A Rails gem that generates a Rust worker for processing background jobs from Redis
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 5.0
>= 4.0
>= 2.0
 Project Readme

RustOnBackground

Gem Version License: MIT

Run background jobs in Rust. Run a few commands and you're all set up - just scaffold and write Rust. Jump to quick start

# Rails: enqueue a job
RustOnBackground.perform("send_email", user_id: 123, template: "welcome")

# Schedule for later
RustOnBackground.perform_at(1.hour.from_now, "cleanup", batch_size: 100)

Features

  • Rails generators for instant setup, very easy
  • Auto-detects database adapter (MySQL, PostgreSQL, SQLite) from your database.yml config
  • Scaffolding: Auto-generates Rust structs from ActiveRecord models (You can do just partial scaffold only to create the structs from schema)
  • Multiple queues with configurable worker counts
  • Scheduled jobs with perform_at
  • Environment-specific configs (config.development.toml, config.production.toml)
  • Automatic retries with configurable retry count
  • Rake tasks for everything: build, start, stop, restart, status, rebuild

Requirements

  • Ruby 2.5+
  • Rails 5.0+
  • Redis 4.0+
  • Rust 1.70+ (install)

Quick Start

Add to your Gemfile:

gem "rust_on_background"

Run bundle install:

bundle install

This creates the Rust project structure at app/jobs/rust/ and detects your database configuration automatically:

rails generate rust_on_background:install
# Create a job (generates app/jobs/rust/src/jobs/send_email.rs)
rails generate rust_on_background:job send_email user_id:integer message:string

# Build
rake rust_on_background:build

# Start workers
rake rust_on_background:start

# Enqueue from Rails
RustOnBackground.perform("send_email", user_id: 1, message: "welcome")

Project Structure

After installation, your Rails app will have:

app/jobs/rust/
├── Cargo.toml                    # Rust dependencies (add crates here)
├── config.development.toml       # Dev config (auto-generated)
├── config.production.toml        # Create this for production
└── src/
    ├── main.rs                   # Don't need to touch this
    ├── worker.rs                 # Don't need to touch this
    ├── scheduler.rs              # Don't need to touch this
    └── jobs/                     # YOUR CODE GOES HERE
        ├── mod.rs                # Auto-updated by generator, this is basically a controller for the jobs(scaffolding creates the action for you in here)
        └── send_email.rs         # Example job file

Configuration

Environment-specific config files in app/jobs/rust/:

# config.development.toml

[database]
url = "mysql://root@localhost/myapp_development"

[redis]
url = "redis://127.0.0.1:6379"

[logging]
path = "log/development.log" # can leave empty for no logs

# Define your queues
[[queues]]
name = "default"
workers = 4

[[queues]]
name = "emails"
workers = 2

# Scheduler settings (optional)
# [scheduler]
# poll_interval_ms = 1000
# batch_size = 100

Generating Jobs

Basic job

rails generate rust_on_background:job cleanup

Job with typed arguments

rails generate rust_on_background:job send_email user_id:integer template:string urgent:boolean

Generates:

#[derive(Debug, Deserialize)]
struct Args {
    user_id: i64,
    template: String,
    urgent: bool,
}

pub async fn run(args: &serde_json::Value, _database_url: &str) -> JobResult {
    let args: Args = serde_json::from_value(args.clone())?;
    // Your logic here
    JobResult::Success
}

Job with ActiveRecord model

rails generate rust_on_background:job process_order order:Order

The generator reads your model's columns from the database and creates a matching Rust struct:

#[derive(Debug, Deserialize)]
struct Order {
    id: i64,
    user_id: i64,
    total: f64,
    status: String,
    created_at: String,
    updated_at: String,
}

#[derive(Debug, Deserialize)]
struct Args {
    order: Order,
}

Enqueuing Jobs

Immediate execution

RustOnBackground.perform("job_name", arg1: "value", arg2: 123) # (default queue if not specified)

With specific queue

RustOnBackground.perform("send_email", queue: "emails", user_id: 1)

With retry count

RustOnBackground.perform("flaky_job", retry_count: 5, data: "...")

Scheduled execution

# Run in 1 hour
RustOnBackground.perform_at(1.hour.from_now, "cleanup")

# Run at specific time
RustOnBackground.perform_at(Date.tomorrow.noon, "daily_report")

# With all options
RustOnBackground.perform_at(
  30.minutes.from_now,
  "send_reminder",
  queue: "emails",
  retry_count: 3,
  user_id: 123
)

Supported Types

Ruby/Rails Type Rust Type
string, text String
integer, bigint i64
float, decimal f64
boolean bool
array, set Vec<serde_json::Value>
hash, json, jsonb serde_json::Value
date, datetime, time String
uuid String
binary, blob Vec<u8>
ActiveRecord model Auto-generated struct

Rake Tasks

# Build the Rust binary
rake rust_on_background:build

# Start all workers
rake rust_on_background:start

# Start specific queue
rake rust_on_background:start[emails]

# Stop all workers
rake rust_on_background:stop

# Stop specific queue
rake rust_on_background:stop[emails]

# Restart workers
rake rust_on_background:restart

# Stop, rebuild, and start - for example when doing a deploy
rake rust_on_background:rebuild

# Check status
rake rust_on_background:status

# Start scheduler (run ONE instance per Redis db)
rake rust_on_background:scheduler:start

# Stop scheduler
rake rust_on_background:scheduler:stop

Writing Job Logic

Jobs are in app/jobs/rust/src/jobs/. Each job is a Rust module with a run function:

use serde::Deserialize;
use crate::worker::JobResult;

#[derive(Debug, Deserialize)]
struct Args {
    user_id: i64,
    message: String,
}

pub async fn run(args: &serde_json::Value, database_url: &str) -> JobResult {
    // Parse args
    let args: Args = match serde_json::from_value(args.clone()) {
        Ok(a) => a,
        Err(e) => return JobResult::Failed(format!("Invalid args: {}", e)),
    };

    // Your logic here
    tracing::info!("Processing user {}", args.user_id);

    // Return result
    JobResult::Success
    // or: JobResult::Retry("temporary failure".to_string())
    // or: JobResult::Failed("permanent failure".to_string())
}

Database access

The database_url parameter gives you direct access to your Rails database:

use sqlx::mysql::MySqlPool;

pub async fn run(args: &serde_json::Value, database_url: &str) -> JobResult {
    let pool = MySqlPool::connect(database_url).await?;

    let users: Vec<User> = sqlx::query_as("SELECT * FROM users WHERE active = ?")
        .bind(true)
        .fetch_all(&pool)
        .await?;

    JobResult::Success
}

HTTP callbacks to Rails (example)

If you need to notify Rails when a job completes, you can use any HTTP client. Example with reqwest:

pub async fn run(args: &serde_json::Value, _database_url: &str) -> JobResult {
    // Do work...

    // Notify Rails
    reqwest::Client::new()
        .post("http://localhost:3000/webhooks/job_complete")
        .json(&serde_json::json!({ "job": "my_job", "status": "done" }))
        .send()
        .await?;

    JobResult::Success
}

Production Deployment

  1. Create app/jobs/rust/config.production.toml with production database/Redis URLs
  2. rake rust_on_background:rebuild (stops workers, builds, starts workers)
  3. rake rust_on_background:scheduler:start

Workers run as background processes with PID files in tmp/pids/.

License

MIT License. See LICENSE for details.