I have a case where I have an http server with axum
that receive payload in a very high throughput (could get to 20m a second). I need to take those bytes from the request and do some heavy computations with them. The problem that the memory reach unpredictably high (could reach to 5Gb). This is the current setup on how I'm trying to achieve it:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tx, mut rx) = mpsc::channel::<WriteRequest>(32);
tokio::spawn(async move {
while let Some(payload) = rx.recv().await {
tokio::task::spawn_blocking(move || {
// Run heavy computation here...
heavy_computation(payload)
}).await;
}
});
// build our application with a route
let app = Router::new()
.route("/write", post(move |req: Bytes| async move {
let data: WriteRequest = Message::decode(req);
// send data here
let _ = tx.send(data).await;
"ok"
}));
let addr = ([0, 0, 0, 0], 8080).into();
let server = axum::Server::bind(&addr)
.serve(app.into_make_service());
if let Err(e) = server.await {
error!("server error: {}", e);
}
Ok(())
}
I think it's the back-pressure on the bounded channel that keeps the requests piling up until they can be sent to the other task
for processing, resulting in the high memory. Because even if I tried to replace the heavy_computation
with a simple sleep
for about 200ms
it ended with the same results. If I eliminate the heavy_computation
part the memory stays low.
What is the right way to approach such problem? Or with this kind of high throughput there is nothing can be done here?