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