mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
feat(labrinth): Redis Cluster (#6771)
* chore(labrinth): bump to redis 1.4.1 * feat(labrinth): redis cluster * chore: cleanup * feat(labrinth): cache locking * fix(labrinth): clippy * chore(labrinth): cleanup env, remove postcard support * chore(ci): fix test env for labrinth * chore(labrinth): bump all key versions * chore(labrinth): improve redis key identities handling * chore(labrinth): simplify deadline handling * chore(labrinth): remove unused lease tracking * chore(labrinth): remove distributed cache locking for now * chore(labrinth): improve redis backend init error * feat(labrinth): expose redis read replica strategy * chore(ci): remove other connection mode tests * chore: split xredis crate * feat(xredis): primaries routing * chore: tombi fmt * chore: clippy * chore: update query cache
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use prometheus::Registry;
|
||||
|
||||
use super::config::{RedisConfig, RedisTopology};
|
||||
use super::connection::RedisBackendBuildError;
|
||||
use super::metrics::{
|
||||
LogicalPoolStatus, LogicalPoolStatusProvider,
|
||||
register_blocking_pool_metrics,
|
||||
};
|
||||
use super::{Error, RedisPool};
|
||||
|
||||
const POOL_RETAIN_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const MAX_IDLE_CONNECTION_AGE: Duration = Duration::from_secs(5 * 60);
|
||||
const MAX_STANDALONE_CONNECTION_AGE: Duration = Duration::from_secs(120);
|
||||
|
||||
/// A pool of Redis connections used for blocking operations.
|
||||
#[derive(Clone)]
|
||||
pub(super) struct RedisBlockingPool {
|
||||
inner: RedisBlockingPoolInner,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum RedisBlockingPoolInner {
|
||||
Standalone(deadpool_redis::Pool),
|
||||
Cluster(deadpool_redis::cluster::Pool),
|
||||
}
|
||||
|
||||
impl RedisBlockingPool {
|
||||
pub(super) async fn new(
|
||||
config: &RedisConfig,
|
||||
) -> Result<Self, RedisBackendBuildError> {
|
||||
let pool_size = config.blocking_pool_size();
|
||||
let inner = match config.topology() {
|
||||
RedisTopology::Standalone => {
|
||||
let connection_config = redis::AsyncConnectionConfig::new()
|
||||
.set_connection_timeout(None)
|
||||
.set_response_timeout(None);
|
||||
let manager = deadpool_redis::Manager::new_with_config(
|
||||
config.seed_urls()[0].clone(),
|
||||
connection_config,
|
||||
)?;
|
||||
let pool = deadpool_redis::Pool::builder(manager)
|
||||
.max_size(pool_size.max())
|
||||
.wait_timeout(Some(Duration::from_millis(
|
||||
config.wait_timeout_ms(),
|
||||
)))
|
||||
.runtime(deadpool_redis::Runtime::Tokio1)
|
||||
.build()?;
|
||||
retain_standalone_pool(pool.clone());
|
||||
RedisBlockingPoolInner::Standalone(pool)
|
||||
}
|
||||
RedisTopology::Cluster => {
|
||||
let manager = deadpool_redis::cluster::Manager::new(
|
||||
config.seed_urls().to_vec(),
|
||||
false,
|
||||
)?;
|
||||
let pool = deadpool_redis::cluster::Pool::builder(manager)
|
||||
.max_size(pool_size.max())
|
||||
.wait_timeout(Some(Duration::from_millis(
|
||||
config.wait_timeout_ms(),
|
||||
)))
|
||||
.runtime(deadpool_redis::Runtime::Tokio1)
|
||||
.build()?;
|
||||
retain_cluster_pool(pool.clone());
|
||||
RedisBlockingPoolInner::Cluster(pool)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
pub(super) fn register_metrics(
|
||||
&self,
|
||||
registry: &Registry,
|
||||
) -> Result<(), prometheus::Error> {
|
||||
register_blocking_pool_metrics(registry, self.clone())
|
||||
}
|
||||
|
||||
async fn brpop(
|
||||
&self,
|
||||
key: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<Option<[Vec<u8>; 2]>, Error> {
|
||||
if timeout.is_zero() {
|
||||
return Err(Error::InvalidBlockingTimeout);
|
||||
}
|
||||
|
||||
let mut command = redis::cmd("BRPOP");
|
||||
command.arg(key).arg(timeout.as_secs_f64());
|
||||
|
||||
let response: Option<(Vec<u8>, Vec<u8>)> = match &self.inner {
|
||||
RedisBlockingPoolInner::Standalone(pool) => {
|
||||
command.query_async(&mut pool.get().await?).await?
|
||||
}
|
||||
RedisBlockingPoolInner::Cluster(pool) => {
|
||||
command.query_async(&mut pool.get().await?).await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(response.map(|(key, value)| [key, value]))
|
||||
}
|
||||
}
|
||||
|
||||
impl LogicalPoolStatusProvider for RedisBlockingPool {
|
||||
fn logical_pool_status(&self) -> LogicalPoolStatus {
|
||||
match &self.inner {
|
||||
RedisBlockingPoolInner::Standalone(pool) => {
|
||||
LogicalPoolStatus::from_deadpool(pool.status())
|
||||
}
|
||||
RedisBlockingPoolInner::Cluster(pool) => {
|
||||
LogicalPoolStatus::from_deadpool(pool.status())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RedisPool {
|
||||
pub async fn brpop(
|
||||
&self,
|
||||
key: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<Option<[Vec<u8>; 2]>, Error> {
|
||||
self.blocking.brpop(key, timeout).await
|
||||
}
|
||||
}
|
||||
|
||||
fn retain_standalone_pool(pool: deadpool_redis::Pool) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(POOL_RETAIN_INTERVAL).await;
|
||||
pool.retain(|_, metrics| {
|
||||
metrics.last_used() < MAX_IDLE_CONNECTION_AGE
|
||||
&& metrics.created.elapsed() < MAX_STANDALONE_CONNECTION_AGE
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn retain_cluster_pool(pool: deadpool_redis::cluster::Pool) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(POOL_RETAIN_INTERVAL).await;
|
||||
pool.retain(|_, metrics| {
|
||||
metrics.last_used() < MAX_IDLE_CONNECTION_AGE
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user