tower-rs / tower-rs/tower-http

ServeFile is slower than simply reading a file

Open
#480 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-request E-medium
Dominant language
Rust
Stars
913
Forks
231
Avg merge
1d 20h
Merged PRs (30d)
8

Description

  • I have looked for existing issues (including closed) about this

Bug Report

Version

tower-http v0.5.2

Platform

Linux [..] 6.7.8-arch1-1 #1 SMP PREEMPT_DYNAMIC Sun, 03 Mar 2024 00:30:36 +0000 x86_64 GNU/Linux

Crates
[dependencies]
axum = version = "0.7.4"
tokio = { version = "1.36.0", features = ["full"] }
tower-http = { version = "0.5.2", features = ["fs"]}
Description

The tests were performed with the wrk utility. My friend also ran tests with rewrk utility, the situation is similar.
I expected to see this happen: ServeFile and read from file will show identical readings.
Instead, this happened: ServeFile proved to be slower.
I tried this code:

use axum::{routing::get, Router};
use tower_http::services::ServeFile;

mod api;

#[tokio::main]
async fn main() {
    let html = std::fs::read_to_string("static/html/index.html").unwrap();

    let app = Router::new()
        .route("/1", get(api::index).with_state(html.leak()))
        .route("/2", get(api::index2))
        .nest_service("/3", ServeFile::new("static/html/index.html"));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

api.rs:

use axum::{extract::State, response::{Html, IntoResponse}};


pub async fn index(State(state): State<&'static str>) -> impl IntoResponse {
    Html(state.to_string())
}

pub async fn index2() -> impl IntoResponse {
    let html = tokio::fs::read_to_string("static/html/index.html").await.unwrap();
    Html(html)
}

"/1" - file is read into memory in advance
"/2" - read data from the file, send it
"/3" - ServeFile

Test results:
In this test, the file weighs 10kb.

Running 30s test @ http://127.0.0.1:3000/1
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     7.11ms    3.30ms  38.70ms   77.42%
    Req/Sec    23.39k     1.14k   26.86k    73.14%
  2791777 requests in 30.10s, 28.63GB read
Requests/sec:  92735.21
Transfer/sec:      0.95GB

Running 30s test @ http://127.0.0.1:3000/2
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    22.32ms   10.53ms  87.24ms   65.98%
    Req/Sec    11.21k   570.81    13.04k    71.82%
  1334820 requests in 30.04s, 13.69GB read
Requests/sec:  44435.60
Transfer/sec:    466.70MB

Running 30s test @ http://127.0.0.1:3000/3
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    62.08ms   16.15ms 156.63ms   79.43%
    Req/Sec     4.03k   217.68     4.70k    69.31%
  479628 requests in 30.02s, 4.94GB read
Requests/sec:  15976.21
Transfer/sec:    168.61MB

In this test, the file weighs 100kb.

Running 30s test @ http://127.0.0.1:3000/1
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    20.03ms    8.55ms 111.49ms   76.94%
    Req/Sec     7.46k   539.25     8.72k    75.13%
  888612 requests in 30.10s, 87.79GB read
Requests/sec:  29526.30
Transfer/sec:      2.92GB

Running 30s test @ http://127.0.0.1:3000/2
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    63.14ms   28.77ms 225.17ms   66.77%
    Req/Sec     3.95k   321.27     5.55k    73.68%
  470250 requests in 30.10s, 46.46GB read
Requests/sec:  15621.18
Transfer/sec:      1.54GB

Running 30s test @ http://127.0.0.1:3000/3
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    89.73ms   25.94ms 264.17ms   70.83%
    Req/Sec     2.79k   154.59     3.18k    77.09%
  331839 requests in 30.04s, 32.83GB read
Requests/sec:  11044.78
Transfer/sec:      1.09GB

I am attaching an archive with my example so that you can try the tests yourself. There are 2 HTML files in the static/html folder: index.html and index-100.html. The first one weighs 10kb, the second one weighs a little more than 100kb.
benchmark.zip

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the attached benchmark.zip and api.rs, then run the provided wrk scenarios against the three routes to reproduce the difference between in-memory responses, tokio file reads, and ServeFile. Trace the ServeFile request path and identify the cause of the slowdown; done means the behavior is explained and any change is validated with comparable benchmarks.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.