Firq — Multi-tenant Scheduler for Rust Services
Firq is an in-process scheduler for Rust backends that need stable tail latency under contention.
Key capabilities:
- Fair scheduling across tenants (DRR).
- Explicit backpressure and bounded memory behavior.
- Deadline-aware dequeue (
DropExpiredsemantics). - Metrics for queue saturation, drops, rejections, and queue-time percentiles.
firq-core is runtime-agnostic. firq-async adds Tokio support. firq-tower integrates with Tower/Axum.
Install
From crates.io
[]
= "0.1.3"
Tokio integration:
[]
= "0.1.3"
= "0.1.3"
Tower/Axum integration:
[]
= "0.1.3"
= "0.1.3"
= "0.1.3"
Optional metrics helpers (firq_core::prometheus) are behind feature metrics (enabled by default):
[]
= "0.1.3"
Disable metrics helpers if you only need scheduling primitives:
[]
= { = "0.1.3", = false }
From source
Quick start
Stability / SemVer
- Firq is currently in
0.x; minor releases may include API-breaking changes. - Breaking changes include removing/renaming public types/functions, changing enum variants, or changing behavior in a way that requires code changes.
- Patch releases (
0.1.z) aim to be backward compatible and focus on fixes/hardening. - For production, pin a concrete release (
=0.1.3) or a conservative range (~0.1.3) and reviewCHANGELOG.mdbefore upgrades. - MSRV: Rust
1.85+(rust-version = "1.85"infirq-core,firq-async, andfirq-tower).
Docs & examples
firq-core: core scheduler API and crate-level minimal example.cargo add firq-core@0.1.3firq-async: Tokio adapter and worker-backed consumer example.cargo add firq-async@0.1.3firq-tower: Tower/Axum layer with header-based tenant extraction.cargo add firq-tower@0.1.3- All three crates include copyable crate-level examples in
lib.rsdocs.
Scheduler guarantees and non-guarantees
Guarantees:
- DRR scheduling gives active tenants recurring turns (no permanent starvation for runnable tenants).
- Queue limits (
max_global,max_per_tenant) bound live pending work. - Cancellation and deadline expiry are reclaimed lazily and should not permanently consume capacity.
stats()counters are monotonic snapshots suitable for alerting and regression checks.- Fairness is cost-weighted: each dequeue charges
task.costagainst tenant deficit, refilled byquantum. - Priority is strict (
High->Normal->Low) in dispatch order, so sustainedHightraffic can delay lower-priority work.
Non-guarantees:
- No strict global FIFO ordering across tenants.
- No cross-process fairness guarantee (scheduler is per-process/in-memory).
- No hard latency SLA by itself; tune
quantum,cost, capacity, and worker parallelism with production traffic.
When to use / not use
Use Firq when:
- You need in-process multi-tenant fairness and backpressure in a Rust service.
- You want deadline-aware admission/dispatch and explicit queue saturation signals.
- You are integrating with sync workers, Tokio, or Tower/Axum middleware.
Do not use Firq when:
- You need durable or distributed queue semantics.
- You need fairness across multiple processes/nodes by itself.
- You need persistent job orchestration/retries as the primary concern.
Scheduling flow
producers -> enqueue(tenant, priority, cost, deadline)
-> sharded per-tenant priority queues
-> DRR selection (cost vs quantum)
-> dequeue
-> worker pool / async consumer / tower layer
How to choose parameters
Use these as starting points, then tune with real traffic and stats() metrics.
shards: Start withmin(physical_cores, 8). Increase when many tenants are hot concurrently and enqueue lock contention is visible.max_globalandmax_per_tenant: Size from memory budget first. Approximate memory asmax_global * avg_task_size. Keepmax_per_tenantlow enough that one tenant cannot monopolize memory.quantumandcost: Treatcostas "work units" per task, andquantumas work units granted per round. If heavy jobs are starving, increasequantumor lower heavy-jobcostcalibration.deadlines: Use when stale work should be discarded (timeouts/SLO breaches). Expired items are removed lazily and should not permanently consume queue limits.- backpressure policy:
Rejectfor strict admission control,DropOldestPerTenant/DropNewestPerTenantfor lossy workloads,Timeoutwhen producers can wait briefly for capacity.
Queue limits are enforced against live pending work. Cancelled/expired entries are compacted lazily on dequeue and enqueue maintenance passes.
Starting profiles (adjust with production traffic):
- Public API:
Rejector shortTimeout(5-20ms), conservativemax_per_tenant,in_flight_limitnear available CPU parallelism. - Internal RPC/workers:
Rejectwith moderatemax_per_tenant, calibratecostfor heavy endpoints, increasequantumif heavy tasks starve. - Lossy telemetry/batch edge:
DropOldestPerTenantorDropNewestPerTenantbased on whether freshness or completeness matters more.
Core usage (firq-core)
Use this when workers are synchronous (threads) or when direct control over dequeue loops is required.
use ;
use HashMap;
use Instant;
let scheduler = new;
let tenant = from;
let task = Task ;
match scheduler.enqueue
match scheduler.dequeue_blocking
let stats = scheduler.stats;
println!;
Async usage (firq-async, Tokio)
Use this when producers/consumers are async and run under Tokio.
use ;
use Arc;
use Instant;
let core = new;
let scheduler = new;
let tenant = from;
let task = Task ;
match scheduler.enqueue
// Recommended for steady consumers: dedicated worker mode.
let mut receiver = scheduler.receiver_with_worker;
while let Some = receiver.recv.await
// Fallback for one-off dequeue calls:
let _ = scheduler.dequeue_async.await;
Axum usage (firq-tower)
firq-tower provides a Tower layer that handles scheduling, cancellation before turn, in-flight gating, and rejection mapping.
use ;
use ;
let firq_layer = new
.with_shards
.with_max_global
.with_max_per_tenant
.with_quantum
.with_in_flight_limit
.
.build;
let app = new
.route
.layer;
Default rejection mapping:
TenantFull->429GlobalFull->503Timeout->503
Actix-web usage (manual scheduling gate)
Firq does not currently provide a first-party firq-actix crate.
Use firq-async in handlers or middleware to gate work before executing heavy logic:
use ;
use ;
use Instant;
;
async
Runnable Actix example in this repository:
crates/firq-examples/src/bin/actix_web.rs
Benchmarks
Run reproducible scenarios:
Quick smoke run (single scenario, short duration):
FIRQ_BENCH_SCENARIO=capacity_pressure FIRQ_BENCH_SECONDS=2 \
No extra features are required for benchmark runs.
Scenarios in the benchmark binary include (real names):
hot_tenant_sustaineddeadline_expirationcapacity_pressure
What to observe (without relying on fixed numbers):
- Fairness: in
hot_tenant_sustained, cold tenants should still be served. - Queue-time behavior: compare p95/p99 queue-time trends between schedulers.
- Backpressure/expiry signals: in
capacity_pressureanddeadline_expiration, verify drop/reject/expired counters respond as load changes.
Use runs to compare parameter sets and regressions; do not treat a single run as a universal baseline.
Repository layout
crates/firq-core: scheduler engine.crates/firq-async: Tokio adapter.crates/firq-tower: Tower layer.crates/firq-examples: runnable examples (publish = false).crates/firq-bench: benchmark runner (publish = false).
Community and governance
- Contribution guide:
CONTRIBUTING.md - Security policy:
SECURITY.md - Support channels:
SUPPORT.md - Release process:
RELEASING.md
Local quality gates
Release dry-runs:
# after firq-core is published on crates.io:
# after firq-async is published on crates.io:
License
This project is dual-licensed under:
- MIT (
LICENSE-MIT) - Apache-2.0 (
LICENSE-APACHE)