diff --git a/README.md b/README.md index 78b17b69..fa9fd13f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ use std::sync::Arc; use anyhow::Result; use chrono::Datelike; use foyer::{ - CacheContext, DirectFsDeviceOptionsBuilder, FifoPicker, HybridCache, HybridCacheBuilder, LruConfig, + CacheContext, DirectFsDeviceOptionsBuilder, HybridCache, HybridCacheBuilder, LfuConfig, LruConfig, OrderPicker, RateLimitPicker, RecoverMode, RuntimeConfigBuilder, TombstoneLogConfigBuilder, }; use tempfile::tempdir; @@ -118,7 +118,7 @@ async fn main() -> Result<()> { .with_flushers(2) .with_reclaimers(2) .with_clean_region_threshold(4) - .with_eviction_pickers(vec![Box::::default()]) + .with_eviction_pickers(vec![Box::new(OrderPicker::new(LfuConfig::default()))]) .with_admission_picker(Arc::new(RateLimitPicker::new(100 * 1024 * 1024))) .with_reinsertion_picker(Arc::new(RateLimitPicker::new(10 * 1024 * 1024))) .with_compression(foyer::Compression::Lz4) diff --git a/examples/hybrid_full.rs b/examples/hybrid_full.rs index 6fdec94a..7797b0a5 100644 --- a/examples/hybrid_full.rs +++ b/examples/hybrid_full.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use anyhow::Result; use chrono::Datelike; use foyer::{ - CacheContext, DirectFsDeviceOptionsBuilder, FifoPicker, HybridCache, HybridCacheBuilder, LruConfig, + CacheContext, DirectFsDeviceOptionsBuilder, HybridCache, HybridCacheBuilder, LfuConfig, LruConfig, OrderPicker, RateLimitPicker, RecoverMode, RuntimeConfigBuilder, TombstoneLogConfigBuilder, }; use tempfile::tempdir; @@ -49,7 +49,7 @@ async fn main() -> Result<()> { .with_flushers(2) .with_reclaimers(2) .with_clean_region_threshold(4) - .with_eviction_pickers(vec![Box::::default()]) + .with_eviction_pickers(vec![Box::new(OrderPicker::new(LfuConfig::default()))]) .with_admission_picker(Arc::new(RateLimitPicker::new(100 * 1024 * 1024))) .with_reinsertion_picker(Arc::new(RateLimitPicker::new(10 * 1024 * 1024))) .with_compression(foyer::Compression::Lz4) diff --git a/foyer-bench/src/main.rs b/foyer-bench/src/main.rs index 1113f18e..70de40d2 100644 --- a/foyer-bench/src/main.rs +++ b/foyer-bench/src/main.rs @@ -14,8 +14,8 @@ use bytesize::MIB; use foyer::{ - DirectFsDeviceOptionsBuilder, FifoPicker, HybridCache, HybridCacheBuilder, LfuConfig, RateLimitPicker, - RuntimeConfigBuilder, + DirectFsDeviceOptionsBuilder, EvictionConfig, FifoConfig, HybridCache, HybridCacheBuilder, LfuConfig, LruConfig, + OrderPicker, RateLimitPicker, RuntimeConfigBuilder, S3FifoConfig, }; use metrics_exporter_prometheus::PrometheusBuilder; @@ -36,7 +36,7 @@ use std::{ }; use analyze::{analyze, monitor, Metrics}; -use clap::Parser; +use clap::{Parser, ValueEnum}; use futures::future::join_all; use itertools::Itertools; @@ -62,6 +62,14 @@ pub struct Args { #[arg(long, default_value_t = 1024)] mem: usize, + /// In-memory cache eviction algorithm. + #[arg(long, value_enum, default_value_t = Eviction::Lfu)] + mem_eviction: Eviction, + + /// Disk cache eviction algorithm. + #[arg(long, value_enum, default_value_t = Eviction::Lfu)] + disk_eviction: Eviction, + /// Disk cache capacity. (MiB) #[arg(long, default_value_t = 1024)] disk: usize, @@ -175,6 +183,26 @@ pub struct Args { flush: bool, } +#[derive(Debug, Clone, ValueEnum)] +#[clap(rename_all = "lower")] +enum Eviction { + Fifo, + S3Fifo, + Lru, + Lfu, +} + +impl From for EvictionConfig { + fn from(value: Eviction) -> Self { + match value { + Eviction::Fifo => Self::Fifo(FifoConfig::default()), + Eviction::S3Fifo => Self::S3Fifo(S3FifoConfig::default()), + Eviction::Lru => Self::Lru(LruConfig::default()), + Eviction::Lfu => Self::Lfu(LfuConfig::default()), + } + } +} + #[derive(Debug)] enum TimeSeriesDistribution { None, @@ -372,7 +400,7 @@ async fn main() { let mut builder = HybridCacheBuilder::new() .memory(args.mem * MIB as usize) .with_shards(args.shards) - .with_eviction_config(LfuConfig::default()) + .with_eviction_config(args.mem_eviction.clone()) .with_weighter(|_: &u64, value: &Value| u64::BITS as usize / 8 + value.len()) .storage() .with_device_config( @@ -386,7 +414,7 @@ async fn main() { .with_recover_concurrency(args.recover_concurrency) .with_flushers(args.flushers) .with_reclaimers(args.reclaimers) - .with_eviction_pickers(vec![Box::::default()]) + .with_eviction_pickers(vec![Box::new(OrderPicker::new(args.disk_eviction.clone()))]) .with_compression( args.compression .as_str() diff --git a/foyer-memory/src/generic.rs b/foyer-memory/src/generic.rs index 333bd717..c5e7c253 100644 --- a/foyer-memory/src/generic.rs +++ b/foyer-memory/src/generic.rs @@ -56,7 +56,7 @@ struct CacheSharedState { // TODO(MrCroxx): use `expect` after `lint_reasons` is stable. #[allow(clippy::type_complexity)] -struct CacheShard +struct GenericCacheShard where K: Key, V: Value, @@ -76,7 +76,7 @@ where state: Arc>, } -impl CacheShard +impl GenericCacheShard where K: Key, V: Value, @@ -368,7 +368,7 @@ where } } -impl Drop for CacheShard +impl Drop for GenericCacheShard where K: Key, V: Value, @@ -465,7 +465,7 @@ where I: Indexer, S: BuildHasher + Send + Sync + 'static, { - shards: Vec>>, + shards: Vec>>, capacity: usize, usages: Vec>, @@ -496,7 +496,9 @@ where let shards = usages .iter() - .map(|usage| CacheShard::new(shard_capacity, &config.eviction_config, usage.clone(), context.clone())) + .map(|usage| { + GenericCacheShard::new(shard_capacity, &config.eviction_config, usage.clone(), context.clone()) + }) .map(Mutex::new) .collect_vec(); diff --git a/foyer-memory/src/lib.rs b/foyer-memory/src/lib.rs index 8fc36691..e447421a 100644 --- a/foyer-memory/src/lib.rs +++ b/foyer-memory/src/lib.rs @@ -71,4 +71,6 @@ mod indexer; mod metrics; mod prelude; +mod order; + pub use prelude::*; diff --git a/foyer-memory/src/order.rs b/foyer-memory/src/order.rs new file mode 100644 index 00000000..58ddbff0 --- /dev/null +++ b/foyer-memory/src/order.rs @@ -0,0 +1,430 @@ +// Copyright 2024 Foyer Project Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{ + borrow::Borrow, + fmt::Debug, + hash::{BuildHasher, Hash}, + ptr::NonNull, +}; + +use ahash::RandomState; +use foyer_common::code::{Key, Value}; +use itertools::Itertools; + +use crate::{ + eviction::{ + fifo::{Fifo, FifoHandle}, + lfu::{Lfu, LfuHandle}, + lru::{Lru, LruHandle}, + s3fifo::{S3Fifo, S3FifoHandle}, + Eviction, + }, + handle::{Handle, HandleExt, KeyedHandle}, + indexer::{HashTableIndexer, Indexer}, + CacheContext, EvictionConfig, +}; + +pub struct GenericOrderMap +where + K: Key, + V: Value, + E: Eviction, + E::Handle: KeyedHandle, + I: Indexer, + S: BuildHasher + Send + Sync + 'static, +{ + indexer: I, + eviction: E, + hash_builder: S, +} + +impl GenericOrderMap +where + K: Key, + V: Value, + E: Eviction, + E::Handle: KeyedHandle, + I: Indexer, + S: BuildHasher + Send + Sync + 'static, +{ + fn new(capacity: usize, config: E::Config, hash_builder: S) -> Self { + let eviction = unsafe { E::new(capacity, &config) }; + Self { + indexer: I::new(), + eviction, + hash_builder, + } + } + + fn insert(&mut self, key: K, value: V) { + self.insert_with_context(key, value, CacheContext::default()) + } + + fn insert_with_context(&mut self, key: K, value: V, context: CacheContext) { + unsafe { + let hash = self.hash_builder.hash_one(&key); + + let mut handle = Box::::default(); + handle.init(hash, (key, value), 1, context.into()); + let ptr = NonNull::new_unchecked(Box::into_raw(handle)); + if let Some(old) = self.indexer.insert(ptr) { + self.eviction.remove(old); + debug_assert!(!old.as_ref().base().is_in_indexer()); + debug_assert!(!old.as_ref().base().is_in_eviction()); + let _ = Box::from_raw(old.as_ptr()); + } + self.eviction.push(ptr); + + debug_assert!(ptr.as_ref().base().is_in_indexer()); + debug_assert!(ptr.as_ref().base().is_in_eviction()); + } + } + + fn contains(&self, key: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + unsafe { + let hash = self.hash_builder.hash_one(key); + self.indexer.get(hash, key).is_some() + } + } + + fn get(&mut self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + unsafe { + let hash = self.hash_builder.hash_one(key); + + let ptr = self.indexer.get(hash, key)?; + + self.eviction.acquire(ptr); + self.eviction.release(ptr); + + debug_assert!(ptr.as_ref().base().is_in_indexer()); + debug_assert!(ptr.as_ref().base().is_in_eviction()); + + Some(&ptr.as_ref().base().data_unwrap_unchecked().1) + } + } + + fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq, + { + unsafe { + let hash = self.hash_builder.hash_one(key); + + let ptr = self.indexer.remove(hash, key)?; + self.eviction.remove(ptr); + + debug_assert!(!ptr.as_ref().base().is_in_indexer()); + debug_assert!(!ptr.as_ref().base().is_in_eviction()); + + let mut handle = Box::from_raw(ptr.as_ptr()); + let ((_, v), _, _) = handle.base_mut().take(); + + Some(v) + } + } + + fn pop(&mut self) -> Option<(K, V)> { + unsafe { + let ptr = self.eviction.pop()?; + let p = self + .indexer + .remove( + ptr.as_ref().base().hash(), + &ptr.as_ref().base().data_unwrap_unchecked().0, + ) + .unwrap(); + + debug_assert_eq!(p, ptr); + debug_assert!(!ptr.as_ref().base().is_in_indexer()); + debug_assert!(!ptr.as_ref().base().is_in_eviction()); + + let mut handle = Box::from_raw(ptr.as_ptr()); + let ((k, v), _, _) = handle.base_mut().take(); + + Some((k, v)) + } + } + + fn clear(&mut self) { + unsafe { + // TODO(MrCroxx): Avoid collecting here? + let ptrs = self.indexer.drain().collect_vec(); + let eptrs = self.eviction.clear(); + + // Assert that the handles in the indexer covers the handles in the eviction container. + if cfg!(debug_assertions) { + use std::{collections::HashSet as StdHashSet, hash::RandomState as StdRandomState}; + let ptrs: StdHashSet<_, StdRandomState> = StdHashSet::from_iter(ptrs.iter().copied()); + let eptrs: StdHashSet<_, StdRandomState> = StdHashSet::from_iter(eptrs.iter().copied()); + debug_assert!((&eptrs - &ptrs).is_empty()); + } + + for ptr in ptrs { + debug_assert!(!ptr.as_ref().base().is_in_indexer()); + debug_assert!(!ptr.as_ref().base().is_in_eviction()); + let _ = Box::from_raw(ptr.as_ptr()); + } + } + } + + fn len(&self) -> usize { + self.eviction.len() + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Drop for GenericOrderMap +where + K: Key, + V: Value, + E: Eviction, + E::Handle: KeyedHandle, + I: Indexer, + S: BuildHasher + Send + Sync + 'static, +{ + fn drop(&mut self) { + self.clear(); + } +} + +pub type FifoOrderMap = GenericOrderMap, HashTableIndexer>, S>; +pub type S3FifoOrderMap = GenericOrderMap, HashTableIndexer>, S>; +pub type LruOrderMap = GenericOrderMap, HashTableIndexer>, S>; +pub type LfuOrderMap = GenericOrderMap, HashTableIndexer>, S>; + +pub enum OrderMap +where + K: Key, + V: Value, + S: BuildHasher + Send + Sync + 'static, +{ + Fifo(FifoOrderMap), + S3Fifo(S3FifoOrderMap), + Lru(LruOrderMap), + Lfu(LfuOrderMap), +} + +impl Debug for OrderMap +where + K: Key, + V: Value, + S: BuildHasher + Send + Sync + 'static + Default, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Fifo(_) => f.debug_tuple("Fifo").finish(), + Self::S3Fifo(_) => f.debug_tuple("S3Fifo").finish(), + Self::Lru(_) => f.debug_tuple("Lru").finish(), + Self::Lfu(_) => f.debug_tuple("Lfu").finish(), + } + } +} + +impl OrderMap +where + K: Key, + V: Value, + S: BuildHasher + Send + Sync + 'static + Default, +{ + pub fn new(capacity: usize, config: impl Into) -> Self { + Self::with_hash_builder(capacity, config, S::default()) + } +} + +impl OrderMap +where + K: Key, + V: Value, + S: BuildHasher + Send + Sync + 'static, +{ + pub fn with_hash_builder(capacity: usize, config: impl Into, hash_builder: S) -> Self { + match config.into() { + EvictionConfig::Fifo(config) => Self::Fifo(GenericOrderMap::new(capacity, config, hash_builder)), + EvictionConfig::S3Fifo(config) => Self::S3Fifo(GenericOrderMap::new(capacity, config, hash_builder)), + EvictionConfig::Lru(config) => Self::Lru(GenericOrderMap::new(capacity, config, hash_builder)), + EvictionConfig::Lfu(config) => Self::Lfu(GenericOrderMap::new(capacity, config, hash_builder)), + } + } + + pub fn insert(&mut self, key: K, value: V) { + match self { + OrderMap::Fifo(om) => om.insert(key, value), + OrderMap::S3Fifo(om) => om.insert(key, value), + OrderMap::Lru(om) => om.insert(key, value), + OrderMap::Lfu(om) => om.insert(key, value), + } + } + + pub fn insert_with_context(&mut self, key: K, value: V, context: CacheContext) { + match self { + OrderMap::Fifo(om) => om.insert_with_context(key, value, context), + OrderMap::S3Fifo(om) => om.insert_with_context(key, value, context), + OrderMap::Lru(om) => om.insert_with_context(key, value, context), + OrderMap::Lfu(om) => om.insert_with_context(key, value, context), + } + } + + pub fn contains(&self, key: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + match self { + OrderMap::Fifo(om) => om.contains(key), + OrderMap::S3Fifo(om) => om.contains(key), + OrderMap::Lru(om) => om.contains(key), + OrderMap::Lfu(om) => om.contains(key), + } + } + + pub fn get(&mut self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + match self { + OrderMap::Fifo(om) => om.get(key), + OrderMap::S3Fifo(om) => om.get(key), + OrderMap::Lru(om) => om.get(key), + OrderMap::Lfu(om) => om.get(key), + } + } + + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq, + { + match self { + OrderMap::Fifo(om) => om.remove(key), + OrderMap::S3Fifo(om) => om.remove(key), + OrderMap::Lru(om) => om.remove(key), + OrderMap::Lfu(om) => om.remove(key), + } + } + + pub fn pop(&mut self) -> Option<(K, V)> { + match self { + OrderMap::Fifo(om) => om.pop(), + OrderMap::S3Fifo(om) => om.pop(), + OrderMap::Lru(om) => om.pop(), + OrderMap::Lfu(om) => om.pop(), + } + } + + pub fn clear(&mut self) { + match self { + OrderMap::Fifo(om) => om.clear(), + OrderMap::S3Fifo(om) => om.clear(), + OrderMap::Lru(om) => om.clear(), + OrderMap::Lfu(om) => om.clear(), + } + } + + pub fn len(&self) -> usize { + match self { + OrderMap::Fifo(om) => om.len(), + OrderMap::S3Fifo(om) => om.len(), + OrderMap::Lru(om) => om.len(), + OrderMap::Lfu(om) => om.len(), + } + } + + pub fn is_empty(&self) -> bool { + match self { + OrderMap::Fifo(om) => om.is_empty(), + OrderMap::S3Fifo(om) => om.is_empty(), + OrderMap::Lru(om) => om.is_empty(), + OrderMap::Lfu(om) => om.is_empty(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{FifoConfig, LfuConfig, LruConfig, S3FifoConfig}; + + use super::*; + + fn case(mut om: OrderMap) { + om.insert(1, 1); + assert_eq!(om.get(&1), Some(&1)); + assert_eq!(om.len(), 1); + + om.insert(1, 1); + assert_eq!(om.get(&1), Some(&1)); + assert_eq!(om.len(), 1); + + om.insert(1, 2); + assert_eq!(om.get(&1), Some(&2)); + assert_eq!(om.len(), 1); + + om.insert(3, 3); + assert_eq!(om.get(&3), Some(&3)); + assert_eq!(om.len(), 2); + + let res = om.remove(&1); + assert_eq!(res, Some(2)); + assert_eq!(om.len(), 1); + + let res = om.pop(); + assert_eq!(res, Some((3, 3))); + assert!(om.is_empty()); + + om.insert(1, 1); + assert_eq!(om.get(&1), Some(&1)); + assert_eq!(om.len(), 1); + + om.clear(); + assert!(om.is_empty()); + assert!(!om.contains(&1)); + } + + #[test] + fn test_fifo_order_map() { + let om = OrderMap::new(10, FifoConfig::default()); + case(om); + } + + #[test] + fn test_s3fifo_order_map() { + let om = OrderMap::new(10, S3FifoConfig::default()); + case(om); + } + + #[test] + fn test_lru_order_map() { + let om = OrderMap::new(10, LruConfig::default()); + case(om); + } + + #[test] + fn test_lfu_order_map() { + let om = OrderMap::new(10, LfuConfig::default()); + case(om); + } +} diff --git a/foyer-memory/src/prelude.rs b/foyer-memory/src/prelude.rs index b637c726..d35c557e 100644 --- a/foyer-memory/src/prelude.rs +++ b/foyer-memory/src/prelude.rs @@ -18,5 +18,6 @@ pub use crate::{ eviction::{fifo::FifoConfig, lfu::LfuConfig, lru::LruConfig, s3fifo::S3FifoConfig}, generic::Weighter, metrics::Metrics, + order::OrderMap, }; pub use ahash::RandomState; diff --git a/foyer-storage/src/large/generic.rs b/foyer-storage/src/large/generic.rs index a1849b02..a2123241 100644 --- a/foyer-storage/src/large/generic.rs +++ b/foyer-storage/src/large/generic.rs @@ -197,7 +197,10 @@ where }; let indexer = Indexer::new(config.indexer_shards); - let eviction_pickers = std::mem::take(&mut config.eviction_pickers); + let mut eviction_pickers = std::mem::take(&mut config.eviction_pickers); + for picker in eviction_pickers.iter_mut() { + picker.init(device.regions()); + } let reclaim_semaphore = Arc::new(Semaphore::new(0)); let region_manager = RegionManager::new(device.clone(), eviction_pickers, reclaim_semaphore.clone()); let sequence = AtomicSequence::default(); @@ -470,7 +473,7 @@ mod tests { use crate::{ device::direct_fs::{DirectFsDevice, DirectFsDeviceOptions}, - picker::utils::{AdmitAllPicker, FifoPicker, RejectAllPicker}, + picker::utils::{AdmitAllPicker, DebugFifoPicker, RejectAllPicker}, test_utils::BiasedPicker, tombstone::TombstoneLogConfigBuilder, }; @@ -513,7 +516,7 @@ mod tests { flushers: 1, reclaimers: 1, clean_region_threshold: 1, - eviction_pickers: vec![Box::::default()], + eviction_pickers: vec![Box::::default()], admission_picker, reinsertion_picker: Arc::>::default(), tombstone_log_config: None, @@ -541,7 +544,7 @@ mod tests { flushers: 1, reclaimers: 1, clean_region_threshold: 1, - eviction_pickers: vec![Box::::default()], + eviction_pickers: vec![Box::::default()], admission_picker: Arc::>::default(), reinsertion_picker, tombstone_log_config: None, @@ -569,7 +572,7 @@ mod tests { flushers: 1, reclaimers: 1, clean_region_threshold: 1, - eviction_pickers: vec![Box::::default()], + eviction_pickers: vec![Box::::default()], admission_picker: Arc::>::default(), reinsertion_picker: Arc::>::default(), tombstone_log_config: Some(TombstoneLogConfigBuilder::new(path).with_flush(true).build()), diff --git a/foyer-storage/src/picker/mod.rs b/foyer-storage/src/picker/mod.rs index c1077536..cbe4cc13 100644 --- a/foyer-storage/src/picker/mod.rs +++ b/foyer-storage/src/picker/mod.rs @@ -28,6 +28,10 @@ pub trait ReinsertionPicker: Send + Sync + 'static + Debug { } pub trait EvictionPicker: Send + Sync + 'static + Debug { + // TODO(MrCroxx): use `expect` after `lint_reasons` is stable. + #[allow(unused_variables)] + fn init(&mut self, regions: usize) {} + fn pick(&mut self, evictable: &HashMap>) -> Option; fn on_region_evictable(&mut self, evictable: &HashMap>, region: RegionId); diff --git a/foyer-storage/src/picker/utils.rs b/foyer-storage/src/picker/utils.rs index 64b457a5..3fc624a8 100644 --- a/foyer-storage/src/picker/utils.rs +++ b/foyer-storage/src/picker/utils.rs @@ -23,6 +23,7 @@ use std::{ }; use foyer_common::{code::StorageKey, rated_ticket::RatedTicket}; +use foyer_memory::{EvictionConfig, OrderMap}; use crate::{device::RegionId, region::RegionStats, statistics::Statistics}; @@ -189,11 +190,11 @@ where } #[derive(Debug, Default)] -pub struct FifoPicker { +pub struct DebugFifoPicker { queue: VecDeque, } -impl EvictionPicker for FifoPicker { +impl EvictionPicker for DebugFifoPicker { fn pick(&mut self, _: &HashMap>) -> Option { let res = self.queue.front().copied(); tracing::trace!("[fifo picker]: pick {res:?}"); @@ -212,13 +213,61 @@ impl EvictionPicker for FifoPicker { } } +#[derive(Debug)] +pub struct OrderPicker { + order: Option>, + config: Option, +} + +impl OrderPicker { + pub fn new(config: impl Into) -> Self { + Self { + order: None, + config: Some(config.into()), + } + } +} + +impl EvictionPicker for OrderPicker { + fn init(&mut self, regions: usize) { + self.order = Some(OrderMap::new(regions, self.config.take().unwrap())); + } + + fn pick(&mut self, _: &HashMap>) -> Option { + let order = unsafe { self.order.as_mut().unwrap_unchecked() }; + + let id = order.pop().map(|(id, _)| id)?; + // `pick` must not modify the evictable states, insert the popped value back. + // No matter what, the value will be removed because the picker always picks if there are evictables. + order.insert(id, ()); + tracing::trace!("[order picker ({order:?})]: pick {id}", order = order); + Some(id) + } + + fn on_region_evictable(&mut self, _: &HashMap>, region: RegionId) { + let order = unsafe { self.order.as_mut().unwrap_unchecked() }; + + tracing::trace!("[order picker ({order:?})]: {region} is evictable", order = order); + debug_assert!(!order.contains(®ion)); + order.insert(region, ()); + } + + fn on_region_evict(&mut self, _: &HashMap>, region: RegionId) { + let order = unsafe { self.order.as_mut().unwrap_unchecked() }; + + tracing::trace!("[order picker ({order:?})]: {region} is evicted", order = order); + debug_assert!(order.contains(®ion)); + order.remove(®ion); + } +} + #[cfg(test)] mod tests { use super::*; #[test] fn test_fifo_picker() { - let mut picker = FifoPicker::default(); + let mut picker = DebugFifoPicker::default(); let m = HashMap::new(); (0..10).for_each(|i| picker.on_region_evictable(&m, i)); diff --git a/foyer-storage/src/prelude.rs b/foyer-storage/src/prelude.rs index b6bd8fcb..dc51adf0 100644 --- a/foyer-storage/src/prelude.rs +++ b/foyer-storage/src/prelude.rs @@ -21,7 +21,7 @@ pub use crate::{ }, large::recover::RecoverMode, picker::{ - utils::{AdmitAllPicker, FifoPicker, RateLimitPicker, RejectAllPicker}, + utils::{AdmitAllPicker, OrderPicker, RateLimitPicker, RejectAllPicker}, AdmissionPicker, EvictionPicker, ReinsertionPicker, }, statistics::Statistics, diff --git a/foyer-storage/src/store/runtime.rs b/foyer-storage/src/store/runtime.rs index 92b025e1..eaa7f0a9 100644 --- a/foyer-storage/src/store/runtime.rs +++ b/foyer-storage/src/store/runtime.rs @@ -188,7 +188,7 @@ mod tests { generic::{GenericStore, GenericStoreConfig}, recover::RecoverMode, }, - picker::utils::{AdmitAllPicker, FifoPicker, RejectAllPicker}, + picker::utils::{AdmitAllPicker, DebugFifoPicker, RejectAllPicker}, storage::Storage, }; @@ -220,7 +220,7 @@ mod tests { flushers: 1, reclaimers: 1, clean_region_threshold: 1, - eviction_pickers: vec![Box::::default()], + eviction_pickers: vec![Box::::default()], admission_picker: Arc::>::default(), reinsertion_picker: Arc::>::default(), tombstone_log_config: None, diff --git a/foyer/src/prelude.rs b/foyer/src/prelude.rs index 01de8017..ed8e6a90 100644 --- a/foyer/src/prelude.rs +++ b/foyer/src/prelude.rs @@ -27,7 +27,7 @@ pub use memory::{CacheContext, EvictionConfig, FetchState, FifoConfig, LfuConfig pub use storage::{ AdmissionPicker, AdmitAllPicker, Compression, Device, DeviceExt, DeviceOptions, DirectFileDevice, DirectFileDeviceOptions, DirectFileDeviceOptionsBuilder, DirectFsDevice, DirectFsDeviceOptions, - DirectFsDeviceOptionsBuilder, EnqueueFuture, EvictionPicker, FifoPicker, RateLimitPicker, RecoverMode, + DirectFsDeviceOptionsBuilder, EnqueueFuture, EvictionPicker, OrderPicker, RateLimitPicker, RecoverMode, ReinsertionPicker, RejectAllPicker, RuntimeConfigBuilder, Storage, Store, StoreBuilder, StoreConfig, TombstoneLogConfigBuilder, };