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::WriteOptions;
42use crate::sst::parquet::writer::ParquetWriter;
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_file_id: RegionFileId,
180 upload_request: SstUploadRequest,
181 write_buffer_size: ReadableSize,
182 ) -> Result<Metrics> {
183 let region_id = region_file_id.region_id();
184 let file_id = region_file_id.file_id();
185 let mut metrics = Metrics::new(WriteType::Flush);
186
187 let parquet_key = IndexKey::new(region_id, file_id, FileType::Parquet);
189
190 let cache_start = Instant::now();
192 let cache_path = self.file_cache.cache_file_path(parquet_key);
193 let store = self.file_cache.local_store();
194 let cleaner = TempFileCleaner::new(region_id, store.clone());
195 let write_res = store
196 .write_with(&cache_path, data.clone())
197 .chunk(write_buffer_size.as_bytes() as usize)
198 .await
199 .context(crate::error::OpenDalSnafu);
200 if let Err(e) = write_res {
201 cleaner.clean_by_file_id(file_id).await;
202 return Err(e);
203 }
204
205 metrics.write_batch = cache_start.elapsed();
206
207 let upload_start = Instant::now();
209 let remote_path = upload_request
210 .dest_path_provider
211 .build_sst_file_path(region_file_id);
212
213 if let Err(e) = self
214 .upload(
215 parquet_key,
216 &remote_path,
217 &upload_request.remote_store,
218 UploadOptions {
219 op_type: OperationType::Flush,
221 write_buffer_size,
222 },
223 )
224 .await
225 {
226 self.remove(parquet_key).await;
228 return Err(e);
229 }
230
231 metrics.upload_parquet = upload_start.elapsed();
232 Ok(metrics)
233 }
234
235 pub(crate) fn intermediate_manager(&self) -> &IntermediateManager {
237 &self.intermediate_manager
238 }
239
240 pub(crate) async fn write_and_upload_sst(
242 &self,
243 write_request: SstWriteRequest,
244 upload_request: SstUploadRequest,
245 write_opts: &WriteOptions,
246 metrics: &mut Metrics,
247 ) -> Result<SstInfoArray> {
248 let region_id = write_request.metadata.region_id;
249 let override_sequence = if write_request.preserve_row_sequence {
250 None
251 } else {
252 write_request.max_sequence
253 };
254
255 let store = self.file_cache.local_store();
256 let path_provider = WriteCachePathProvider::new(self.file_cache.clone());
257 let indexer = IndexerBuilderImpl {
258 build_type: write_request.op_type.into(),
259 metadata: write_request.metadata.clone(),
260 puffin_manager: self
261 .puffin_manager_factory
262 .build(store.clone(), path_provider.clone()),
263 write_cache_enabled: true,
264 intermediate_manager: self.intermediate_manager.clone(),
265 index_options: write_request.index_options,
266 inverted_index_config: write_request.inverted_index_config,
267 fulltext_index_config: write_request.fulltext_index_config,
268 bloom_filter_index_config: write_request.bloom_filter_index_config,
269 };
270
271 let cleaner = TempFileCleaner::new(region_id, store.clone());
272 let mut writer = ParquetWriter::new_with_object_store(
274 store.clone(),
275 write_request.metadata,
276 write_request.index_config,
277 indexer,
278 path_provider.clone(),
279 metrics,
280 )
281 .await
282 .with_file_cleaner(cleaner);
283
284 let sst_info = match write_request.sst_write_format {
285 crate::sst::FormatType::PrimaryKey => {
286 writer
287 .write_all_flat_as_primary_key(
288 write_request.source,
289 override_sequence,
290 write_opts,
291 )
292 .await?
293 }
294 crate::sst::FormatType::Flat => {
295 writer
296 .write_all_flat(write_request.source, override_sequence, write_opts)
297 .await?
298 }
299 };
300
301 if sst_info.is_empty() {
303 return Ok(sst_info);
304 }
305
306 let mut upload_tracker = UploadTracker::new(region_id);
307 let mut err = None;
308 let op_type = write_request.op_type;
309 let remote_store = &upload_request.remote_store;
310 for sst in &sst_info {
311 let parquet_key = IndexKey::new(region_id, sst.file_id, FileType::Parquet);
312 let parquet_path = upload_request
313 .dest_path_provider
314 .build_sst_file_path(RegionFileId::new(region_id, sst.file_id));
315 let start = Instant::now();
316 if let Err(e) = self
317 .upload(
318 parquet_key,
319 &parquet_path,
320 remote_store,
321 UploadOptions {
322 op_type,
323 write_buffer_size: write_opts.write_buffer_size,
324 },
325 )
326 .await
327 {
328 err = Some(e);
329 break;
330 }
331 metrics.upload_parquet += start.elapsed();
332 upload_tracker.push_uploaded_file(parquet_path);
333
334 if sst.index_metadata.file_size > 0 {
335 let puffin_key = IndexKey::new(region_id, sst.file_id, FileType::Puffin(0));
336 let puffin_path = upload_request
337 .dest_path_provider
338 .build_index_file_path(RegionFileId::new(region_id, sst.file_id));
339 let start = Instant::now();
340 if let Err(e) = self
341 .upload(
342 puffin_key,
343 &puffin_path,
344 remote_store,
345 UploadOptions {
346 op_type,
347 write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE,
348 },
349 )
350 .await
351 {
352 err = Some(e);
353 break;
354 }
355 metrics.upload_puffin += start.elapsed();
356 upload_tracker.push_uploaded_file(puffin_path);
357 }
358 }
359
360 if let Some(err) = err {
361 upload_tracker
363 .clean(&sst_info, &self.file_cache, remote_store)
364 .await;
365 return Err(err);
366 }
367
368 Ok(sst_info)
369 }
370
371 pub(crate) async fn remove(&self, index_key: IndexKey) {
373 self.file_cache.remove(index_key).await
374 }
375
376 pub(crate) async fn download(
379 &self,
380 index_key: IndexKey,
381 remote_path: &str,
382 remote_store: &ObjectStore,
383 file_size: u64,
384 ) -> Result<()> {
385 self.file_cache
386 .download(index_key, remote_path, remote_store, file_size)
387 .await
388 }
389
390 pub(crate) async fn download_if_absent(
395 &self,
396 index_key: IndexKey,
397 remote_path: &str,
398 remote_store: &ObjectStore,
399 file_size: u64,
400 ) -> Result<bool> {
401 if self.file_cache.contains_key(&index_key) {
402 debug!(
403 "Skip downloading file already in write cache, region: {}, file: {}",
404 index_key.region_id, index_key.file_id
405 );
406 return Ok(false);
407 }
408
409 self.download(index_key, remote_path, remote_store, file_size)
410 .await?;
411 Ok(true)
412 }
413
414 pub(crate) async fn upload(
416 &self,
417 index_key: IndexKey,
418 upload_path: &str,
419 remote_store: &ObjectStore,
420 options: UploadOptions,
421 ) -> Result<()> {
422 let region_id = index_key.region_id;
423 let file_id = index_key.file_id;
424 let file_type = index_key.file_type;
425 let cache_path = self.file_cache.cache_file_path(index_key);
426
427 let start = Instant::now();
428 let cached_value = self
429 .file_cache
430 .local_store()
431 .stat(&cache_path)
432 .await
433 .context(error::OpenDalSnafu)?;
434 let reader = self
435 .file_cache
436 .local_store()
437 .reader(&cache_path)
438 .await
439 .context(error::OpenDalSnafu)?
440 .into_futures_async_read(0..cached_value.content_length())
441 .await
442 .context(error::OpenDalSnafu)?;
443
444 let upload_store = self.upload_store_wrapper.as_ref().map_or_else(
445 || remote_store.clone(),
446 |wrapper| wrapper.wrap(remote_store.clone(), options.op_type),
447 );
448 let mut writer = upload_store
449 .writer_with(upload_path)
450 .chunk(options.write_buffer_size.as_bytes() as usize)
451 .concurrent(DEFAULT_WRITE_CONCURRENCY)
452 .await
453 .context(error::OpenDalSnafu)?
454 .into_futures_async_write();
455
456 let bytes_written =
457 futures::io::copy(reader, &mut writer)
458 .await
459 .context(error::UploadSnafu {
460 region_id,
461 file_id,
462 file_type,
463 })?;
464
465 writer.close().await.context(error::UploadSnafu {
467 region_id,
468 file_id,
469 file_type,
470 })?;
471
472 UPLOAD_BYTES_TOTAL.inc_by(bytes_written);
473
474 debug!(
475 "Successfully upload file to remote, region: {}, file: {}, upload_path: {}, cost: {:?}",
476 region_id,
477 file_id,
478 upload_path,
479 start.elapsed(),
480 );
481
482 let index_value = IndexValue {
483 file_size: bytes_written as _,
484 };
485 self.file_cache.put(index_key, index_value).await;
487
488 Ok(())
489 }
490
491 pub(crate) fn load_region_cache(&self, task: RegionLoadCacheTask) {
495 let _ = self.task_sender.send(task);
496 }
497}
498
499pub struct SstUploadRequest {
501 pub dest_path_provider: RegionFilePathFactory,
503 pub remote_store: ObjectStore,
505}
506
507pub(crate) struct UploadOptions {
509 pub op_type: OperationType,
511 pub write_buffer_size: ReadableSize,
513}
514
515pub(crate) struct UploadTracker {
517 region_id: RegionId,
519 files_uploaded: Vec<String>,
521}
522
523impl UploadTracker {
524 pub(crate) fn new(region_id: RegionId) -> Self {
526 Self {
527 region_id,
528 files_uploaded: Vec::new(),
529 }
530 }
531
532 pub(crate) fn push_uploaded_file(&mut self, path: String) {
534 self.files_uploaded.push(path);
535 }
536
537 pub(crate) async fn clean(
539 &self,
540 sst_info: &SstInfoArray,
541 file_cache: &FileCacheRef,
542 remote_store: &ObjectStore,
543 ) {
544 common_telemetry::info!(
545 "Start cleaning files on upload failure, region: {}, num_ssts: {}",
546 self.region_id,
547 sst_info.len()
548 );
549
550 for sst in sst_info {
552 let parquet_key = IndexKey::new(self.region_id, sst.file_id, FileType::Parquet);
553 file_cache.remove(parquet_key).await;
554
555 if sst.index_metadata.file_size > 0 {
556 let puffin_key = IndexKey::new(
557 self.region_id,
558 sst.file_id,
559 FileType::Puffin(sst.index_metadata.version),
560 );
561 file_cache.remove(puffin_key).await;
562 }
563 }
564
565 for file_path in &self.files_uploaded {
567 if let Err(e) = remote_store.delete(file_path).await {
568 common_telemetry::error!(e; "Failed to delete file {}", file_path);
569 }
570 }
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use std::sync::Mutex;
577
578 use bytes::Bytes;
579 use common_test_util::temp_dir::create_temp_dir;
580 use object_store::services::Memory;
581 use object_store::{ATOMIC_WRITE_DIR, ObjectStore};
582 use parquet::file::metadata::PageIndexPolicy;
583 use store_api::region_request::PathType;
584 use store_api::storage::FileId;
585
586 use super::*;
587 use crate::access_layer::OperationType;
588 use crate::cache::file_cache::IndexValue;
589 use crate::cache::test_util::{assert_parquet_metadata_equal, new_fs_store};
590 use crate::cache::{CacheManager, CacheStrategy};
591 use crate::error::InvalidBatchSnafu;
592 use crate::read::FlatSource;
593 use crate::region::options::IndexOptions;
594 use crate::sst::parquet::reader::ParquetReaderBuilder;
595 use crate::test_util::TestEnv;
596 use crate::test_util::sst_util::{
597 WriteChunkRecorder, new_flat_source_from_record_batches, new_record_batch_by_range,
598 sst_file_handle_with_file_id, sst_region_metadata,
599 };
600
601 struct RedirectUploadStoreWrapper {
602 target_store: ObjectStore,
603 last_op_type: Mutex<Option<OperationType>>,
604 }
605
606 impl WriteCacheUploadStoreWrapper for RedirectUploadStoreWrapper {
607 fn wrap(&self, _store: ObjectStore, op_type: OperationType) -> ObjectStore {
608 *self.last_op_type.lock().unwrap() = Some(op_type);
609 self.target_store.clone()
610 }
611 }
612
613 #[tokio::test]
614 async fn test_upload_uses_wrapped_remote_store() {
615 let env = TestEnv::new().await;
616 let local_store = ObjectStore::new(Memory::default()).unwrap();
617 let original_store = ObjectStore::new(Memory::default()).unwrap();
618 let target_store = ObjectStore::new(Memory::default()).unwrap();
619 let wrapper = Arc::new(RedirectUploadStoreWrapper {
620 target_store: target_store.clone(),
621 last_op_type: Mutex::new(None),
622 });
623 let write_cache = WriteCache::new(
624 local_store.clone(),
625 ReadableSize::mb(10),
626 None,
627 None,
628 false,
629 env.get_puffin_manager(),
630 env.get_intermediate_manager(),
631 None,
632 )
633 .await
634 .unwrap()
635 .with_upload_store_wrapper(Some(wrapper.clone()));
636
637 let region_id = RegionId::new(1024, 1);
638 let file_id = FileId::random();
639 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
640 let cache_path = write_cache.file_cache.cache_file_path(key);
641 let upload_path = "wrapped-upload.parquet";
642 let data = Bytes::from_static(b"wrapped upload data");
643 local_store.write(&cache_path, data.clone()).await.unwrap();
644
645 write_cache
646 .upload(
647 key,
648 upload_path,
649 &original_store,
650 UploadOptions {
651 op_type: OperationType::Compact,
652 write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE,
653 },
654 )
655 .await
656 .unwrap();
657
658 assert_eq!(
659 *wrapper.last_op_type.lock().unwrap(),
660 Some(OperationType::Compact)
661 );
662
663 assert!(original_store.stat(upload_path).await.is_err());
664 assert_eq!(
665 target_store.read(upload_path).await.unwrap().to_vec(),
666 data.to_vec()
667 );
668 }
669
670 #[rstest::rstest]
671 #[tokio::test]
672 async fn test_write_and_upload_sst(
673 #[values(OperationType::Flush, OperationType::Compact)] op_type: OperationType,
674 ) {
675 let mut env = TestEnv::new().await;
678 let remote_chunks = WriteChunkRecorder::default();
679 let mock_store = env.init_object_store_manager().layer(remote_chunks.layer());
680 let path_provider = RegionFilePathFactory::new("test".to_string(), PathType::Bare);
681
682 let local_dir = create_temp_dir("");
683 let local_chunks = WriteChunkRecorder::default();
684 let local_store =
685 new_fs_store(local_dir.path().to_str().unwrap()).layer(local_chunks.layer());
686
687 let write_cache = env
688 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
689 .await;
690
691 let metadata = Arc::new(sst_region_metadata());
693 let region_id = metadata.region_id;
694 let source = new_flat_source_from_record_batches(vec![
695 new_record_batch_by_range(&["a", "d"], 0, 60),
696 new_record_batch_by_range(&["b", "f"], 0, 40),
697 new_record_batch_by_range(&["b", "h"], 100, 200),
698 ]);
699
700 let write_request = SstWriteRequest {
701 op_type,
702 metadata,
703 source,
704 max_sequence: None,
705 sst_write_format: Default::default(),
706 cache_manager: Default::default(),
707 preserve_row_sequence: false,
708 index_options: IndexOptions::default(),
709 index_config: Default::default(),
710 inverted_index_config: Default::default(),
711 fulltext_index_config: Default::default(),
712 bloom_filter_index_config: Default::default(),
713 };
714
715 let upload_request = SstUploadRequest {
716 dest_path_provider: path_provider.clone(),
717 remote_store: mock_store.clone(),
718 };
719
720 let write_opts = WriteOptions {
721 write_buffer_size: ReadableSize::kb(1),
722 row_group_size: 512,
723 ..Default::default()
724 };
725
726 let mut metrics = Metrics::new(WriteType::Flush);
728 let mut sst_infos = write_cache
729 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
730 .await
731 .unwrap();
732 let sst_info = sst_infos.remove(0);
733
734 let file_id = sst_info.file_id;
735 let sst_upload_path =
736 path_provider.build_sst_file_path(RegionFileId::new(region_id, file_id));
737 let index_upload_path =
738 path_provider.build_index_file_path(RegionFileId::new(region_id, file_id));
739
740 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
742 assert!(write_cache.file_cache.contains_key(&key));
743
744 let remote_data = mock_store.read(&sst_upload_path).await.unwrap();
746 let cache_data = local_store
747 .read(&write_cache.file_cache.cache_file_path(key))
748 .await
749 .unwrap();
750 assert_eq!(remote_data.to_vec(), cache_data.to_vec());
751 let chunk_size = write_opts.write_buffer_size.as_bytes() as usize;
752 assert!(remote_data.len() > chunk_size);
753 remote_chunks.assert_chunks(&sst_upload_path, chunk_size, remote_data.len());
754 local_chunks.assert_chunks(
755 &write_cache.file_cache.cache_file_path(key),
756 chunk_size,
757 cache_data.len(),
758 );
759
760 let index_key = IndexKey::new(region_id, file_id, FileType::Puffin(0));
762 assert!(write_cache.file_cache.contains_key(&index_key));
763
764 let remote_index_data = mock_store.read(&index_upload_path).await.unwrap();
765 let cache_index_data = local_store
766 .read(&write_cache.file_cache.cache_file_path(index_key))
767 .await
768 .unwrap();
769 assert_eq!(remote_index_data.to_vec(), cache_index_data.to_vec());
770 remote_chunks.assert_chunks(
771 &index_upload_path,
772 DEFAULT_WRITE_BUFFER_SIZE.as_bytes() as usize,
773 remote_index_data.len(),
774 );
775
776 let sst_index_key = IndexKey::new(region_id, file_id, FileType::Parquet);
778 write_cache.remove(sst_index_key).await;
779 assert!(!write_cache.file_cache.contains_key(&sst_index_key));
780 write_cache.remove(index_key).await;
781 assert!(!write_cache.file_cache.contains_key(&index_key));
782 }
783
784 #[tokio::test]
785 async fn test_read_metadata_from_write_cache() {
786 common_telemetry::init_default_ut_logging();
787 let mut env = TestEnv::new().await;
788 let data_home = "write-cache-test".to_string();
790 let mock_store = env.init_object_store_manager();
791
792 let local_dir = create_temp_dir("");
793 let local_path = local_dir.path().to_str().unwrap();
794 let local_store = new_fs_store(local_path);
795
796 let write_cache = env
798 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
799 .await;
800 let cache_manager = Arc::new(
801 CacheManager::builder()
802 .write_cache(Some(write_cache.clone()))
803 .build(),
804 );
805 assert!(!cache_manager.sst_meta_cache_enabled());
806
807 let metadata = Arc::new(sst_region_metadata());
809
810 let source = new_flat_source_from_record_batches(vec![
811 new_record_batch_by_range(&["a", "d"], 0, 60),
812 new_record_batch_by_range(&["b", "f"], 0, 40),
813 new_record_batch_by_range(&["b", "h"], 100, 200),
814 ]);
815
816 let write_request = SstWriteRequest {
818 op_type: OperationType::Flush,
819 metadata,
820 source,
821 max_sequence: None,
822 sst_write_format: Default::default(),
823 cache_manager: cache_manager.clone(),
824 preserve_row_sequence: false,
825 index_options: IndexOptions::default(),
826 index_config: Default::default(),
827 inverted_index_config: Default::default(),
828 fulltext_index_config: Default::default(),
829 bloom_filter_index_config: Default::default(),
830 };
831 let write_opts = WriteOptions {
832 row_group_size: 512,
833 ..Default::default()
834 };
835 let upload_request = SstUploadRequest {
836 dest_path_provider: RegionFilePathFactory::new(data_home.clone(), PathType::Bare),
837 remote_store: mock_store.clone(),
838 };
839
840 let mut metrics = Metrics::new(WriteType::Flush);
841 let mut sst_infos = write_cache
842 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
843 .await
844 .unwrap();
845 let sst_info = sst_infos.remove(0);
846 let write_parquet_metadata = sst_info.file_metadata.unwrap();
847
848 let handle = sst_file_handle_with_file_id(sst_info.file_id, 0, 1000);
850 let builder = ParquetReaderBuilder::new(
851 data_home,
852 PathType::Bare,
853 handle.clone(),
854 mock_store.clone(),
855 )
856 .cache(CacheStrategy::EnableAll(cache_manager.clone()))
857 .page_index_policy(PageIndexPolicy::Optional);
858 let reader = builder.build().await.unwrap().unwrap();
859 let cached_write_parquet_metadata = crate::cache::CachedSstMeta::try_new(
860 "test.sst",
861 Arc::unwrap_or_clone(write_parquet_metadata),
862 )
863 .unwrap()
864 .parquet_metadata();
865
866 assert_parquet_metadata_equal(cached_write_parquet_metadata, reader.parquet_metadata());
868 }
869
870 #[tokio::test]
871 async fn test_write_cache_clean_tmp_files() {
872 common_telemetry::init_default_ut_logging();
873 let mut env = TestEnv::new().await;
874 let data_home = env.data_home().display().to_string();
875 let mock_store = env.init_object_store_manager();
876
877 let write_cache_dir = create_temp_dir("");
878 let write_cache_path = write_cache_dir.path().to_str().unwrap();
879 let write_cache = env
880 .create_write_cache_from_path(write_cache_path, ReadableSize::mb(10))
881 .await;
882
883 let cache_manager = Arc::new(
885 CacheManager::builder()
886 .write_cache(Some(write_cache.clone()))
887 .build(),
888 );
889
890 let metadata = Arc::new(sst_region_metadata());
892
893 let record_batch = new_record_batch_by_range(&["a", "d"], 0, 60);
895 let schema = record_batch.schema();
896 let iter = Box::new(
897 [
898 Ok(record_batch),
899 InvalidBatchSnafu {
900 reason: "Abort the writer",
901 }
902 .fail(),
903 ]
904 .into_iter(),
905 );
906 let source = FlatSource::new_iter(schema, iter);
907
908 let write_request = SstWriteRequest {
910 op_type: OperationType::Flush,
911 metadata,
912 source,
913 max_sequence: None,
914 sst_write_format: Default::default(),
915 cache_manager: cache_manager.clone(),
916 preserve_row_sequence: false,
917 index_options: IndexOptions::default(),
918 index_config: Default::default(),
919 inverted_index_config: Default::default(),
920 fulltext_index_config: Default::default(),
921 bloom_filter_index_config: Default::default(),
922 };
923 let write_opts = WriteOptions {
924 row_group_size: 512,
925 ..Default::default()
926 };
927 let upload_request = SstUploadRequest {
928 dest_path_provider: RegionFilePathFactory::new(data_home.clone(), PathType::Bare),
929 remote_store: mock_store.clone(),
930 };
931
932 let mut metrics = Metrics::new(WriteType::Flush);
933 write_cache
934 .write_and_upload_sst(write_request, upload_request, &write_opts, &mut metrics)
935 .await
936 .unwrap_err();
937 let atomic_write_dir = write_cache_dir.path().join(ATOMIC_WRITE_DIR);
938 let mut entries = tokio::fs::read_dir(&atomic_write_dir).await.unwrap();
939 let mut has_files = false;
940 while let Some(entry) = entries.next_entry().await.unwrap() {
941 if entry.file_type().await.unwrap().is_dir() {
942 continue;
943 }
944 has_files = true;
945 common_telemetry::warn!(
946 "Found remaining temporary file in atomic dir: {}",
947 entry.path().display()
948 );
949 }
950
951 assert!(!has_files);
952 }
953
954 #[tokio::test]
955 async fn test_download_if_absent_skips_when_cached() {
956 let mut env = TestEnv::new().await;
957 let remote_store = env.init_object_store_manager();
958
959 let local_dir = create_temp_dir("");
960 let local_store = new_fs_store(local_dir.path().to_str().unwrap());
961 let write_cache = env
962 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
963 .await;
964
965 let region_id = RegionId::new(1024, 1);
966 let file_id = FileId::random();
967 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
968 write_cache
969 .file_cache()
970 .put(key, IndexValue { file_size: 1 })
971 .await;
972
973 let downloaded = write_cache
974 .download_if_absent(key, "missing/path.parquet", &remote_store, 1)
975 .await
976 .unwrap();
977
978 assert!(!downloaded);
979 }
980
981 #[tokio::test]
982 async fn test_download_if_absent_downloads_when_missing() {
983 let mut env = TestEnv::new().await;
984 let remote_store = env.init_object_store_manager();
985
986 let local_dir = create_temp_dir("");
987 let local_store = new_fs_store(local_dir.path().to_str().unwrap());
988 let write_cache = env
989 .create_write_cache(local_store.clone(), ReadableSize::mb(10))
990 .await;
991
992 let region_id = RegionId::new(1024, 2);
993 let file_id = FileId::random();
994 let key = IndexKey::new(region_id, file_id, FileType::Parquet);
995 let remote_path = format!("download-if-absent/{file_id}.parquet");
996 let remote_data = Bytes::from_static(b"download-if-absent-test");
997 remote_store
998 .write(&remote_path, remote_data.clone())
999 .await
1000 .unwrap();
1001
1002 let downloaded = write_cache
1003 .download_if_absent(key, &remote_path, &remote_store, remote_data.len() as u64)
1004 .await
1005 .unwrap();
1006
1007 assert!(downloaded);
1008 assert!(write_cache.file_cache().contains_key(&key));
1009
1010 let cached_data = local_store
1011 .read(&write_cache.file_cache().cache_file_path(key))
1012 .await
1013 .unwrap();
1014 assert_eq!(cached_data.to_vec(), remote_data.to_vec());
1015 }
1016}