1use std::fmt;
18use std::ops::Range;
19use std::sync::Arc;
20use std::time::{Duration, Instant};
21
22use bytes::Bytes;
23use common_base::readable_size::ReadableSize;
24use common_telemetry::{debug, error, info, warn};
25use futures::{AsyncWriteExt, FutureExt, TryStreamExt};
26use moka::future::Cache;
27use moka::notification::RemovalCause;
28use moka::policy::EvictionPolicy;
29use object_store::util::join_path;
30use object_store::{ErrorKind, ObjectStore, Reader};
31use parquet::file::metadata::{PageIndexPolicy, ParquetMetaData};
32use snafu::ResultExt;
33use store_api::storage::{FileId, RegionId};
34use tokio::sync::mpsc::{Sender, UnboundedReceiver};
35
36use crate::access_layer::TempFileCleaner;
37use crate::cache::{
38 CachedSstMeta, FILE_TYPE, INDEX_TYPE, SstMetaPreparation, decode_sst_meta, prepare_sst_meta,
39};
40use crate::error::{self, OpenDalSnafu, Result};
41use crate::metrics::{
42 CACHE_BYTES, CACHE_HIT, CACHE_MISS, WRITE_CACHE_DOWNLOAD_BYTES_TOTAL,
43 WRITE_CACHE_DOWNLOAD_ELAPSED,
44};
45use crate::region::opener::RegionLoadCacheTask;
46use crate::sst::parquet::helper::fetch_byte_ranges;
47use crate::sst::parquet::metadata::MetadataLoader;
48use crate::sst::parquet::reader::MetadataCacheMetrics;
49
50const FILE_DIR: &str = "cache/object/write/";
54
55pub(crate) const DEFAULT_INDEX_CACHE_PERCENT: u8 = 20;
57
58const MIN_CACHE_CAPACITY: u64 = 512 * 1024 * 1024;
60
61const DOWNLOAD_TASK_CHANNEL_SIZE: usize = 64;
63
64struct DownloadTask {
66 index_key: IndexKey,
67 remote_path: String,
68 remote_store: ObjectStore,
69 file_size: u64,
70}
71
72#[derive(Debug)]
74struct FileCacheInner {
75 local_store: ObjectStore,
77 parquet_index: Cache<IndexKey, IndexValue>,
79 puffin_index: Cache<IndexKey, IndexValue>,
81}
82
83impl FileCacheInner {
84 fn memory_index(&self, file_type: FileType) -> &Cache<IndexKey, IndexValue> {
86 match file_type {
87 FileType::Parquet => &self.parquet_index,
88 FileType::Puffin { .. } => &self.puffin_index,
89 }
90 }
91
92 fn cache_file_path(&self, key: IndexKey) -> String {
94 cache_file_path(FILE_DIR, key)
95 }
96
97 async fn put(&self, key: IndexKey, value: IndexValue) {
101 CACHE_BYTES
102 .with_label_values(&[key.file_type.metric_label()])
103 .add(value.file_size.into());
104 let index = self.memory_index(key.file_type);
105 index.insert(key, value).await;
106
107 index.run_pending_tasks().await;
109 }
110
111 async fn recover(&self) -> Result<()> {
113 let now = Instant::now();
114 let mut lister = self
115 .local_store
116 .lister_with(FILE_DIR)
117 .await
118 .context(OpenDalSnafu)?;
119 let (mut total_size, mut total_keys) = (0i64, 0);
122 let (mut parquet_size, mut puffin_size) = (0i64, 0i64);
123 while let Some(entry) = lister.try_next().await.context(OpenDalSnafu)? {
124 let meta = entry.metadata();
125 if !meta.is_file() {
126 continue;
127 }
128 let Some(key) = parse_index_key(entry.name()) else {
129 continue;
130 };
131
132 let meta = self
133 .local_store
134 .stat(entry.path())
135 .await
136 .context(OpenDalSnafu)?;
137 let file_size = meta.content_length() as u32;
138 let index = self.memory_index(key.file_type);
139 index.insert(key, IndexValue { file_size }).await;
140 let size = i64::from(file_size);
141 total_size += size;
142 total_keys += 1;
143
144 match key.file_type {
146 FileType::Parquet => parquet_size += size,
147 FileType::Puffin { .. } => puffin_size += size,
148 }
149 }
150 CACHE_BYTES
152 .with_label_values(&[FILE_TYPE])
153 .add(parquet_size);
154 CACHE_BYTES
155 .with_label_values(&[INDEX_TYPE])
156 .add(puffin_size);
157
158 self.parquet_index.run_pending_tasks().await;
161 self.puffin_index.run_pending_tasks().await;
162
163 let parquet_weight = self.parquet_index.weighted_size();
164 let parquet_count = self.parquet_index.entry_count();
165 let puffin_weight = self.puffin_index.weighted_size();
166 let puffin_count = self.puffin_index.entry_count();
167 info!(
168 "Recovered file cache, num_keys: {}, num_bytes: {}, parquet(count: {}, weight: {}), puffin(count: {}, weight: {}), cost: {:?}",
169 total_keys,
170 total_size,
171 parquet_count,
172 parquet_weight,
173 puffin_count,
174 puffin_weight,
175 now.elapsed()
176 );
177 Ok(())
178 }
179
180 async fn download_without_cleaning(
182 &self,
183 index_key: IndexKey,
184 remote_path: &str,
185 remote_store: &ObjectStore,
186 file_size: u64,
187 concurrency: usize,
188 ) -> Result<()> {
189 const DOWNLOAD_READER_CHUNK_SIZE: ReadableSize = ReadableSize::mb(8);
190
191 let file_type = index_key.file_type;
192 let timer = WRITE_CACHE_DOWNLOAD_ELAPSED
193 .with_label_values(&[match file_type {
194 FileType::Parquet => "download_parquet",
195 FileType::Puffin { .. } => "download_puffin",
196 }])
197 .start_timer();
198
199 let reader = remote_store
200 .reader_with(remote_path)
201 .concurrent(concurrency)
202 .chunk(DOWNLOAD_READER_CHUNK_SIZE.as_bytes() as usize)
203 .await
204 .context(error::OpenDalSnafu)?
205 .into_futures_async_read(0..file_size)
206 .await
207 .context(error::OpenDalSnafu)?;
208
209 let cache_path = self.cache_file_path(index_key);
210 let mut writer = self
211 .local_store
212 .writer(&cache_path)
213 .await
214 .context(error::OpenDalSnafu)?
215 .into_futures_async_write();
216
217 let region_id = index_key.region_id;
218 let file_id = index_key.file_id;
219 let bytes_written =
220 futures::io::copy(reader, &mut writer)
221 .await
222 .context(error::DownloadSnafu {
223 region_id,
224 file_id,
225 file_type,
226 })?;
227 writer.close().await.context(error::DownloadSnafu {
228 region_id,
229 file_id,
230 file_type,
231 })?;
232
233 WRITE_CACHE_DOWNLOAD_BYTES_TOTAL.inc_by(bytes_written);
234
235 let elapsed = timer.stop_and_record();
236 debug!(
237 "Successfully download file '{}' to local '{}', file size: {}, region: {}, cost: {:?}s",
238 remote_path, cache_path, bytes_written, region_id, elapsed,
239 );
240
241 let index_value = IndexValue {
242 file_size: bytes_written as _,
243 };
244 self.put(index_key, index_value).await;
245 Ok(())
246 }
247
248 async fn download(
250 &self,
251 index_key: IndexKey,
252 remote_path: &str,
253 remote_store: &ObjectStore,
254 file_size: u64,
255 concurrency: usize,
256 ) -> Result<()> {
257 if let Err(e) = self
258 .download_without_cleaning(index_key, remote_path, remote_store, file_size, concurrency)
259 .await
260 {
261 error!(e; "Failed to download file '{}' for region {}", remote_path, index_key.region_id);
262
263 let filename = index_key.to_string();
264 TempFileCleaner::clean_atomic_dir_files(&self.local_store, &[&filename]).await;
265
266 return Err(e);
267 }
268
269 Ok(())
270 }
271
272 fn contains_key(&self, key: &IndexKey) -> bool {
274 self.memory_index(key.file_type).contains_key(key)
275 }
276}
277
278#[derive(Debug, Clone)]
281pub(crate) struct FileCache {
282 inner: Arc<FileCacheInner>,
284 puffin_capacity: u64,
286 download_task_tx: Option<Sender<DownloadTask>>,
288}
289
290pub(crate) type FileCacheRef = Arc<FileCache>;
291
292impl FileCache {
293 fn split_cache_capacities(total_capacity: u64, index_percent: u8) -> (u64, u64) {
296 let desired_puffin_capacity = total_capacity * u64::from(index_percent) / 100;
297 let min_cache_capacity = MIN_CACHE_CAPACITY.min(total_capacity / 2);
298 let puffin_capacity =
299 desired_puffin_capacity.clamp(min_cache_capacity, total_capacity - min_cache_capacity);
300 let parquet_capacity = total_capacity - puffin_capacity;
301 (parquet_capacity, puffin_capacity)
302 }
303
304 pub(crate) fn new(
306 local_store: ObjectStore,
307 capacity: ReadableSize,
308 ttl: Option<Duration>,
309 index_cache_percent: Option<u8>,
310 enable_background_worker: bool,
311 ) -> FileCache {
312 let index_percent = index_cache_percent
314 .filter(|&percent| percent > 0 && percent < 100)
315 .unwrap_or(DEFAULT_INDEX_CACHE_PERCENT);
316 let total_capacity = capacity.as_bytes();
317
318 let (parquet_capacity, puffin_capacity) =
319 Self::split_cache_capacities(total_capacity, index_percent);
320
321 info!(
322 "Initializing file cache with index_percent: {}%, total_capacity: {}, parquet_capacity: {}, puffin_capacity: {}",
323 index_percent,
324 ReadableSize(total_capacity),
325 ReadableSize(parquet_capacity),
326 ReadableSize(puffin_capacity)
327 );
328
329 let parquet_index = Self::build_cache(local_store.clone(), parquet_capacity, ttl, "file");
330 let puffin_index = Self::build_cache(local_store.clone(), puffin_capacity, ttl, "index");
331
332 let inner = Arc::new(FileCacheInner {
334 local_store,
335 parquet_index,
336 puffin_index,
337 });
338
339 let download_task_tx = if enable_background_worker {
341 let (tx, rx) = tokio::sync::mpsc::channel(DOWNLOAD_TASK_CHANNEL_SIZE);
342 Self::spawn_download_worker(inner.clone(), rx);
343 Some(tx)
344 } else {
345 None
346 };
347
348 FileCache {
349 inner,
350 puffin_capacity,
351 download_task_tx,
352 }
353 }
354
355 fn spawn_download_worker(
357 inner: Arc<FileCacheInner>,
358 mut download_task_rx: tokio::sync::mpsc::Receiver<DownloadTask>,
359 ) {
360 tokio::spawn(async move {
361 info!("Background download worker started");
362 while let Some(task) = download_task_rx.recv().await {
363 if inner.contains_key(&task.index_key) {
365 debug!(
366 "Skipping background download for region {}, file {} - already in cache",
367 task.index_key.region_id, task.index_key.file_id
368 );
369 continue;
370 }
371
372 let _ = inner
374 .download(
375 task.index_key,
376 &task.remote_path,
377 &task.remote_store,
378 task.file_size,
379 1, )
381 .await;
382 }
383 info!("Background download worker stopped");
384 });
385 }
386
387 fn build_cache(
389 local_store: ObjectStore,
390 capacity: u64,
391 ttl: Option<Duration>,
392 label: &'static str,
393 ) -> Cache<IndexKey, IndexValue> {
394 let cache_store = local_store;
395 let mut builder = Cache::builder()
396 .eviction_policy(EvictionPolicy::lru())
397 .weigher(|_key, value: &IndexValue| -> u32 {
398 value.file_size
400 })
401 .max_capacity(capacity)
402 .async_eviction_listener(move |key, value, cause| {
403 let store = cache_store.clone();
404 let file_path = cache_file_path(FILE_DIR, *key);
406 async move {
407 if let RemovalCause::Replaced = cause {
408 CACHE_BYTES.with_label_values(&[label]).sub(value.file_size.into());
411 return;
412 }
413
414 match store.delete(&file_path).await {
415 Ok(()) => {
416 CACHE_BYTES.with_label_values(&[label]).sub(value.file_size.into());
417 }
418 Err(e) => {
419 warn!(e; "Failed to delete cached file {} for region {}", file_path, key.region_id);
420 }
421 }
422 }
423 .boxed()
424 });
425 if let Some(ttl) = ttl {
426 builder = builder.time_to_idle(ttl);
427 }
428 builder.build()
429 }
430
431 pub(crate) async fn put(&self, key: IndexKey, value: IndexValue) {
435 self.inner.put(key, value).await
436 }
437
438 pub(crate) async fn get(&self, key: IndexKey) -> Option<IndexValue> {
439 self.inner.memory_index(key.file_type).get(&key).await
440 }
441
442 #[allow(unused)]
444 pub(crate) async fn reader(&self, key: IndexKey) -> Option<Reader> {
445 let index = self.inner.memory_index(key.file_type);
448 if index.get(&key).await.is_none() {
449 CACHE_MISS
450 .with_label_values(&[key.file_type.metric_label()])
451 .inc();
452 return None;
453 }
454
455 let file_path = self.inner.cache_file_path(key);
456 match self.get_reader(&file_path).await {
457 Ok(Some(reader)) => {
458 CACHE_HIT
459 .with_label_values(&[key.file_type.metric_label()])
460 .inc();
461 return Some(reader);
462 }
463 Err(e) => {
464 if e.kind() != ErrorKind::NotFound {
465 warn!(e; "Failed to get file for key {:?}", key);
466 }
467 }
468 Ok(None) => {}
469 }
470
471 index.remove(&key).await;
473 CACHE_MISS
474 .with_label_values(&[key.file_type.metric_label()])
475 .inc();
476 None
477 }
478
479 pub(crate) async fn read_ranges(
481 &self,
482 key: IndexKey,
483 ranges: &[Range<u64>],
484 ) -> Option<Vec<Bytes>> {
485 let index = self.inner.memory_index(key.file_type);
486 if index.get(&key).await.is_none() {
487 CACHE_MISS
488 .with_label_values(&[key.file_type.metric_label()])
489 .inc();
490 return None;
491 }
492
493 let file_path = self.inner.cache_file_path(key);
494 let bytes_result =
497 fetch_byte_ranges(&file_path, self.inner.local_store.clone(), ranges).await;
498 match bytes_result {
499 Ok(bytes) => {
500 CACHE_HIT
501 .with_label_values(&[key.file_type.metric_label()])
502 .inc();
503 Some(bytes)
504 }
505 Err(e) => {
506 if e.kind() != ErrorKind::NotFound {
507 warn!(e; "Failed to get file for key {:?}", key);
508 }
509
510 index.remove(&key).await;
512 CACHE_MISS
513 .with_label_values(&[key.file_type.metric_label()])
514 .inc();
515 None
516 }
517 }
518 }
519
520 pub(crate) async fn remove(&self, key: IndexKey) {
524 let file_path = self.inner.cache_file_path(key);
525 self.inner.memory_index(key.file_type).remove(&key).await;
526 if let Err(e) = self.inner.local_store.delete(&file_path).await {
528 warn!(e; "Failed to delete a cached file {}", file_path);
529 }
530 }
531
532 pub(crate) async fn recover(
537 &self,
538 sync: bool,
539 task_receiver: Option<UnboundedReceiver<RegionLoadCacheTask>>,
540 ) {
541 let moved_self = self.clone();
542 let handle = tokio::spawn(async move {
543 if let Err(err) = moved_self.inner.recover().await {
544 error!(err; "Failed to recover file cache.")
545 }
546
547 if let Some(mut receiver) = task_receiver {
550 info!("Spawning background task for processing region load cache tasks");
551 tokio::spawn(async move {
552 while let Some(task) = receiver.recv().await {
553 task.fill_cache(&moved_self).await;
554 }
555 info!("Background task for processing region load cache tasks stopped");
556 });
557 }
558 });
559
560 if sync {
561 let _ = handle.await;
562 }
563 }
564
565 pub(crate) fn cache_file_path(&self, key: IndexKey) -> String {
567 self.inner.cache_file_path(key)
568 }
569
570 pub(crate) fn local_store(&self) -> ObjectStore {
572 self.inner.local_store.clone()
573 }
574
575 pub(crate) async fn get_parquet_meta_data(
578 &self,
579 key: IndexKey,
580 cache_metrics: &mut MetadataCacheMetrics,
581 page_index_policy: PageIndexPolicy,
582 ) -> Option<ParquetMetaData> {
583 if let Some(index_value) = self.inner.parquet_index.get(&key).await {
585 let local_store = self.local_store();
587 let file_path = self.inner.cache_file_path(key);
588 let file_size = index_value.file_size as u64;
589 let mut metadata_loader = MetadataLoader::new(local_store, &file_path, file_size);
590 metadata_loader.with_page_index_policy(page_index_policy);
591
592 match metadata_loader.load(cache_metrics).await {
593 Ok(metadata) => {
594 CACHE_HIT
595 .with_label_values(&[key.file_type.metric_label()])
596 .inc();
597 Some(metadata)
598 }
599 Err(e) => {
600 if !e.is_object_not_found() {
601 warn!(
602 e; "Failed to get parquet metadata for key {:?}",
603 key
604 );
605 }
606 self.inner.parquet_index.remove(&key).await;
608 CACHE_MISS
609 .with_label_values(&[key.file_type.metric_label()])
610 .inc();
611 None
612 }
613 }
614 } else {
615 CACHE_MISS
616 .with_label_values(&[key.file_type.metric_label()])
617 .inc();
618 None
619 }
620 }
621
622 pub(crate) async fn get_sst_meta_data(
626 &self,
627 key: IndexKey,
628 cache_metrics: &mut MetadataCacheMetrics,
629 page_index_policy: PageIndexPolicy,
630 ) -> Option<SstMetaPreparation> {
631 let file_path = self.inner.cache_file_path(key);
632 let metadata = self
633 .get_parquet_meta_data(key, cache_metrics, page_index_policy)
634 .await?;
635 match prepare_sst_meta(&file_path, metadata, None, page_index_policy).await {
636 Ok(metadata) => Some(metadata),
637 Err(err) => {
638 CACHE_MISS
639 .with_label_values(&[key.file_type.metric_label()])
640 .inc();
641 warn!(
642 err; "Failed to prepare cached parquet metadata for key {:?}",
643 key
644 );
645 None
646 }
647 }
648 }
649
650 pub(crate) async fn get_decoded_sst_meta_data(
652 &self,
653 key: IndexKey,
654 cache_metrics: &mut MetadataCacheMetrics,
655 page_index_policy: PageIndexPolicy,
656 ) -> Option<Arc<CachedSstMeta>> {
657 let file_path = self.inner.cache_file_path(key);
658 let metadata = self
659 .get_parquet_meta_data(key, cache_metrics, page_index_policy)
660 .await?;
661 match decode_sst_meta(&file_path, metadata, None, page_index_policy).await {
662 Ok(metadata) => Some(metadata),
663 Err(err) => {
664 CACHE_MISS
665 .with_label_values(&[key.file_type.metric_label()])
666 .inc();
667 warn!(
668 err; "Failed to decode cached parquet metadata for key {:?}",
669 key
670 );
671 None
672 }
673 }
674 }
675
676 async fn get_reader(&self, file_path: &str) -> object_store::Result<Option<Reader>> {
677 if self.inner.local_store.exists(file_path).await? {
678 Ok(Some(self.inner.local_store.reader(file_path).await?))
679 } else {
680 Ok(None)
681 }
682 }
683
684 pub(crate) fn contains_key(&self, key: &IndexKey) -> bool {
686 self.inner.contains_key(key)
687 }
688
689 pub(crate) fn puffin_cache_capacity(&self) -> u64 {
691 self.puffin_capacity
692 }
693
694 pub(crate) fn puffin_cache_size(&self) -> u64 {
696 self.inner.puffin_index.weighted_size()
697 }
698
699 pub(crate) async fn download(
702 &self,
703 index_key: IndexKey,
704 remote_path: &str,
705 remote_store: &ObjectStore,
706 file_size: u64,
707 ) -> Result<()> {
708 self.inner
709 .download(index_key, remote_path, remote_store, file_size, 8) .await
711 }
712
713 pub(crate) fn maybe_download_background(
719 &self,
720 index_key: IndexKey,
721 remote_path: String,
722 remote_store: ObjectStore,
723 file_size: u64,
724 ) {
725 let Some(tx) = &self.download_task_tx else {
727 return;
728 };
729
730 let task = DownloadTask {
731 index_key,
732 remote_path,
733 remote_store,
734 file_size,
735 };
736
737 if let Err(e) = tx.try_send(task) {
739 debug!(
740 "Failed to queue background download task for region {}, file {}: {:?}",
741 index_key.region_id, index_key.file_id, e
742 );
743 }
744 }
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
749pub struct IndexKey {
750 pub region_id: RegionId,
751 pub file_id: FileId,
752 pub file_type: FileType,
753}
754
755impl IndexKey {
756 pub fn new(region_id: RegionId, file_id: FileId, file_type: FileType) -> IndexKey {
758 IndexKey {
759 region_id,
760 file_id,
761 file_type,
762 }
763 }
764}
765
766impl fmt::Display for IndexKey {
767 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
768 write!(
769 f,
770 "{}.{}.{}",
771 self.region_id.as_u64(),
772 self.file_id,
773 self.file_type
774 )
775 }
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
780pub enum FileType {
781 Parquet,
783 Puffin(u64),
785}
786
787impl fmt::Display for FileType {
788 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789 match self {
790 FileType::Parquet => write!(f, "parquet"),
791 FileType::Puffin(version) => write!(f, "{}.puffin", version),
792 }
793 }
794}
795
796impl FileType {
797 pub(crate) fn parse(s: &str) -> Option<FileType> {
799 match s {
800 "parquet" => Some(FileType::Parquet),
801 "puffin" => Some(FileType::Puffin(0)),
802 _ => {
803 if let Some(version_str) = s.strip_suffix(".puffin") {
805 let version = version_str.parse::<u64>().ok()?;
806 Some(FileType::Puffin(version))
807 } else {
808 None
809 }
810 }
811 }
812 }
813
814 fn metric_label(&self) -> &'static str {
816 match self {
817 FileType::Parquet => FILE_TYPE,
818 FileType::Puffin(_) => INDEX_TYPE,
819 }
820 }
821}
822
823#[derive(Debug, Clone)]
827pub(crate) struct IndexValue {
828 pub(crate) file_size: u32,
830}
831
832fn cache_file_path(cache_file_dir: &str, key: IndexKey) -> String {
836 join_path(cache_file_dir, &key.to_string())
837}
838
839fn parse_index_key(name: &str) -> Option<IndexKey> {
841 let mut split = name.splitn(3, '.');
842 let region_id = split.next().and_then(|s| {
843 let id = s.parse::<u64>().ok()?;
844 Some(RegionId::from_u64(id))
845 })?;
846 let file_id = split.next().and_then(|s| FileId::parse_str(s).ok())?;
847 let file_type = split.next().and_then(FileType::parse)?;
848
849 Some(IndexKey::new(region_id, file_id, file_type))
850}
851
852#[cfg(test)]
853mod tests {
854 use common_test_util::temp_dir::create_temp_dir;
855 use object_store::services::Fs;
856
857 use super::*;
858
859 fn new_fs_store(path: &str) -> ObjectStore {
860 let builder = Fs::default().root(path);
861 ObjectStore::new(builder).unwrap().finish()
862 }
863
864 #[tokio::test]
865 async fn test_file_cache_ttl() {
866 let dir = create_temp_dir("");
867 let local_store = new_fs_store(dir.path().to_str().unwrap());
868
869 let cache = FileCache::new(
870 local_store.clone(),
871 ReadableSize::mb(10),
872 Some(Duration::from_millis(10)),
873 None,
874 true, );
876 let region_id = RegionId::new(2000, 0);
877 let file_id = FileId::random();
878 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
879 let file_path = cache.cache_file_path(key);
880
881 assert!(cache.reader(key).await.is_none());
883
884 local_store
886 .write(&file_path, b"hello".as_slice())
887 .await
888 .unwrap();
889
890 cache
892 .put(
893 IndexKey::new(region_id, file_id, FileType::Parquet),
894 IndexValue { file_size: 5 },
895 )
896 .await;
897
898 let exist = cache.reader(key).await;
899 assert!(exist.is_some());
900 tokio::time::sleep(Duration::from_millis(15)).await;
901 cache.inner.parquet_index.run_pending_tasks().await;
902 let non = cache.reader(key).await;
903 assert!(non.is_none());
904 }
905
906 #[tokio::test]
907 async fn test_file_cache_basic() {
908 let dir = create_temp_dir("");
909 let local_store = new_fs_store(dir.path().to_str().unwrap());
910
911 let cache = FileCache::new(
912 local_store.clone(),
913 ReadableSize::mb(10),
914 None,
915 None,
916 true, );
918 let region_id = RegionId::new(2000, 0);
919 let file_id = FileId::random();
920 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
921 let file_path = cache.cache_file_path(key);
922
923 assert!(cache.reader(key).await.is_none());
925
926 local_store
928 .write(&file_path, b"hello".as_slice())
929 .await
930 .unwrap();
931 cache
933 .put(
934 IndexKey::new(region_id, file_id, FileType::Parquet),
935 IndexValue { file_size: 5 },
936 )
937 .await;
938
939 let reader = cache.reader(key).await.unwrap();
941 let buf = reader.read(..).await.unwrap().to_vec();
942 assert_eq!("hello", String::from_utf8(buf).unwrap());
943
944 cache.inner.parquet_index.run_pending_tasks().await;
946 assert_eq!(5, cache.inner.parquet_index.weighted_size());
947
948 cache.remove(key).await;
950 assert!(cache.reader(key).await.is_none());
951
952 cache.inner.parquet_index.run_pending_tasks().await;
954
955 assert!(!local_store.exists(&file_path).await.unwrap());
957 assert_eq!(0, cache.inner.parquet_index.weighted_size());
958 }
959
960 #[tokio::test]
961 async fn test_file_cache_file_removed() {
962 let dir = create_temp_dir("");
963 let local_store = new_fs_store(dir.path().to_str().unwrap());
964
965 let cache = FileCache::new(
966 local_store.clone(),
967 ReadableSize::mb(10),
968 None,
969 None,
970 true, );
972 let region_id = RegionId::new(2000, 0);
973 let file_id = FileId::random();
974 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
975 let file_path = cache.cache_file_path(key);
976
977 local_store
979 .write(&file_path, b"hello".as_slice())
980 .await
981 .unwrap();
982 cache
984 .put(
985 IndexKey::new(region_id, file_id, FileType::Parquet),
986 IndexValue { file_size: 5 },
987 )
988 .await;
989
990 local_store.delete(&file_path).await.unwrap();
992
993 assert!(cache.reader(key).await.is_none());
995 assert!(!cache.inner.parquet_index.contains_key(&key));
997 }
998
999 #[tokio::test]
1000 async fn test_file_cache_recover() {
1001 let dir = create_temp_dir("");
1002 let local_store = new_fs_store(dir.path().to_str().unwrap());
1003 let cache = FileCache::new(
1004 local_store.clone(),
1005 ReadableSize::mb(10),
1006 None,
1007 None,
1008 true, );
1010
1011 let region_id = RegionId::new(2000, 0);
1012 let file_type = FileType::Parquet;
1013 let file_ids: Vec<_> = (0..10).map(|_| FileId::random()).collect();
1015 let mut total_size = 0;
1016 for (i, file_id) in file_ids.iter().enumerate() {
1017 let key = IndexKey::new(region_id, *file_id, file_type);
1018 let file_path = cache.cache_file_path(key);
1019 let bytes = i.to_string().into_bytes();
1020 local_store.write(&file_path, bytes.clone()).await.unwrap();
1021
1022 cache
1024 .put(
1025 IndexKey::new(region_id, *file_id, file_type),
1026 IndexValue {
1027 file_size: bytes.len() as u32,
1028 },
1029 )
1030 .await;
1031 total_size += bytes.len();
1032 }
1033
1034 let cache = FileCache::new(
1036 local_store.clone(),
1037 ReadableSize::mb(10),
1038 None,
1039 None,
1040 true, );
1042 assert!(
1044 cache
1045 .reader(IndexKey::new(region_id, file_ids[0], file_type))
1046 .await
1047 .is_none()
1048 );
1049 cache.recover(true, None).await;
1050
1051 cache.inner.parquet_index.run_pending_tasks().await;
1053 assert_eq!(
1054 total_size,
1055 cache.inner.parquet_index.weighted_size() as usize
1056 );
1057
1058 for (i, file_id) in file_ids.iter().enumerate() {
1059 let key = IndexKey::new(region_id, *file_id, file_type);
1060 let reader = cache.reader(key).await.unwrap();
1061 let buf = reader.read(..).await.unwrap().to_vec();
1062 assert_eq!(i.to_string(), String::from_utf8(buf).unwrap());
1063 }
1064 }
1065
1066 #[tokio::test]
1067 async fn test_file_cache_read_ranges() {
1068 let dir = create_temp_dir("");
1069 let local_store = new_fs_store(dir.path().to_str().unwrap());
1070 let file_cache = FileCache::new(
1071 local_store.clone(),
1072 ReadableSize::mb(10),
1073 None,
1074 None,
1075 true, );
1077 let region_id = RegionId::new(2000, 0);
1078 let file_id = FileId::random();
1079 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
1080 let file_path = file_cache.cache_file_path(key);
1081 let data = b"hello greptime database";
1083 local_store
1084 .write(&file_path, data.as_slice())
1085 .await
1086 .unwrap();
1087 file_cache.put(key, IndexValue { file_size: 5 }).await;
1089 let ranges = vec![0..5, 6..10, 15..19, 0..data.len() as u64];
1091 let bytes = file_cache.read_ranges(key, &ranges).await.unwrap();
1092
1093 assert_eq!(4, bytes.len());
1094 assert_eq!(b"hello", bytes[0].as_ref());
1095 assert_eq!(b"grep", bytes[1].as_ref());
1096 assert_eq!(b"data", bytes[2].as_ref());
1097 assert_eq!(data, bytes[3].as_ref());
1098 }
1099
1100 #[test]
1101 fn test_file_cache_capacity_respects_total_budget() {
1102 let total_capacity = ReadableSize::mb(256).as_bytes();
1103 let (parquet_capacity, puffin_capacity) =
1104 FileCache::split_cache_capacities(total_capacity, 20);
1105
1106 assert_eq!(total_capacity, parquet_capacity + puffin_capacity);
1107 assert_eq!(ReadableSize::mb(128).as_bytes(), parquet_capacity);
1108 assert_eq!(ReadableSize::mb(128).as_bytes(), puffin_capacity);
1109 }
1110
1111 #[test]
1112 fn test_file_cache_capacity_keeps_split_when_total_allows_it() {
1113 let total_capacity = ReadableSize::gb(5).as_bytes();
1114 let (parquet_capacity, puffin_capacity) =
1115 FileCache::split_cache_capacities(total_capacity, 20);
1116
1117 assert_eq!(total_capacity, parquet_capacity + puffin_capacity);
1118 assert_eq!(ReadableSize::gb(4).as_bytes(), parquet_capacity);
1119 assert_eq!(ReadableSize::gb(1).as_bytes(), puffin_capacity);
1120 }
1121
1122 #[test]
1123 fn test_cache_file_path() {
1124 let file_id = FileId::parse_str("3368731b-a556-42b8-a5df-9c31ce155095").unwrap();
1125 assert_eq!(
1126 "test_dir/5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.parquet",
1127 cache_file_path(
1128 "test_dir",
1129 IndexKey::new(RegionId::new(1234, 5), file_id, FileType::Parquet)
1130 )
1131 );
1132 assert_eq!(
1133 "test_dir/5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.parquet",
1134 cache_file_path(
1135 "test_dir/",
1136 IndexKey::new(RegionId::new(1234, 5), file_id, FileType::Parquet)
1137 )
1138 );
1139 }
1140
1141 #[test]
1142 fn test_parse_file_name() {
1143 let file_id = FileId::parse_str("3368731b-a556-42b8-a5df-9c31ce155095").unwrap();
1144 let region_id = RegionId::new(1234, 5);
1145 assert_eq!(
1146 IndexKey::new(region_id, file_id, FileType::Parquet),
1147 parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.parquet").unwrap()
1148 );
1149 assert_eq!(
1150 IndexKey::new(region_id, file_id, FileType::Puffin(0)),
1151 parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.puffin").unwrap()
1152 );
1153 assert_eq!(
1154 IndexKey::new(region_id, file_id, FileType::Puffin(42)),
1155 parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.42.puffin")
1156 .unwrap()
1157 );
1158 assert!(parse_index_key("").is_none());
1159 assert!(parse_index_key(".").is_none());
1160 assert!(parse_index_key("5299989643269").is_none());
1161 assert!(parse_index_key("5299989643269.").is_none());
1162 assert!(parse_index_key(".5299989643269").is_none());
1163 assert!(parse_index_key("5299989643269.").is_none());
1164 assert!(parse_index_key("5299989643269.3368731b-a556-42b8-a5df").is_none());
1165 assert!(parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095").is_none());
1166 assert!(
1167 parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.parque").is_none()
1168 );
1169 assert!(
1170 parse_index_key("5299989643269.3368731b-a556-42b8-a5df-9c31ce155095.parquet.puffin")
1171 .is_none()
1172 );
1173 }
1174}