1use std::sync::Arc;
18use std::time::{Duration, Instant};
19
20use common_base::readable_size::ReadableSize;
21use common_telemetry::{debug, info};
22use futures::AsyncWriteExt;
23use object_store::ObjectStore;
24use snafu::ResultExt;
25use store_api::storage::RegionId;
26use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
27
28use crate::access_layer::{
29 FilePathProvider, Metrics, RegionFilePathFactory, SstInfoArray, SstWriteRequest,
30 TempFileCleaner, WriteCachePathProvider, WriteType, new_fs_cache_store,
31};
32use crate::cache::file_cache::{FileCache, FileCacheRef, FileType, IndexKey, IndexValue};
33use crate::cache::manifest_cache::ManifestCache;
34use crate::error::{self, Result};
35use crate::metrics::UPLOAD_BYTES_TOTAL;
36use crate::region::opener::RegionLoadCacheTask;
37use crate::sst::file::RegionFileId;
38use crate::sst::index::IndexerBuilderImpl;
39use crate::sst::index::intermediate::IntermediateManager;
40use crate::sst::index::puffin_manager::{PuffinManagerFactory, SstPuffinManager};
41use crate::sst::parquet::writer::ParquetWriter;
42use crate::sst::parquet::{SstInfo, WriteOptions};
43use crate::sst::{DEFAULT_WRITE_BUFFER_SIZE, DEFAULT_WRITE_CONCURRENCY};
44
45pub trait WriteCacheUploadStoreWrapper: Send + Sync {
47 fn wrap(&self, store: ObjectStore) -> ObjectStore;
49}
50
51pub type WriteCacheUploadStoreWrapperRef = Arc<dyn WriteCacheUploadStoreWrapper>;
52
53pub struct WriteCache {
57 file_cache: FileCacheRef,
59 puffin_manager_factory: PuffinManagerFactory,
61 intermediate_manager: IntermediateManager,
63 task_sender: UnboundedSender<RegionLoadCacheTask>,
65 manifest_cache: Option<ManifestCache>,
67 upload_store_wrapper: Option<WriteCacheUploadStoreWrapperRef>,
69}
70
71pub type WriteCacheRef = Arc<WriteCache>;
72
73impl WriteCache {
74 #[allow(clippy::too_many_arguments)]
77 pub async fn new(
78 local_store: ObjectStore,
79 cache_capacity: ReadableSize,
80 ttl: Option<Duration>,
81 index_cache_percent: Option<u8>,
82 enable_background_worker: bool,
83 puffin_manager_factory: PuffinManagerFactory,
84 intermediate_manager: IntermediateManager,
85 manifest_cache: Option<ManifestCache>,
86 ) -> Result<Self> {
87 let (task_sender, task_receiver) = unbounded_channel();
88
89 let file_cache = Arc::new(FileCache::new(
90 local_store,
91 cache_capacity,
92 ttl,
93 index_cache_percent,
94 enable_background_worker,
95 ));
96 file_cache.recover(false, Some(task_receiver)).await;
97
98 Ok(Self {
99 file_cache,
100 puffin_manager_factory,
101 intermediate_manager,
102 task_sender,
103 manifest_cache,
104 upload_store_wrapper: None,
105 })
106 }
107
108 #[allow(clippy::too_many_arguments)]
110 pub async fn new_fs(
111 cache_dir: &str,
112 cache_capacity: ReadableSize,
113 ttl: Option<Duration>,
114 index_cache_percent: Option<u8>,
115 enable_background_worker: bool,
116 puffin_manager_factory: PuffinManagerFactory,
117 intermediate_manager: IntermediateManager,
118 manifest_cache_capacity: ReadableSize,
119 ) -> Result<Self> {
120 info!("Init write cache on {cache_dir}, capacity: {cache_capacity}");
121
122 let local_store = new_fs_cache_store(cache_dir).await?;
123
124 let manifest_cache = if manifest_cache_capacity.as_bytes() > 0 {
126 Some(ManifestCache::new(local_store.clone(), manifest_cache_capacity, ttl, false).await)
127 } else {
128 None
129 };
130
131 Self::new(
132 local_store,
133 cache_capacity,
134 ttl,
135 index_cache_percent,
136 enable_background_worker,
137 puffin_manager_factory,
138 intermediate_manager,
139 manifest_cache,
140 )
141 .await
142 }
143
144 pub(crate) fn file_cache(&self) -> FileCacheRef {
146 self.file_cache.clone()
147 }
148
149 pub(crate) fn manifest_cache(&self) -> Option<ManifestCache> {
151 self.manifest_cache.clone()
152 }
153
154 pub(crate) fn with_upload_store_wrapper(
156 mut self,
157 upload_store_wrapper: Option<WriteCacheUploadStoreWrapperRef>,
158 ) -> Self {
159 self.upload_store_wrapper = upload_store_wrapper;
160 self
161 }
162
163 pub(crate) fn build_puffin_manager(&self) -> SstPuffinManager {
165 let store = self.file_cache.local_store();
166 let path_provider = WriteCachePathProvider::new(self.file_cache.clone());
167 self.puffin_manager_factory.build(store, path_provider)
168 }
169
170 pub(crate) async fn put_and_upload_sst(
172 &self,
173 data: &bytes::Bytes,
174 region_id: RegionId,
175 sst_info: &SstInfo,
176 upload_request: SstUploadRequest,
177 ) -> Result<Metrics> {
178 let file_id = sst_info.file_id;
179 let mut metrics = Metrics::new(WriteType::Flush);
180
181 let parquet_key = IndexKey::new(region_id, file_id, FileType::Parquet);
183
184 let cache_start = Instant::now();
186 let cache_path = self.file_cache.cache_file_path(parquet_key);
187 let store = self.file_cache.local_store();
188 let cleaner = TempFileCleaner::new(region_id, store.clone());
189 let write_res = store
190 .write(&cache_path, data.clone())
191 .await
192 .context(crate::error::OpenDalSnafu);
193 if let Err(e) = write_res {
194 cleaner.clean_by_file_id(file_id).await;
195 return Err(e);
196 }
197
198 metrics.write_batch = cache_start.elapsed();
199
200 let upload_start = Instant::now();
202 let region_file_id = RegionFileId::new(region_id, file_id);
203 let remote_path = upload_request
204 .dest_path_provider
205 .build_sst_file_path(region_file_id);
206
207 if let Err(e) = self
208 .upload(parquet_key, &remote_path, &upload_request.remote_store)
209 .await
210 {
211 self.remove(parquet_key).await;
213 return Err(e);
214 }
215
216 metrics.upload_parquet = upload_start.elapsed();
217 Ok(metrics)
218 }
219
220 pub(crate) fn intermediate_manager(&self) -> &IntermediateManager {
222 &self.intermediate_manager
223 }
224
225 pub(crate) async fn write_and_upload_sst(
227 &self,
228 write_request: SstWriteRequest,
229 upload_request: SstUploadRequest,
230 write_opts: &WriteOptions,
231 metrics: &mut Metrics,
232 ) -> Result<SstInfoArray> {
233 let region_id = write_request.metadata.region_id;
234
235 let store = self.file_cache.local_store();
236 let path_provider = WriteCachePathProvider::new(self.file_cache.clone());
237 let indexer = IndexerBuilderImpl {
238 build_type: write_request.op_type.into(),
239 metadata: write_request.metadata.clone(),
240 puffin_manager: self
241 .puffin_manager_factory
242 .build(store.clone(), path_provider.clone()),
243 write_cache_enabled: true,
244 intermediate_manager: self.intermediate_manager.clone(),
245 index_options: write_request.index_options,
246 inverted_index_config: write_request.inverted_index_config,
247 fulltext_index_config: write_request.fulltext_index_config,
248 bloom_filter_index_config: write_request.bloom_filter_index_config,
249 #[cfg(feature = "vector_index")]
250 vector_index_config: write_request.vector_index_config,
251 };
252
253 let cleaner = TempFileCleaner::new(region_id, store.clone());
254 let mut writer = ParquetWriter::new_with_object_store(
256 store.clone(),
257 write_request.metadata,
258 write_request.index_config,
259 indexer,
260 path_provider.clone(),
261 metrics,
262 )
263 .await
264 .with_file_cleaner(cleaner);
265
266 let sst_info = match write_request.sst_write_format {
267 crate::sst::FormatType::PrimaryKey => {
268 writer
269 .write_all_flat_as_primary_key(
270 write_request.source,
271 write_request.max_sequence,
272 write_opts,
273 )
274 .await?
275 }
276 crate::sst::FormatType::Flat => {
277 writer
278 .write_all_flat(write_request.source, write_request.max_sequence, write_opts)
279 .await?
280 }
281 };
282
283 if sst_info.is_empty() {
285 return Ok(sst_info);
286 }
287
288 let mut upload_tracker = UploadTracker::new(region_id);
289 let mut err = None;
290 let remote_store = &upload_request.remote_store;
291 for sst in &sst_info {
292 let parquet_key = IndexKey::new(region_id, sst.file_id, FileType::Parquet);
293 let parquet_path = upload_request
294 .dest_path_provider
295 .build_sst_file_path(RegionFileId::new(region_id, sst.file_id));
296 let start = Instant::now();
297 if let Err(e) = self.upload(parquet_key, &parquet_path, remote_store).await {
298 err = Some(e);
299 break;
300 }
301 metrics.upload_parquet += start.elapsed();
302 upload_tracker.push_uploaded_file(parquet_path);
303
304 if sst.index_metadata.file_size > 0 {
305 let puffin_key = IndexKey::new(region_id, sst.file_id, FileType::Puffin(0));
306 let puffin_path = upload_request
307 .dest_path_provider
308 .build_index_file_path(RegionFileId::new(region_id, sst.file_id));
309 let start = Instant::now();
310 if let Err(e) = self.upload(puffin_key, &puffin_path, remote_store).await {
311 err = Some(e);
312 break;
313 }
314 metrics.upload_puffin += start.elapsed();
315 upload_tracker.push_uploaded_file(puffin_path);
316 }
317 }
318
319 if let Some(err) = err {
320 upload_tracker
322 .clean(&sst_info, &self.file_cache, remote_store)
323 .await;
324 return Err(err);
325 }
326
327 Ok(sst_info)
328 }
329
330 pub(crate) async fn remove(&self, index_key: IndexKey) {
332 self.file_cache.remove(index_key).await
333 }
334
335 pub(crate) async fn download(
338 &self,
339 index_key: IndexKey,
340 remote_path: &str,
341 remote_store: &ObjectStore,
342 file_size: u64,
343 ) -> Result<()> {
344 self.file_cache
345 .download(index_key, remote_path, remote_store, file_size)
346 .await
347 }
348
349 pub(crate) async fn download_if_absent(
354 &self,
355 index_key: IndexKey,
356 remote_path: &str,
357 remote_store: &ObjectStore,
358 file_size: u64,
359 ) -> Result<bool> {
360 if self.file_cache.contains_key(&index_key) {
361 debug!(
362 "Skip downloading file already in write cache, region: {}, file: {}",
363 index_key.region_id, index_key.file_id
364 );
365 return Ok(false);
366 }
367
368 self.download(index_key, remote_path, remote_store, file_size)
369 .await?;
370 Ok(true)
371 }
372
373 pub(crate) async fn upload(
375 &self,
376 index_key: IndexKey,
377 upload_path: &str,
378 remote_store: &ObjectStore,
379 ) -> Result<()> {
380 let region_id = index_key.region_id;
381 let file_id = index_key.file_id;
382 let file_type = index_key.file_type;
383 let cache_path = self.file_cache.cache_file_path(index_key);
384
385 let start = Instant::now();
386 let cached_value = self
387 .file_cache
388 .local_store()
389 .stat(&cache_path)
390 .await
391 .context(error::OpenDalSnafu)?;
392 let reader = self
393 .file_cache
394 .local_store()
395 .reader(&cache_path)
396 .await
397 .context(error::OpenDalSnafu)?
398 .into_futures_async_read(0..cached_value.content_length())
399 .await
400 .context(error::OpenDalSnafu)?;
401
402 let upload_store = self.upload_store_wrapper.as_ref().map_or_else(
403 || remote_store.clone(),
404 |wrapper| wrapper.wrap(remote_store.clone()),
405 );
406 let mut writer = upload_store
407 .writer_with(upload_path)
408 .chunk(DEFAULT_WRITE_BUFFER_SIZE.as_bytes() as usize)
409 .concurrent(DEFAULT_WRITE_CONCURRENCY)
410 .await
411 .context(error::OpenDalSnafu)?
412 .into_futures_async_write();
413
414 let bytes_written =
415 futures::io::copy(reader, &mut writer)
416 .await
417 .context(error::UploadSnafu {
418 region_id,
419 file_id,
420 file_type,
421 })?;
422
423 writer.close().await.context(error::UploadSnafu {
425 region_id,
426 file_id,
427 file_type,
428 })?;
429
430 UPLOAD_BYTES_TOTAL.inc_by(bytes_written);
431
432 debug!(
433 "Successfully upload file to remote, region: {}, file: {}, upload_path: {}, cost: {:?}",
434 region_id,
435 file_id,
436 upload_path,
437 start.elapsed(),
438 );
439
440 let index_value = IndexValue {
441 file_size: bytes_written as _,
442 };
443 self.file_cache.put(index_key, index_value).await;
445
446 Ok(())
447 }
448
449 pub(crate) fn load_region_cache(&self, task: RegionLoadCacheTask) {
453 let _ = self.task_sender.send(task);
454 }
455}
456
457pub struct SstUploadRequest {
459 pub dest_path_provider: RegionFilePathFactory,
461 pub remote_store: ObjectStore,
463}
464
465pub(crate) struct UploadTracker {
467 region_id: RegionId,
469 files_uploaded: Vec<String>,
471}
472
473impl UploadTracker {
474 pub(crate) fn new(region_id: RegionId) -> Self {
476 Self {
477 region_id,
478 files_uploaded: Vec::new(),
479 }
480 }
481
482 pub(crate) fn push_uploaded_file(&mut self, path: String) {
484 self.files_uploaded.push(path);
485 }
486
487 pub(crate) async fn clean(
489 &self,
490 sst_info: &SstInfoArray,
491 file_cache: &FileCacheRef,
492 remote_store: &ObjectStore,
493 ) {
494 common_telemetry::info!(
495 "Start cleaning files on upload failure, region: {}, num_ssts: {}",
496 self.region_id,
497 sst_info.len()
498 );
499
500 for sst in sst_info {
502 let parquet_key = IndexKey::new(self.region_id, sst.file_id, FileType::Parquet);
503 file_cache.remove(parquet_key).await;
504
505 if sst.index_metadata.file_size > 0 {
506 let puffin_key = IndexKey::new(
507 self.region_id,
508 sst.file_id,
509 FileType::Puffin(sst.index_metadata.version),
510 );
511 file_cache.remove(puffin_key).await;
512 }
513 }
514
515 for file_path in &self.files_uploaded {
517 if let Err(e) = remote_store.delete(file_path).await {
518 common_telemetry::error!(e; "Failed to delete file {}", file_path);
519 }
520 }
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use bytes::Bytes;
527 use common_test_util::temp_dir::create_temp_dir;
528 use object_store::services::Memory;
529 use object_store::{ATOMIC_WRITE_DIR, ObjectStore};
530 use parquet::file::metadata::PageIndexPolicy;
531 use store_api::region_request::PathType;
532 use store_api::storage::FileId;
533
534 use super::*;
535 use crate::access_layer::OperationType;
536 use crate::cache::file_cache::IndexValue;
537 use crate::cache::test_util::{assert_parquet_metadata_equal, new_fs_store};
538 use crate::cache::{CacheManager, CacheStrategy};
539 use crate::error::InvalidBatchSnafu;
540 use crate::read::FlatSource;
541 use crate::region::options::IndexOptions;
542 use crate::sst::parquet::reader::ParquetReaderBuilder;
543 use crate::test_util::TestEnv;
544 use crate::test_util::sst_util::{
545 new_flat_source_from_record_batches, new_record_batch_by_range,
546 sst_file_handle_with_file_id, sst_region_metadata,
547 };
548
549 struct RedirectUploadStoreWrapper {
550 target_store: ObjectStore,
551 }
552
553 impl WriteCacheUploadStoreWrapper for RedirectUploadStoreWrapper {
554 fn wrap(&self, _store: ObjectStore) -> ObjectStore {
555 self.target_store.clone()
556 }
557 }
558
559 #[tokio::test]
560 async fn test_upload_uses_wrapped_remote_store() {
561 let env = TestEnv::new().await;
562 let local_store = ObjectStore::new(Memory::default()).unwrap().finish();
563 let original_store = ObjectStore::new(Memory::default()).unwrap().finish();
564 let target_store = ObjectStore::new(Memory::default()).unwrap().finish();
565 let wrapper = Arc::new(RedirectUploadStoreWrapper {
566 target_store: target_store.clone(),
567 });
568 let write_cache = WriteCache::new(
569 local_store.clone(),
570 ReadableSize::mb(10),
571 None,
572 None,
573 false,
574 env.get_puffin_manager(),
575 env.get_intermediate_manager(),
576 None,
577 )
578 .await
579 .unwrap()
580 .with_upload_store_wrapper(Some(wrapper.clone()));
581
582 let region_id = RegionId::new(1024, 1);
583 let file_id = FileId::random();
584 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
585 let cache_path = write_cache.file_cache.cache_file_path(key);
586 let upload_path = "wrapped-upload.parquet";
587 let data = Bytes::from_static(b"wrapped upload data");
588 local_store.write(&cache_path, data.clone()).await.unwrap();
589
590 write_cache
591 .upload(key, upload_path, &original_store)
592 .await
593 .unwrap();
594
595 assert!(original_store.stat(upload_path).await.is_err());
596 assert_eq!(
597 target_store.read(upload_path).await.unwrap().to_vec(),
598 data.to_vec()
599 );
600 }
601
602 #[tokio::test]
603 async fn test_write_and_upload_sst() {
604 let mut env = TestEnv::new().await;
607 let mock_store = env.init_object_store_manager();
608 let path_provider = RegionFilePathFactory::new("test".to_string(), PathType::Bare);
609
610 let local_dir = create_temp_dir("");
611 let local_store = new_fs_store(local_dir.path().to_str().unwrap());
612
613 let write_cache = env
614 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
615 .await;
616
617 let metadata = Arc::new(sst_region_metadata());
619 let region_id = metadata.region_id;
620 let source = new_flat_source_from_record_batches(vec![
621 new_record_batch_by_range(&["a", "d"], 0, 60),
622 new_record_batch_by_range(&["b", "f"], 0, 40),
623 new_record_batch_by_range(&["b", "h"], 100, 200),
624 ]);
625
626 let write_request = SstWriteRequest {
627 op_type: OperationType::Flush,
628 metadata,
629 source,
630 storage: None,
631 max_sequence: None,
632 sst_write_format: Default::default(),
633 cache_manager: Default::default(),
634 index_options: IndexOptions::default(),
635 index_config: Default::default(),
636 inverted_index_config: Default::default(),
637 fulltext_index_config: Default::default(),
638 bloom_filter_index_config: Default::default(),
639 #[cfg(feature = "vector_index")]
640 vector_index_config: Default::default(),
641 };
642
643 let upload_request = SstUploadRequest {
644 dest_path_provider: path_provider.clone(),
645 remote_store: mock_store.clone(),
646 };
647
648 let write_opts = WriteOptions {
649 row_group_size: 512,
650 ..Default::default()
651 };
652
653 let mut metrics = Metrics::new(WriteType::Flush);
655 let mut sst_infos = write_cache
656 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
657 .await
658 .unwrap();
659 let sst_info = sst_infos.remove(0);
660
661 let file_id = sst_info.file_id;
662 let sst_upload_path =
663 path_provider.build_sst_file_path(RegionFileId::new(region_id, file_id));
664 let index_upload_path =
665 path_provider.build_index_file_path(RegionFileId::new(region_id, file_id));
666
667 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
669 assert!(write_cache.file_cache.contains_key(&key));
670
671 let remote_data = mock_store.read(&sst_upload_path).await.unwrap();
673 let cache_data = local_store
674 .read(&write_cache.file_cache.cache_file_path(key))
675 .await
676 .unwrap();
677 assert_eq!(remote_data.to_vec(), cache_data.to_vec());
678
679 let index_key = IndexKey::new(region_id, file_id, FileType::Puffin(0));
681 assert!(write_cache.file_cache.contains_key(&index_key));
682
683 let remote_index_data = mock_store.read(&index_upload_path).await.unwrap();
684 let cache_index_data = local_store
685 .read(&write_cache.file_cache.cache_file_path(index_key))
686 .await
687 .unwrap();
688 assert_eq!(remote_index_data.to_vec(), cache_index_data.to_vec());
689
690 let sst_index_key = IndexKey::new(region_id, file_id, FileType::Parquet);
692 write_cache.remove(sst_index_key).await;
693 assert!(!write_cache.file_cache.contains_key(&sst_index_key));
694 write_cache.remove(index_key).await;
695 assert!(!write_cache.file_cache.contains_key(&index_key));
696 }
697
698 #[tokio::test]
699 async fn test_read_metadata_from_write_cache() {
700 common_telemetry::init_default_ut_logging();
701 let mut env = TestEnv::new().await;
702 let data_home = env.data_home().display().to_string();
703 let mock_store = env.init_object_store_manager();
704
705 let local_dir = create_temp_dir("");
706 let local_path = local_dir.path().to_str().unwrap();
707 let local_store = new_fs_store(local_path);
708
709 let write_cache = env
711 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
712 .await;
713 let cache_manager = Arc::new(
714 CacheManager::builder()
715 .write_cache(Some(write_cache.clone()))
716 .build(),
717 );
718 assert!(!cache_manager.sst_meta_cache_enabled());
719
720 let metadata = Arc::new(sst_region_metadata());
722
723 let source = new_flat_source_from_record_batches(vec![
724 new_record_batch_by_range(&["a", "d"], 0, 60),
725 new_record_batch_by_range(&["b", "f"], 0, 40),
726 new_record_batch_by_range(&["b", "h"], 100, 200),
727 ]);
728
729 let write_request = SstWriteRequest {
731 op_type: OperationType::Flush,
732 metadata,
733 source,
734 storage: None,
735 max_sequence: None,
736 sst_write_format: Default::default(),
737 cache_manager: cache_manager.clone(),
738 index_options: IndexOptions::default(),
739 index_config: Default::default(),
740 inverted_index_config: Default::default(),
741 fulltext_index_config: Default::default(),
742 bloom_filter_index_config: Default::default(),
743 #[cfg(feature = "vector_index")]
744 vector_index_config: Default::default(),
745 };
746 let write_opts = WriteOptions {
747 row_group_size: 512,
748 ..Default::default()
749 };
750 let upload_request = SstUploadRequest {
751 dest_path_provider: RegionFilePathFactory::new(data_home.clone(), PathType::Bare),
752 remote_store: mock_store.clone(),
753 };
754
755 let mut metrics = Metrics::new(WriteType::Flush);
756 let mut sst_infos = write_cache
757 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
758 .await
759 .unwrap();
760 let sst_info = sst_infos.remove(0);
761 let write_parquet_metadata = sst_info.file_metadata.unwrap();
762
763 let handle = sst_file_handle_with_file_id(sst_info.file_id, 0, 1000);
765 let builder = ParquetReaderBuilder::new(
766 data_home,
767 PathType::Bare,
768 handle.clone(),
769 mock_store.clone(),
770 )
771 .cache(CacheStrategy::EnableAll(cache_manager.clone()))
772 .page_index_policy(PageIndexPolicy::Optional);
773 let reader = builder.build().await.unwrap().unwrap();
774 let cached_write_parquet_metadata = crate::cache::CachedSstMeta::try_new(
775 "test.sst",
776 Arc::unwrap_or_clone(write_parquet_metadata),
777 )
778 .unwrap()
779 .parquet_metadata();
780
781 assert_parquet_metadata_equal(cached_write_parquet_metadata, reader.parquet_metadata());
783 }
784
785 #[tokio::test]
786 async fn test_write_cache_clean_tmp_files() {
787 common_telemetry::init_default_ut_logging();
788 let mut env = TestEnv::new().await;
789 let data_home = env.data_home().display().to_string();
790 let mock_store = env.init_object_store_manager();
791
792 let write_cache_dir = create_temp_dir("");
793 let write_cache_path = write_cache_dir.path().to_str().unwrap();
794 let write_cache = env
795 .create_write_cache_from_path(write_cache_path, ReadableSize::mb(10))
796 .await;
797
798 let cache_manager = Arc::new(
800 CacheManager::builder()
801 .write_cache(Some(write_cache.clone()))
802 .build(),
803 );
804
805 let metadata = Arc::new(sst_region_metadata());
807
808 let record_batch = new_record_batch_by_range(&["a", "d"], 0, 60);
810 let schema = record_batch.schema();
811 let iter = Box::new(
812 [
813 Ok(record_batch),
814 InvalidBatchSnafu {
815 reason: "Abort the writer",
816 }
817 .fail(),
818 ]
819 .into_iter(),
820 );
821 let source = FlatSource::new_iter(schema, iter);
822
823 let write_request = SstWriteRequest {
825 op_type: OperationType::Flush,
826 metadata,
827 source,
828 storage: None,
829 max_sequence: None,
830 sst_write_format: Default::default(),
831 cache_manager: cache_manager.clone(),
832 index_options: IndexOptions::default(),
833 index_config: Default::default(),
834 inverted_index_config: Default::default(),
835 fulltext_index_config: Default::default(),
836 bloom_filter_index_config: Default::default(),
837 #[cfg(feature = "vector_index")]
838 vector_index_config: Default::default(),
839 };
840 let write_opts = WriteOptions {
841 row_group_size: 512,
842 ..Default::default()
843 };
844 let upload_request = SstUploadRequest {
845 dest_path_provider: RegionFilePathFactory::new(data_home.clone(), PathType::Bare),
846 remote_store: mock_store.clone(),
847 };
848
849 let mut metrics = Metrics::new(WriteType::Flush);
850 write_cache
851 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
852 .await
853 .unwrap_err();
854 let atomic_write_dir = write_cache_dir.path().join(ATOMIC_WRITE_DIR);
855 let mut entries = tokio::fs::read_dir(&atomic_write_dir).await.unwrap();
856 let mut has_files = false;
857 while let Some(entry) = entries.next_entry().await.unwrap() {
858 if entry.file_type().await.unwrap().is_dir() {
859 continue;
860 }
861 has_files = true;
862 common_telemetry::warn!(
863 "Found remaining temporary file in atomic dir: {}",
864 entry.path().display()
865 );
866 }
867
868 assert!(!has_files);
869 }
870
871 #[tokio::test]
872 async fn test_download_if_absent_skips_when_cached() {
873 let mut env = TestEnv::new().await;
874 let remote_store = env.init_object_store_manager();
875
876 let local_dir = create_temp_dir("");
877 let local_store = new_fs_store(local_dir.path().to_str().unwrap());
878 let write_cache = env
879 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
880 .await;
881
882 let region_id = RegionId::new(1024, 1);
883 let file_id = FileId::random();
884 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
885 write_cache
886 .file_cache()
887 .put(key, IndexValue { file_size: 1 })
888 .await;
889
890 let downloaded = write_cache
891 .download_if_absent(key, "missing/path.parquet", &remote_store, 1)
892 .await
893 .unwrap();
894
895 assert!(!downloaded);
896 }
897
898 #[tokio::test]
899 async fn test_download_if_absent_downloads_when_missing() {
900 let mut env = TestEnv::new().await;
901 let remote_store = env.init_object_store_manager();
902
903 let local_dir = create_temp_dir("");
904 let local_store = new_fs_store(local_dir.path().to_str().unwrap());
905 let write_cache = env
906 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
907 .await;
908
909 let region_id = RegionId::new(1024, 2);
910 let file_id = FileId::random();
911 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
912 let remote_path = format!("download-if-absent/{file_id}.parquet");
913 let remote_data = Bytes::from_static(b"download-if-absent-test");
914 remote_store
915 .write(&remote_path, remote_data.clone())
916 .await
917 .unwrap();
918
919 let downloaded = write_cache
920 .download_if_absent(key, &remote_path, &remote_store, remote_data.len() as u64)
921 .await
922 .unwrap();
923
924 assert!(downloaded);
925 assert!(write_cache.file_cache().contains_key(&key));
926
927 let cached_data = local_store
928 .read(&write_cache.file_cache().cache_file_path(key))
929 .await
930 .unwrap();
931 assert_eq!(cached_data.to_vec(), remote_data.to_vec());
932 }
933}