diff --git a/Cargo.lock b/Cargo.lock index afec32da8e..49c9b947df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5354,6 +5354,17 @@ dependencies = [ "url 2.3.1", ] +[[package]] +name = "redis_caching" +version = "0.0.1" +dependencies = [ + "errors", + "r2d2_redis", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "redis_common" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index c053d2722e..c52346b3cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ members = [ # Shared libraries "crates/lib/actix_helpers", + "crates/lib/caching/memory_caching", + "crates/lib/caching/redis_caching", "crates/lib/clients/cloud_storage", "crates/lib/clients/fakeyou_client", "crates/lib/clients/google_drive_common", @@ -32,7 +34,6 @@ members = [ "crates/lib/idempotency", "crates/lib/jobs_common", "crates/lib/media", - "crates/lib/memory_caching", "crates/lib/redis_common", "crates/lib/sqlite_queries", "crates/lib/subprocess_common", diff --git a/crates/lib/memory_caching/Cargo.toml b/crates/lib/caching/memory_caching/Cargo.toml similarity index 76% rename from crates/lib/memory_caching/Cargo.toml rename to crates/lib/caching/memory_caching/Cargo.toml index 01476f65bf..4c59504487 100644 --- a/crates/lib/memory_caching/Cargo.toml +++ b/crates/lib/caching/memory_caching/Cargo.toml @@ -15,9 +15,9 @@ path = "src/lib.rs" [dependencies] # Internal -config = { path = "../config" } -container_common = { path = "../container_common" } -errors = { path = "../errors" } +config = { path = "../../config" } +container_common = { path = "../../container_common" } +errors = { path = "../../errors" } # External anyhow = "1.0.40" diff --git a/crates/lib/memory_caching/src/lib.rs b/crates/lib/caching/memory_caching/src/lib.rs similarity index 100% rename from crates/lib/memory_caching/src/lib.rs rename to crates/lib/caching/memory_caching/src/lib.rs diff --git a/crates/lib/memory_caching/src/multi_item_ttl_cache.rs b/crates/lib/caching/memory_caching/src/multi_item_ttl_cache.rs similarity index 100% rename from crates/lib/memory_caching/src/multi_item_ttl_cache.rs rename to crates/lib/caching/memory_caching/src/multi_item_ttl_cache.rs diff --git a/crates/lib/memory_caching/src/single_item_ttl_cache.rs b/crates/lib/caching/memory_caching/src/single_item_ttl_cache.rs similarity index 100% rename from crates/lib/memory_caching/src/single_item_ttl_cache.rs rename to crates/lib/caching/memory_caching/src/single_item_ttl_cache.rs diff --git a/crates/lib/caching/redis_caching/Cargo.toml b/crates/lib/caching/redis_caching/Cargo.toml new file mode 100644 index 0000000000..5313624419 --- /dev/null +++ b/crates/lib/caching/redis_caching/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "redis_caching" +edition = "2018" +version = "0.0.1" +authors = [ + "Brandon Thomas ", + "Brandon Thomas ", +] +publish = false + +[lib] +name = "redis_caching" +path = "src/lib.rs" + +[dependencies] +# Internal +errors = { path = "../../errors" } + +# Redis Database +r2d2_redis = "0.14.0" + +# Serialization +serde = "1.0.125" +serde_derive = "1.0.125" +serde_json = "1.0.64" + +[dev-dependencies] +# None yet diff --git a/crates/lib/caching/redis_caching/src/lib.rs b/crates/lib/caching/redis_caching/src/lib.rs new file mode 100644 index 0000000000..bb8234c690 --- /dev/null +++ b/crates/lib/caching/redis_caching/src/lib.rs @@ -0,0 +1,20 @@ +//! redis_caching +//! +//! Easy-to use Redis caching utility. +//! + +// Never allow these +#![forbid(private_in_public)] +#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things + +// Okay to toggle +#![forbid(unreachable_patterns)] +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +// Always allow +#![allow(dead_code)] +#![allow(non_snake_case)] + +pub mod redis_ttl_cache; \ No newline at end of file diff --git a/crates/lib/caching/redis_caching/src/redis_ttl_cache.rs b/crates/lib/caching/redis_caching/src/redis_ttl_cache.rs new file mode 100644 index 0000000000..10c6f95871 --- /dev/null +++ b/crates/lib/caching/redis_caching/src/redis_ttl_cache.rs @@ -0,0 +1,93 @@ +use r2d2_redis::RedisConnectionManager; +use r2d2_redis::r2d2::{Pool, PooledConnection}; +use r2d2_redis::redis::Commands; +use serde::{Deserialize, Serialize}; +use errors::AnyhowResult; + +const DEFAULT_TTL_SECONDS : usize = 60; + +const REDIS_KEY_PREFIX : &'static str = "_c"; + +// TODO: Async Redis + +#[derive(Clone)] +pub struct RedisTtlCache { + redis_pool: Pool, + default_key_ttl: usize, +} + +impl RedisTtlCache { + pub fn new(redis_pool: Pool) -> Self { + Self { + redis_pool, + default_key_ttl: DEFAULT_TTL_SECONDS, + } + } + + pub fn get_connection(&self) -> AnyhowResult { + let redis = self.redis_pool.get()?; + Ok(RedisTtlCacheConnection { + redis_connection: redis, + default_key_ttl: self.default_key_ttl, + }) + } +} + +pub struct RedisTtlCacheConnection { + redis_connection: PooledConnection, + default_key_ttl: usize, +} + +impl RedisTtlCacheConnection { + + pub fn delete_from_cache(&mut self, item_key: &str) -> AnyhowResult<()> { + let redis_key = Self::cache_key(item_key); + let _r : Option = self.redis_connection.del(redis_key)?; + Ok(()) + } + + pub fn get_from_cache(&mut self, item_key: &str) -> AnyhowResult> where T: for <'a> Deserialize<'a> { + let redis_key = Self::cache_key(item_key); + let maybe_value : Option = self.redis_connection.get(redis_key)?; + + if let Some(value) = maybe_value { + let hydrated = serde_json::from_str::(&value)?; + return Ok(Some(hydrated)); + } + + Ok(None) + } + + pub fn persist_to_cache(&mut self, item_key: &str, item: T) -> AnyhowResult<()> where T: Serialize { + let redis_key = Self::cache_key(item_key); + let value = serde_json::to_string(&item)?; + let _r : Option = self.redis_connection.set_ex(redis_key, value, self.default_key_ttl)?; + Ok(()) + } + + pub fn lazy_load_if_not_cached(&mut self, item_key: &str, loader_func: &mut F) + -> AnyhowResult> + where for<'a> T: Serialize + Deserialize<'a> + Clone, + F: FnMut() -> AnyhowResult>, + { + let maybe_value = self.get_from_cache(item_key)?; + + if let Some(value) = maybe_value { + return Ok(value); + } + + let maybe_value = loader_func()?; + + if let Some(value) = maybe_value { + self.persist_to_cache(item_key, value.clone())?; + return Ok(Some(value)); + } + + Ok(None) + } + + + fn cache_key(key: &str) -> String { + format!("{}:{}", REDIS_KEY_PREFIX, key) + } +} diff --git a/crates/service/job/inference_job/Cargo.toml b/crates/service/job/inference_job/Cargo.toml index 9d8975b553..16a7d056ba 100644 --- a/crates/service/job/inference_job/Cargo.toml +++ b/crates/service/job/inference_job/Cargo.toml @@ -22,7 +22,7 @@ database_queries = { path = "../../../lib/database_queries" } enums = { path = "../../../_types/enums" } errors = { path = "../../../lib/errors" } jobs_common = { path = "../../../lib/jobs_common" } -memory_caching = { path = "../../../lib/memory_caching" } +memory_caching = { path = "../../../lib/caching/memory_caching" } tokens = { path = "../../../_types/tokens" } tts_common = { path = "../../../lib/tts_common" } diff --git a/crates/service/job/tts_inference_job/Cargo.toml b/crates/service/job/tts_inference_job/Cargo.toml index e5e7da3870..e63eed5983 100644 --- a/crates/service/job/tts_inference_job/Cargo.toml +++ b/crates/service/job/tts_inference_job/Cargo.toml @@ -21,7 +21,7 @@ container_common = { path = "../../../lib/container_common" } database_queries = { path = "../../../lib/database_queries" } hashing = { path = "../../../lib/hashing" } jobs_common = { path = "../../../lib/jobs_common" } -memory_caching = { path = "../../../lib/memory_caching" } +memory_caching = { path = "../../../lib/caching/memory_caching" } tts_common = { path = "../../../lib/tts_common" } # Vendored code diff --git a/crates/service/web/image_generation_service/Cargo.toml b/crates/service/web/image_generation_service/Cargo.toml index 29ef2fd750..179bfe1eec 100644 --- a/crates/service/web/image_generation_service/Cargo.toml +++ b/crates/service/web/image_generation_service/Cargo.toml @@ -24,7 +24,7 @@ errors = { path = "../../../lib/errors" } google_drive_common = { path = "../../../lib/clients/google_drive_common" } http_server_common = { path = "../../../lib/http_server_common" } jobs_common = { path = "../../../lib/jobs_common" } -memory_caching = { path = "../../../lib/memory_caching" } +memory_caching = { path = "../../../lib/caching/memory_caching" } redis_common = { path = "../../../lib/redis_common" } reusable_types = { path = "../../../_types/reusable_types" } tts_common = { path = "../../../lib/tts_common" } diff --git a/crates/service/web/storyteller_web/Cargo.toml b/crates/service/web/storyteller_web/Cargo.toml index 02703d68e0..3631e0c2f5 100644 --- a/crates/service/web/storyteller_web/Cargo.toml +++ b/crates/service/web/storyteller_web/Cargo.toml @@ -30,7 +30,7 @@ google_drive_common = { path = "../../../lib/clients/google_drive_common" } http_server_common = { path = "../../../lib/http_server_common" } jobs_common = { path = "../../../lib/jobs_common" } media = { path = "../../../lib/media" } -memory_caching = { path = "../../../lib/memory_caching" } +memory_caching = { path = "../../../lib/caching/memory_caching" } redis_common = { path = "../../../lib/redis_common" } reusable_types = { path = "../../../_types/reusable_types" } tokens = { path = "../../../_types/tokens" }