Skip to content

Axum 0.8.9 (Rust 1.97 + tokio-postgres)

Benchmark · Rust · July 2026

Part of the PostgreSQL REST API Benchmark, July 2026 series.

At a glance

VersionAxum 0.8.9 on Rust 1.97 — new entrant this round
PostgreSQL drivertokio-postgres via deadpool-postgres (pool 100)
Concurrency modelTokio multi-threaded runtime, workers = cores
Lines of code248
Podiumsnone — of 38 combinations
Sourceaxum-app-v0.8.9

The verdict in one sentence: a line-for-line port of the Actix app produced statistically identical results — for this workload class, choosing between Rust web frameworks is an ergonomics decision, not a performance one.

Implementation

This app was added specifically as a controlled experiment: same deadpool-postgres pool, same tokio runtime, same handler bodies as Actix — including the same format! SQL-interpolation workaround for tokio-postgres type handling. Only the framework layer differs. Where Actix uses attribute macros (#[get("/api/perf-test")]), Axum wires plain async functions into a Router (main.rs):

rust
rust
async fn get_test_data(
    State(pool): State<Pool>,
    Query(query): Query<QueryParams>,
) -> Json<Vec<TestResult>> { /* identical body to the Actix handler */ }

let app = Router::new()
    .route("/api/perf-test", get(get_test_data))
    .route("/api/perf-minimal", get(perf_minimal))
    .route("/api/perf-post", post(perf_post))
    .route("/api/perf-nested", get(perf_nested))
    .route("/api/perf-large-payload", get(perf_large_payload))
    .route("/api/perf-many-params", get(perf_many_params))
    .with_state(pool);

let listener = tokio::net::TcpListener::bind("0.0.0.0:5301").await.unwrap();
axum::serve(listener, app).await.unwrap();

Extractors in the signature instead of macros above it; with_state instead of app_data. It is also 8 lines shorter (248 vs 256).

Results

The twin experiment

The headline table. Same machine, same PostgreSQL functions, same driver, two frameworks:

CombinationActixAxumΔ
minimal, 100 VU12,669 (#15)12,644 (#16)−0.2%
perf-test 100 VU / 1 rec2,246 (#17)2,263 (#15)+0.8%
perf-test 50 VU / 10 rec1,481 (#6)1,493 (#4)+0.8%
POST 100 VU / 10 rec3,836 (#9)3,842 (#7)+0.2%
params 100 VU9,462 (#16)9,731 (#15)+2.8%
nested 100 VU / depth 31,090 (#14)1,096 (#5)+0.6%
large 50 VU / 100 KB1,300 (#18)1,303 (#17)+0.2%

Every delta is within run-to-run noise. The nested row is the best illustration of how compressed this field is: 6 req/s separates rank #14 from rank #5. The rank columns look like different frameworks; the throughput columns are the same framework.

On its own terms

The profile is Actix's profile: mid-table on the server-bound scenarios — minimal at 12,644 (#16) / 12,749 (#14) / 12,574 (#15) across 100/200/500 VU, many-parameters #15 at every level (9,768 req/s at 50 VU) — and top-five once rows get heavy: #4 at 50 VU/10 records (1,493 req/s), #5 at 100 VU for both 100 and 500 records, and #5 across all three depths of nested JSON at 100 VU. Like Actix, its minimal baseline is nearly flat from 100 to 500 VU (12,644 → 12,574, −0.6%) while the leaders shed 16–19%.

Latency

p99 of 3 ms at 1 VU on the single-record perf-test, 23 ms on the minimal baseline at 100 VU — one millisecond inside its sibling. It held the sub-1s p99 SLO in 11 of 16 perf-test combinations, the field's standard result. Worst tail: 4,384 ms at 200 VU × 500 records, mid-pack for that DB-saturated combo.

Resource usage

Peak memoryAvg memoryAvg CPU
145 MB25 MB49%

The lowest average CPU in the entire benchmark — on a four-core (400%) budget, Axum used less than half of one core. Its 25 MB average memory matches Go and is 1 MB shy of Swoole's field-best 24 MB. Peak memory (145 MB) even came in below Actix's 167 MB. Whatever the throughput ranks say, this is the cheapest-running HTTP service in the field per unit of work delivered alongside its sibling.

Analysis

The conclusion a data analyst draws from the twin table is unambiguous: the Rust web framework you pick will not change your API's throughput on database-call workloads. Actix and Axum share tokio and diverge at the routing/extraction layer, and that layer measures at +0.8% on the headline combo — noise. The costs that dominate (driver round-trip, JSON pass-through) sit below both frameworks; the differences that remain (macro attributes vs Router composition, web::Data vs State extractors, ecosystem, error-handling style) are exactly the things benchmarks can't rank. Pick on ergonomics.

The same is true of the efficiency story: the two lowest CPU averages in the field belong to this pair (49% and 58%), with memory profiles in Go/Swoole territory. If the deciding factor is cost per instance rather than peak req/s, the Rust pair's mid-table ranks undersell them.

January → July movement: none to report — Axum is new this round, so there is no January position to compare. Its reference point is its sibling: see Actix's January movement, which now describes both of them.

Explore on GitHub

Everything this page claims can be checked against the running code and the raw output:


Series: Introduction · Overall Analysis · Raw Results

Next framework: Spring Boot →

Comments