Skip to content

Commit

Permalink
refactor cache metrics and use feature flags for cache selection
Browse files Browse the repository at this point in the history
  • Loading branch information
wagpa committed Feb 10, 2024
1 parent c8fb875 commit 45ae151
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 130 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: cargo build --verbose

- name: Run tests with cargo
run: cargo test --verbose
run: cargo test --verbose --all-features

- name: Check format
run: cargo fmt --check
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ edition = "2021"
[dependencies]
prost = "0.12"
prost-types = { version = "0.12.3" }
redis = { version = "0.24", features = ["serde_json", "json", "aio", "tokio-comp", "async-std-comp", "connection-manager"] }
redis = { version = "0.24", features = ["serde_json", "json", "aio", "tokio-comp", "async-std-comp", "connection-manager"], optional = true }
tokio = { version = "1.35", features = ["full"] }
tonic = "0.10"
tonic-health = "0.10"
Expand All @@ -36,10 +36,21 @@ hyper = "1.1"
futures = "0.3.30"
async-trait = "0.1.77"
chrono = "0.4"
prometheus = "0.13"
prometheus = { version = "0.13" }
futures-util = "0.3.30"
actix-web = { version = "4", features = ["rustls"] }
config = "0.14.0"
cfg-if = "1.0.0"

[build-dependencies]
tonic-build = { version = "0.10", features = ["prost"] }

[dev-dependencies]
xenos = { path = ".", features = ["full"] }

[features]
default = ["cache_redis"]
full = ["cache_redis", "cache_memory", "mojang_stub"]
cache_redis = ["dep:redis"]
cache_memory = []
mojang_stub = []
5 changes: 0 additions & 5 deletions config/default.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
[memory_cache]
enabled = true
cache_time = 300 # 5 minutes

[redis_cache]
enabled = true
cache_time = 300 # 5 minutes

[metrics_server]
Expand Down
69 changes: 34 additions & 35 deletions src/cache/memory.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::cache::Cached::{Expired, Hit, Miss};
use crate::cache::{
has_elapsed, CacheEntry, Cached, HeadEntry, ProfileEntry, SkinEntry, UuidEntry, XenosCache,
};
use crate::cache::{Cached, HeadEntry, IntoCached, ProfileEntry, SkinEntry, UuidEntry, XenosCache};
use crate::error::XenosError;
use async_trait::async_trait;
use lazy_static::lazy_static;

use prometheus::{register_int_counter_vec, IntCounterVec};
use std::collections::HashMap;
use uuid::Uuid;
Expand All @@ -24,6 +23,26 @@ lazy_static! {
.unwrap();
}

fn track_cache_result<T>(cached: &Cached<T>, request_type: &str) {
match cached {
Expired(_) => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "expired"])
.inc();
}
Hit(_) => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "hit"])
.inc();
}
Miss => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "miss"])
.inc();
}
}
}

#[derive(Default)]
pub struct MemoryCache {
pub cache_time: u64,
Expand All @@ -40,34 +59,6 @@ impl MemoryCache {
..Default::default()
}
}

// converts a option into a cached while also incrementing memory cache response metrics
fn cached_from<T: CacheEntry>(&self, value: Option<T>, request_type: &str) -> Cached<T> {
match value {
Some(value) if self.has_expired(&value.get_timestamp()) => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "expired"])
.inc();
Expired(value)
}
Some(value) => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "hit"])
.inc();
Hit(value)
}
None => {
MEMORY_CACHE_GET_TOTAL
.with_label_values(&[request_type, "miss"])
.inc();
Miss
}
}
}

fn has_expired(&self, timestamp: &u64) -> bool {
has_elapsed(timestamp, &self.cache_time)
}
}

#[async_trait]
Expand All @@ -77,7 +68,9 @@ impl XenosCache for MemoryCache {
username: &str,
) -> Result<Cached<UuidEntry>, XenosError> {
let entry = self.uuids.get(username).cloned();
Ok(self.cached_from(entry, "uuid"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "uuid");
Ok(cached)
}

async fn set_uuid_by_username(
Expand All @@ -95,7 +88,9 @@ impl XenosCache for MemoryCache {
uuid: &Uuid,
) -> Result<Cached<ProfileEntry>, XenosError> {
let entry = self.profiles.get(uuid).cloned();
Ok(self.cached_from(entry, "profile"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "profile");
Ok(cached)
}

async fn set_profile_by_uuid(
Expand All @@ -110,7 +105,9 @@ impl XenosCache for MemoryCache {

async fn get_skin_by_uuid(&mut self, uuid: &Uuid) -> Result<Cached<SkinEntry>, XenosError> {
let entry = self.skins.get(uuid).cloned();
Ok(self.cached_from(entry, "skin"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "skin");
Ok(cached)
}

async fn set_skin_by_uuid(&mut self, uuid: Uuid, entry: SkinEntry) -> Result<(), XenosError> {
Expand All @@ -126,7 +123,9 @@ impl XenosCache for MemoryCache {
) -> Result<Cached<HeadEntry>, XenosError> {
let uuid_str = uuid.simple().to_string();
let entry = self.heads.get(&format!("{uuid_str}.{overlay}")).cloned();
Ok(self.cached_from(entry, "head"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "head");
Ok(cached)
}

async fn set_head_by_uuid(
Expand Down
18 changes: 14 additions & 4 deletions src/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#[cfg(feature = "cache_memory")]
pub mod memory;
#[cfg(feature = "cache_redis")]
pub mod redis;
pub mod uncached;

use crate::cache::Cached::{Hit, Miss};
use crate::cache::Cached::{Expired, Hit, Miss};
use crate::error::XenosError;
use crate::mojang::TexturesProperty;
use async_trait::async_trait;
Expand All @@ -19,10 +21,18 @@ pub enum Cached<T> {
Miss,
}

impl<T> From<Option<T>> for Cached<T> {
fn from(value: Option<T>) -> Self {
match value {
trait IntoCached<T> {
fn into_cached(self, ttl: &u64) -> Cached<T>;
}

impl<T> IntoCached<T> for Option<T>
where
T: CacheEntry,
{
fn into_cached(self, ttl: &u64) -> Cached<T> {
match self {
None => Miss,
Some(v) if has_elapsed(&v.get_timestamp(), ttl) => Expired(v),
Some(v) => Hit(v),
}
}
Expand Down
88 changes: 41 additions & 47 deletions src/cache/redis.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::cache::Cached::{Expired, Hit, Miss};
use crate::cache::{
has_elapsed, CacheEntry, Cached, HeadEntry, ProfileEntry, SkinEntry, UuidEntry, XenosCache,
};
use crate::cache::{Cached, HeadEntry, IntoCached, ProfileEntry, SkinEntry, UuidEntry, XenosCache};
use crate::error::XenosError;
use async_trait::async_trait;
use lazy_static::lazy_static;
Expand Down Expand Up @@ -35,48 +33,40 @@ lazy_static! {
.unwrap();
}

fn track_cache_result<T>(cached: &Cached<T>, request_type: &str) {
match cached {
Expired(_) => {
REDIS_SET_TOTAL
.with_label_values(&[request_type, "expired"])
.inc();
}
Hit(_) => {
REDIS_SET_TOTAL
.with_label_values(&[request_type, "hit"])
.inc();
}
Miss => {
REDIS_SET_TOTAL
.with_label_values(&[request_type, "miss"])
.inc();
}
}
}

pub struct RedisCache {
pub cache_time: u64,
pub expiration: Option<usize>,
pub redis_manager: ConnectionManager,
}

impl RedisCache {
fn has_expired(&self, timestamp: &u64) -> bool {
has_elapsed(timestamp, &self.cache_time)
}

fn build_set_options(&self) -> SetOptions {
let mut opts = SetOptions::default();
if let Some(expiration) = self.expiration {
opts = opts.with_expiration(SetExpiry::EX(expiration));
}
opts
}

// converts a option into a cached while also incrementing redis cache response metrics
fn cached_from<T: CacheEntry>(&self, value: Option<T>, request_type: &str) -> Cached<T> {
match value {
Some(value) if self.has_expired(&value.get_timestamp()) => {
REDIS_GET_TOTAL
.with_label_values(&[request_type, "expired"])
.inc();
Expired(value)
}
Some(value) => {
REDIS_GET_TOTAL
.with_label_values(&[request_type, "hit"])
.inc();
Hit(value)
}
None => {
REDIS_GET_TOTAL
.with_label_values(&[request_type, "miss"])
.inc();
Miss
}
}
}
}

pub fn build_key(ns: &str, sub: &str) -> String {
Expand All @@ -89,15 +79,16 @@ impl XenosCache for RedisCache {
&mut self,
username: &str,
) -> Result<Cached<UuidEntry>, XenosError> {
let timer = REDIS_GET_HISTOGRAM
let _timer = REDIS_GET_HISTOGRAM
.with_label_values(&["uuid"])
.start_timer();
let cached: Option<UuidEntry> = self
let entry: Option<UuidEntry> = self
.redis_manager
.get(build_key("uuid", username.to_lowercase().as_str()))
.await?;
timer.observe_duration();
Ok(self.cached_from(cached, "uuid"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "uuid");
Ok(cached)
}

async fn set_uuid_by_username(
Expand All @@ -120,15 +111,16 @@ impl XenosCache for RedisCache {
&mut self,
uuid: &Uuid,
) -> Result<Cached<ProfileEntry>, XenosError> {
let timer = REDIS_GET_HISTOGRAM
let _timer = REDIS_GET_HISTOGRAM
.with_label_values(&["profile"])
.start_timer();
let cached: Option<ProfileEntry> = self
let entry: Option<ProfileEntry> = self
.redis_manager
.get(build_key("profile", uuid.simple().to_string().as_str()))
.await?;
timer.observe_duration();
Ok(self.cached_from(cached, "profile"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "profile");
Ok(cached)
}

async fn set_profile_by_uuid(
Expand All @@ -148,15 +140,16 @@ impl XenosCache for RedisCache {
}

async fn get_skin_by_uuid(&mut self, uuid: &Uuid) -> Result<Cached<SkinEntry>, XenosError> {
let timer = REDIS_GET_HISTOGRAM
let _timer = REDIS_GET_HISTOGRAM
.with_label_values(&["skin"])
.start_timer();
let cached: Option<SkinEntry> = self
let entry: Option<SkinEntry> = self
.redis_manager
.get(build_key("skin", uuid.simple().to_string().as_str()))
.await?;
timer.observe_duration();
Ok(self.cached_from(cached, "skin"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "skin");
Ok(cached)
}

async fn set_skin_by_uuid(&mut self, uuid: Uuid, entry: SkinEntry) -> Result<(), XenosError> {
Expand All @@ -176,16 +169,17 @@ impl XenosCache for RedisCache {
uuid: &Uuid,
overlay: &bool,
) -> Result<Cached<HeadEntry>, XenosError> {
let timer = REDIS_GET_HISTOGRAM
let _timer = REDIS_GET_HISTOGRAM
.with_label_values(&["head"])
.start_timer();
let uuid_str = uuid.simple().to_string();
let cached: Option<HeadEntry> = self
let entry: Option<HeadEntry> = self
.redis_manager
.get(build_key("head", &format!("{uuid_str}.{overlay}")))
.await?;
timer.observe_duration();
Ok(self.cached_from(cached, "head"))
let cached = entry.into_cached(&self.cache_time);
track_cache_result(&cached, "head");
Ok(cached)
}

async fn set_head_by_uuid(
Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[derive(thiserror::Error, Debug)]
pub enum XenosError {
#[cfg(feature = "cache_redis")]
#[error(transparent)]
Redis(#[from] redis::RedisError),
#[error(transparent)]
Expand Down
Loading

0 comments on commit 45ae151

Please sign in to comment.