Heya, trying to debug what's going on here.
I have a web API using axum that provides aggregation of stats stored in a database. Because the aggregation needs to be fast, and the data is small enough to fit into ram, it's designed in a way where it periodically grabs all relevant rows from postgres, partially aggregates it, then swaps out a mutex with the current live copy. I use sqlx to talk to the database. The application looks roughly like this (pseudocode):
struct Context {
pool: sqlx::PgPool,
data: RwLock<Vec<MyData>>,
}
fn update_endless(context: Arc<Context>) {
loop {
reload_rows(context).await;
tokio::time::sleep(Duration::from_secs(10 * 60)).await;
}
}
fn reload_rows(context: Arc<Context>) {
let mut stream =
sqlx::query!("SELECT ...").fetch(&context.pool);
let mut new_data = vec![];
while let Some(row) = stream.next().await {
// not shown here: a zstd decompress and a serde deserialization
// not the cheapest operations in the world, but not horribly expensive either
}
*context.data.write().await = new_data;
}
#[tokio::main]
async fn main() -> Result<()> {
let context = Arc::new(Context { ... });
tokio::spawn(update_endless(context.clone()));
let listener = ...;
let app = axum::Router::new()
.route("/api/", ...)
.with_state(context.clone());
axum::serve(app, listener).await?;
Ok(())
}
This runs on a remote server with 6 cores that's not the most efficient in the world but not underpowered by any definition. Postgres runs locally. Even when a row reload is ongoing, CPU usage across the entire machine does not exceed ~40%.
When deploying this to users, I received reports that occasionally requests that would end up querying this service could stall for seconds or even minutes. I observed this behavior myself, with the stalling behavior always coinciding with a row reload.
(cont in thread)