From 19e191e9220c081bcb377b55d4be7b54e0b798ed Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Fri, 13 Feb 2026 13:06:10 +0000 Subject: [PATCH] perf: parallelize independent async calls in detail pages Use tokio::try_join! to run independent repo queries and image URL lookups concurrently in bag, brew, and checkin page handlers, and for gear option loading in the brew form data loader. --- src/application/routes/app/bags.rs | 21 ++++++++++------ src/application/routes/app/brews.rs | 36 +++++++++++++++++---------- src/application/routes/app/checkin.rs | 6 +++-- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/application/routes/app/bags.rs b/src/application/routes/app/bags.rs index 8ca80cc..a22fa2f 100644 --- a/src/application/routes/app/bags.rs +++ b/src/application/routes/app/bags.rs @@ -27,11 +27,20 @@ pub(crate) async fn bag_detail_page( .await .map_err(|e| map_app_error(e.into()))?; - let roast = state - .roast_repo - .get(bag.bag.roast_id) - .await - .map_err(|e| map_app_error(e.into()))?; + let (roast, image_url) = tokio::try_join!( + async { + state + .roast_repo + .get(bag.bag.roast_id) + .await + .map_err(|e| map_app_error(e.into())) + }, + async { + Ok::<_, StatusCode>( + resolve_image_url(&state, "roast", i64::from(bag.bag.roast_id)).await, + ) + }, + )?; let roaster = state .roaster_repo @@ -39,8 +48,6 @@ pub(crate) async fn bag_detail_page( .await .map_err(|e| map_app_error(e.into()))?; - let image_url = resolve_image_url(&state, "roast", i64::from(roast.id)).await; - let view = BagDetailView::from_parts(bag, &roast, &roaster); let template = BagDetailTemplate { diff --git a/src/application/routes/app/brews.rs b/src/application/routes/app/brews.rs index b7f3597..e26d007 100644 --- a/src/application/routes/app/brews.rs +++ b/src/application/routes/app/brews.rs @@ -27,17 +27,29 @@ pub(crate) async fn brew_detail_page( .await .map_err(|e| map_app_error(e.into()))?; - let bag = state - .bag_repo - .get(brew_details.brew.bag_id) - .await - .map_err(|e| map_app_error(e.into()))?; + let (bag, brew_image_url) = tokio::try_join!( + async { + state + .bag_repo + .get(brew_details.brew.bag_id) + .await + .map_err(|e| map_app_error(e.into())) + }, + async { Ok::<_, StatusCode>(resolve_image_url(&state, "brew", i64::from(id)).await) }, + )?; - let roast = state - .roast_repo - .get(bag.roast_id) - .await - .map_err(|e| map_app_error(e.into()))?; + let (roast, roast_image_url) = tokio::try_join!( + async { + state + .roast_repo + .get(bag.roast_id) + .await + .map_err(|e| map_app_error(e.into())) + }, + async { + Ok::<_, StatusCode>(resolve_image_url(&state, "roast", i64::from(bag.roast_id)).await) + }, + )?; let roaster = state .roaster_repo @@ -45,9 +57,7 @@ pub(crate) async fn brew_detail_page( .await .map_err(|e| map_app_error(e.into()))?; - let image_url = resolve_image_url(&state, "brew", i64::from(id)) - .await - .or(resolve_image_url(&state, "roast", i64::from(roast.id)).await); + let image_url = brew_image_url.or(roast_image_url); let view = BrewDetailView::from_parts(brew_details, &roast, &roaster); diff --git a/src/application/routes/app/checkin.rs b/src/application/routes/app/checkin.rs index f45e20c..2d4477e 100644 --- a/src/application/routes/app/checkin.rs +++ b/src/application/routes/app/checkin.rs @@ -18,8 +18,10 @@ pub(crate) async fn checkin_page( return Ok(Redirect::to("/login").into_response()); } - let roast_options = load_roast_options(&state).await.map_err(map_app_error)?; - let cafe_options = load_cafe_options(&state).await.map_err(map_app_error)?; + let (roast_options, cafe_options) = tokio::try_join!( + async { load_roast_options(&state).await.map_err(map_app_error) }, + async { load_cafe_options(&state).await.map_err(map_app_error) }, + )?; let template = CheckInTemplate { nav_active: "checkin",