Skip to content

Commit

Permalink
support TTL in redis storage (#4164)
Browse files Browse the repository at this point in the history
Fix #4163

Co-authored-by: Coenen Benjamin <[email protected]>
  • Loading branch information
Geal and bnjjj authored Nov 10, 2023
1 parent 40759b9 commit 03c1309
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 50 deletions.
15 changes: 15 additions & 0 deletions .changesets/feat_geal_redis_ttl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### support TTL in redis storage ([Issue #4163](https://github.com/apollographql/router/issues/4163))

It is now possible to set an expiration for distributed caching entries, both for APQ and query planning caches, using the configuration file:

```yaml title="router.yaml"
supergraph:
query_planning:
experimental_cache:
redis:
urls: ["redis://..."]
timeout: 5ms # Optional, by default: 2ms
ttl: 24h # Optional, by default no expiration
```
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/4164
24 changes: 7 additions & 17 deletions apollo-router/src/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::broadcast;
use tokio::sync::oneshot;
Expand All @@ -10,6 +9,7 @@ use tokio::sync::Mutex;
use self::storage::CacheStorage;
use self::storage::KeyType;
use self::storage::ValueType;
use crate::configuration::RedisCache;

pub(crate) mod redis;
pub(crate) mod storage;
Expand All @@ -34,27 +34,20 @@ where
{
pub(crate) async fn with_capacity(
capacity: NonZeroUsize,
redis_urls: Option<Vec<url::Url>>,
timeout: Option<Duration>,
redis: Option<RedisCache>,
caller: &str,
) -> Self {
Self {
wait_map: Arc::new(Mutex::new(HashMap::new())),
storage: CacheStorage::new(capacity, redis_urls, timeout, caller).await,
storage: CacheStorage::new(capacity, redis, caller).await,
}
}

pub(crate) async fn from_configuration(
config: &crate::configuration::Cache,
caller: &str,
) -> Self {
Self::with_capacity(
config.in_memory.limit,
config.redis.as_ref().map(|c| c.urls.clone()),
config.redis.as_ref().and_then(|r| r.timeout),
caller,
)
.await
Self::with_capacity(config.in_memory.limit, config.redis.clone(), caller).await
}

pub(crate) async fn get(&self, key: &K) -> Entry<K, V> {
Expand Down Expand Up @@ -214,8 +207,7 @@ mod tests {
async fn example_cache_usage() {
let k = "key".to_string();
let cache =
DeduplicatingCache::with_capacity(NonZeroUsize::new(1).unwrap(), None, None, "test")
.await;
DeduplicatingCache::with_capacity(NonZeroUsize::new(1).unwrap(), None, "test").await;

let entry = cache.get(&k).await;

Expand All @@ -232,8 +224,7 @@ mod tests {
#[test(tokio::test)]
async fn it_should_enforce_cache_limits() {
let cache: DeduplicatingCache<usize, usize> =
DeduplicatingCache::with_capacity(NonZeroUsize::new(13).unwrap(), None, None, "test")
.await;
DeduplicatingCache::with_capacity(NonZeroUsize::new(13).unwrap(), None, "test").await;

for i in 0..14 {
let entry = cache.get(&i).await;
Expand All @@ -256,8 +247,7 @@ mod tests {
mock.expect_retrieve().times(1).return_const(1usize);

let cache: DeduplicatingCache<usize, usize> =
DeduplicatingCache::with_capacity(NonZeroUsize::new(10).unwrap(), None, None, "test")
.await;
DeduplicatingCache::with_capacity(NonZeroUsize::new(10).unwrap(), None, "test").await;

// Let's trigger 100 concurrent gets of the same value and ensure only
// one delegated retrieve is made
Expand Down
20 changes: 10 additions & 10 deletions apollo-router/src/cache/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use url::Url;

use super::KeyType;
use super::ValueType;
use crate::configuration::RedisCache;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct RedisKey<K>(pub(crate) K)
Expand Down Expand Up @@ -115,18 +116,17 @@ where
}

impl RedisCacheStorage {
pub(crate) async fn new(
urls: Vec<Url>,
ttl: Option<Duration>,
timeout: Option<Duration>,
) -> Result<Self, RedisError> {
let url = Self::preprocess_urls(urls)?;
let config = RedisConfig::from_url(url.as_str())?;
pub(crate) async fn new(config: RedisCache) -> Result<Self, RedisError> {
let url = Self::preprocess_urls(config.urls)?;
let client_config = RedisConfig::from_url(url.as_str())?;

let client = RedisClient::new(
config,
client_config,
Some(PerformanceConfig {
default_command_timeout_ms: timeout.map(|t| t.as_millis() as u64).unwrap_or(2),
default_command_timeout_ms: config
.timeout
.map(|t| t.as_millis() as u64)
.unwrap_or(2),
..Default::default()
}),
Some(ReconnectPolicy::new_exponential(0, 1, 2000, 5)),
Expand Down Expand Up @@ -158,7 +158,7 @@ impl RedisCacheStorage {
tracing::trace!("redis connection established");
Ok(Self {
inner: Arc::new(client),
ttl,
ttl: config.ttl,
})
}

Expand Down
9 changes: 4 additions & 5 deletions apollo-router/src/cache/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::fmt::{self};
use std::hash::Hash;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Duration;

use lru::LruCache;
use serde::de::DeserializeOwned;
Expand All @@ -12,6 +11,7 @@ use tokio::sync::Mutex;
use tokio::time::Instant;

use super::redis::*;
use crate::configuration::RedisCache;

pub(crate) trait KeyType:
Clone + fmt::Debug + fmt::Display + Hash + Eq + Send + Sync
Expand Down Expand Up @@ -58,15 +58,14 @@ where
{
pub(crate) async fn new(
max_capacity: NonZeroUsize,
redis_urls: Option<Vec<url::Url>>,
timeout: Option<Duration>,
config: Option<RedisCache>,
caller: &str,
) -> Self {
Self {
caller: caller.to_string(),
inner: Arc::new(Mutex::new(LruCache::new(max_capacity))),
redis: if let Some(urls) = redis_urls {
match RedisCacheStorage::new(urls, None, timeout).await {
redis: if let Some(config) = config {
match RedisCacheStorage::new(config).await {
Err(e) => {
tracing::error!(
"could not open connection to Redis for {} caching: {:?}",
Expand Down
5 changes: 5 additions & 0 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,11 @@ pub(crate) struct RedisCache {
#[schemars(with = "Option<String>", default)]
/// Redis request timeout (default: 2ms)
pub(crate) timeout: Option<Duration>,

#[serde(deserialize_with = "humantime_serde::deserialize", default)]
#[schemars(with = "Option<String>", default)]
/// TTL for entries
pub(crate) ttl: Option<Duration>,
}

/// TLS related configuration options.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ expression: "&schema"
"type": "string",
"nullable": true
},
"ttl": {
"description": "TTL for entries",
"default": null,
"type": "string",
"nullable": true
},
"urls": {
"description": "List of URLs to the Redis cluster",
"type": "array",
Expand Down Expand Up @@ -2113,6 +2119,12 @@ expression: "&schema"
"type": "string",
"nullable": true
},
"ttl": {
"description": "TTL for entries",
"default": null,
"type": "string",
"nullable": true
},
"urls": {
"description": "List of URLs to the Redis cluster",
"type": "array",
Expand Down Expand Up @@ -5729,6 +5741,12 @@ expression: "&schema"
"type": "string",
"nullable": true
},
"ttl": {
"description": "TTL for entries",
"default": null,
"type": "string",
"nullable": true
},
"urls": {
"description": "List of URLs to the Redis cluster",
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl Introspection {
capacity: NonZeroUsize,
) -> Self {
Self {
cache: CacheStorage::new(capacity, None, None, "introspection").await,
cache: CacheStorage::new(capacity, None, "introspection").await,
planner,
}
}
Expand Down
19 changes: 2 additions & 17 deletions apollo-router/src/plugins/traffic_shaping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,23 +286,8 @@ impl Plugin for TrafficShaping {
.transpose()?;

{
let storage = if let Some(urls) = init
.config
.experimental_cache
.as_ref()
.map(|cache| cache.urls.clone())
{
Some(
RedisCacheStorage::new(
urls,
None,
init.config
.experimental_cache
.as_ref()
.and_then(|c| c.timeout),
)
.await?,
)
let storage = if let Some(config) = init.config.experimental_cache.as_ref() {
Some(RedisCacheStorage::new(config.clone()).await?)
} else {
None
};
Expand Down
2 changes: 2 additions & 0 deletions docs/source/configuration/distributed-caching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ supergraph:
redis: #highlight-line
urls: ["redis://..."] #highlight-line
timeout: 5ms # Optional, by default: 2ms
ttl: 24h # Optional, by default no expiration
```
The value of `urls` is a list of URLs for all Redis instances in your cluster.
Expand All @@ -113,6 +114,7 @@ apq:
redis: #highlight-line
urls: ["redis://..."] #highlight-line
timeout: 5ms # Optional, by default: 2ms
ttl: 24h # Optional, by default no expiration
```

The value of `urls` is a list of URLs for all Redis instances in your cluster.
Expand Down

0 comments on commit 03c1309

Please sign in to comment.