Skip to content

Commit

Permalink
feat(caching): implement hash_table and merchant table caching (#55)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
NishantJoshi00 and hyperswitch-bot[bot] authored Jan 29, 2024
1 parent 358cdb8 commit f0d4cc4
Show file tree
Hide file tree
Showing 12 changed files with 818 additions and 316 deletions.
868 changes: 558 additions & 310 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ default-run = "locker"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = []
release = ["kms-aws", "middleware", "key_custodian", "limit", "kms-hashicorp-vault"]
default = ["caching"]
release = ["kms-aws", "middleware", "key_custodian", "limit", "kms-hashicorp-vault", "caching"]
kms-aws = ["dep:aws-config", "dep:aws-sdk-kms"]
kms-hashicorp-vault = ["dep:vaultrs"]
limit = []
middleware = []
key_custodian = []
caching = ["dep:moka"]

[dependencies]
aws-config = { version = "1.0.1", optional = true }
Expand Down Expand Up @@ -60,6 +61,7 @@ hex = "0.4.3"
time = "0.3.30"
async-trait = "0.1.74"
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }
moka = { version = "0.12.1", features = ["future"], optional = true }

argh = "0.1.12"

Expand Down
5 changes: 5 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ port = 8080 # The port where the server should be hosted on
request_count = 1 # The requests per duration
duration = 60 # duration to rate limit the delete api (in sec)


[cache]
tti = 7200 # Idle time after a get/insert of a cache entry to free the cache (in secs)
max_capacity = 5000 # Max capacity of a single table cache

[database]
username = "sam" # username for the database
password = "damn" # password of the database
Expand Down
4 changes: 4 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ host = "localhost"
port = 5432
dbname = "locker"

[cache]
tti = 7200 # i.e. 2 hours
max_capacity = 5000

[secrets]
tenant = "hyperswitch"
master_key = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308"
Expand Down
3 changes: 3 additions & 0 deletions docs/guides/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ LOCKER__SECRETS__MASTER_KEY=
LOCKER__SECRETS__LOCKER_PRIVATE_KEY=
LOCKER__SECRETS__TENANT_PUBLIC_KEY=
LOCKER__CACHE__MAX_CAPACITY=5000
LOCKER__CACHE__TTI=7200
LOCKER__AWS_KMS__KEY_ID=
LOCKER__AWS_KMS__REGION=
```
Expand Down
29 changes: 28 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ use crate::{
storage::{self},
};

#[cfg(feature = "caching")]
use crate::storage::{caching::Caching, types};

#[cfg(feature = "kms")]
use crate::crypto::{
kms::{self, Base64Encoded, KmsData, Raw},
Encryption,
};

#[cfg(feature = "caching")]
type Storage = Caching<Caching<storage::Storage, types::HashTable>, types::Merchant>;

#[cfg(not(feature = "caching"))]
type Storage = storage::Storage;

///
/// AppState:
///
Expand All @@ -23,7 +38,7 @@ use crate::{
///
#[derive(Clone)]
pub struct AppState {
pub db: storage::Storage,
pub db: Storage,
pub config: config::Config,
}

Expand Down Expand Up @@ -177,6 +192,18 @@ impl AppState {
Ok(Self {
db: storage::Storage::new(&config.database)
.await
.map(
#[cfg(feature = "caching")]
storage::caching::implement_cache("hash", &config.cache),
#[cfg(not(feature = "caching"))]
std::convert::identity,
)
.map(
#[cfg(feature = "caching")]
storage::caching::implement_cache("merchant", &config.cache),
#[cfg(not(feature = "caching"))]
std::convert::identity,
)
.change_context(error::ConfigurationError::DatabaseError)?,

config: config.clone(),
Expand Down
11 changes: 11 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct Config {
pub log: Log,
#[cfg(feature = "limit")]
pub limit: Limit,
#[cfg(feature = "caching")]
pub cache: Cache,
}

#[derive(Debug, Clone, serde::Deserialize)]
Expand Down Expand Up @@ -54,6 +56,15 @@ pub struct Database {
pub pool_size: Option<usize>,
}

#[cfg(feature = "caching")]
#[derive(Clone, serde::Deserialize, Debug)]
pub struct Cache {
// time to idle (in secs)
pub tti: Option<u64>,
// maximum capacity of the cache
pub max_capacity: u64,
}

#[derive(Clone, serde::Deserialize, Debug)]
pub struct Secrets {
pub tenant: String,
Expand Down
23 changes: 22 additions & 1 deletion src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use diesel_async::{
use error_stack::ResultExt;
use masking::{PeekInterface, Secret};

#[cfg(feature = "caching")]
pub mod caching;

pub mod db;
pub mod schema;
pub mod types;
Expand Down Expand Up @@ -68,13 +71,31 @@ impl Storage {
}
}

#[cfg(feature = "caching")]
pub trait Cacheable<Table> {
type Key: std::hash::Hash + Eq + PartialEq + Send + Sync + 'static;
type Value: Clone + Send + Sync + 'static;
}

#[cfg(feature = "caching")]
impl Cacheable<types::Merchant> for Storage {
type Key = (String, String);
type Value = types::Merchant;
}

#[cfg(feature = "caching")]
impl Cacheable<types::HashTable> for Storage {
type Key = Vec<u8>;
type Value = types::HashTable;
}

///
/// MerchantInterface:
///
/// Interface providing functional to interface with the merchant table in database
#[async_trait::async_trait]
pub trait MerchantInterface {
type Algorithm: Encryption<Vec<u8>, Vec<u8>>;
type Algorithm: Encryption<Vec<u8>, Vec<u8>> + Sync;
type Error;

/// find merchant from merchant table with `merchant_id` and `tenant_id` with key as master key
Expand Down
75 changes: 75 additions & 0 deletions src/storage/caching.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::sync::Arc;

#[derive(Clone)]
pub struct Caching<T, U>
where
T: super::Cacheable<U>,
{
inner: T,
cache: moka::future::Cache<T::Key, Arc<T::Value>>,
}

// impl<U, T: super::Cacheable<U>> super::Cacheable<U> for Caching<T, U> {
// type Key = T::Key;
// type Value = T::Value;
// }

impl<U1, U2, T> super::Cacheable<U2> for Caching<T, U1>
where
T: super::Cacheable<U2> + super::Cacheable<U1>,
{
type Key = <T as super::Cacheable<U2>>::Key;

type Value = <T as super::Cacheable<U2>>::Value;
}

impl<T: super::Cacheable<U>, U> std::ops::Deref for Caching<T, U> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl<T, U> Caching<T, U>
where
T: super::Cacheable<U>,
{
#[inline(always)]
pub async fn lookup(&self, key: T::Key) -> Option<T::Value> {
self.cache.get(&key).await.map(|value| {
let data = value.as_ref();
data.clone()
})
}

#[inline(always)]
pub async fn cache_data(&self, key: T::Key, value: T::Value) {
self.cache.insert(key, value.into()).await;
}
}

pub fn implement_cache<'a, T, U>(
name: &'a str,
config: &'a crate::config::Cache,
) -> impl Fn(T) -> Caching<T, U> + 'a
where
T: super::Cacheable<U>,
{
// Caching { inner, cache }
move |inner| {
let cache = moka::future::CacheBuilder::new(config.max_capacity).name(name);
let cache = match config.tti {
Some(value) => cache.time_to_idle(std::time::Duration::from_secs(value)),
None => cache,
};

Caching {
inner,
cache: cache.build(),
}
}
}

pub mod hash_table;
pub mod merchant;
40 changes: 40 additions & 0 deletions src/storage/caching/hash_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::{
error::ContainerError,
storage::{self, types},
};

#[async_trait::async_trait]
impl<T> storage::HashInterface for super::Caching<T, types::HashTable>
where
T: storage::HashInterface
+ storage::Cacheable<types::HashTable, Key = Vec<u8>, Value = types::HashTable>
+ Sync
+ Send,
{
type Error = T::Error;

async fn find_by_data_hash(
&self,
data_hash: &[u8],
) -> Result<Option<types::HashTable>, ContainerError<Self::Error>> {
match self.lookup(data_hash.to_vec()).await {
value @ Some(_) => Ok(value),
None => Ok(match self.inner.find_by_data_hash(data_hash).await? {
None => None,
Some(value) => {
self.cache_data(data_hash.to_vec(), value.clone()).await;
Some(value)
}
}),
}
}

async fn insert_hash(
&self,
data_hash: Vec<u8>,
) -> Result<types::HashTable, ContainerError<Self::Error>> {
let output = self.inner.insert_hash(data_hash.clone()).await?;
self.cache_data(data_hash, output.clone()).await;
Ok(output)
}
}
66 changes: 66 additions & 0 deletions src/storage/caching/merchant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::{
error::ContainerError,
storage::{self, types},
};

#[async_trait::async_trait]
impl<T> storage::MerchantInterface for super::Caching<T, types::Merchant>
where
T: storage::MerchantInterface
+ storage::Cacheable<types::Merchant, Key = (String, String), Value = types::Merchant>
+ Sync
+ Send,
{
type Algorithm = T::Algorithm;
type Error = T::Error;

async fn find_by_merchant_id(
&self,
merchant_id: &str,
tenant_id: &str,
key: &Self::Algorithm,
) -> Result<types::Merchant, ContainerError<Self::Error>> {
let cached_data = self
.lookup((tenant_id.to_string(), merchant_id.to_string()))
.await;
match cached_data {
Some(value) => Ok(value),
None => {
let output = self
.inner
.find_by_merchant_id(merchant_id, tenant_id, key)
.await?;
self.cache_data(
(output.tenant_id.to_string(), output.merchant_id.to_string()),
output.clone(),
)
.await;
Ok(output)
}
}
}

async fn find_or_create_by_merchant_id(
&self,
merchant_id: &str,
tenant_id: &str,
key: &Self::Algorithm,
) -> Result<types::Merchant, ContainerError<Self::Error>> {
self.inner
.find_or_create_by_merchant_id(merchant_id, tenant_id, key)
.await
}

async fn insert_merchant(
&self,
new: types::MerchantNew<'_>,
key: &Self::Algorithm,
) -> Result<types::Merchant, ContainerError<Self::Error>> {
let merchant_id = new.merchant_id.to_string();
let tenant_id = new.tenant_id.to_string();
let output = self.inner.insert_merchant(new, key).await?;
self.cache_data((tenant_id, merchant_id), output.clone())
.await;
Ok(output)
}
}
4 changes: 2 additions & 2 deletions src/storage/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(super) struct MerchantInner {
created_at: time::PrimitiveDateTime,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Merchant {
pub tenant_id: String,
pub merchant_id: String,
Expand Down Expand Up @@ -88,7 +88,7 @@ pub(super) struct LockerNewInner<'a> {
hash_id: &'a str,
}

#[derive(Debug, Identifiable, Queryable)]
#[derive(Debug, Clone, Identifiable, Queryable)]
#[diesel(table_name = schema::hash_table)]
pub struct HashTable {
pub id: i32,
Expand Down

0 comments on commit f0d4cc4

Please sign in to comment.