Warp+hyper: Returning a Response without cloning a string

So I'm loading very large (~5 MB) shapefiles in memory (via the geojson crate) and trying to reply with their contents using warp. When my server starts, I (synchronously) generate the filters from a configuration that I load from a file.

My filter looks like:

let shapefiles: Arc<HashMap<String, Shapefile>> = /* ... */;

let shapefiles_show = {
    let shapefiles = shapefiles.clone();
    warp::get()
        .and(warp::path!(String))
        .map(move |id: String| shapefiles::show(&shapefiles, &id))
        .with(warp::compression::gzip())
};

and the definition of shapefiles::show (which produces the response to a request) looks like:

mod shapefiles {
pub fn show(
    shapefiles: &Arc<HashMap<String, Shapefile>>,
    id: &String,
) -> hyper::Response<String> {
    if let Some(shapefile) = shapefiles.get(id) {
        let data: String = shapefile.data.to_string();

        let response = {
            http::response::Builder::new()
                .status(hyper::StatusCode::OK)
                .header(hyper::header::CONTENT_TYPE, "application/vnd.geo+json")
                .header(hyper::header::CACHE_CONTROL, "public")
                .body(data)
                .unwrap()
        };
        response
    } else {
        let response = {
            http::response::Builder::new()
                .status(hyper::StatusCode::NOT_FOUND)
                .body("{}".to_string())
                .unwrap()
        };
        response
    }
}
}

(The full source code is available here if more context is needed. I didn't want to copy-paste 400 lines into a post.)

This code compiles and executes correctly, but the main problem is that the line I've commented with a FIXME takes 3 seconds to execute, and while this is happening everything is blocked. (Namely, it clones 5 MB from a &String to a String, which is very wasteful and seemingly not async-friendly.) My show function currently has a return type of hyper::Response<String>, but I am looking to change it to something that will instead pass out a reference.

The Big Problem™️ I am facing is that cloning such a string (i) at request time, and (ii) in such a way that blocks the rest of the server, including other routes is unacceptable. I have tried messing around with returning a &String, but hyper::Body only has From<...> impls for 'static references… so, is there a way to store my data in a "static" way, such that I can easily get a &'static str (or something similar) to pass out? Thanks in advance!

Actually, I found that this is not the case. The Big Problem actually seems to be that somehow the full request time (from client request -> client received complete response) is consistently above 5 seconds, and seems to be blocking. Even when I remove the warp::compression::gzip() wrapper, the request still takes at least 5 seconds.

Are you entirely sure it's the cloning of a single 5MB String itself that takes 3 seconds? That's about 3 to 4 orders of magnitude slower than where today's typical several-GHz processors operate. How are you measuring this?

2 Likes

Cloning that string should certainly not take three seconds. In any case, to avoid the clone, you could store it in a Bytes instead of a String, which is directly convertible to a hyper body and cheap to clone due to ref-counting.

1 Like

Ah shoot. You're right. I got confused by my numbers, and I will edit the post to reflect that. (I've got log::trace! lines printing out durations and misread the numbers.)

I still have a performance issue though, as the request takes nearly 8 seconds to reach my client. Most of that time is spent parked in the "waiting" state. (And, any requests sent after one that hits this Shapefile route are blocked until that resolves.)

Here's a screenshot from DevTools:


and from the server console:

I'm curious what I might blame the disparity between the listed response time (2ms is probably good enough…) and the observed response time to the client?

Nope, I'm wrong again!

For some reason, GZip encoding is taking all the time here, so I should remove it. The server rolls over and responds immediately when I disable gzip compression.

(When I made my request with server-side gzip compression enabled, it'd sit for at least 5 seconds before even starting to send headers back. With that turned off, it sends data back immediately!)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.