1use std::clone::Clone;
16use std::collections::VecDeque;
17use std::ops::Deref;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicUsize, Ordering};
21
22use await_tree::{InstrumentAwait, SpanExt};
23use bytes::Bytes;
24use fail::fail_point;
25use foyer::{
26 Cache, CacheBuilder, CacheEntry, EventListener, Hint, HybridCache, HybridCacheBuilder,
27 HybridCacheEntry, HybridCacheProperties,
28};
29use futures::{FutureExt, StreamExt, future};
30use prost::Message;
31use risingwave_hummock_sdk::sstable_info::SstableInfo;
32use risingwave_hummock_sdk::vector_index::{HnswGraphFileInfo, VectorFileInfo};
33use risingwave_hummock_sdk::{
34 HummockHnswGraphFileId, HummockObjectId, HummockRawObjectId, HummockSstableObjectId,
35 HummockVectorFileId, SST_OBJECT_SUFFIX,
36};
37use risingwave_hummock_trace::TracedCachePolicy;
38use risingwave_object_store::object::{
39 ObjectError, ObjectMetadataIter, ObjectResult, ObjectStoreRef, ObjectStreamingUploader,
40};
41use risingwave_pb::hummock::PbHnswGraph;
42use serde::{Deserialize, Serialize};
43use thiserror_ext::AsReport;
44use tokio::time::Instant;
45
46use super::{
47 BatchUploadWriter, Block, BlockMeta, BlockResponse, RecentFilter, Sstable, SstableMeta,
48 SstableWriterOptions,
49};
50use crate::hummock::block_stream::{BlockDataStream, MemoryUsageTracker, PrefetchBlockStream};
51use crate::hummock::none::NoneRecentFilter;
52use crate::hummock::vector::file::{VectorBlock, VectorBlockMeta, VectorFileMeta};
53use crate::hummock::vector::monitor::VectorStoreCacheStats;
54use crate::hummock::{BlockEntry, BlockHolder, HummockError, HummockResult, RecentFilterTrait};
55use crate::monitor::{HummockStateStoreMetrics, StoreLocalStatistic};
56
57macro_rules! impl_vector_index_meta_file {
58 ($($type_name:ident),+) => {
59 pub enum HummockVectorIndexMetaFile {
60 $(
61 $type_name(Pin<Box<$type_name>>),
62 )+
63 }
64
65 $(
66 impl From<$type_name> for HummockVectorIndexMetaFile {
67 fn from(v: $type_name) -> Self {
68 Self::$type_name(Box::pin(v))
69 }
70 }
71
72 unsafe impl Send for VectorMetaFileHolder<$type_name> {}
73
74 impl VectorMetaFileHolder<$type_name> {
75 fn try_from_entry(
76 entry: CacheEntry<HummockRawObjectId, HummockVectorIndexMetaFile>,
77 object_id: HummockRawObjectId
78 ) -> HummockResult<Self> {
79 let HummockVectorIndexMetaFile::$type_name(file_meta) = &*entry else {
80 return Err(HummockError::decode_error(format!(
81 "expect {} for object {}",
82 stringify!($type_name),
83 object_id
84 )));
85 };
86 let ptr = file_meta.as_ref().get_ref() as *const _;
87 Ok(VectorMetaFileHolder {
88 _cache_entry: entry,
89 ptr,
90 })
91 }
92 }
93 )+
94 };
95}
96
97impl_vector_index_meta_file!(VectorFileMeta, PbHnswGraph);
98
99pub struct VectorMetaFileHolder<T> {
100 _cache_entry: CacheEntry<HummockRawObjectId, HummockVectorIndexMetaFile>,
101 ptr: *const T,
102}
103
104impl<T> Deref for VectorMetaFileHolder<T> {
105 type Target = T;
106
107 fn deref(&self) -> &Self::Target {
108 unsafe { &*self.ptr }
110 }
111}
112
113#[derive(Debug)]
118pub struct SstableMetaCacheValue(Box<Sstable>);
119
120impl From<Sstable> for SstableMetaCacheValue {
121 fn from(sstable: Sstable) -> Self {
122 Self(Box::new(sstable))
123 }
124}
125
126impl Deref for SstableMetaCacheValue {
127 type Target = Sstable;
128
129 fn deref(&self) -> &Self::Target {
130 &self.0
131 }
132}
133
134impl AsRef<Sstable> for SstableMetaCacheValue {
135 fn as_ref(&self) -> &Sstable {
136 &self.0
137 }
138}
139
140impl foyer::Code for SstableMetaCacheValue {
141 fn encode(&self, writer: &mut impl std::io::Write) -> foyer::Result<()> {
142 bincode::serialize_into(writer, &self.0).map_err(foyer::Error::bincode_error)
143 }
144
145 fn decode(reader: &mut impl std::io::Read) -> foyer::Result<Self> {
146 bincode::deserialize_from(reader)
147 .map(Self)
148 .map_err(foyer::Error::bincode_error)
149 }
150
151 fn estimated_size(&self) -> usize {
152 self.0.serde_encoded_size()
153 }
154}
155
156pub type TableHolder = HybridCacheEntry<HummockSstableObjectId, SstableMetaCacheValue>;
157
158pub type VectorBlockHolder = CacheEntry<(HummockVectorFileId, usize), Box<VectorBlock>>;
159
160pub type VectorFileHolder = VectorMetaFileHolder<VectorFileMeta>;
161pub type HnswGraphFileHolder = VectorMetaFileHolder<PbHnswGraph>;
162
163#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
164pub struct SstableBlockIndex {
165 pub sst_id: HummockSstableObjectId,
166 pub block_idx: u64,
167}
168
169pub struct BlockCacheEventListener {
170 metrics: Arc<HummockStateStoreMetrics>,
171}
172
173impl BlockCacheEventListener {
174 pub fn new(metrics: Arc<HummockStateStoreMetrics>) -> Self {
175 Self { metrics }
176 }
177}
178
179impl EventListener for BlockCacheEventListener {
180 type Key = SstableBlockIndex;
181 type Value = Box<Block>;
182
183 fn on_leave(&self, _reason: foyer::Event, _key: &Self::Key, value: &Self::Value)
184 where
185 Self::Key: foyer::Key,
186 Self::Value: foyer::Value,
187 {
188 self.metrics
189 .block_efficiency_histogram
190 .observe(value.efficiency());
191 }
192}
193
194#[derive(Clone, Copy, Eq, PartialEq)]
196pub enum CachePolicy {
197 Disable,
199 Fill(Hint),
201 NotFill,
203}
204
205impl Default for CachePolicy {
206 fn default() -> Self {
207 CachePolicy::Fill(Hint::Normal)
208 }
209}
210
211impl From<TracedCachePolicy> for CachePolicy {
212 fn from(policy: TracedCachePolicy) -> Self {
213 match policy {
214 TracedCachePolicy::Disable => Self::Disable,
215 TracedCachePolicy::Fill(priority) => Self::Fill(priority.into()),
216 TracedCachePolicy::NotFill => Self::NotFill,
217 }
218 }
219}
220
221impl From<CachePolicy> for TracedCachePolicy {
222 fn from(policy: CachePolicy) -> Self {
223 match policy {
224 CachePolicy::Disable => Self::Disable,
225 CachePolicy::Fill(priority) => Self::Fill(priority.into()),
226 CachePolicy::NotFill => Self::NotFill,
227 }
228 }
229}
230
231pub struct SstableStoreConfig {
232 pub store: ObjectStoreRef,
233 pub path: String,
234
235 pub prefetch_buffer_capacity: usize,
236 pub max_prefetch_block_number: usize,
237 pub recent_filter: Arc<RecentFilter<(HummockSstableObjectId, usize)>>,
238 pub state_store_metrics: Arc<HummockStateStoreMetrics>,
239 pub use_new_object_prefix_strategy: bool,
240 pub skip_bloom_filter_in_serde: bool,
241
242 pub meta_cache: HybridCache<HummockSstableObjectId, SstableMetaCacheValue>,
243 pub block_cache: HybridCache<SstableBlockIndex, Box<Block>>,
244
245 pub vector_meta_cache: Cache<HummockRawObjectId, HummockVectorIndexMetaFile>,
246 pub vector_block_cache: Cache<(HummockVectorFileId, usize), Box<VectorBlock>>,
247}
248
249pub struct SstableStore {
250 path: String,
251 store: ObjectStoreRef,
252
253 meta_cache: HybridCache<HummockSstableObjectId, SstableMetaCacheValue>,
254 block_cache: HybridCache<SstableBlockIndex, Box<Block>>,
255 pub vector_meta_cache: Cache<HummockRawObjectId, HummockVectorIndexMetaFile>,
256 pub vector_block_cache: Cache<(HummockVectorFileId, usize), Box<VectorBlock>>,
257
258 recent_filter: Arc<RecentFilter<(HummockSstableObjectId, usize)>>,
262 prefetch_buffer_usage: Arc<AtomicUsize>,
263 prefetch_buffer_capacity: usize,
264 max_prefetch_block_number: usize,
265 use_new_object_prefix_strategy: bool,
274
275 skip_bloom_filter_in_serde: bool,
279}
280
281impl SstableStore {
282 pub fn new(config: SstableStoreConfig) -> Self {
283 Self {
287 path: config.path,
288 store: config.store,
289
290 meta_cache: config.meta_cache,
291 block_cache: config.block_cache,
292 vector_meta_cache: config.vector_meta_cache,
293 vector_block_cache: config.vector_block_cache,
294
295 recent_filter: config.recent_filter,
296 prefetch_buffer_usage: Arc::new(AtomicUsize::new(0)),
297 prefetch_buffer_capacity: config.prefetch_buffer_capacity,
298 max_prefetch_block_number: config.max_prefetch_block_number,
299 use_new_object_prefix_strategy: config.use_new_object_prefix_strategy,
300 skip_bloom_filter_in_serde: config.skip_bloom_filter_in_serde,
301 }
302 }
303
304 #[expect(clippy::borrowed_box)]
307 pub async fn for_compactor(
308 store: ObjectStoreRef,
309 path: String,
310 block_cache_capacity: usize,
311 meta_cache_capacity: usize,
312 use_new_object_prefix_strategy: bool,
313 ) -> HummockResult<Self> {
314 let meta_cache = HybridCacheBuilder::new()
315 .memory(meta_cache_capacity)
316 .with_shards(1)
317 .with_weighter(
318 |_: &HummockSstableObjectId, value: &SstableMetaCacheValue| {
319 std::mem::size_of::<HummockSstableObjectId>()
320 + value.estimated_meta_cache_memory_weight()
321 },
322 )
323 .storage()
324 .build()
325 .await
326 .map_err(HummockError::foyer_error)?;
327
328 let block_cache = HybridCacheBuilder::new()
329 .memory(block_cache_capacity)
330 .with_shards(1)
331 .with_weighter(|_: &SstableBlockIndex, value: &Box<Block>| {
332 std::mem::size_of::<SstableBlockIndex>() + value.estimated_memory_weight()
333 })
334 .storage()
335 .build()
336 .await
337 .map_err(HummockError::foyer_error)?;
338
339 Ok(Self {
340 path,
341 store,
342
343 prefetch_buffer_usage: Arc::new(AtomicUsize::new(0)),
344 prefetch_buffer_capacity: block_cache_capacity,
345 max_prefetch_block_number: 16, recent_filter: Arc::new(NoneRecentFilter::default().into()),
347 use_new_object_prefix_strategy,
348 skip_bloom_filter_in_serde: false,
349
350 meta_cache,
351 block_cache,
352 vector_meta_cache: CacheBuilder::new(1 << 10).build(),
353 vector_block_cache: CacheBuilder::new(1 << 10).build(),
354 })
355 }
356
357 pub async fn delete(&self, object_id: HummockSstableObjectId) -> HummockResult<()> {
358 self.store
359 .delete(self.get_sst_data_path(object_id).as_str())
360 .await?;
361 self.meta_cache.remove(&object_id);
362 Ok(())
364 }
365
366 pub(crate) async fn put_sst_data(
367 &self,
368 object_id: HummockSstableObjectId,
369 data: Bytes,
370 ) -> HummockResult<()> {
371 let data_path = self.get_sst_data_path(object_id);
372 self.store
373 .upload(&data_path, data)
374 .await
375 .map_err(Into::into)
376 }
377
378 pub async fn prefetch_blocks(
382 &self,
383 sst: &Sstable,
384 block_index: usize,
385 end_index: usize,
386 policy: CachePolicy,
387 stats: &mut StoreLocalStatistic,
388 ) -> HummockResult<Box<PrefetchBlockStream>> {
389 let object_id = sst.id;
390 if self.prefetch_buffer_usage.load(Ordering::Acquire) > self.prefetch_buffer_capacity {
391 let block = self.get(sst, block_index, policy, stats).await?;
392 return Ok(Box::new(PrefetchBlockStream::new(
393 VecDeque::from([block]),
394 block_index,
395 None,
396 )));
397 }
398 if policy != CachePolicy::Disable
399 && let Some(entry) = self
400 .block_cache
401 .get(&SstableBlockIndex {
402 sst_id: object_id,
403 block_idx: block_index as _,
404 })
405 .await
406 .map_err(HummockError::foyer_error)?
407 {
408 stats.cache_data_block_total += 1;
409 if entry.source() == foyer::Source::Outer {
410 stats.cache_data_block_miss += 1;
411 }
412 let block = BlockHolder::from_hybrid_cache_entry(entry);
413 return Ok(Box::new(PrefetchBlockStream::new(
414 VecDeque::from([block]),
415 block_index,
416 None,
417 )));
418 }
419 let end_index = std::cmp::min(
420 end_index,
421 block_index.saturating_add(self.max_prefetch_block_number),
422 );
423 let mut end_index = std::cmp::min(end_index, sst.meta.block_metas.len());
424 let start_offset = sst.meta.block_metas[block_index].offset as usize;
425 if policy != CachePolicy::Disable {
426 let mut min_hit_index = end_index;
427 let mut hit_count = 0;
428 for idx in block_index..end_index {
429 if self.block_cache.contains(&SstableBlockIndex {
430 sst_id: object_id,
431 block_idx: idx as _,
432 }) {
433 if min_hit_index > idx && idx > block_index {
434 min_hit_index = idx;
435 }
436 hit_count += 1;
437 }
438 }
439
440 if hit_count * 3 >= (end_index - block_index)
441 || min_hit_index * 2 > block_index + end_index
442 {
443 end_index = min_hit_index;
444 }
445 }
446 stats.cache_data_prefetch_count += 1;
447 stats.cache_data_prefetch_block_count += (end_index - block_index) as u64;
448 let end_offset = start_offset
449 + sst.meta.block_metas[block_index..end_index]
450 .iter()
451 .map(|meta| meta.len as usize)
452 .sum::<usize>();
453 let data_path = self.get_sst_data_path(object_id);
454 let memory_usage = end_offset - start_offset;
455 let tracker = MemoryUsageTracker::new(self.prefetch_buffer_usage.clone(), memory_usage);
456 let span = await_tree::span!("Prefetch SST-{}", object_id).verbose();
457 let store = self.store.clone();
458 let join_handle = tokio::spawn(async move {
459 store
460 .read(&data_path, start_offset..end_offset)
461 .instrument_await(span)
462 .await
463 });
464 let buf = match join_handle.await {
465 Ok(Ok(data)) => data,
466 Ok(Err(e)) => {
467 tracing::error!(
468 "prefetch meet error when read {}..{} from sst-{} ({})",
469 start_offset,
470 end_offset,
471 object_id,
472 sst.meta.estimated_size,
473 );
474 return Err(e.into());
475 }
476 Err(_) => {
477 return Err(HummockError::other("cancel by other thread"));
478 }
479 };
480 let mut offset = 0;
481 let mut blocks = VecDeque::default();
482 for idx in block_index..end_index {
483 let end = offset + sst.meta.block_metas[idx].len as usize;
484 if end > buf.len() {
485 return Err(ObjectError::internal("read unexpected EOF").into());
486 }
487 let block = Block::decode_with_copy(
489 buf.slice(offset..end),
490 sst.meta.block_metas[idx].uncompressed_size as usize,
491 true,
492 )?;
493 let holder = if let CachePolicy::Fill(hint) = policy {
494 let hint = if idx == block_index { hint } else { Hint::Low };
495 let entry = self.block_cache.insert_with_properties(
496 SstableBlockIndex {
497 sst_id: object_id,
498 block_idx: idx as _,
499 },
500 Box::new(block),
501 HybridCacheProperties::default().with_hint(hint),
502 );
503 BlockHolder::from_hybrid_cache_entry(entry)
504 } else {
505 BlockHolder::from_owned_block(Box::new(block))
506 };
507
508 blocks.push_back(holder);
509 offset = end;
510 }
511 Ok(Box::new(PrefetchBlockStream::new(
512 blocks,
513 block_index,
514 Some(tracker),
515 )))
516 }
517
518 pub async fn get_block_response(
519 &self,
520 sst: &Sstable,
521 block_index: usize,
522 policy: CachePolicy,
523 ) -> HummockResult<BlockResponse> {
524 let object_id = sst.id;
525 let (range, uncompressed_capacity) = sst.calculate_block_info(block_index);
526 let store = self.store.clone();
527
528 let file_size = sst.meta.estimated_size;
529 let data_path = Arc::new(self.get_sst_data_path(object_id));
530
531 let disable_cache: fn() -> bool = || {
532 fail_point!("disable_block_cache", |_| true);
533 false
534 };
535
536 let policy = if disable_cache() {
537 CachePolicy::Disable
538 } else {
539 policy
540 };
541
542 let idx = SstableBlockIndex {
543 sst_id: object_id,
544 block_idx: block_index as _,
545 };
546
547 self.recent_filter
548 .extend([(object_id, usize::MAX), (object_id, block_index)]);
549
550 let fetch_block = async move {
552 let block_data = match store
553 .read(&data_path, range.clone())
554 .instrument_await("get_block_response".verbose())
555 .await
556 {
557 Ok(data) => data,
558 Err(e) => {
559 tracing::error!(
560 "get_block_response meet error when read {:?} from sst-{}, total length: {}",
561 range,
562 object_id,
563 file_size
564 );
565 return Err(HummockError::from(e));
566 }
567 };
568 let block = Box::new(Block::decode(block_data, uncompressed_capacity)?);
569 Ok(block)
570 };
571
572 match policy {
573 CachePolicy::Fill(hint) => {
574 let properties = HybridCacheProperties::default().with_hint(hint);
575 let fetch = self.block_cache.get_or_fetch(&idx, || {
576 fetch_block.map(|res| res.map(|block| (block, properties)))
577 });
578 Ok(BlockResponse::Fetch(fetch))
579 }
580 CachePolicy::NotFill => {
581 match self
582 .block_cache
583 .get(&idx)
584 .await
585 .map_err(HummockError::foyer_error)?
586 {
587 Some(entry) => Ok(BlockResponse::Block(BlockHolder::from_hybrid_cache_entry(
588 entry,
589 ))),
590 _ => {
591 let block = fetch_block.await?;
592 Ok(BlockResponse::Block(BlockHolder::from_owned_block(block)))
593 }
594 }
595 }
596 CachePolicy::Disable => {
597 let block = fetch_block.await?;
598 Ok(BlockResponse::Block(BlockHolder::from_owned_block(block)))
599 }
600 }
601 }
602
603 pub async fn get(
604 &self,
605 sst: &Sstable,
606 block_index: usize,
607 policy: CachePolicy,
608 stats: &mut StoreLocalStatistic,
609 ) -> HummockResult<BlockHolder> {
610 let block_response = self.get_block_response(sst, block_index, policy).await?;
611 let block_holder = block_response.wait().await?;
612 stats.cache_data_block_total += 1;
613 if let BlockEntry::HybridCache(entry) = block_holder.entry()
614 && entry.source() == foyer::Source::Outer
615 {
616 stats.cache_data_block_miss += 1;
617 }
618 Ok(block_holder)
619 }
620
621 pub async fn get_vector_file_meta(
622 &self,
623 vector_file: &VectorFileInfo,
624 stats: &mut VectorStoreCacheStats,
625 ) -> HummockResult<VectorFileHolder> {
626 let store = self.store.clone();
627 let path = self.get_object_data_path(HummockObjectId::VectorFile(vector_file.object_id));
628 let meta_offset = vector_file.meta_offset;
629 let entry = self
630 .vector_meta_cache
631 .get_or_fetch(&vector_file.object_id.as_raw(), || async move {
632 let encoded_footer = store.read(&path, meta_offset..).await?;
633 let meta = VectorFileMeta::decode_footer(&encoded_footer)?;
634 Ok::<_, anyhow::Error>(HummockVectorIndexMetaFile::from(meta))
635 })
636 .await?;
637 stats.file_meta_total += 1;
638 if entry.source() == foyer::Source::Outer {
639 stats.file_meta_miss += 1;
640 }
641 VectorFileHolder::try_from_entry(entry, vector_file.object_id.as_raw())
642 }
643
644 pub async fn get_vector_block(
645 &self,
646 vector_file: &VectorFileInfo,
647 block_idx: usize,
648 block_meta: &VectorBlockMeta,
649 stats: &mut VectorStoreCacheStats,
650 ) -> HummockResult<VectorBlockHolder> {
651 let store = self.store.clone();
652 let path = self.get_object_data_path(HummockObjectId::VectorFile(vector_file.object_id));
653 let start_offset = block_meta.offset;
654 let end_offset = start_offset + block_meta.block_size;
655 let entry = self
656 .vector_block_cache
657 .get_or_fetch(&(vector_file.object_id, block_idx), || async move {
658 let encoded_block = store.read(&path, start_offset..end_offset).await?;
659 let block = VectorBlock::decode(&encoded_block)?;
660 Ok::<_, anyhow::Error>(Box::new(block))
661 })
662 .await
663 .map_err(HummockError::foyer_error)?;
664
665 stats.file_block_total += 1;
666 if entry.source() == foyer::Source::Outer {
667 stats.file_block_miss += 1;
668 }
669 Ok(entry)
670 }
671
672 pub fn insert_vector_cache(
673 &self,
674 object_id: HummockVectorFileId,
675 meta: VectorFileMeta,
676 blocks: Vec<VectorBlock>,
677 ) {
678 self.vector_meta_cache
679 .insert(object_id.as_raw(), meta.into());
680 for (idx, block) in blocks.into_iter().enumerate() {
681 self.vector_block_cache
682 .insert((object_id, idx), Box::new(block));
683 }
684 }
685
686 pub fn insert_hnsw_graph_cache(&self, object_id: HummockHnswGraphFileId, graph: PbHnswGraph) {
687 self.vector_meta_cache
688 .insert(object_id.as_raw(), graph.into());
689 }
690
691 pub async fn get_hnsw_graph(
692 &self,
693 graph_file: &HnswGraphFileInfo,
694 stats: &mut VectorStoreCacheStats,
695 ) -> HummockResult<HnswGraphFileHolder> {
696 let store = self.store.clone();
697 let graph_file_path =
698 self.get_object_data_path(HummockObjectId::HnswGraphFile(graph_file.object_id));
699 let entry = self
700 .vector_meta_cache
701 .get_or_fetch(&graph_file.object_id.as_raw(), || async move {
702 let encoded_graph = store.read(&graph_file_path, ..).await?;
703 let graph = PbHnswGraph::decode(encoded_graph.as_ref())?;
704 Ok::<_, anyhow::Error>(HummockVectorIndexMetaFile::from(graph))
705 })
706 .await
707 .map_err(HummockError::foyer_error)?;
708 stats.hnsw_graph_total += 1;
709 if entry.source() == foyer::Source::Outer {
710 stats.hnsw_graph_miss += 1;
711 }
712 HnswGraphFileHolder::try_from_entry(entry, graph_file.object_id.as_raw())
713 }
714
715 pub fn get_sst_data_path(&self, object_id: impl Into<HummockSstableObjectId>) -> String {
716 self.get_object_data_path(HummockObjectId::Sstable(object_id.into()))
717 }
718
719 pub fn get_object_data_path(&self, object_id: HummockObjectId) -> String {
720 let obj_prefix = self.store.get_object_prefix(
721 object_id.as_raw().as_raw_id(),
722 self.use_new_object_prefix_strategy,
723 );
724 risingwave_hummock_sdk::get_object_data_path(&obj_prefix, &self.path, object_id)
725 }
726
727 pub fn get_object_id_from_path(path: &str) -> HummockObjectId {
728 risingwave_hummock_sdk::get_object_id_from_path(path)
729 }
730
731 pub fn store(&self) -> ObjectStoreRef {
732 self.store.clone()
733 }
734
735 #[cfg(any(test, feature = "test"))]
736 pub async fn clear_block_cache(&self) -> HummockResult<()> {
737 self.block_cache
738 .clear()
739 .await
740 .map_err(HummockError::foyer_error)
741 }
742
743 #[cfg(any(test, feature = "test"))]
744 pub async fn clear_meta_cache(&self) -> HummockResult<()> {
745 self.meta_cache
746 .clear()
747 .await
748 .map_err(HummockError::foyer_error)
749 }
750
751 pub async fn sstable_cached(
752 &self,
753 sst_obj_id: HummockSstableObjectId,
754 ) -> HummockResult<Option<TableHolder>> {
755 self.meta_cache
756 .get(&sst_obj_id)
757 .await
758 .map_err(HummockError::foyer_error)
759 }
760
761 pub async fn sstable(
763 &self,
764 sstable_info_ref: &SstableInfo,
765 stats: &mut StoreLocalStatistic,
766 ) -> HummockResult<TableHolder> {
767 let object_id = sstable_info_ref.object_id;
768 let store = self.store.clone();
769 let meta_path = self.get_sst_data_path(object_id);
770 let stats_ptr = stats.remote_io_time.clone();
771 let range = sstable_info_ref.meta_offset as usize..;
772 let skip_bloom_filter_in_serde = self.skip_bloom_filter_in_serde;
773
774 let fetch = self.meta_cache.get_or_fetch(&object_id, || async move {
775 let now = Instant::now();
776 let buf = store
777 .read(&meta_path, range)
778 .instrument_await("get_meta_response".verbose())
779 .await?;
780 let sst = Sstable::from_meta_bytes(object_id, &buf, skip_bloom_filter_in_serde)?;
781 let add = (now.elapsed().as_secs_f64() * 1000.0).ceil();
782 stats_ptr.fetch_add(add as u64, Ordering::Relaxed);
783 Ok::<_, anyhow::Error>(SstableMetaCacheValue::from(sst))
784 });
785
786 stats.cache_meta_block_total += 1;
787 let entry = fetch
788 .instrument_await("fetch_meta".verbose())
789 .await
790 .map_err(HummockError::foyer_error);
791 if let Ok(ref entry) = entry
792 && entry.source() == foyer::Source::Outer
793 {
794 stats.cache_meta_block_miss += 1;
795 }
796 entry
797 }
798
799 pub async fn list_sst_object_metadata_from_object_store(
800 &self,
801 prefix: Option<String>,
802 start_after: Option<String>,
803 limit: Option<usize>,
804 ) -> HummockResult<ObjectMetadataIter> {
805 let list_path = format!("{}/{}", self.path, prefix.unwrap_or("".into()));
806 let raw_iter = self.store.list(&list_path, start_after, limit).await?;
807 let iter = raw_iter.filter(|r| match r {
808 Ok(i) => future::ready(i.key.ends_with(&format!(".{}", SST_OBJECT_SUFFIX))),
809 Err(_) => future::ready(true),
810 });
811 Ok(Box::pin(iter))
812 }
813
814 pub fn create_sst_writer(
815 self: Arc<Self>,
816 object_id: impl Into<HummockSstableObjectId>,
817 options: SstableWriterOptions,
818 ) -> BatchUploadWriter {
819 BatchUploadWriter::new(object_id, self, options)
820 }
821
822 pub fn insert_meta_cache(&self, object_id: HummockSstableObjectId, meta: SstableMeta) {
823 let sst = Sstable::new(object_id, meta, self.skip_bloom_filter_in_serde);
824 self.meta_cache
825 .insert(object_id, SstableMetaCacheValue::from(sst));
826 }
827
828 pub fn get_prefetch_memory_usage(&self) -> usize {
829 self.prefetch_buffer_usage.load(Ordering::Acquire)
830 }
831
832 pub async fn get_stream_for_blocks(
833 &self,
834 object_id: HummockSstableObjectId,
835 metas: &[BlockMeta],
836 ) -> HummockResult<BlockDataStream> {
837 fail_point!("get_stream_err");
838 let data_path = self.get_sst_data_path(object_id);
839 let store = self.store();
840 let block_meta = &metas[0];
841 let start_pos = block_meta.offset as usize;
842 let end_pos = metas.iter().map(|meta| meta.len as usize).sum::<usize>() + start_pos;
843 let range = start_pos..end_pos;
844 let ret = tokio::spawn(async move { store.streaming_read(&data_path, range).await }).await;
846
847 let reader = match ret {
848 Ok(Ok(reader)) => reader,
849 Ok(Err(e)) => return Err(HummockError::from(e)),
850 Err(e) => {
851 return Err(HummockError::other(format!(
852 "failed to get result, this read request may be canceled: {}",
853 e.as_report()
854 )));
855 }
856 };
857 Ok(BlockDataStream::new(reader, metas))
858 }
859
860 pub fn meta_cache(&self) -> &HybridCache<HummockSstableObjectId, SstableMetaCacheValue> {
861 &self.meta_cache
862 }
863
864 pub fn block_cache(&self) -> &HybridCache<SstableBlockIndex, Box<Block>> {
865 &self.block_cache
866 }
867
868 pub fn recent_filter(&self) -> &Arc<RecentFilter<(HummockSstableObjectId, usize)>> {
869 &self.recent_filter
870 }
871
872 pub async fn create_streaming_uploader(
873 &self,
874 path: &str,
875 ) -> ObjectResult<ObjectStreamingUploader> {
876 self.store.streaming_upload(path).await
877 }
878}
879
880pub type SstableStoreRef = Arc<SstableStore>;
881#[cfg(test)]
882mod tests {
883 use std::ops::Range;
884 use std::sync::Arc;
885
886 use risingwave_hummock_sdk::HummockObjectId;
887 use risingwave_hummock_sdk::sstable_info::SstableInfo;
888
889 use super::{SstableStoreRef, SstableWriterOptions};
890 use crate::hummock::iterator::HummockIterator;
891 use crate::hummock::iterator::test_utils::{iterator_test_key_of, mock_sstable_store};
892 use crate::hummock::sstable::SstableIteratorReadOptions;
893 use crate::hummock::test_utils::{
894 default_builder_opt_for_test, gen_default_test_sstable, gen_test_sstable_data, put_sst,
895 test_key_of,
896 };
897 use crate::hummock::value::HummockValue;
898 use crate::hummock::{CachePolicy, SstableIterator, SstableMeta, SstableStore};
899 use crate::monitor::StoreLocalStatistic;
900
901 const SST_ID: u64 = 1;
902
903 fn get_hummock_value(x: usize) -> HummockValue<Vec<u8>> {
904 HummockValue::put(format!("overlapped_new_{}", x).as_bytes().to_vec())
905 }
906
907 async fn validate_sst(
908 sstable_store: SstableStoreRef,
909 info: &SstableInfo,
910 mut meta: SstableMeta,
911 x_range: Range<usize>,
912 ) {
913 let mut stats = StoreLocalStatistic::default();
914 let holder = sstable_store.sstable(info, &mut stats).await.unwrap();
915 std::mem::take(&mut meta.bloom_filter);
916 assert_eq!(holder.meta, meta);
917 let holder = sstable_store.sstable(info, &mut stats).await.unwrap();
918 assert_eq!(holder.meta, meta);
919 let mut iter = SstableIterator::new(
920 holder,
921 sstable_store,
922 Arc::new(SstableIteratorReadOptions::default()),
923 info,
924 );
925 iter.rewind().await.unwrap();
926 for i in x_range {
927 let key = iter.key();
928 let value = iter.value();
929 assert_eq!(key, iterator_test_key_of(i).to_ref());
930 assert_eq!(value, get_hummock_value(i).as_slice());
931 iter.next().await.unwrap();
932 }
933 }
934
935 #[tokio::test]
936 async fn test_batch_upload() {
937 let sstable_store = mock_sstable_store().await;
938 let x_range = 0..100;
939 let (data, meta) = gen_test_sstable_data(
940 default_builder_opt_for_test(),
941 x_range
942 .clone()
943 .map(|x| (iterator_test_key_of(x), get_hummock_value(x))),
944 )
945 .await;
946 let writer_opts = SstableWriterOptions {
947 capacity_hint: None,
948 tracker: None,
949 policy: CachePolicy::Disable,
950 };
951 let info = put_sst(
952 SST_ID,
953 data.clone(),
954 meta.clone(),
955 sstable_store.clone(),
956 writer_opts,
957 vec![0],
958 )
959 .await
960 .unwrap();
961
962 validate_sst(sstable_store, &info, meta, x_range).await;
963 }
964
965 #[tokio::test]
966 async fn test_streaming_upload() {
967 let sstable_store = mock_sstable_store().await;
969 let x_range = 0..100;
970 let (data, meta) = gen_test_sstable_data(
971 default_builder_opt_for_test(),
972 x_range
973 .clone()
974 .map(|x| (iterator_test_key_of(x), get_hummock_value(x))),
975 )
976 .await;
977 let writer_opts = SstableWriterOptions {
978 capacity_hint: None,
979 tracker: None,
980 policy: CachePolicy::Disable,
981 };
982 let info = put_sst(
983 SST_ID,
984 data.clone(),
985 meta.clone(),
986 sstable_store.clone(),
987 writer_opts,
988 vec![0],
989 )
990 .await
991 .unwrap();
992
993 validate_sst(sstable_store, &info, meta, x_range).await;
994 }
995
996 #[tokio::test]
997 async fn test_empty_prefetch_falls_back_to_block_get() {
998 let mut sstable_store = mock_sstable_store().await;
999 Arc::get_mut(&mut sstable_store)
1001 .unwrap()
1002 .max_prefetch_block_number = 0;
1003 let (sstable, info) =
1004 gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone())
1005 .await;
1006 sstable_store.clear_block_cache().await.unwrap();
1007 let mut iter = SstableIterator::new(
1008 sstable,
1009 sstable_store.clone(),
1010 Arc::new(SstableIteratorReadOptions {
1011 prefetch: true,
1012 cache_policy: CachePolicy::Disable,
1013 ..Default::default()
1014 }),
1015 &info,
1016 );
1017 tokio::time::timeout(std::time::Duration::from_secs(5), iter.rewind())
1018 .await
1019 .expect("empty prefetch must not cause an unbounded refill loop")
1020 .unwrap();
1021 assert_eq!(iter.key(), test_key_of(0).to_ref());
1022 let mut stats = StoreLocalStatistic::default();
1023 iter.collect_local_statistic(&mut stats);
1024 assert_eq!(stats.cache_data_prefetch_count, 1);
1025 assert_eq!(stats.cache_data_block_total, 1);
1026 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1027 }
1028
1029 #[tokio::test]
1030 async fn test_prefetch_failure_falls_back_and_releases_tracker() {
1031 for truncate in [true, false] {
1032 let sstable_store = mock_sstable_store().await;
1033 let (sstable, info) =
1034 gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone())
1035 .await;
1036 assert!(sstable.meta.block_metas.len() > 1);
1037 sstable_store.clear_block_cache().await.unwrap();
1038 let path = sstable_store.get_sst_data_path(sstable.id);
1039 let mut data = sstable_store.store.read(&path, ..).await.unwrap().to_vec();
1040 let second_offset = sstable.meta.block_metas[1].offset as usize;
1041 if truncate {
1042 data.truncate(second_offset);
1044 } else {
1045 data[second_offset] ^= 1;
1047 }
1048 sstable_store
1049 .store
1050 .upload(&path, data.into())
1051 .await
1052 .unwrap();
1053 let mut iter = SstableIterator::new(
1054 sstable.clone(),
1055 sstable_store.clone(),
1056 Arc::new(SstableIteratorReadOptions {
1057 prefetch: true,
1058 cache_policy: CachePolicy::Disable,
1059 ..Default::default()
1060 }),
1061 &info,
1062 );
1063 iter.rewind().await.unwrap();
1064 assert_eq!(iter.key(), test_key_of(0).to_ref());
1065 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1066 let mut stats = StoreLocalStatistic::default();
1067 iter.collect_local_statistic(&mut stats);
1068 assert_eq!(stats.cache_data_prefetch_count, 1);
1069 assert_eq!(stats.cache_data_block_total, 1);
1070 let second_key = risingwave_hummock_sdk::key::FullKey::decode(
1072 &sstable.meta.block_metas[1].smallest_key,
1073 );
1074 assert!(iter.seek(second_key).await.is_err());
1075 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1076 }
1077 }
1078
1079 #[tokio::test]
1080 async fn test_prefetch_releases_old_budget_before_refill() {
1081 let mut sstable_store = mock_sstable_store().await;
1082 Arc::get_mut(&mut sstable_store)
1085 .unwrap()
1086 .prefetch_buffer_capacity = 0;
1087 let (sstable, info) =
1088 gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone())
1089 .await;
1090 sstable_store.clear_block_cache().await.unwrap();
1091 let next_batch = sstable_store.max_prefetch_block_number;
1092 assert!(sstable.meta.block_metas.len() > next_batch + 1);
1093 let mut iter = SstableIterator::new(
1094 sstable.clone(),
1095 sstable_store.clone(),
1096 Arc::new(SstableIteratorReadOptions {
1097 prefetch: true,
1098 cache_policy: CachePolicy::Disable,
1099 ..Default::default()
1100 }),
1101 &info,
1102 );
1103 for idx in [0, next_batch] {
1104 let key = risingwave_hummock_sdk::key::FullKey::decode(
1105 &sstable.meta.block_metas[idx].smallest_key,
1106 );
1107 iter.seek(key).await.unwrap();
1108 assert_eq!(iter.key(), key);
1109 assert!(sstable_store.get_prefetch_memory_usage() > 0);
1110 }
1111 let mut stats = StoreLocalStatistic::default();
1112 iter.collect_local_statistic(&mut stats);
1113 assert_eq!(stats.cache_data_prefetch_count, 2);
1114 assert_eq!(stats.cache_data_block_total, 0);
1115 drop(iter);
1116 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1117 }
1118
1119 #[tokio::test]
1120 async fn test_prefetch_producer_large_limit() {
1121 let mut sstable_store = mock_sstable_store().await;
1122 Arc::get_mut(&mut sstable_store)
1123 .unwrap()
1124 .max_prefetch_block_number = usize::MAX;
1125 let (sstable, _) =
1126 gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone())
1127 .await;
1128 sstable_store.clear_block_cache().await.unwrap();
1129 let mut stats = StoreLocalStatistic::default();
1130 let mut stream = sstable_store
1131 .prefetch_blocks(&sstable, 1, 3, CachePolicy::Disable, &mut stats)
1132 .await
1133 .unwrap();
1134 assert_eq!(stats.cache_data_prefetch_block_count, 2);
1135 for idx in [1, 2] {
1136 assert!(matches!(
1137 stream.take_block(idx),
1138 crate::hummock::block_stream::PrefetchLookup::Hit(_)
1139 ));
1140 }
1141 assert!(matches!(
1142 stream.take_block(3),
1143 crate::hummock::block_stream::PrefetchLookup::Exhausted
1144 ));
1145 drop(stream);
1146 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1147 }
1148
1149 #[tokio::test]
1150 async fn test_prefetch_producer_respects_disabled_cache() {
1151 let sstable_store = mock_sstable_store().await;
1152 let (sstable, _) =
1153 gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone())
1154 .await;
1155 sstable_store.clear_block_cache().await.unwrap();
1156 sstable_store
1158 .get(
1159 &sstable,
1160 1,
1161 CachePolicy::Fill(foyer::Hint::Normal),
1162 &mut StoreLocalStatistic::default(),
1163 )
1164 .await
1165 .unwrap();
1166 let mut stats = StoreLocalStatistic::default();
1167 let stream = sstable_store
1168 .prefetch_blocks(&sstable, 0, 3, CachePolicy::Disable, &mut stats)
1169 .await
1170 .unwrap();
1171 assert_eq!(stats.cache_data_prefetch_block_count, 3);
1172 drop(stream);
1173 assert!(
1174 !sstable_store
1175 .block_cache
1176 .contains(&super::SstableBlockIndex {
1177 sst_id: sstable.id,
1178 block_idx: 0,
1179 })
1180 );
1181
1182 sstable_store
1184 .get(
1185 &sstable,
1186 0,
1187 CachePolicy::Fill(foyer::Hint::Normal),
1188 &mut StoreLocalStatistic::default(),
1189 )
1190 .await
1191 .unwrap();
1192 sstable_store.delete(sstable.id).await.unwrap();
1193 assert!(
1194 sstable_store
1195 .prefetch_blocks(
1196 &sstable,
1197 0,
1198 3,
1199 CachePolicy::NotFill,
1200 &mut StoreLocalStatistic::default()
1201 )
1202 .await
1203 .is_ok()
1204 );
1205 assert!(
1206 sstable_store
1207 .prefetch_blocks(
1208 &sstable,
1209 0,
1210 3,
1211 CachePolicy::Disable,
1212 &mut StoreLocalStatistic::default()
1213 )
1214 .await
1215 .is_err()
1216 );
1217 assert_eq!(sstable_store.get_prefetch_memory_usage(), 0);
1218 }
1219
1220 #[tokio::test]
1221 async fn test_basic() {
1222 let sstable_store = mock_sstable_store().await;
1223 let object_id = 123;
1224 let data_path = sstable_store.get_sst_data_path(object_id);
1225 assert_eq!(data_path, "test/123.data");
1226 assert_eq!(
1227 SstableStore::get_object_id_from_path(&data_path),
1228 HummockObjectId::Sstable(object_id.into())
1229 );
1230 }
1231
1232 #[tokio::test]
1233 async fn test_clear_file_cache_and_refetch() {
1234 let sstable_store = mock_sstable_store().await;
1235 let x_range = 0..10;
1236 let (data, meta) = gen_test_sstable_data(
1237 default_builder_opt_for_test(),
1238 x_range.map(|x| (iterator_test_key_of(x), get_hummock_value(x))),
1239 )
1240 .await;
1241 let info = put_sst(
1242 SST_ID,
1243 data,
1244 meta,
1245 sstable_store.clone(),
1246 SstableWriterOptions {
1247 capacity_hint: None,
1248 tracker: None,
1249 policy: CachePolicy::Fill(foyer::Hint::Normal),
1250 },
1251 vec![0],
1252 )
1253 .await
1254 .unwrap();
1255
1256 let mut stats = StoreLocalStatistic::default();
1257 assert!(
1258 sstable_store
1259 .sstable_cached(info.object_id)
1260 .await
1261 .unwrap()
1262 .is_some()
1263 );
1264 let sstable = sstable_store.sstable(&info, &mut stats).await.unwrap();
1265 sstable_store
1266 .get(
1267 &sstable,
1268 0,
1269 CachePolicy::Fill(foyer::Hint::Normal),
1270 &mut stats,
1271 )
1272 .await
1273 .unwrap();
1274 assert!(
1275 sstable_store
1276 .block_cache()
1277 .get(&super::SstableBlockIndex {
1278 sst_id: info.object_id,
1279 block_idx: 0,
1280 })
1281 .await
1282 .unwrap()
1283 .is_some()
1284 );
1285
1286 sstable_store.clear_meta_cache().await.unwrap();
1287 assert!(
1288 sstable_store
1289 .sstable_cached(info.object_id)
1290 .await
1291 .unwrap()
1292 .is_none()
1293 );
1294 sstable_store.sstable(&info, &mut stats).await.unwrap();
1295 assert!(
1296 sstable_store
1297 .sstable_cached(info.object_id)
1298 .await
1299 .unwrap()
1300 .is_some()
1301 );
1302
1303 sstable_store.clear_block_cache().await.unwrap();
1304 assert!(
1305 sstable_store
1306 .block_cache()
1307 .get(&super::SstableBlockIndex {
1308 sst_id: info.object_id,
1309 block_idx: 0,
1310 })
1311 .await
1312 .unwrap()
1313 .is_none()
1314 );
1315 sstable_store
1316 .get(
1317 &sstable,
1318 0,
1319 CachePolicy::Fill(foyer::Hint::Normal),
1320 &mut stats,
1321 )
1322 .await
1323 .unwrap();
1324 assert!(
1325 sstable_store
1326 .block_cache()
1327 .get(&super::SstableBlockIndex {
1328 sst_id: info.object_id,
1329 block_idx: 0,
1330 })
1331 .await
1332 .unwrap()
1333 .is_some()
1334 );
1335 }
1336}