You've already forked AstralRinth
forked from didirus/AstralRinth
More tracing spans for Labrinth Redis (#5182)
* more tracing in redis ops * Improve Redis tracing * improve messages * make lpush and brpop use traced cmds
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -4523,6 +4523,7 @@ dependencies = [
|
||||
"const_format",
|
||||
"dashmap",
|
||||
"deadpool-redis",
|
||||
"derive_more 2.0.1",
|
||||
"dotenv-build",
|
||||
"dotenvy",
|
||||
"either",
|
||||
|
||||
@@ -27,6 +27,7 @@ async-stripe = { workspace = true, features = [
|
||||
"connect",
|
||||
"webhook-events",
|
||||
] }
|
||||
derive_more = { workspace = true, features = ["deref", "deref_mut"]}
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@@ -5,10 +5,7 @@ use dashmap::DashMap;
|
||||
use deadpool_redis::{Config, Runtime};
|
||||
use futures::future::Either;
|
||||
use prometheus::{IntGauge, Registry};
|
||||
use redis::{
|
||||
AsyncTypedCommands, Cmd, ExistenceCheck, SetExpiry, SetOptions,
|
||||
ToRedisArgs, cmd,
|
||||
};
|
||||
use redis::{ExistenceCheck, SetExpiry, SetOptions, ToRedisArgs};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@@ -17,6 +14,10 @@ use std::future::Future;
|
||||
use std::hash::Hash;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tracing::{Instrument, info_span};
|
||||
use util::{cmd, redis_pipe};
|
||||
|
||||
pub mod util;
|
||||
|
||||
const DEFAULT_EXPIRY: i64 = 60 * 60 * 12; // 12 hours
|
||||
const ACTUAL_EXPIRY: i64 = 60 * 30; // 30 minutes
|
||||
@@ -24,7 +25,7 @@ const ACTUAL_EXPIRY: i64 = 60 * 30; // 30 minutes
|
||||
#[derive(Clone)]
|
||||
pub struct RedisPool {
|
||||
pub url: String,
|
||||
pub pool: deadpool_redis::Pool,
|
||||
pub pool: util::InstrumentedPool,
|
||||
meta_namespace: String,
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ impl RedisPool {
|
||||
|
||||
let pool = RedisPool {
|
||||
url,
|
||||
pool,
|
||||
pool: util::InstrumentedPool::new(pool),
|
||||
meta_namespace: meta_namespace.unwrap_or("".to_string()),
|
||||
};
|
||||
|
||||
@@ -269,12 +270,14 @@ impl RedisPool {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
let get_cached_values = |ids: DashMap<String, I>| async move {
|
||||
let slug_ids = if let Some(slug_namespace) = slug_namespace {
|
||||
let mut connection = self.pool.get().await?;
|
||||
cmd("MGET")
|
||||
.arg(
|
||||
ids.iter()
|
||||
let get_cached_values = |ids: DashMap<String, I>| {
|
||||
async move {
|
||||
let slug_ids = if let Some(slug_namespace) = slug_namespace {
|
||||
async {
|
||||
let mut connection = self.pool.get().await?;
|
||||
|
||||
let args = ids
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}_{slug_namespace}:{}",
|
||||
@@ -286,45 +289,53 @@ impl RedisPool {
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let v = cmd("MGET")
|
||||
.arg(&args)
|
||||
.query_async::<Vec<Option<String>>>(&mut connection)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
Ok::<_, DatabaseError>(v)
|
||||
}
|
||||
.instrument(info_span!("get slug ids"))
|
||||
.await?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let mut connection = self.pool.get().await?;
|
||||
let args = ids
|
||||
.iter()
|
||||
.map(|x| x.value().to_string())
|
||||
.chain(ids.iter().filter_map(|x| {
|
||||
parse_base62(&x.value().to_string())
|
||||
.ok()
|
||||
.map(|x| x.to_string())
|
||||
}))
|
||||
.chain(slug_ids)
|
||||
.map(|x| format!("{}_{namespace}:{x}", self.meta_namespace))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cached_values = cmd("MGET")
|
||||
.arg(&args)
|
||||
.query_async::<Vec<Option<String>>>(&mut connection)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let mut connection = self.pool.get().await?;
|
||||
let cached_values = cmd("MGET")
|
||||
.arg(
|
||||
ids.iter()
|
||||
.map(|x| x.value().to_string())
|
||||
.chain(ids.iter().filter_map(|x| {
|
||||
parse_base62(&x.value().to_string())
|
||||
.filter_map(|x| {
|
||||
x.and_then(|val| {
|
||||
serde_json::from_str::<RedisValue<T, K, S>>(&val)
|
||||
.ok()
|
||||
.map(|x| x.to_string())
|
||||
}))
|
||||
.chain(slug_ids)
|
||||
.map(|x| {
|
||||
format!("{}_{namespace}:{x}", self.meta_namespace)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.query_async::<Vec<Option<String>>>(&mut connection)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
x.and_then(|val| {
|
||||
serde_json::from_str::<RedisValue<T, K, S>>(&val).ok()
|
||||
.map(|val| (val.key.clone(), val))
|
||||
})
|
||||
.map(|val| (val.key.clone(), val))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Ok::<_, DatabaseError>((cached_values, ids))
|
||||
Ok::<_, DatabaseError>((cached_values, ids))
|
||||
}
|
||||
.instrument(info_span!("get cached values"))
|
||||
};
|
||||
|
||||
let current_time = Utc::now();
|
||||
@@ -361,7 +372,7 @@ impl RedisPool {
|
||||
let subscribe_ids = DashMap::new();
|
||||
|
||||
if !ids.is_empty() {
|
||||
let mut pipe = redis::pipe();
|
||||
let mut pipe = redis_pipe();
|
||||
|
||||
let fetch_ids =
|
||||
ids.iter().map(|x| x.key().clone()).collect::<Vec<_>>();
|
||||
@@ -424,7 +435,7 @@ impl RedisPool {
|
||||
let vals = closure(fetch_ids).await?;
|
||||
let mut return_values = HashMap::new();
|
||||
|
||||
let mut pipe = redis::pipe();
|
||||
let mut pipe = redis_pipe();
|
||||
if !vals.is_empty() {
|
||||
for (key, (slug, value)) in vals {
|
||||
let value = RedisValue {
|
||||
@@ -524,21 +535,21 @@ impl RedisPool {
|
||||
let results = {
|
||||
let acquire_start = Instant::now();
|
||||
let mut connection = self.pool.get().await?;
|
||||
let args = subscribe_ids
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}_{namespace}:{}/lock",
|
||||
self.meta_namespace,
|
||||
// We lowercase key because locks are stored in lowercase
|
||||
x.key().to_lowercase()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
redis_budget += acquire_start.elapsed();
|
||||
|
||||
cmd("MGET")
|
||||
.arg(
|
||||
subscribe_ids
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}_{namespace}:{}/lock",
|
||||
self.meta_namespace,
|
||||
// We lowercase key because locks are stored in lowercase
|
||||
x.key().to_lowercase()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.arg(&args)
|
||||
.query_async::<Vec<Option<String>>>(&mut connection)
|
||||
.await?
|
||||
};
|
||||
@@ -749,10 +760,14 @@ impl RedisConnection {
|
||||
&mut self,
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
value: impl ToRedisArgs + Send + Sync,
|
||||
value: impl ToRedisArgs + Send + Sync + Debug,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let key = format!("{}_{namespace}:{key}", self.meta_namespace);
|
||||
self.connection.lpush(key, value).await?;
|
||||
cmd("LPUSH")
|
||||
.arg(key)
|
||||
.arg(value)
|
||||
.query_async::<()>(&mut self.connection)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -766,7 +781,11 @@ impl RedisConnection {
|
||||
let key = format!("{}_{namespace}:{key}", self.meta_namespace);
|
||||
// a timeout of 0 is infinite
|
||||
let timeout = timeout.unwrap_or(0.0);
|
||||
let values = self.connection.brpop(key, timeout).await?;
|
||||
let values = cmd("BRPOP")
|
||||
.arg(key)
|
||||
.arg(timeout)
|
||||
.query_async(&mut self.connection)
|
||||
.await?;
|
||||
Ok(values)
|
||||
}
|
||||
}
|
||||
@@ -780,14 +799,14 @@ pub struct RedisValue<T, K, S> {
|
||||
val: T,
|
||||
}
|
||||
|
||||
pub fn redis_args(cmd: &mut Cmd, args: &[String]) {
|
||||
pub fn redis_args(cmd: &mut util::InstrumentedCmd, args: &[String]) {
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn redis_execute<T>(
|
||||
cmd: &mut Cmd,
|
||||
cmd: &mut util::InstrumentedCmd,
|
||||
redis: &mut deadpool_redis::Connection,
|
||||
) -> Result<T, deadpool_redis::PoolError>
|
||||
where
|
||||
93
apps/labrinth/src/database/redis/util.rs
Normal file
93
apps/labrinth/src/database/redis/util.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use deadpool_redis::PoolError;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use redis::{FromRedisValue, RedisResult, ToRedisArgs};
|
||||
use tracing::{Instrument, info_span};
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut)]
|
||||
pub struct InstrumentedPool {
|
||||
inner: deadpool_redis::Pool,
|
||||
}
|
||||
|
||||
impl InstrumentedPool {
|
||||
pub fn new(inner: deadpool_redis::Pool) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub async fn get(&self) -> Result<deadpool_redis::Connection, PoolError> {
|
||||
self.inner
|
||||
.get()
|
||||
.instrument(info_span!("get redis connection"))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redis_pipe() -> InstrumentedPipeline {
|
||||
InstrumentedPipeline {
|
||||
inner: redis::pipe(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, DerefMut)]
|
||||
pub struct InstrumentedPipeline {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
inner: redis::Pipeline,
|
||||
}
|
||||
|
||||
impl InstrumentedPipeline {
|
||||
pub fn atomic(&mut self) -> &mut Self {
|
||||
self.inner.atomic();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn query_async<T: FromRedisValue>(
|
||||
&self,
|
||||
con: &mut impl redis::aio::ConnectionLike,
|
||||
) -> RedisResult<T> {
|
||||
self.inner
|
||||
.query_async(con)
|
||||
.instrument(info_span!("execute redis pipeline"))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmd(name: &str) -> InstrumentedCmd {
|
||||
InstrumentedCmd {
|
||||
inner: redis::cmd(name),
|
||||
name: name.to_string(),
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InstrumentedCmd {
|
||||
inner: redis::Cmd,
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl InstrumentedCmd {
|
||||
#[inline]
|
||||
pub fn arg<T: ToRedisArgs + Debug>(&mut self, arg: T) -> &mut Self {
|
||||
self.args.push(format!("{arg:?}"));
|
||||
self.inner.arg(arg);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn query_async<T: FromRedisValue>(
|
||||
&self,
|
||||
con: &mut impl redis::aio::ConnectionLike,
|
||||
) -> RedisResult<T> {
|
||||
let span = info_span!(
|
||||
"query_async",
|
||||
// <https://opentelemetry.io/docs/specs/semconv/db/redis/>
|
||||
db.system.name = "redis",
|
||||
db.operation.name = self.name,
|
||||
db.query.text = format!("{} {}", self.name, self.args.join(" ")),
|
||||
);
|
||||
self.inner.query_async(con).instrument(span).await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user