From 30d58865b82421675c8923164ad278685bcb0971 Mon Sep 17 00:00:00 2001
From: Nutomic <me@nutomic.com>
Date: Tue, 12 Dec 2023 17:56:39 +0100
Subject: [PATCH] Speed up GET /api/v3/site endpoint (#4245)

* Make db queries for GET /api/v3/site in parallel (ref #4244)

* Cache site response

* machete

* Use try_join_with_pool macro

* machete

* taplo

* ttl 1s
---
 Cargo.lock                       |  4 ++
 crates/api_crud/Cargo.toml       |  9 ++-
 crates/api_crud/src/site/read.rs | 97 +++++++++++++++++---------------
 crates/utils/translations        |  2 +-
 4 files changed, 65 insertions(+), 47 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 29296775e..8e6a78a32 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2589,12 +2589,16 @@ version = "0.19.0-rc.13"
 dependencies = [
  "activitypub_federation",
  "actix-web",
+ "anyhow",
  "bcrypt",
+ "futures",
  "lemmy_api_common",
  "lemmy_db_schema",
  "lemmy_db_views",
  "lemmy_db_views_actor",
  "lemmy_utils",
+ "moka",
+ "once_cell",
  "tracing",
  "url",
  "uuid",
diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml
index 867c3f215..ea7c81399 100644
--- a/crates/api_crud/Cargo.toml
+++ b/crates/api_crud/Cargo.toml
@@ -22,5 +22,12 @@ bcrypt = { workspace = true }
 actix-web = { workspace = true }
 tracing = { workspace = true }
 url = { workspace = true }
-webmention = "0.5.0"
+futures.workspace = true
 uuid = { workspace = true }
+moka.workspace = true
+once_cell.workspace = true
+anyhow.workspace = true
+webmention = "0.5.0"
+
+[package.metadata.cargo-machete]
+ignored = ["futures"]
diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs
index aceee29d4..c340ce44b 100644
--- a/crates/api_crud/src/site/read.rs
+++ b/crates/api_crud/src/site/read.rs
@@ -21,46 +21,68 @@ use lemmy_utils::{
   error::{LemmyError, LemmyErrorExt, LemmyErrorType},
   version,
 };
+use moka::future::Cache;
+use once_cell::sync::Lazy;
+use std::time::Duration;
 
 #[tracing::instrument(skip(context))]
 pub async fn get_site(
   local_user_view: Option<LocalUserView>,
   context: Data<LemmyContext>,
 ) -> Result<Json<GetSiteResponse>, LemmyError> {
-  let site_view = SiteView::read_local(&mut context.pool()).await?;
+  static CACHE: Lazy<Cache<(), GetSiteResponse>> = Lazy::new(|| {
+    Cache::builder()
+      .max_capacity(1)
+      .time_to_live(Duration::from_secs(1))
+      .build()
+  });
 
-  let admins = PersonView::admins(&mut context.pool()).await?;
+  // This data is independent from the user account so we can cache it across requests
+  let mut site_response = CACHE
+    .try_get_with::<_, LemmyError>((), async {
+      let site_view = SiteView::read_local(&mut context.pool()).await?;
+      let admins = PersonView::admins(&mut context.pool()).await?;
+      let all_languages = Language::read_all(&mut context.pool()).await?;
+      let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
+      let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
+      let custom_emojis =
+        CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
+      Ok(GetSiteResponse {
+        site_view,
+        admins,
+        version: version::VERSION.to_string(),
+        my_user: None,
+        all_languages,
+        discussion_languages,
+        taglines,
+        custom_emojis,
+      })
+    })
+    .await
+    .map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?;
 
-  // Build the local user
-  let my_user = if let Some(local_user_view) = local_user_view {
+  // Build the local user with parallel queries and add it to site response
+  site_response.my_user = if let Some(local_user_view) = local_user_view {
     let person_id = local_user_view.person.id;
     let local_user_id = local_user_view.local_user.id;
+    let pool = &mut context.pool();
 
-    let follows = CommunityFollowerView::for_person(&mut context.pool(), person_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
-
-    let person_id = local_user_view.person.id;
-    let community_blocks = CommunityBlockView::for_person(&mut context.pool(), person_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
-
-    let instance_blocks = InstanceBlockView::for_person(&mut context.pool(), person_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
-
-    let person_id = local_user_view.person.id;
-    let person_blocks = PersonBlockView::for_person(&mut context.pool(), person_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
-
-    let moderates = CommunityModeratorView::for_person(&mut context.pool(), person_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
-
-    let discussion_languages = LocalUserLanguage::read(&mut context.pool(), local_user_id)
-      .await
-      .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
+    let (
+      follows,
+      community_blocks,
+      instance_blocks,
+      person_blocks,
+      moderates,
+      discussion_languages,
+    ) = lemmy_db_schema::try_join_with_pool!(pool => (
+      |pool| CommunityFollowerView::for_person(pool, person_id),
+      |pool| CommunityBlockView::for_person(pool, person_id),
+      |pool| InstanceBlockView::for_person(pool, person_id),
+      |pool| PersonBlockView::for_person(pool, person_id),
+      |pool| CommunityModeratorView::for_person(pool, person_id),
+      |pool| LocalUserLanguage::read(pool, local_user_id)
+    ))
+    .with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
 
     Some(MyUserInfo {
       local_user_view,
@@ -75,20 +97,5 @@ pub async fn get_site(
     None
   };
 
-  let all_languages = Language::read_all(&mut context.pool()).await?;
-  let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
-  let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
-  let custom_emojis =
-    CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
-
-  Ok(Json(GetSiteResponse {
-    site_view,
-    admins,
-    version: version::VERSION.to_string(),
-    my_user,
-    all_languages,
-    discussion_languages,
-    taglines,
-    custom_emojis,
-  }))
+  Ok(Json(site_response))
 }
diff --git a/crates/utils/translations b/crates/utils/translations
index aa9438c49..b3343aef7 160000
--- a/crates/utils/translations
+++ b/crates/utils/translations
@@ -1 +1 @@
-Subproject commit aa9438c4930deb23ae0dc54a061ed4b0b3824582
+Subproject commit b3343aef72e5a7e5df34cf328b910ed798027270
-- 
GitLab