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