From 8889299cc7db8ae1da27607f2eea7a1ea47a87b3 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 12 Jul 2024 01:29:58 -0400 Subject: [PATCH] in-memory cache featured item list --- .../src/{sieve.rs => arc_sieve.rs} | 8 +- crates/lib/caching/memory_caching/src/lib.rs | 2 +- .../list/list_featured_media_files.rs | 3 + .../list/list_featured_media_files_handler.rs | 142 +++++++++--------- .../service/web/storyteller_web/src/main.rs | 2 + .../storyteller_web/src/state/server_state.rs | 5 + 6 files changed, 90 insertions(+), 72 deletions(-) rename crates/lib/caching/memory_caching/src/{sieve.rs => arc_sieve.rs} (86%) diff --git a/crates/lib/caching/memory_caching/src/sieve.rs b/crates/lib/caching/memory_caching/src/arc_sieve.rs similarity index 86% rename from crates/lib/caching/memory_caching/src/sieve.rs rename to crates/lib/caching/memory_caching/src/arc_sieve.rs index 00b426d102..80249e7f2f 100644 --- a/crates/lib/caching/memory_caching/src/sieve.rs +++ b/crates/lib/caching/memory_caching/src/arc_sieve.rs @@ -7,11 +7,11 @@ use sieve_cache::SieveCache; use errors::AnyhowResult; #[derive(Clone)] -struct Sieve { +pub struct ArcSieve { cache: Arc>>, } -impl Sieve { +impl ArcSieve { pub fn with_capacity(capacity: usize) -> AnyhowResult { let cache = SieveCache::new(capacity) @@ -54,11 +54,11 @@ impl Sieve { #[cfg(test)] mod tests { - use crate::sieve::Sieve; + use crate::arc_sieve::ArcSieve; #[test] fn test_get_copy() { - let sieve : Sieve = Sieve::with_capacity(1).unwrap(); + let sieve : ArcSieve = ArcSieve::with_capacity(1).unwrap(); let key = "key".to_string(); let value = "value".to_string(); diff --git a/crates/lib/caching/memory_caching/src/lib.rs b/crates/lib/caching/memory_caching/src/lib.rs index 2f93fa382a..7bad86985d 100644 --- a/crates/lib/caching/memory_caching/src/lib.rs +++ b/crates/lib/caching/memory_caching/src/lib.rs @@ -14,7 +14,7 @@ #![allow(dead_code)] #![allow(non_snake_case)] +pub mod arc_sieve; pub mod multi_item_ttl_cache; pub mod single_item_ttl_cache; pub mod ttl_key_counter; -mod sieve; diff --git a/crates/lib/mysql_queries/src/queries/media_files/list/list_featured_media_files.rs b/crates/lib/mysql_queries/src/queries/media_files/list/list_featured_media_files.rs index dba1396309..f3b7fbd4b9 100644 --- a/crates/lib/mysql_queries/src/queries/media_files/list/list_featured_media_files.rs +++ b/crates/lib/mysql_queries/src/queries/media_files/list/list_featured_media_files.rs @@ -20,6 +20,8 @@ use tokens::tokens::users::UserToken; use crate::payloads::prompt_args::prompt_inner_payload::PromptInnerPayload; +// NB: Clone is for caches. +#[derive(Clone)] pub struct FeaturedMediaFileListPage { pub records: Vec, @@ -32,6 +34,7 @@ pub struct FeaturedMediaFileListPage { pub last_id: Option, } +#[derive(Clone)] pub struct FeaturedMediaFileListItem { pub token: MediaFileToken, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs index c34e339a79..181f6d7bbd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs @@ -20,21 +20,21 @@ use enums::by_table::media_files::media_file_type::MediaFileType; use enums::common::view_as::ViewAs; use enums::no_table::style_transfer::style_transfer_name::StyleTransferName; use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::batch_get_media_files_by_tokens; -use mysql_queries::queries::media_files::list::list_featured_media_files::{list_featured_media_files, ListFeaturedMediaFilesArgs}; +use mysql_queries::queries::media_files::list::list_featured_media_files::{FeaturedMediaFileListPage, list_featured_media_files, ListFeaturedMediaFilesArgs}; use tokens::tokens::media_files::MediaFileToken; -use crate::http_server::common_responses::user_details_lite::UserDetailsLight; use crate::http_server::common_responses::media_file_cover_image_details::{MediaFileCoverImageDetails, MediaFileDefaultCover}; use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails; use crate::http_server::common_responses::pagination_cursors::PaginationCursors; use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats; +use crate::http_server::common_responses::user_details_lite::UserDetailsLight; use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories; use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types::get_scoped_media_types; use crate::state::server_state::ServerState; use crate::util::allowed_explore_media_access::allowed_explore_media_access; -#[derive(Deserialize, ToSchema, IntoParams)] +#[derive(Deserialize, ToSchema, IntoParams, Hash, Clone, PartialEq, Eq)] pub struct ListFeaturedMediaFilesQueryParams { pub sort_ascending: Option, pub page_size: Option, @@ -181,72 +181,19 @@ pub async fn list_featured_media_files_handler( http_request: HttpRequest, query: Query, server_state: web::Data> -) -> Result { +) -> Result { - let maybe_user_session = server_state - .session_checker - .maybe_get_user_session(&http_request, &server_state.mysql_pool) - .await - .map_err(|e| { - warn!("Session checker error: {:?}", e); - ListFeaturedMediaFilesError::ServerError - })?; + let maybe_cached_results = server_state + .caches + .ephemeral + .featured_media_files_sieve + .get_copy(&query) + .ok() + .flatten(); - let mut is_mod = false; - - match maybe_user_session { - None => {}, - Some(session) => { - is_mod = session.can_ban_users; - }, - }; - - // TODO(bt,2023-12-04): Enforce real maximums and defaults - let limit = query.page_size.unwrap_or(25); - - let sort_ascending = query.sort_ascending.unwrap_or(false); - let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false); - - let cursor = if let Some(cursor) = query.cursor.as_deref() { - let cursor = server_state.sort_key_crypto.decrypt_id(cursor) - .map_err(|e| { - warn!("crypto error: {:?}", e); - ListFeaturedMediaFilesError::ServerError - })?; - Some(cursor as usize) - } else { - None - }; - - let view_as = if is_mod { - ViewAs::Moderator - } else { - ViewAs::AnotherUser - }; - - let mut maybe_filter_media_types = get_scoped_media_types(query.filter_media_type.as_deref()); - let mut maybe_filter_media_classes = get_scoped_media_classes(query.filter_media_classes.as_deref()); - let mut maybe_filter_engine_categories = get_scoped_engine_categories(query.filter_engine_categories.as_deref()); - - let query_results = - list_featured_media_files(ListFeaturedMediaFilesArgs { - limit, - maybe_offset: cursor, - cursor_is_reversed, - sort_ascending, - view_as, - maybe_filter_media_types: maybe_filter_media_types.as_ref(), - maybe_filter_media_classes: maybe_filter_media_classes.as_ref(), - maybe_filter_engine_categories: maybe_filter_engine_categories.as_ref(), - mysql_pool: &server_state.mysql_pool, - }).await; - - let results_page = match query_results { - Ok(results) => results, - Err(e) => { - warn!("Query error: {:?}", e); - return Err(ListFeaturedMediaFilesError::ServerError); - } + let results_page = match maybe_cached_results { + Some(cached_results) => cached_results, + None => database_lookup(&query, &server_state).await?, }; let cursor_next = if let Some(id) = results_page.last_id { @@ -271,6 +218,12 @@ pub async fn list_featured_media_files_handler( None }; + // TODO(bt,2023-12-04): Enforce real maximums and defaults + let limit = query.page_size.unwrap_or(25); + + let sort_ascending = query.sort_ascending.unwrap_or(false); + let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false); + let results = results_page.records.into_iter() .map(|m| { let public_bucket_path = MediaFileBucketPath::from_object_hash( @@ -342,3 +295,58 @@ pub async fn list_featured_media_files_handler( .content_type("application/json") .body(body)) } + +async fn database_lookup( + query: &ListFeaturedMediaFilesQueryParams, + server_state: &ServerState, +) -> Result { + + // TODO(bt,2023-12-04): Enforce real maximums and defaults + let limit = query.page_size.unwrap_or(25); + + let sort_ascending = query.sort_ascending.unwrap_or(false); + let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false); + + let cursor = if let Some(cursor) = query.cursor.as_deref() { + let cursor = server_state.sort_key_crypto.decrypt_id(cursor) + .map_err(|e| { + warn!("crypto error: {:?}", e); + ListFeaturedMediaFilesError::ServerError + })?; + Some(cursor as usize) + } else { + None + }; + + let mut maybe_filter_media_types = get_scoped_media_types(query.filter_media_type.as_deref()); + let mut maybe_filter_media_classes = get_scoped_media_classes(query.filter_media_classes.as_deref()); + let mut maybe_filter_engine_categories = get_scoped_engine_categories(query.filter_engine_categories.as_deref()); + + // NB: No reason to show deleted or non-public featured items to mods or authors. + // That's just confusing and wastes an extra query. + const VIEW_AS: ViewAs = ViewAs::AnotherUser; + + let results = list_featured_media_files(ListFeaturedMediaFilesArgs { + limit, + maybe_offset: cursor, + cursor_is_reversed, + sort_ascending, + view_as: VIEW_AS, + maybe_filter_media_types: maybe_filter_media_types.as_ref(), + maybe_filter_media_classes: maybe_filter_media_classes.as_ref(), + maybe_filter_engine_categories: maybe_filter_engine_categories.as_ref(), + mysql_pool: &server_state.mysql_pool, + }).await.map_err(|err| { + error!("DB error: {:?}", err); + ListFeaturedMediaFilesError::ServerError + })?; + + // NB: Fail open. + let _result = server_state + .caches + .ephemeral + .featured_media_files_sieve + .store_copy(&query, &results); + + Ok(results) +} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/main.rs b/crates/service/web/storyteller_web/src/main.rs index 7e45cfe55c..ac1232db22 100644 --- a/crates/service/web/storyteller_web/src/main.rs +++ b/crates/service/web/storyteller_web/src/main.rs @@ -58,6 +58,7 @@ use config::shared_constants::DEFAULT_MYSQL_CONNECTION_STRING; use config::shared_constants::DEFAULT_RUST_LOG; use email_sender::smtp_email_sender::SmtpEmailSender; use errors::AnyhowResult; +use memory_caching::arc_sieve::ArcSieve; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use mysql_queries::mediators::badge_granter::BadgeGranter; use mysql_queries::mediators::firehose_publisher::FirehosePublisher; @@ -428,6 +429,7 @@ async fn main() -> AnyhowResult<()> { easyenv::get_env_duration_seconds_or_default( "QUEUE_STATS_CACHE_TTL_SECONDS", Duration::from_secs(60))), + featured_media_files_sieve: ArcSieve::with_capacity(100)?, } }, ip_ban_list, diff --git a/crates/service/web/storyteller_web/src/state/server_state.rs b/crates/service/web/storyteller_web/src/state/server_state.rs index 1e63026ac9..32444876ce 100644 --- a/crates/service/web/storyteller_web/src/state/server_state.rs +++ b/crates/service/web/storyteller_web/src/state/server_state.rs @@ -7,10 +7,12 @@ use actix_helpers::middleware::banned_ip_filter::ip_ban_list::ip_ban_list::IpBan use billing_component::stripe::stripe_config::StripeConfig; use cloud_storage::bucket_client::BucketClient; use email_sender::smtp_email_sender::SmtpEmailSender; +use memory_caching::arc_sieve::ArcSieve; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use mysql_queries::mediators::badge_granter::BadgeGranter; use mysql_queries::mediators::firehose_publisher::FirehosePublisher; use mysql_queries::queries::generic_inference::web::get_pending_inference_job_count::InferenceQueueLengthResult; +use mysql_queries::queries::media_files::list::list_featured_media_files::FeaturedMediaFileListPage; use mysql_queries::queries::model_categories::list_categories_query_builder::CategoryList; use mysql_queries::queries::tts::tts_inference_jobs::get_pending_tts_inference_job_count::TtsQueueLengthResult; use mysql_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateRecordForList; @@ -23,6 +25,7 @@ use crate::configs::static_api_tokens::StaticApiTokenSet; use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager; use crate::http_server::endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::ModelTokensByCategoryToken; use crate::http_server::endpoints::leaderboard::get_leaderboard::LeaderboardInfo; +use crate::http_server::endpoints::media_files::list::list_featured_media_files_handler::ListFeaturedMediaFilesQueryParams; use crate::http_server::endpoints::stats::result_transformer::CacheableQueueStats; use crate::http_server::endpoints::tts::list_tts_models::TtsModelRecordForResponse; use crate::http_server::endpoints::voice_conversion::models::list_voice_conversion_models::VoiceConversionModel; @@ -191,6 +194,8 @@ pub struct EphemeralInMemoryCaches { pub tts_queue_length: SingleItemTtlCache, pub leaderboard: SingleItemTtlCache, + + pub featured_media_files_sieve: ArcSieve, } #[derive(Clone)]