Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In memory backend #85

Merged
merged 2 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions examples/examples/axum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use axum::{extract::Path, routing::get, Json, Router};

use hitbox_redis::RedisBackend;
use hitbox_stretto::StrettoBackend;
use hitbox_tower::Cache;
use http::StatusCode;
use tower::ServiceBuilder;
Expand Down Expand Up @@ -38,20 +39,17 @@ async fn main() {
tracing::subscriber::set_global_default(subscriber).unwrap();

let backend = RedisBackend::new().unwrap();
let inmemory = hitbox_stretto::StrettoBackendBuilder::new(12960, 1e6 as i64)
let inmemory = StrettoBackend::builder(2 ^ 16)
.finalize()
.unwrap();
// build our application with a single route
let app = Router::new()
.route("/greet/:name/", get(handler_result))
.route("/", get(handler))
.route(
"/json/",
get(handler_json), // .layer(Cache::builder().backend(inmemory.clone()).build()),
)
.route("/json/", get(handler_json))
.layer(
ServiceBuilder::new()
// .layer(Cache::builder().backend(inmemory).build())
.layer(Cache::builder().backend(inmemory).build())
.layer(Cache::builder().backend(backend).build()),
);

Expand Down
8 changes: 3 additions & 5 deletions examples/examples/tower.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use hitbox_stretto::StrettoBackend;
use hitbox_redis::RedisBackend;
use hitbox_stretto::builder::StrettoBackendBuilder;
use hitbox_tower::Cache;
Expand All @@ -19,14 +20,11 @@ async fn main() {
.finish();
tracing::subscriber::set_global_default(subscriber).unwrap();

let inmemory = StrettoBackendBuilder::new(12960, 1e6 as i64)
.finalize()
.unwrap();
let inmemory = StrettoBackend::builder(10_000_000).finalize().unwrap();
let redis = RedisBackend::builder().build().unwrap();

let service = tower::ServiceBuilder::new()
.layer(tower_http::trace::TraceLayer::new_for_http())
// .layer(Cache::builder().backend(inmemory).build())
.layer(Cache::builder().backend(inmemory).build())
.layer(Cache::builder().backend(redis).build())
.service_fn(handle);

Expand Down
1 change: 1 addition & 0 deletions hitbox-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ futures = { version = "0.3", default-features = false }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bincode = "1"
thiserror = "1"
44 changes: 43 additions & 1 deletion hitbox-backend/src/serializer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::marker::PhantomData;

use crate::{
response::{CachePolicy, CacheableResponse},

Check warning on line 4 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / check (stable)

unused imports: `CachePolicy`, `CacheableResponse`

Check failure on line 4 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

unused imports: `CachePolicy`, `CacheableResponse`

error: unused imports: `CachePolicy`, `CacheableResponse` --> hitbox-backend/src/serializer.rs:4:16 | 4 | response::{CachePolicy, CacheableResponse}, | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
CachedValue,
};
use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -36,7 +36,7 @@
}

impl<U> SerializableCachedValue<U> {
pub fn new(data: U, expired: DateTime<Utc>) -> Self {

Check warning on line 39 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / check (stable)

associated function `new` is never used

Check failure on line 39 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

associated function `new` is never used

error: associated function `new` is never used --> hitbox-backend/src/serializer.rs:39:12 | 38 | impl<U> SerializableCachedValue<U> { | ---------------------------------- associated function in this implementation 39 | pub fn new(data: U, expired: DateTime<Utc>) -> Self { | ^^^ | = note: `-D dead-code` implied by `-D warnings`
SerializableCachedValue { data, expired }
}

Expand All @@ -61,7 +61,7 @@
.map_err(|err| SerializerError::Deserialize(Box::new(err)))?;
let cached_value = deserialized.into_cached_value();
Ok(CachedValue::new(
cached_value.data.into(),

Check failure on line 64 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

useless conversion to the same type: `T`

error: useless conversion to the same type: `T` --> hitbox-backend/src/serializer.rs:64:13 | 64 | cached_value.data.into(), | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `cached_value.data` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion = note: `-D clippy::useless-conversion` implied by `-D warnings`
cached_value.expired,
))
}
Expand All @@ -74,8 +74,8 @@
data: &value.data,
expired: value.expired,
};
Ok(serde_json::to_vec(&serializable_value)
.map_err(|err| SerializerError::Serialize(Box::new(err)))?)

Check failure on line 78 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

question mark operator is useless here

error: question mark operator is useless here --> hitbox-backend/src/serializer.rs:77:9 | 77 | / Ok(serde_json::to_vec(&serializable_value) 78 | | .map_err(|err| SerializerError::Serialize(Box::new(err)))?) | |_______________________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark = note: `-D clippy::needless-question-mark` implied by `-D warnings` help: try removing question mark and `Ok()` | 77 ~ serde_json::to_vec(&serializable_value) 78 + .map_err(|err| SerializerError::Serialize(Box::new(err))) |
}
}

Expand All @@ -90,7 +90,7 @@
.map_err(|err| SerializerError::Deserialize(Box::new(err)))?;
let cached_value = deserialized.into_cached_value();
Ok(CachedValue::new(
cached_value.data.into(),

Check failure on line 93 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

useless conversion to the same type: `T`

error: useless conversion to the same type: `T` --> hitbox-backend/src/serializer.rs:93:13 | 93 | cached_value.data.into(), | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `cached_value.data` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
cached_value.expired,
))
}
Expand All @@ -103,11 +103,45 @@
data: &value.data,
expired: value.expired,
};
Ok(serde_json::to_string(&serializable_value)
.map_err(|err| SerializerError::Serialize(Box::new(err)))?)

Check failure on line 107 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

question mark operator is useless here

error: question mark operator is useless here --> hitbox-backend/src/serializer.rs:106:9 | 106 | / Ok(serde_json::to_string(&serializable_value) 107 | | .map_err(|err| SerializerError::Serialize(Box::new(err)))?) | |_______________________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark help: try removing question mark and `Ok()` | 106 ~ serde_json::to_string(&serializable_value) 107 + .map_err(|err| SerializerError::Serialize(Box::new(err))) |
}
}

#[derive(Default)]
pub struct BinSerializer<Raw = Vec<u8>> {
_raw: PhantomData<Raw>,
}

impl Serializer for BinSerializer<Vec<u8>> {
type Raw = Vec<u8>;

fn deserialize<T>(data: Self::Raw) -> Result<CachedValue<T>, SerializerError>
where
T: DeserializeOwned,
{
let deserialized: SerializableCachedValue<T> = bincode::deserialize(&data)
.map_err(|err| SerializerError::Deserialize(Box::new(err)))?;
let cached_value = deserialized.into_cached_value();
Ok(CachedValue::new(
cached_value.data.into(),

Check failure on line 127 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

useless conversion to the same type: `T`

error: useless conversion to the same type: `T` --> hitbox-backend/src/serializer.rs:127:13 | 127 | cached_value.data.into(), | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `cached_value.data` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
cached_value.expired,
))
}

fn serialize<T>(value: &CachedValue<T>) -> Result<Self::Raw, SerializerError>
where
T: Serialize,
{
let serializable_value: SerializableCachedValue<&T> = SerializableCachedValue {
data: &value.data,
expired: value.expired,
};
Ok(bincode::serialize(&serializable_value)
.map_err(|err| SerializerError::Serialize(Box::new(err)))?)

Check failure on line 141 in hitbox-backend/src/serializer.rs

View workflow job for this annotation

GitHub Actions / clippy

question mark operator is useless here

error: question mark operator is useless here --> hitbox-backend/src/serializer.rs:140:9 | 140 | / Ok(bincode::serialize(&serializable_value) 141 | | .map_err(|err| SerializerError::Serialize(Box::new(err)))?) | |_______________________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark help: try removing question mark and `Ok()` | 140 ~ bincode::serialize(&serializable_value) 141 + .map_err(|err| SerializerError::Serialize(Box::new(err))) |
}
}

#[cfg(test)]
mod test {
use std::convert::Infallible;
Expand Down Expand Up @@ -155,7 +189,15 @@
fn test_json_string_serializer() {
let value = CachedValue::new(Test::new(), Utc::now());
let raw = JsonSerializer::<String>::serialize(&value).unwrap();
dbg!(&raw);
assert_eq!(raw.len(), 71);
assert_eq!(value, JsonSerializer::<String>::deserialize(raw).unwrap());
}

#[test]
fn test_bincode_serializer() {
let value = CachedValue::new(Test::new(), Utc::now());
let raw = <BinSerializer>::serialize(&value).unwrap();
assert_eq!(raw.len(), 54);
assert_eq!(value, BinSerializer::<Vec<u8>>::deserialize(raw).unwrap());
}
}
3 changes: 3 additions & 0 deletions hitbox-stretto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.4", features = ["full"] }
http = "0.2"
lazy_static = "1"

[dev-dependencies]
chrono = { version = "0.4", features = ["serde"] }
103 changes: 72 additions & 31 deletions hitbox-stretto/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
use crate::builder::StrettoBackendBuilder;
use axum::async_trait;
use hitbox_backend::{
serializer::{JsonSerializer, Serializer},
serializer::{BinSerializer, Serializer},
BackendError, BackendResult, CacheBackend, CacheableResponse, CachedValue, DeleteStatus,
};
use std::time::Duration;
use stretto::AsyncCache;

const COST: i64 = 0;

#[derive(Clone)]
pub struct StrettoBackend {
cache: AsyncCache<String, Vec<u8>>,
pub(crate) cache: AsyncCache<String, Vec<u8>>,
}

impl StrettoBackend {
pub fn new(cache: AsyncCache<String, Vec<u8>>) -> Self {
Self { cache }
pub fn builder(max_size: i64) -> StrettoBackendBuilder {
StrettoBackendBuilder::new(max_size)
}
}

Expand All @@ -26,12 +25,18 @@ impl CacheBackend for StrettoBackend {
T: CacheableResponse,
<T as CacheableResponse>::Cached: serde::de::DeserializeOwned,
{
let () = self
.cache
.wait()
.await
.map_err(crate::error::Error::from)
.map_err(BackendError::from)?;

match self.cache.get(&key).await {
Some(cached) => Ok(Some(
JsonSerializer::<Vec<u8>>::deserialize(cached.value().to_owned())
.map_err(BackendError::from)
.unwrap(),
)),
Some(cached) => BinSerializer::<Vec<u8>>::deserialize(cached.value().to_owned())
.map_err(BackendError::from)
.map(Some),

None => Ok(None),
}
}
Expand All @@ -46,15 +51,20 @@ impl CacheBackend for StrettoBackend {
T: CacheableResponse + Send,
T::Cached: serde::Serialize + Send + Sync,
{
let serialized =
JsonSerializer::<Vec<u8>>::serialize(&value).map_err(BackendError::from)?;
let serialized = BinSerializer::<Vec<u8>>::serialize(&value).map_err(BackendError::from)?;
let cost = serialized.len();
let inserted = match ttl {
Some(ttl) => {
self.cache
.insert_with_ttl(key, serialized, COST, Duration::from_secs(ttl as u64))
.insert_with_ttl(
key,
serialized,
cost as i64,
Duration::from_secs(ttl as u64),
)
.await
}
None => self.cache.insert(key, serialized, COST).await,
None => self.cache.insert(key, serialized, cost as i64).await,
};
if inserted {
Ok(())
Expand All @@ -65,32 +75,63 @@ impl CacheBackend for StrettoBackend {

async fn delete(&self, key: String) -> BackendResult<DeleteStatus> {
self.cache.remove(&key).await;
Ok(DeleteStatus::Deleted(0))
Ok(DeleteStatus::Deleted(1))
}

async fn start(&self) -> BackendResult<()> {
Ok(())
}
}

#[tokio::test]
async fn test() {
let c: AsyncCache<String, String> = AsyncCache::new(1000, 100, tokio::spawn).unwrap();
#[cfg(test)]
mod test {
use axum::async_trait;
use chrono::Utc;
use serde::{Deserialize, Serialize};

use super::*;
use hitbox_backend::CacheableResponse;

for i in 0..100 {
let key = format!("key-{}", i);
let r = c.insert(key, "value".to_string(), 1).await;
dbg!(r);
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
struct Test {
a: i32,
b: String,
}

c.wait().await.unwrap();
#[async_trait]
impl CacheableResponse for Test {
type Cached = Self;

for i in 0..100 {
let key = format!("key-{}", i);
let value = c.get(&key).await;
match value {
Some(v) => dbg!(v.to_string()),
None => dbg!("None".to_string()),
};
async fn into_cached(self) -> Self::Cached {
self
}
async fn from_cached(cached: Self::Cached) -> Self {
cached
}
}

impl Test {
pub fn new() -> Self {
Self {
a: 42,
b: "nope".to_owned(),
}
}
}

#[tokio::test]
async fn test_set_and_get() {
let cache = crate::StrettoBackend::builder(100).finalize().unwrap();
let value = CachedValue::new(Test::new(), Utc::now());
let res = cache.set::<Test>("key-1".to_string(), &value, None).await;
assert!(res.is_ok());
let value = cache
.get::<Test>("key-1".to_string())
.await
.unwrap()
.unwrap()
.into_inner();
assert_eq!(value.a, 42);
assert_eq!(value.b, "nope".to_owned());
}
}
12 changes: 5 additions & 7 deletions hitbox-stretto/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ type Cache = AsyncCacheBuilder<String, Vec<u8>>;
pub struct StrettoBackendBuilder(Cache);

impl StrettoBackendBuilder {
pub fn new(num_counters: usize, max_cost: i64) -> Self {
Self(AsyncCacheBuilder::new(num_counters, max_cost))
pub fn new(max_size: i64) -> Self {
let num_counters = max_size * 10;
Self(AsyncCacheBuilder::new(num_counters as usize, max_size))
}

pub fn set_buffer_size(self, sz: usize) -> Self {
Expand All @@ -19,18 +20,15 @@ impl StrettoBackendBuilder {
Self(self.0.set_buffer_items(sz))
}

pub fn set_ingore_internal_cost(self, val: bool) -> Self {
Self(self.0.set_ignore_internal_cost(val))
}

pub fn set_cleanup_duration(self, d: Duration) -> Self {
Self(self.0.set_cleanup_duration(d))
}

pub fn finalize(self) -> Result<crate::backend::StrettoBackend, Error> {
self.0
.set_ignore_internal_cost(true)
.finalize(tokio::spawn)
.map(crate::backend::StrettoBackend::new)
.map(|cache| crate::backend::StrettoBackend { cache })
.map_err(Error::from)
}
}
5 changes: 2 additions & 3 deletions hitbox-stretto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod backend;
pub mod builder;
mod backend;
mod builder;
pub mod error;

pub use crate::backend::StrettoBackend;
pub use crate::builder::StrettoBackendBuilder;
Loading