feat(routing): Use Moka cache for routing with cache invalidation (#3216)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sarthak Soni
2024-05-22 14:02:50 +05:30
committed by GitHub
parent 8fa2cd556b
commit 431560b7fb
9 changed files with 134 additions and 122 deletions

View File

@ -1245,17 +1245,6 @@ pub async fn update_payment_connector(
} }
} }
// The purpose of this merchant account update is just to update the
// merchant account `modified_at` field for KGraph cache invalidation
db.update_specific_fields_in_merchant(
merchant_id,
storage::MerchantAccountUpdate::ModifiedAtUpdate,
&key_store,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("error updating the merchant account when updating payment connector")?;
let payment_connector = storage::MerchantConnectorAccountUpdate::Update { let payment_connector = storage::MerchantConnectorAccountUpdate::Update {
merchant_id: None, merchant_id: None,
connector_type: Some(req.connector_type), connector_type: Some(req.connector_type),

View File

@ -3192,7 +3192,6 @@ where
connectors = routing::perform_eligibility_analysis_with_fallback( connectors = routing::perform_eligibility_analysis_with_fallback(
&state.clone(), &state.clone(),
key_store, key_store,
merchant_account.modified_at.assume_utc().unix_timestamp(),
connectors, connectors,
&TransactionData::Payment(payment_data), &TransactionData::Payment(payment_data),
eligible_connectors, eligible_connectors,
@ -3250,7 +3249,6 @@ where
connectors = routing::perform_eligibility_analysis_with_fallback( connectors = routing::perform_eligibility_analysis_with_fallback(
&state, &state,
key_store, key_store,
merchant_account.modified_at.assume_utc().unix_timestamp(),
connectors, connectors,
&TransactionData::Payment(payment_data), &TransactionData::Payment(payment_data),
eligible_connectors, eligible_connectors,
@ -3680,7 +3678,6 @@ where
let connectors = routing::perform_eligibility_analysis_with_fallback( let connectors = routing::perform_eligibility_analysis_with_fallback(
&state.clone(), &state.clone(),
key_store, key_store,
merchant_account.modified_at.assume_utc().unix_timestamp(),
connectors, connectors,
&transaction_data, &transaction_data,
eligible_connectors, eligible_connectors,

View File

@ -13,7 +13,6 @@ use api_models::{
payments::Address, payments::Address,
routing::ConnectorSelection, routing::ConnectorSelection,
}; };
use common_utils::static_cache::StaticCache;
use diesel_models::enums as storage_enums; use diesel_models::enums as storage_enums;
use error_stack::ResultExt; use error_stack::ResultExt;
use euclid::{ use euclid::{
@ -33,6 +32,7 @@ use rand::{
SeedableRng, SeedableRng,
}; };
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use storage_impl::redis::cache::{CGRAPH_CACHE, ROUTING_CACHE};
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
use crate::core::payouts; use crate::core::payouts;
@ -53,7 +53,7 @@ use crate::{
AppState, AppState,
}; };
pub(super) enum CachedAlgorithm { pub enum CachedAlgorithm {
Single(Box<routing_types::RoutableConnectorChoice>), Single(Box<routing_types::RoutableConnectorChoice>),
Priority(Vec<routing_types::RoutableConnectorChoice>), Priority(Vec<routing_types::RoutableConnectorChoice>),
VolumeSplit(Vec<routing_types::ConnectorVolumeSplit>), VolumeSplit(Vec<routing_types::ConnectorVolumeSplit>),
@ -73,7 +73,6 @@ pub struct SessionFlowRoutingInput<'a> {
pub struct SessionRoutingPmTypeInput<'a> { pub struct SessionRoutingPmTypeInput<'a> {
state: &'a AppState, state: &'a AppState,
key_store: &'a domain::MerchantKeyStore, key_store: &'a domain::MerchantKeyStore,
merchant_last_modified: i64,
attempt_id: &'a str, attempt_id: &'a str,
routing_algorithm: &'a MerchantAccountRoutingAlgorithm, routing_algorithm: &'a MerchantAccountRoutingAlgorithm,
backend_input: dsl_inputs::BackendInput, backend_input: dsl_inputs::BackendInput,
@ -84,10 +83,6 @@ pub struct SessionRoutingPmTypeInput<'a> {
))] ))]
profile_id: Option<String>, profile_id: Option<String>,
} }
static ROUTING_CACHE: StaticCache<CachedAlgorithm> = StaticCache::new();
static KGRAPH_CACHE: StaticCache<
hyperswitch_constraint_graph::ConstraintGraph<'_, euclid_dir::DirValue>,
> = StaticCache::new();
type RoutingResult<O> = oss_errors::CustomResult<O, errors::RoutingError>; type RoutingResult<O> = oss_errors::CustomResult<O, errors::RoutingError>;
@ -302,20 +297,15 @@ pub async fn perform_static_routing_v1<F: Clone>(
return Ok(fallback_config); return Ok(fallback_config);
}; };
let key = ensure_algorithm_cached_v1( let cached_algorithm = ensure_algorithm_cached_v1(
state, state,
merchant_id, merchant_id,
algorithm_ref.timestamp,
&algorithm_id, &algorithm_id,
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
Some(profile_id).cloned(), Some(profile_id).cloned(),
&api_enums::TransactionType::from(transaction_data), &api_enums::TransactionType::from(transaction_data),
) )
.await?; .await?;
let cached_algorithm: Arc<CachedAlgorithm> = ROUTING_CACHE
.retrieve(&key)
.change_context(errors::RoutingError::CacheMiss)
.attach_printable("Unable to retrieve cached routing algorithm even after refresh")?;
Ok(match cached_algorithm.as_ref() { Ok(match cached_algorithm.as_ref() {
CachedAlgorithm::Single(conn) => vec![(**conn).clone()], CachedAlgorithm::Single(conn) => vec![(**conn).clone()],
@ -342,11 +332,10 @@ pub async fn perform_static_routing_v1<F: Clone>(
async fn ensure_algorithm_cached_v1( async fn ensure_algorithm_cached_v1(
state: &AppState, state: &AppState,
merchant_id: &str, merchant_id: &str,
timestamp: i64,
algorithm_id: &str, algorithm_id: &str,
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>, #[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
transaction_type: &api_enums::TransactionType, transaction_type: &api_enums::TransactionType,
) -> RoutingResult<String> { ) -> RoutingResult<Arc<CachedAlgorithm>> {
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
let key = { let key = {
let profile_id = profile_id let profile_id = profile_id
@ -376,29 +365,24 @@ async fn ensure_algorithm_cached_v1(
} }
}; };
let present = ROUTING_CACHE let cached_algorithm = ROUTING_CACHE
.present(&key) .get_val::<Arc<CachedAlgorithm>>(key.as_str())
.change_context(errors::RoutingError::DslCachePoisoned) .await;
.attach_printable("Error checking presence of DSL")?;
let expired = ROUTING_CACHE let algorithm = if let Some(algo) = cached_algorithm {
.expired(&key, timestamp) algo
.change_context(errors::RoutingError::DslCachePoisoned) } else {
.attach_printable("Error checking expiry of DSL in cache")?;
if !present || expired {
refresh_routing_cache_v1( refresh_routing_cache_v1(
state, state,
key.clone(), key.clone(),
algorithm_id, algorithm_id,
timestamp,
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
profile_id, profile_id,
) )
.await?; .await?
}; };
Ok(key) Ok(algorithm)
} }
pub fn perform_straight_through_routing( pub fn perform_straight_through_routing(
@ -447,9 +431,8 @@ pub async fn refresh_routing_cache_v1(
state: &AppState, state: &AppState,
key: String, key: String,
algorithm_id: &str, algorithm_id: &str,
timestamp: i64,
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>, #[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
) -> RoutingResult<()> { ) -> RoutingResult<Arc<CachedAlgorithm>> {
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
let algorithm = { let algorithm = {
let algorithm = state let algorithm = state
@ -498,12 +481,11 @@ pub async fn refresh_routing_cache_v1(
} }
}; };
ROUTING_CACHE let arc_cached_algorithm = Arc::new(cached_algorithm);
.save(key, cached_algorithm, timestamp)
.change_context(errors::RoutingError::DslCachePoisoned)
.attach_printable("Error saving DSL to cache")?;
Ok(()) ROUTING_CACHE.push(key, arc_cached_algorithm.clone()).await;
Ok(arc_cached_algorithm)
} }
pub fn perform_volume_split( pub fn perform_volume_split(
@ -540,10 +522,9 @@ pub fn perform_volume_split(
Ok(splits.into_iter().map(|sp| sp.connector).collect()) Ok(splits.into_iter().map(|sp| sp.connector).collect())
} }
pub async fn get_merchant_kgraph<'a>( pub async fn get_merchant_cgraph<'a>(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_last_modified: i64,
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>, #[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
transaction_type: &api_enums::TransactionType, transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<'a, euclid_dir::DirValue>>> { ) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<'a, euclid_dir::DirValue>>> {
@ -556,10 +537,10 @@ pub async fn get_merchant_kgraph<'a>(
.get_required_value("profile_id") .get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?; .change_context(errors::RoutingError::ProfileIdMissing)?;
match transaction_type { match transaction_type {
api_enums::TransactionType::Payment => format!("kgraph_{}_{}", merchant_id, profile_id), api_enums::TransactionType::Payment => format!("cgraph_{}_{}", merchant_id, profile_id),
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
api_enums::TransactionType::Payout => { api_enums::TransactionType::Payout => {
format!("kgraph_po_{}_{}", merchant_id, profile_id) format!("cgraph_po_{}_{}", merchant_id, profile_id)
} }
} }
}; };
@ -571,45 +552,36 @@ pub async fn get_merchant_kgraph<'a>(
api_enums::TransactionType::Payout => format!("kgraph_po_{}", merchant_id), api_enums::TransactionType::Payout => format!("kgraph_po_{}", merchant_id),
}; };
let kgraph_present = KGRAPH_CACHE let cached_cgraph = CGRAPH_CACHE
.present(&key) .get_val::<Arc<hyperswitch_constraint_graph::ConstraintGraph<'_, euclid_dir::DirValue>>>(
.change_context(errors::RoutingError::KgraphCacheFailure) key.as_str(),
.attach_printable("when checking kgraph presence")?; )
.await;
let kgraph_expired = KGRAPH_CACHE let cgraph = if let Some(graph) = cached_cgraph {
.expired(&key, merchant_last_modified) graph
.change_context(errors::RoutingError::KgraphCacheFailure) } else {
.attach_printable("when checking kgraph expiry")?; refresh_cgraph_cache(
if !kgraph_present || kgraph_expired {
refresh_kgraph_cache(
state, state,
key_store, key_store,
merchant_last_modified,
key.clone(), key.clone(),
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
profile_id, profile_id,
transaction_type, transaction_type,
) )
.await?; .await?
} };
let cached_kgraph = KGRAPH_CACHE Ok(cgraph)
.retrieve(&key)
.change_context(errors::RoutingError::CacheMiss)
.attach_printable("when retrieving kgraph")?;
Ok(cached_kgraph)
} }
pub async fn refresh_kgraph_cache( pub async fn refresh_cgraph_cache<'a>(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
timestamp: i64,
key: String, key: String,
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>, #[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
transaction_type: &api_enums::TransactionType, transaction_type: &api_enums::TransactionType,
) -> RoutingResult<()> { ) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<'a, euclid_dir::DirValue>>> {
let mut merchant_connector_accounts = state let mut merchant_connector_accounts = state
.store .store
.find_merchant_connector_account_by_merchant_id_and_disabled_list( .find_merchant_connector_account_by_merchant_id_and_disabled_list(
@ -672,23 +644,21 @@ pub async fn refresh_kgraph_cache(
connector_configs, connector_configs,
default_configs, default_configs,
}; };
let kgraph = mca_graph::make_mca_graph(api_mcas, &config_pm_filters) let cgraph = Arc::new(
.change_context(errors::RoutingError::KgraphCacheRefreshFailed) mca_graph::make_mca_graph(api_mcas, &config_pm_filters)
.attach_printable("when construction kgraph")?; .change_context(errors::RoutingError::KgraphCacheRefreshFailed)
.attach_printable("when construction cgraph")?,
);
KGRAPH_CACHE CGRAPH_CACHE.push(key, Arc::clone(&cgraph)).await;
.save(key, kgraph, timestamp)
.change_context(errors::RoutingError::KgraphCacheRefreshFailed)
.attach_printable("when saving kgraph to cache")?;
Ok(()) Ok(cgraph)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn perform_kgraph_filtering( async fn perform_cgraph_filtering(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_last_modified: i64,
chosen: Vec<routing_types::RoutableConnectorChoice>, chosen: Vec<routing_types::RoutableConnectorChoice>,
backend_input: dsl_inputs::BackendInput, backend_input: dsl_inputs::BackendInput,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>, eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
@ -700,10 +670,9 @@ async fn perform_kgraph_filtering(
.into_context() .into_context()
.change_context(errors::RoutingError::KgraphAnalysisError)?, .change_context(errors::RoutingError::KgraphAnalysisError)?,
); );
let cached_kgraph = get_merchant_kgraph( let cached_cgraph = get_merchant_cgraph(
state, state,
key_store, key_store,
merchant_last_modified,
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
profile_id, profile_id,
transaction_type, transaction_type,
@ -717,7 +686,7 @@ async fn perform_kgraph_filtering(
let dir_val = euclid_choice let dir_val = euclid_choice
.into_dir_value() .into_dir_value()
.change_context(errors::RoutingError::KgraphAnalysisError)?; .change_context(errors::RoutingError::KgraphAnalysisError)?;
let kgraph_eligible = cached_kgraph let cgraph_eligible = cached_cgraph
.check_value_validity( .check_value_validity(
dir_val, dir_val,
&context, &context,
@ -730,7 +699,7 @@ async fn perform_kgraph_filtering(
let filter_eligible = let filter_eligible =
eligible_connectors.map_or(true, |list| list.contains(&routable_connector)); eligible_connectors.map_or(true, |list| list.contains(&routable_connector));
if kgraph_eligible && filter_eligible { if cgraph_eligible && filter_eligible {
final_selection.push(choice); final_selection.push(choice);
} }
} }
@ -741,7 +710,6 @@ async fn perform_kgraph_filtering(
pub async fn perform_eligibility_analysis<F: Clone>( pub async fn perform_eligibility_analysis<F: Clone>(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_last_modified: i64,
chosen: Vec<routing_types::RoutableConnectorChoice>, chosen: Vec<routing_types::RoutableConnectorChoice>,
transaction_data: &routing::TransactionData<'_, F>, transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>, eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
@ -753,10 +721,9 @@ pub async fn perform_eligibility_analysis<F: Clone>(
routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?, routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?,
}; };
perform_kgraph_filtering( perform_cgraph_filtering(
state, state,
key_store, key_store,
merchant_last_modified,
chosen, chosen,
backend_input, backend_input,
eligible_connectors, eligible_connectors,
@ -770,7 +737,6 @@ pub async fn perform_eligibility_analysis<F: Clone>(
pub async fn perform_fallback_routing<F: Clone>( pub async fn perform_fallback_routing<F: Clone>(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_last_modified: i64,
transaction_data: &routing::TransactionData<'_, F>, transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>, eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>, #[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
@ -801,10 +767,9 @@ pub async fn perform_fallback_routing<F: Clone>(
routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?, routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?,
}; };
perform_kgraph_filtering( perform_cgraph_filtering(
state, state,
key_store, key_store,
merchant_last_modified,
fallback_config, fallback_config,
backend_input, backend_input,
eligible_connectors, eligible_connectors,
@ -818,7 +783,6 @@ pub async fn perform_fallback_routing<F: Clone>(
pub async fn perform_eligibility_analysis_with_fallback<F: Clone>( pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
state: &AppState, state: &AppState,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_last_modified: i64,
chosen: Vec<routing_types::RoutableConnectorChoice>, chosen: Vec<routing_types::RoutableConnectorChoice>,
transaction_data: &routing::TransactionData<'_, F>, transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<Vec<api_enums::RoutableConnectors>>, eligible_connectors: Option<Vec<api_enums::RoutableConnectors>>,
@ -827,7 +791,6 @@ pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
let mut final_selection = perform_eligibility_analysis( let mut final_selection = perform_eligibility_analysis(
state, state,
key_store, key_store,
merchant_last_modified,
chosen, chosen,
transaction_data, transaction_data,
eligible_connectors.as_ref(), eligible_connectors.as_ref(),
@ -839,7 +802,6 @@ pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
let fallback_selection = perform_fallback_routing( let fallback_selection = perform_fallback_routing(
state, state,
key_store, key_store,
merchant_last_modified,
transaction_data, transaction_data,
eligible_connectors.as_ref(), eligible_connectors.as_ref(),
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
@ -873,11 +835,6 @@ pub async fn perform_session_flow_routing(
) -> RoutingResult<FxHashMap<api_enums::PaymentMethodType, routing_types::SessionRoutingChoice>> { ) -> RoutingResult<FxHashMap<api_enums::PaymentMethodType, routing_types::SessionRoutingChoice>> {
let mut pm_type_map: FxHashMap<api_enums::PaymentMethodType, FxHashMap<String, api::GetToken>> = let mut pm_type_map: FxHashMap<api_enums::PaymentMethodType, FxHashMap<String, api::GetToken>> =
FxHashMap::default(); FxHashMap::default();
let merchant_last_modified = session_input
.merchant_account
.modified_at
.assume_utc()
.unix_timestamp();
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
let routing_algorithm: MerchantAccountRoutingAlgorithm = { let routing_algorithm: MerchantAccountRoutingAlgorithm = {
@ -995,7 +952,6 @@ pub async fn perform_session_flow_routing(
let session_pm_input = SessionRoutingPmTypeInput { let session_pm_input = SessionRoutingPmTypeInput {
state: session_input.state, state: session_input.state,
key_store: session_input.key_store, key_store: session_input.key_store,
merchant_last_modified,
attempt_id: &session_input.payment_attempt.attempt_id, attempt_id: &session_input.payment_attempt.attempt_id,
routing_algorithm: &routing_algorithm, routing_algorithm: &routing_algorithm,
backend_input: backend_input.clone(), backend_input: backend_input.clone(),
@ -1035,10 +991,9 @@ async fn perform_session_routing_for_pm_type(
let chosen_connectors = match session_pm_input.routing_algorithm { let chosen_connectors = match session_pm_input.routing_algorithm {
MerchantAccountRoutingAlgorithm::V1(algorithm_ref) => { MerchantAccountRoutingAlgorithm::V1(algorithm_ref) => {
if let Some(ref algorithm_id) = algorithm_ref.algorithm_id { if let Some(ref algorithm_id) = algorithm_ref.algorithm_id {
let key = ensure_algorithm_cached_v1( let cached_algorithm = ensure_algorithm_cached_v1(
&session_pm_input.state.clone(), &session_pm_input.state.clone(),
merchant_id, merchant_id,
algorithm_ref.timestamp,
algorithm_id, algorithm_id,
#[cfg(feature = "business_profile_routing")] #[cfg(feature = "business_profile_routing")]
session_pm_input.profile_id.clone(), session_pm_input.profile_id.clone(),
@ -1046,11 +1001,6 @@ async fn perform_session_routing_for_pm_type(
) )
.await?; .await?;
let cached_algorithm = ROUTING_CACHE
.retrieve(&key)
.change_context(errors::RoutingError::CacheMiss)
.attach_printable("unable to retrieve cached routing algorithm")?;
match cached_algorithm.as_ref() { match cached_algorithm.as_ref() {
CachedAlgorithm::Single(conn) => vec![(**conn).clone()], CachedAlgorithm::Single(conn) => vec![(**conn).clone()],
CachedAlgorithm::Priority(plist) => plist.clone(), CachedAlgorithm::Priority(plist) => plist.clone(),
@ -1084,10 +1034,9 @@ async fn perform_session_routing_for_pm_type(
} }
}; };
let mut final_selection = perform_kgraph_filtering( let mut final_selection = perform_cgraph_filtering(
&session_pm_input.state.clone(), &session_pm_input.state.clone(),
session_pm_input.key_store, session_pm_input.key_store,
session_pm_input.merchant_last_modified,
chosen_connectors, chosen_connectors,
session_pm_input.backend_input.clone(), session_pm_input.backend_input.clone(),
None, None,
@ -1115,10 +1064,9 @@ async fn perform_session_routing_for_pm_type(
.await .await
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?; .change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
final_selection = perform_kgraph_filtering( final_selection = perform_cgraph_filtering(
&session_pm_input.state.clone(), &session_pm_input.state.clone(),
session_pm_input.key_store, session_pm_input.key_store,
session_pm_input.merchant_last_modified,
fallback, fallback,
session_pm_input.backend_input, session_pm_input.backend_input,
None, None,

View File

@ -669,7 +669,6 @@ pub async fn decide_payout_connector(
connectors = routing::perform_eligibility_analysis_with_fallback( connectors = routing::perform_eligibility_analysis_with_fallback(
state, state,
key_store, key_store,
merchant_account.modified_at.assume_utc().unix_timestamp(),
connectors, connectors,
&TransactionData::<()>::Payout(payout_data), &TransactionData::<()>::Payout(payout_data),
eligible_connectors, eligible_connectors,
@ -728,7 +727,6 @@ pub async fn decide_payout_connector(
connectors = routing::perform_eligibility_analysis_with_fallback( connectors = routing::perform_eligibility_analysis_with_fallback(
state, state,
key_store, key_store,
merchant_account.modified_at.assume_utc().unix_timestamp(),
connectors, connectors,
&TransactionData::<()>::Payout(payout_data), &TransactionData::<()>::Payout(payout_data),
eligible_connectors, eligible_connectors,

View File

@ -10,10 +10,11 @@ use diesel_models::{
}; };
use error_stack::ResultExt; use error_stack::ResultExt;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use storage_impl::redis::cache as redis_cache;
use crate::{ use crate::{
core::errors::{self, RouterResult}, core::errors::{self, RouterResult},
db::StorageInterface, db::{cache, StorageInterface},
types::{domain, storage}, types::{domain, storage},
utils::StringExt, utils::StringExt,
}; };
@ -239,6 +240,17 @@ pub async fn update_business_profile_active_algorithm_ref(
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to convert routing ref to value")?; .attach_printable("Failed to convert routing ref to value")?;
let merchant_id = current_business_profile.merchant_id.clone();
#[cfg(feature = "business_profile_routing")]
let profile_id = current_business_profile.profile_id.clone();
#[cfg(feature = "business_profile_routing")]
let routing_cache_key = redis_cache::CacheKind::Routing(
format!("routing_config_{merchant_id}_{profile_id}").into(),
);
#[cfg(not(feature = "business_profile_routing"))]
let routing_cache_key = redis_cache::CacheKind::Routing(format!("dsl_{merchant_id}").into());
let (routing_algorithm, payout_routing_algorithm) = match transaction_type { let (routing_algorithm, payout_routing_algorithm) = match transaction_type {
storage::enums::TransactionType::Payment => (Some(ref_val), None), storage::enums::TransactionType::Payment => (Some(ref_val), None),
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
@ -272,6 +284,11 @@ pub async fn update_business_profile_active_algorithm_ref(
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to update routing algorithm ref in business profile")?; .attach_printable("Failed to update routing algorithm ref in business profile")?;
cache::publish_into_redact_channel(db, [routing_cache_key])
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to invalidate routing cache")?;
Ok(()) Ok(())
} }

View File

@ -513,11 +513,29 @@ async fn publish_and_redact_merchant_account_cache(
.as_ref() .as_ref()
.map(|publishable_key| CacheKind::Accounts(publishable_key.into())); .map(|publishable_key| CacheKind::Accounts(publishable_key.into()));
#[cfg(feature = "business_profile_routing")]
let kgraph_key = merchant_account.default_profile.as_ref().map(|profile_id| {
CacheKind::CGraph(
format!(
"kgraph_{}_{}",
merchant_account.merchant_id.clone(),
profile_id,
)
.into(),
)
});
#[cfg(not(feature = "business_profile_routing"))]
let kgraph_key = Some(CacheKind::CGraph(
format!("kgraph_{}", merchant_account.merchant_id.clone()).into(),
));
let mut cache_keys = vec![CacheKind::Accounts( let mut cache_keys = vec![CacheKind::Accounts(
merchant_account.merchant_id.as_str().into(), merchant_account.merchant_id.as_str().into(),
)]; )];
cache_keys.extend(publishable_key.into_iter()); cache_keys.extend(publishable_key.into_iter());
cache_keys.extend(kgraph_key.into_iter());
super::cache::publish_into_redact_channel(store, cache_keys).await?; super::cache::publish_into_redact_channel(store, cache_keys).await?;
Ok(()) Ok(())

View File

@ -427,6 +427,9 @@ impl MerchantConnectorAccountInterface for Store {
cache::CacheKind::Accounts( cache::CacheKind::Accounts(
format!("{}_{}", _merchant_id, _merchant_connector_id).into(), format!("{}_{}", _merchant_id, _merchant_connector_id).into(),
), ),
cache::CacheKind::CGraph(
format!("cgraph_{}_{_profile_id}", _merchant_id).into(),
),
], ],
update_call, update_call,
) )
@ -475,9 +478,16 @@ impl MerchantConnectorAccountInterface for Store {
"profile_id".to_string(), "profile_id".to_string(),
))?; ))?;
super::cache::publish_and_redact( super::cache::publish_and_redact_multiple(
self, self,
cache::CacheKind::Accounts(format!("{}_{}", mca.merchant_id, _profile_id).into()), [
cache::CacheKind::Accounts(
format!("{}_{}", mca.merchant_id, _profile_id).into(),
),
cache::CacheKind::CGraph(
format!("cgraph_{}_{_profile_id}", mca.merchant_id).into(),
),
],
delete_call, delete_call,
) )
.await .await

View File

@ -21,6 +21,12 @@ const CONFIG_CACHE_PREFIX: &str = "config";
/// Prefix for accounts cache key /// Prefix for accounts cache key
const ACCOUNTS_CACHE_PREFIX: &str = "accounts"; const ACCOUNTS_CACHE_PREFIX: &str = "accounts";
/// Prefix for routing cache key
const ROUTING_CACHE_PREFIX: &str = "routing";
/// Prefix for kgraph cache key
const CGRAPH_CACHE_PREFIX: &str = "cgraph";
/// Prefix for all kinds of cache key /// Prefix for all kinds of cache key
const ALL_CACHE_PREFIX: &str = "all_cache_kind"; const ALL_CACHE_PREFIX: &str = "all_cache_kind";
@ -40,6 +46,14 @@ pub static CONFIG_CACHE: Lazy<Cache> = Lazy::new(|| Cache::new(CACHE_TTL, CACHE_
pub static ACCOUNTS_CACHE: Lazy<Cache> = pub static ACCOUNTS_CACHE: Lazy<Cache> =
Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY)));
/// Routing Cache
pub static ROUTING_CACHE: Lazy<Cache> =
Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY)));
/// CGraph Cache
pub static CGRAPH_CACHE: Lazy<Cache> =
Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY)));
/// Trait which defines the behaviour of types that's gonna be stored in Cache /// Trait which defines the behaviour of types that's gonna be stored in Cache
pub trait Cacheable: Any + Send + Sync + DynClone { pub trait Cacheable: Any + Send + Sync + DynClone {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -48,6 +62,8 @@ pub trait Cacheable: Any + Send + Sync + DynClone {
pub enum CacheKind<'a> { pub enum CacheKind<'a> {
Config(Cow<'a, str>), Config(Cow<'a, str>),
Accounts(Cow<'a, str>), Accounts(Cow<'a, str>),
Routing(Cow<'a, str>),
CGraph(Cow<'a, str>),
All(Cow<'a, str>), All(Cow<'a, str>),
} }
@ -56,6 +72,8 @@ impl<'a> From<CacheKind<'a>> for RedisValue {
let value = match kind { let value = match kind {
CacheKind::Config(s) => format!("{CONFIG_CACHE_PREFIX},{s}"), CacheKind::Config(s) => format!("{CONFIG_CACHE_PREFIX},{s}"),
CacheKind::Accounts(s) => format!("{ACCOUNTS_CACHE_PREFIX},{s}"), CacheKind::Accounts(s) => format!("{ACCOUNTS_CACHE_PREFIX},{s}"),
CacheKind::Routing(s) => format!("{ROUTING_CACHE_PREFIX},{s}"),
CacheKind::CGraph(s) => format!("{CGRAPH_CACHE_PREFIX},{s}"),
CacheKind::All(s) => format!("{ALL_CACHE_PREFIX},{s}"), CacheKind::All(s) => format!("{ALL_CACHE_PREFIX},{s}"),
}; };
Self::from_string(value) Self::from_string(value)
@ -73,6 +91,8 @@ impl<'a> TryFrom<RedisValue> for CacheKind<'a> {
match split.0 { match split.0 {
ACCOUNTS_CACHE_PREFIX => Ok(Self::Accounts(Cow::Owned(split.1.to_string()))), ACCOUNTS_CACHE_PREFIX => Ok(Self::Accounts(Cow::Owned(split.1.to_string()))),
CONFIG_CACHE_PREFIX => Ok(Self::Config(Cow::Owned(split.1.to_string()))), CONFIG_CACHE_PREFIX => Ok(Self::Config(Cow::Owned(split.1.to_string()))),
ROUTING_CACHE_PREFIX => Ok(Self::Routing(Cow::Owned(split.1.to_string()))),
CGRAPH_CACHE_PREFIX => Ok(Self::CGraph(Cow::Owned(split.1.to_string()))),
ALL_CACHE_PREFIX => Ok(Self::All(Cow::Owned(split.1.to_string()))), ALL_CACHE_PREFIX => Ok(Self::All(Cow::Owned(split.1.to_string()))),
_ => Err(validation_err.into()), _ => Err(validation_err.into()),
} }
@ -123,6 +143,11 @@ impl Cache {
(*val).as_any().downcast_ref::<T>().cloned() (*val).as_any().downcast_ref::<T>().cloned()
} }
/// Check if a key exists in cache
pub async fn exists(&self, key: &str) -> bool {
self.inner.contains_key(key)
}
pub async fn remove(&self, key: &str) { pub async fn remove(&self, key: &str) {
self.inner.invalidate(key).await; self.inner.invalidate(key).await;
} }

View File

@ -2,7 +2,7 @@ use error_stack::ResultExt;
use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue}; use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue};
use router_env::logger; use router_env::logger;
use crate::redis::cache::{CacheKind, ACCOUNTS_CACHE, CONFIG_CACHE}; use crate::redis::cache::{CacheKind, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, ROUTING_CACHE};
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait PubSubInterface { pub trait PubSubInterface {
@ -67,9 +67,19 @@ impl PubSubInterface for redis_interface::RedisConnectionPool {
ACCOUNTS_CACHE.remove(key.as_ref()).await; ACCOUNTS_CACHE.remove(key.as_ref()).await;
key key
} }
CacheKind::CGraph(key) => {
CGRAPH_CACHE.remove(key.as_ref()).await;
key
}
CacheKind::Routing(key) => {
ROUTING_CACHE.remove(key.as_ref()).await;
key
}
CacheKind::All(key) => { CacheKind::All(key) => {
CONFIG_CACHE.remove(key.as_ref()).await; CONFIG_CACHE.remove(key.as_ref()).await;
ACCOUNTS_CACHE.remove(key.as_ref()).await; ACCOUNTS_CACHE.remove(key.as_ref()).await;
CGRAPH_CACHE.remove(key.as_ref()).await;
ROUTING_CACHE.remove(key.as_ref()).await;
key key
} }
}; };