1use std::{
16 any::{Any, TypeId},
17 borrow::Cow,
18 fmt::Debug,
19 hash::Hash,
20 sync::Arc,
21 time::Instant,
22};
23
24use equivalent::Equivalent;
25use foyer_common::{
26 code::{HashBuilder, StorageKey, StorageValue},
27 error::Result,
28 metrics::Metrics,
29 properties::{Age, Properties},
30 spawn::Spawner,
31};
32use foyer_memory::{Cache, Piece};
33
34#[cfg(any(test, feature = "test_utils"))]
35use crate::test_utils::*;
36use crate::{
37 StorageFilterResult,
38 compress::Compression,
39 engine::{
40 Engine, EngineBuildContext, EngineConfig, Load, Populated, RecoverMode,
41 noop::{NoopEngine, NoopEngineConfig},
42 },
43 io::{
44 device::{Device, statistics::Statistics, throttle::Throttle},
45 engine::{IoEngineBuildContext, IoEngineConfig, monitor::MonitoredIoEngine, psync::PsyncIoEngineConfig},
46 },
47 keeper::Keeper,
48 serde::EntrySerializer,
49};
50
51pub struct Store<K, V, S, P>
53where
54 K: StorageKey,
55 V: StorageValue,
56 S: HashBuilder + Debug,
57 P: Properties,
58{
59 inner: Arc<StoreInner<K, V, S, P>>,
60}
61
62struct StoreInner<K, V, S, P>
63where
64 K: StorageKey,
65 V: StorageValue,
66 S: HashBuilder + Debug,
67 P: Properties,
68{
69 hasher: Arc<S>,
70
71 keeper: Keeper<K, V, P>,
72 engine: Arc<dyn Engine<K, V, P>>,
73
74 compression: Compression,
75
76 spawner: Spawner,
77
78 metrics: Arc<Metrics>,
79
80 #[cfg(any(test, feature = "test_utils"))]
81 load_throttle_switch: LoadThrottleSwitch,
82}
83
84impl<K, V, S, P> Debug for Store<K, V, S, P>
85where
86 K: StorageKey,
87 V: StorageValue,
88 S: HashBuilder + Debug,
89 P: Properties,
90{
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("Store")
93 .field("keeper", &self.inner.keeper)
94 .field("engine", &self.inner.engine)
95 .field("compression", &self.inner.compression)
96 .field("runtimes", &self.inner.spawner)
97 .finish()
98 }
99}
100
101impl<K, V, S, P> Clone for Store<K, V, S, P>
102where
103 K: StorageKey,
104 V: StorageValue,
105 S: HashBuilder + Debug,
106 P: Properties,
107{
108 fn clone(&self) -> Self {
109 Self {
110 inner: self.inner.clone(),
111 }
112 }
113}
114
115impl<K, V, S, P> Store<K, V, S, P>
116where
117 K: StorageKey,
118 V: StorageValue,
119 S: HashBuilder + Debug,
120 P: Properties,
121{
122 pub async fn close(&self) -> Result<()> {
126 self.inner.engine.close().await
127 }
128
129 pub fn filter(&self, hash: u64, estimated_size: usize) -> StorageFilterResult {
131 self.inner.engine.filter(hash, estimated_size)
132 }
133
134 pub fn enqueue(&self, piece: Piece<K, V, P>, force: bool) {
136 tracing::trace!(hash = piece.hash(), "[store]: enqueue piece");
137 let now = Instant::now();
138
139 if force
140 || self
141 .filter(
142 piece.hash(),
143 piece.key().estimated_size() + piece.value().estimated_size(),
144 )
145 .is_admitted()
146 {
147 let estimated_size = EntrySerializer::estimated_size(piece.key(), piece.value());
148 let rpiece = self.inner.keeper.insert(piece);
149 self.inner.engine.enqueue(rpiece, estimated_size);
150 } else {
151 self.delete(piece.key());
152 }
153
154 self.inner.metrics.storage_enqueue.increase(1);
155 self.inner
156 .metrics
157 .storage_enqueue_duration
158 .record(now.elapsed().as_secs_f64());
159 }
160
161 pub async fn load<Q>(&self, key: &Q) -> Result<Load<K, V, P>>
163 where
164 Q: Hash + Equivalent<K> + ?Sized,
165 {
166 let now = Instant::now();
167
168 let hash = self.inner.hasher.hash_one(key);
169
170 if let Some(piece) = self.inner.keeper.get(hash, key) {
171 tracing::trace!(hash, "[store]: load from keeper");
172 return Ok(Load::Piece {
173 piece,
174 populated: Populated { age: Age::Young },
175 });
176 }
177
178 #[cfg(feature = "test_utils")]
179 if self.inner.load_throttle_switch.is_throttled() {
180 self.inner.metrics.storage_throttled.increase(1);
181 self.inner
182 .metrics
183 .storage_throttled_duration
184 .record(now.elapsed().as_secs_f64());
185 return Ok(Load::Throttled);
186 }
187
188 match self.inner.engine.load(hash).await {
189 Ok(Load::Entry {
190 key: k,
191 value: v,
192 populated: p,
193 }) if key.equivalent(&k) => {
194 self.inner.metrics.storage_hit.increase(1);
195 self.inner
196 .metrics
197 .storage_hit_duration
198 .record(now.elapsed().as_secs_f64());
199 Ok(Load::Entry {
200 key: k,
201 value: v,
202 populated: p,
203 })
204 }
205 Ok(Load::Piece { piece, populated }) if key.equivalent(piece.key()) => {
206 self.inner.metrics.storage_hit.increase(1);
207 self.inner
208 .metrics
209 .storage_hit_duration
210 .record(now.elapsed().as_secs_f64());
211 Ok(Load::Piece { piece, populated })
212 }
213 Ok(Load::Entry { .. }) | Ok(Load::Piece { .. }) => {
214 self.inner.metrics.storage_miss.increase(1);
215 self.inner.metrics.storage_false_positive.increase(1);
216 self.inner
217 .metrics
218 .storage_miss_duration
219 .record(now.elapsed().as_secs_f64());
220 Ok(Load::Miss)
221 }
222 Ok(Load::Miss) => {
223 self.inner.metrics.storage_miss.increase(1);
224 self.inner
225 .metrics
226 .storage_miss_duration
227 .record(now.elapsed().as_secs_f64());
228 Ok(Load::Miss)
229 }
230 Ok(Load::Throttled) => {
231 self.inner.metrics.storage_throttled.increase(1);
232 self.inner
233 .metrics
234 .storage_throttled_duration
235 .record(now.elapsed().as_secs_f64());
236 Ok(Load::Throttled)
237 }
238 Err(e) => {
239 self.inner.metrics.storage_error.increase(1);
240 Err(e)
241 }
242 }
243 }
244
245 pub fn delete<'a, Q>(&'a self, key: &'a Q)
247 where
248 Q: Hash + Equivalent<K> + ?Sized,
249 {
250 let now = Instant::now();
251
252 let hash = self.inner.hasher.hash_one(key);
253 self.inner.engine.delete(hash);
254
255 self.inner.metrics.storage_delete.increase(1);
256 self.inner
257 .metrics
258 .storage_delete_duration
259 .record(now.elapsed().as_secs_f64());
260 }
261
262 pub fn may_contains<Q>(&self, key: &Q) -> bool
266 where
267 Q: Hash + Equivalent<K> + ?Sized,
268 {
269 let hash = self.inner.hasher.hash_one(key);
270 self.inner.engine.may_contains(hash)
271 }
272
273 pub async fn destroy(&self) -> Result<()> {
275 self.inner.engine.destroy().await
276 }
277
278 pub fn device(&self) -> &Arc<dyn Device> {
280 self.inner.engine.device()
281 }
282
283 pub fn statistics(&self) -> &Arc<Statistics> {
285 self.inner.engine.device().statistics()
286 }
287
288 pub fn throttle(&self) -> &Throttle {
290 self.inner.engine.device().statistics().throttle()
291 }
292
293 pub fn spawner(&self) -> &Spawner {
295 &self.inner.spawner
296 }
297
298 pub async fn wait(&self) {
300 self.inner.engine.wait().await
301 }
302
303 pub fn entry_estimated_size(&self, key: &K, value: &V) -> usize {
305 EntrySerializer::estimated_size(key, value)
306 }
307
308 #[cfg(feature = "test_utils")]
310 pub fn load_throttle_switch(&self) -> &LoadThrottleSwitch {
311 &self.inner.load_throttle_switch
312 }
313
314 pub fn is_enabled(&self) -> bool {
316 self.inner.engine.type_id() != TypeId::of::<Arc<NoopEngine<K, V, P>>>()
317 }
318}
319
320pub struct StoreBuilder<K, V, S, P>
322where
323 K: StorageKey,
324 V: StorageValue,
325 S: HashBuilder + Debug,
326 P: Properties,
327{
328 name: Cow<'static, str>,
329 memory: Cache<K, V, S, P>,
330 metrics: Arc<Metrics>,
331
332 io_engine_config: Option<Box<dyn IoEngineConfig>>,
333 engine_config: Option<Box<dyn EngineConfig<K, V, P>>>,
334
335 spawner: Option<Spawner>,
336
337 compression: Compression,
338 recover_mode: RecoverMode,
339
340 #[cfg(any(test, feature = "test_utils"))]
341 load_throttle_switch: LoadThrottleSwitch,
342}
343
344impl<K, V, S, P> Debug for StoreBuilder<K, V, S, P>
345where
346 K: StorageKey,
347 V: StorageValue,
348 S: HashBuilder + Debug,
349 P: Properties,
350{
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 f.debug_struct("StoreBuilder")
353 .field("name", &self.name)
354 .field("memory", &self.memory)
355 .field("metrics", &self.metrics)
356 .field("io_engine_builder", &self.io_engine_config)
357 .field("engine_builder", &self.engine_config)
358 .field("spawner", &self.spawner)
359 .field("compression", &self.compression)
360 .field("recover_mode", &self.recover_mode)
361 .finish()
362 }
363}
364
365impl<K, V, S, P> StoreBuilder<K, V, S, P>
366where
367 K: StorageKey,
368 V: StorageValue,
369 S: HashBuilder + Debug,
370 P: Properties,
371{
372 pub fn new(name: impl Into<Cow<'static, str>>, memory: Cache<K, V, S, P>, metrics: Arc<Metrics>) -> Self {
374 Self {
375 name: name.into(),
376 memory,
377 metrics,
378
379 io_engine_config: None,
380 engine_config: None,
381
382 spawner: None,
383
384 compression: Compression::default(),
385 recover_mode: RecoverMode::default(),
386 #[cfg(any(test, feature = "test_utils"))]
387 load_throttle_switch: LoadThrottleSwitch::default(),
388 }
389 }
390
391 pub fn with_io_engine_config(mut self, io_engine_builder: impl Into<Box<dyn IoEngineConfig>>) -> Self {
395 self.io_engine_config = Some(io_engine_builder.into());
396 self
397 }
398
399 pub fn with_engine_config(mut self, config: impl Into<Box<dyn EngineConfig<K, V, P>>>) -> Self {
401 self.engine_config = Some(config.into());
402 self
403 }
404
405 pub fn with_compression(mut self, compression: Compression) -> Self {
409 self.compression = compression;
410 self
411 }
412
413 pub fn with_recover_mode(mut self, recover_mode: RecoverMode) -> Self {
419 self.recover_mode = recover_mode;
420 self
421 }
422
423 pub fn with_spawner(mut self, spawner: Spawner) -> Self {
431 self.spawner = Some(spawner);
432 self
433 }
434
435 #[cfg(any(test, feature = "test_utils"))]
437 pub fn with_load_throttle_switch(mut self, switch: LoadThrottleSwitch) -> Self {
438 self.load_throttle_switch = switch;
439 self
440 }
441
442 #[doc(hidden)]
443 pub fn is_noop(&self) -> bool {
444 self.engine_config.is_none()
445 }
446
447 pub async fn build(self) -> Result<Store<K, V, S, P>> {
449 let memory = self.memory;
450 let metrics = self.metrics;
451
452 let compression = self.compression;
453
454 let spawner = self.spawner.unwrap_or_else(Spawner::current);
455
456 let io_engine_builder = match self.io_engine_config {
457 Some(builder) => builder,
458 None => {
459 tracing::info!(
460 "[store builder]: No I/O engine builder is provided, use `PsyncIoEngineConfig` with default parameters as default."
461 );
462 PsyncIoEngineConfig::new().boxed()
463 }
464 };
465 let io_engine = io_engine_builder
466 .build(IoEngineBuildContext {
467 spawner: spawner.clone(),
468 })
469 .await?;
470 let io_engine = MonitoredIoEngine::new(io_engine, metrics.clone());
471
472 let engine_builder = match self.engine_config {
473 Some(eb) => eb,
474 None => {
475 tracing::info!(
476 "[store builder]: No engine builder is provided, run disk cache in mock mode that do nothing."
477 );
478
479 Box::<NoopEngineConfig<K, V, P>>::default()
480 }
481 };
482
483 let engine = engine_builder
484 .build(EngineBuildContext {
485 io_engine,
486 metrics: metrics.clone(),
487 spawner: spawner.clone(),
488 recover_mode: self.recover_mode,
489 })
490 .await?;
491
492 let keeper = Keeper::new(memory.shards());
493 let hasher = memory.hash_builder().clone();
494 #[cfg(any(test, feature = "test_utils"))]
495 let load_throttle_switch = self.load_throttle_switch;
496 let inner = StoreInner {
497 hasher,
498 keeper,
499 engine,
500 compression,
501 spawner,
502 metrics,
503 #[cfg(any(test, feature = "test_utils"))]
504 load_throttle_switch,
505 };
506 let inner = Arc::new(inner);
507 let store = Store { inner };
508
509 Ok(store)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use foyer_common::hasher::ModHasher;
516 use foyer_memory::CacheBuilder;
517
518 use super::*;
519 use crate::{
520 DeviceBuilder,
521 engine::block::engine::BlockEngineConfig,
522 io::{device::fs::FsDeviceBuilder, engine::psync::PsyncIoEngineConfig},
523 };
524
525 #[tokio::test]
526 async fn test_build_with_unaligned_buffer_pool_size() {
527 let dir = tempfile::tempdir().unwrap();
528 let metrics = Arc::new(Metrics::noop());
529 let memory: Cache<u64, u64> = CacheBuilder::new(10).build();
530 let _ = StoreBuilder::new("test", memory, metrics)
531 .with_io_engine_config(PsyncIoEngineConfig::new())
532 .with_engine_config(
533 BlockEngineConfig::new(
534 FsDeviceBuilder::new(dir.path())
535 .with_capacity(64 * 1024)
536 .build()
537 .unwrap(),
538 )
539 .with_flushers(3)
540 .with_block_size(16 * 1024)
541 .with_buffer_pool_size(128 * 1024 * 1024),
542 )
543 .build()
544 .await
545 .unwrap();
546 }
547
548 #[tokio::test]
549 async fn test_entry_hash_collision() {
550 let dir = tempfile::tempdir().unwrap();
551 let metrics = Arc::new(Metrics::noop());
552 let memory: Cache<u128, String, ModHasher> =
553 CacheBuilder::new(10).with_hash_builder(ModHasher::default()).build();
554
555 let e1 = memory.insert(1, "foo".to_string());
556 let e2 = memory.insert(1 + 1 + u64::MAX as u128, "bar".to_string());
557
558 assert_eq!(memory.hash(e1.key()), memory.hash(e2.key()));
559
560 let store = StoreBuilder::new("test", memory, metrics)
561 .with_io_engine_config(PsyncIoEngineConfig::new())
562 .with_engine_config(
563 BlockEngineConfig::new(
564 FsDeviceBuilder::new(dir.path())
565 .with_capacity(4 * 1024 * 1024)
566 .build()
567 .unwrap(),
568 )
569 .with_block_size(16 * 1024),
570 )
571 .build()
572 .await
573 .unwrap();
574
575 store.enqueue(e1.piece(), true);
576 store.enqueue(e2.piece(), true);
577 store.wait().await;
578
579 let l1 = store.load(e1.key()).await.unwrap();
580 let l2 = store.load(e2.key()).await.unwrap();
581
582 assert!(matches!(l1, Load::Miss));
583 assert!(matches!(l2, Load::Entry { .. }));
584 assert_eq!(l2.entry().unwrap().1, "bar");
585 }
586
587 #[tokio::test]
588 async fn test_store_enqueue_admission_reject_update() {
589 use crate::filter::StorageFilter;
590
591 let dir = tempfile::tempdir().unwrap();
592 let metrics = Arc::new(Metrics::noop());
593 let memory: Cache<u64, Vec<u8>> = CacheBuilder::new(10).build();
594
595 let switch = Switch::default();
596 switch.on();
597 let filter = StorageFilter::new().with_condition(switch.clone());
598
599 let store = StoreBuilder::new("test", memory.clone(), metrics)
600 .with_io_engine_config(PsyncIoEngineConfig::new())
601 .with_engine_config(
602 BlockEngineConfig::new(
603 FsDeviceBuilder::new(dir.path())
604 .with_capacity(4 * 1024 * 1024)
605 .build()
606 .unwrap(),
607 )
608 .with_block_size(16 * 1024)
609 .with_admission_filter(filter),
610 )
611 .build()
612 .await
613 .unwrap();
614
615 let e1 = memory.insert(1, b"v1".to_vec());
616 store.enqueue(e1.piece(), false);
617 store.wait().await;
618
619 let l1 = store.load(&1).await.unwrap();
620 assert!(matches!(l1, Load::Entry { ref value, .. } if value == b"v1"));
621
622 switch.off();
623
624 let e2 = memory.insert(1, b"v2".to_vec());
625 store.enqueue(e2.piece(), false);
626 store.wait().await;
627
628 let l2 = store.load(&1).await.unwrap();
629 assert!(matches!(l2, Load::Miss));
630 }
631}