Skip to content

Commit

Permalink
feat(cache): store data in redis with borsh
Browse files Browse the repository at this point in the history
  • Loading branch information
uku3lig committed Sep 16, 2024
1 parent 6f3a9f6 commit 756d734
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 43 deletions.
103 changes: 103 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow = "1.0"
axum = "0.7.5"
bb8 = "0.8.5"
bb8-redis = "0.16.0"
borsh = { version = "1.5.1", features = ["derive"] }
dotenvy = "0.15.7"
metrics = "0.23.0"
metrics-exporter-prometheus = { version = "0.15.3", default-features = false }
Expand All @@ -36,7 +37,7 @@ tokio = { version = "1.40.0", features = [
tower-http = { version = "0.5.2", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1.10.0", features = ["serde"] }
uuid = { version = "1.10.0", features = ["serde", "borsh"] }

[profile.release]
strip = true
Expand Down
59 changes: 21 additions & 38 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use anyhow::Context;
use anyhow::Result;
use bb8::Pool;
use bb8_redis::RedisConnectionManager;
use borsh::BorshDeserialize;
use borsh::BorshSerialize;
use redis::FromRedisValue;
use redis::ToRedisArgs;
use redis::{AsyncCommands, Client, ConnectionLike};
use redis_macros::ToRedisArgs;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::tiers::PlayerInfo;
Expand Down Expand Up @@ -49,7 +50,7 @@ impl Storage {

let player: OptionalPlayerInfo = con.get(&key).await?;

Ok(player.into())
Ok(player.0)
}

pub async fn set_player_info(
Expand All @@ -60,10 +61,8 @@ impl Storage {
let key = format!("{PROFILE_KEY}:{uuid}");
let mut con = self.pool.get().await?;

let player: OptionalPlayerInfo = player.into();

redis::pipe()
.set(&key, player)
.set(&key, OptionalPlayerInfo(player))
.expire(&key, 60 * 60 * 12)
.query_async(&mut *con)
.await
Expand All @@ -88,33 +87,35 @@ impl Storage {
.collect::<(Vec<_>, Vec<_>)>();

let values: Vec<OptionalPlayerInfo> = con.mget(&keys).await?;
let values: Vec<Option<PlayerInfo>> = values.into_iter().map(Into::into).collect();
let values: Vec<Option<PlayerInfo>> = values.into_iter().map(|o| o.0).collect();

Ok(uuids.into_iter().zip(values).collect())
}
}

#[derive(Debug, Clone, Serialize, Deserialize, ToRedisArgs)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum OptionalPlayerInfo {
Present(PlayerInfo),
Unknown,
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct OptionalPlayerInfo(Option<PlayerInfo>);

impl ToRedisArgs for OptionalPlayerInfo {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + redis::RedisWrite,
{
let buf = borsh::to_vec(&self).unwrap();
out.write_arg(&buf);
}
}

impl FromRedisValue for OptionalPlayerInfo {
fn from_redis_value(v: &redis::Value) -> redis::RedisResult<Self> {
match *v {
redis::Value::Nil => Ok(Self::Unknown),
redis::Value::Nil => Ok(Self(None)),
redis::Value::BulkString(ref bytes) => {
if let Ok(s) = std::str::from_utf8(bytes) {
if let Ok(s) = serde_json::from_str(s) {
Ok(s)
} else {
redis_error(format!("Response type not deserializable with serde_json. (response was {v:?})"))
}
if let Ok(s) = borsh::from_slice(bytes) {
Ok(s)
} else {
redis_error(format!(
"Response was not valid UTF-8 string. (response was {v:?})"
"Response type not deserializable with borsh. (response was {v:?})"
))
}
}
Expand All @@ -125,24 +126,6 @@ impl FromRedisValue for OptionalPlayerInfo {
}
}

impl From<Option<PlayerInfo>> for OptionalPlayerInfo {
fn from(player: Option<PlayerInfo>) -> Self {
match player {
Some(player) => Self::Present(player),
None => Self::Unknown,
}
}
}

impl From<OptionalPlayerInfo> for Option<PlayerInfo> {
fn from(val: OptionalPlayerInfo) -> Self {
match val {
OptionalPlayerInfo::Present(player) => Some(player),
OptionalPlayerInfo::Unknown => None,
}
}
}

fn redis_error(msg: String) -> redis::RedisResult<OptionalPlayerInfo> {
Err(redis::RedisError::from((
redis::ErrorKind::TypeError,
Expand Down
9 changes: 5 additions & 4 deletions src/tiers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{collections::HashMap, fmt::Display, time::Instant};

use axum::{extract::Path, response::IntoResponse, routing::get, Json, Router};
use borsh::{BorshDeserialize, BorshSerialize};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand All @@ -10,7 +11,7 @@ use crate::{get_cache, RouteResponse};
const MCTIERS_REQS_KEY: &str = "api_rs_mctiers_reqs_total";
const MCTIERS_REQ_DURATION_KEY: &str = "api_rs_mctiers_req_duration_seconds";

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct PlayerInfo {
pub uuid: Uuid,
name: String,
Expand All @@ -21,7 +22,7 @@ pub struct PlayerInfo {
badges: Vec<Badge>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Ranking {
tier: u8,
pos: u8,
Expand All @@ -38,13 +39,13 @@ impl Display for Ranking {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Badge {
title: String,
desc: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize)]
pub struct AllPlayerInfo {
players: Vec<PlayerInfo>,
unknown: Vec<Uuid>,
Expand Down

0 comments on commit 756d734

Please sign in to comment.