Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/contextforge-gateway-rs-lib/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ pub struct Config {
#[arg(long, env = "CONTEXTFORGE_GATEWAY_RS_TLS_UPSTREAM_TRUST_BUNDLE")]
pub upstream_trust_bundle: Option<PathBuf>,

/// Expiry in seconds for the in-process user config cache in front of
/// Redis. The control-plane dataplane publisher rewrites UserConfig keys
/// every 60s, so this bounds how stale a subject's config can get.
/// 0 disables caching and reads Redis on every request.
#[arg(long, env = "CONTEXTFORGE_GATEWAY_RS_USER_CONFIG_CACHE_EXPIRY_SECONDS", default_value_t = 60)]
pub user_config_cache_expiry_seconds: u64,

#[arg(long, env = "CONTEXTFORGE_GATEWAY_RS_REDIS_HOSTNAME")]
pub redis_address: String,
#[arg(long, env = "CONTEXTFORGE_GATEWAY_RS_REDIS_PORT")]
Expand Down
3 changes: 2 additions & 1 deletion crates/contextforge-gateway-rs-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,6 @@ impl Gateway {

pub async fn get_config_store(config: &Config) -> Result<RedisUserConfigStore> {
let redis_config = RedisConfig::try_from(config)?;
RedisUserConfigStore::new(&RedisClient::try_from(redis_config)?).await
let cache_expiry = std::time::Duration::from_secs(config.user_config_cache_expiry_seconds);
RedisUserConfigStore::new(&RedisClient::try_from(redis_config)?, cache_expiry).await
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{sync::Arc, time::Duration};

use async_trait::async_trait;
use contextforge_gateway_rs_apis::{User, user_store::UserConfig};
Expand All @@ -14,17 +14,18 @@ use tracing::{debug, warn};
use super::{ConfigStoreError, UserConfigStore};
use crate::{
common::RedisClient,
const_values::{LRU_CACHE_ENTRIES, LRU_CACHE_EXPIRY_DURATION, REDIS_RETRIES},
const_values::{LRU_CACHE_ENTRIES, REDIS_RETRIES},
};

#[derive(Clone)]
pub struct RedisUserConfigStore {
connection: ConnectionManager,
cache: Arc<Mutex<LruCache<String, UserConfig>>>,
/// `None` when caching is disabled (zero expiry): every request reads Redis.
cache: Option<Arc<Mutex<LruCache<String, UserConfig>>>>,
}

impl RedisUserConfigStore {
pub async fn new(redis_client: &RedisClient) -> crate::Result<Self> {
pub async fn new(redis_client: &RedisClient, cache_expiry: Duration) -> crate::Result<Self> {
Ok(Self {
connection: redis_client
.get_connection_manager_with_config(
Expand All @@ -35,10 +36,9 @@ impl RedisUserConfigStore {
warn!("RedisUserConfigStore::new - failed to create Redis user config connection error = {error}");
ConfigStoreError::InvalidConnection
})?,
cache: Arc::new(Mutex::new(LruCache::with_expiry_duration_and_capacity(
LRU_CACHE_EXPIRY_DURATION,
LRU_CACHE_ENTRIES,
))),
cache: (!cache_expiry.is_zero()).then(|| {
Arc::new(Mutex::new(LruCache::with_expiry_duration_and_capacity(cache_expiry, LRU_CACHE_ENTRIES)))
}),
})
}
}
Expand All @@ -48,18 +48,17 @@ impl UserConfigStore for RedisUserConfigStore {
async fn get_config<'a>(&self, user_key: &'a User) -> Result<UserConfig, ConfigStoreError> {
let subject = user_key.key();

{
let mut cache = self.cache.lock().await;
if let Some(user_config) = cache.get_mut(subject) {
if let Some(cache) = &self.cache {
if let Some(user_config) = cache.lock().await.get_mut(subject) {
let virtual_hosts = user_config.virtual_hosts.len();
debug!(
"RedisUserConfigStore::get_config - user config cache hit subject = {subject} virtual_hosts = {virtual_hosts}"
);
return Ok(user_config.clone());
}
}

debug!("RedisUserConfigStore::get_config - user config cache miss subject = {subject}");
debug!("RedisUserConfigStore::get_config - user config cache miss subject = {subject}");
}

let Ok(key) = rmp_serde::encode::to_vec::<User>(user_key) else {
warn!("RedisUserConfigStore::get_config - failed to encode Redis user config key subject = {subject}");
Expand Down Expand Up @@ -105,7 +104,9 @@ impl UserConfigStore for RedisUserConfigStore {
"RedisUserConfigStore::get_config - decoded user config subject = {subject} virtual_hosts = {virtual_hosts}"
);

self.cache.lock().await.insert(subject.to_owned(), user_config.clone());
if let Some(cache) = &self.cache {
cache.lock().await.insert(subject.to_owned(), user_config.clone());
}
Ok(user_config)
}

Expand Down Expand Up @@ -134,7 +135,9 @@ impl UserConfigStore for RedisUserConfigStore {
debug!(
"RedisUserConfigStore::set_config - wrote user config to Redis subject = {subject} bytes = {bytes} virtual_hosts = {virtual_hosts}"
);
self.cache.lock().await.insert(subject.to_owned(), config.clone());
if let Some(cache) = &self.cache {
cache.lock().await.insert(subject.to_owned(), config.clone());
}
Ok(())
},
Err(error) => {
Expand Down