use crate::db_state::SstType;
use crate::object_store_tag::{ObjectStoreCallTag, TableStoreKind};
pub(crate) trait GetPolicy: Send + Sync + 'static + std::fmt::Debug {
fn get_action(&self, tag: Option<&ObjectStoreCallTag>) -> GetAction;
fn head_action(&self, tag: Option<&ObjectStoreCallTag>) -> HeadAction;
}
#[derive(Debug, Clone, Default)]
pub(crate) struct DefaultGetPolicy;
impl GetPolicy for DefaultGetPolicy {
fn get_action(&self, tag: Option<&ObjectStoreCallTag>) -> GetAction {
let Some(tag) = tag else {
return GetAction::Bypass;
};
if matches!(tag.kind, TableStoreKind::Compactor | TableStoreKind::GC)
|| tag.sst_type == SstType::Wal
{
GetAction::Bypass
} else if tag.retry.is_some() {
GetAction::Refetch
} else {
GetAction::ReadThrough
}
}
fn head_action(&self, tag: Option<&ObjectStoreCallTag>) -> HeadAction {
match tag {
None => HeadAction::Bypass,
Some(t) if t.sst_type == SstType::Wal => HeadAction::Bypass,
Some(t) if t.kind == TableStoreKind::GC => HeadAction::Bypass,
Some(t) if t.kind == TableStoreKind::Compactor => HeadAction::Probe,
Some(_) => HeadAction::ReadThrough,
}
}
}
pub(crate) trait PutPolicy: Send + Sync + 'static + std::fmt::Debug {
fn put_action(&self, tag: Option<&ObjectStoreCallTag>) -> PutAction;
}
#[derive(Debug, Clone)]
pub(crate) struct DefaultPutPolicy {
pub(crate) put: CachePutConfig,
}
impl PutPolicy for DefaultPutPolicy {
fn put_action(&self, tag: Option<&ObjectStoreCallTag>) -> PutAction {
let Some(tag) = tag else {
return PutAction::Skip;
};
match tag.sst_type {
SstType::Wal => PutAction::Skip,
SstType::Compacted => match tag.kind {
TableStoreKind::Main if self.put.cache_on_flush => PutAction::Cache,
TableStoreKind::Compactor if self.put.cache_on_compaction => PutAction::Cache,
_ => PutAction::Skip,
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum GetAction {
Bypass,
Refetch,
ReadThrough,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HeadAction {
Bypass,
Probe,
ReadThrough,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PutAction {
Cache,
Skip,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct CachePutConfig {
pub(crate) cache_on_flush: bool,
pub(crate) cache_on_compaction: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::RetryReason;
use rstest::rstest;
fn tag(
kind: TableStoreKind,
sst_type: SstType,
retry: Option<RetryReason>,
) -> ObjectStoreCallTag {
ObjectStoreCallTag {
kind,
sst_type,
retry,
}
}
#[rstest]
#[case(
Some(tag(TableStoreKind::Compactor, SstType::Compacted, None)),
GetAction::Bypass
)]
#[case(
Some(tag(
TableStoreKind::Compactor,
SstType::Compacted,
Some(RetryReason::CrcMismatch)
)),
GetAction::Bypass
)]
#[case(
Some(tag(TableStoreKind::GC, SstType::Compacted, None)),
GetAction::Bypass
)]
#[case(Some(tag(TableStoreKind::Main, SstType::Wal, None)), GetAction::Bypass)]
#[case(
Some(tag(TableStoreKind::Reader, SstType::Wal, None)),
GetAction::Bypass
)]
#[case(
Some(tag(
TableStoreKind::Main,
SstType::Wal,
Some(RetryReason::BlockDecodeError)
)),
GetAction::Bypass
)]
#[case(
Some(tag(
TableStoreKind::Main,
SstType::Compacted,
Some(RetryReason::CrcMismatch)
)),
GetAction::Refetch
)]
#[case(
Some(tag(
TableStoreKind::Reader,
SstType::Compacted,
Some(RetryReason::BlockDecodeError)
)),
GetAction::Refetch
)]
#[case(
Some(tag(TableStoreKind::Main, SstType::Compacted, None)),
GetAction::ReadThrough
)]
#[case(
Some(tag(TableStoreKind::Reader, SstType::Compacted, None)),
GetAction::ReadThrough
)]
#[case(None, GetAction::Bypass)]
fn test_get_action(#[case] tag: Option<ObjectStoreCallTag>, #[case] expected: GetAction) {
assert_eq!(DefaultGetPolicy.get_action(tag.as_ref()), expected);
}
#[rstest]
#[case(None, HeadAction::Bypass)]
#[case(
Some(tag(TableStoreKind::Main, SstType::Wal, None)),
HeadAction::Bypass
)]
#[case(
Some(tag(TableStoreKind::Reader, SstType::Wal, None)),
HeadAction::Bypass
)]
#[case(
Some(tag(
TableStoreKind::Main,
SstType::Wal,
Some(RetryReason::BlockDecodeError)
)),
HeadAction::Bypass
)]
#[case(
Some(tag(TableStoreKind::Compactor, SstType::Compacted, None)),
HeadAction::Probe
)]
#[case(
Some(tag(
TableStoreKind::Compactor,
SstType::Compacted,
Some(RetryReason::CrcMismatch)
)),
HeadAction::Probe
)]
#[case(
Some(tag(TableStoreKind::Main, SstType::Compacted, None)),
HeadAction::ReadThrough
)]
#[case(
Some(tag(TableStoreKind::Reader, SstType::Compacted, None)),
HeadAction::ReadThrough
)]
#[case(
Some(tag(TableStoreKind::GC, SstType::Compacted, None)),
HeadAction::Bypass
)]
#[case(
Some(tag(
TableStoreKind::Main,
SstType::Compacted,
Some(RetryReason::CrcMismatch)
)),
HeadAction::ReadThrough
)]
fn test_head_action(#[case] tag: Option<ObjectStoreCallTag>, #[case] expected: HeadAction) {
assert_eq!(DefaultGetPolicy.head_action(tag.as_ref()), expected);
}
#[rstest]
#[case(
Some(tag(TableStoreKind::Main, SstType::Wal, None)),
CachePutConfig { cache_on_flush: true, cache_on_compaction: true },
PutAction::Skip
)]
#[case(
None,
CachePutConfig { cache_on_flush: true, cache_on_compaction: true },
PutAction::Skip
)]
#[case(
Some(tag(TableStoreKind::Main, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: true, cache_on_compaction: false },
PutAction::Cache
)]
#[case(
Some(tag(TableStoreKind::Main, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: false, cache_on_compaction: true },
PutAction::Skip
)]
#[case(
Some(tag(TableStoreKind::Compactor, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: false, cache_on_compaction: true },
PutAction::Cache
)]
#[case(
Some(tag(TableStoreKind::Compactor, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: true, cache_on_compaction: false },
PutAction::Skip
)]
#[case(
Some(tag(TableStoreKind::Reader, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: true, cache_on_compaction: true },
PutAction::Skip
)]
#[case(
Some(tag(TableStoreKind::GC, SstType::Compacted, None)),
CachePutConfig { cache_on_flush: true, cache_on_compaction: true },
PutAction::Skip
)]
fn test_put_action(
#[case] tag: Option<ObjectStoreCallTag>,
#[case] policy: CachePutConfig,
#[case] expected: PutAction,
) {
assert_eq!(
DefaultPutPolicy { put: policy }.put_action(tag.as_ref()),
expected
);
}
#[test]
fn test_default_put_policy_caches_nothing() {
let policy = CachePutConfig::default();
assert!(!policy.cache_on_flush);
assert!(!policy.cache_on_compaction);
for kind in [
TableStoreKind::Main,
TableStoreKind::Compactor,
TableStoreKind::Reader,
TableStoreKind::GC,
] {
assert_eq!(
DefaultPutPolicy { put: policy }.put_action(Some(&tag(
kind,
SstType::Compacted,
None
))),
PutAction::Skip
);
}
}
}