1use std::sync::Arc;
16use std::time::{Duration, Instant};
17
18use async_stream::try_stream;
19use common_telemetry::warn;
20use common_time::Timestamp;
21use futures::{Stream, TryStreamExt};
22use object_store::services::Fs;
23use object_store::util::{join_dir, with_instrument_layers};
24use object_store::{ATOMIC_WRITE_DIR, ErrorKind, OLD_ATOMIC_WRITE_DIR, ObjectStore};
25use parquet::file::metadata::PageIndexPolicy;
26use smallvec::SmallVec;
27use snafu::ResultExt;
28use store_api::metadata::RegionMetadataRef;
29use store_api::region_request::PathType;
30use store_api::sst_entry::StorageSstEntry;
31use store_api::storage::{FileId, RegionId, SequenceNumber};
32
33use crate::cache::file_cache::{FileCacheRef, FileType, IndexKey};
34use crate::cache::write_cache::SstUploadRequest;
35use crate::cache::{CacheManagerRef, SstMetaPreparation, prepare_sst_meta_sync};
36use crate::config::{BloomFilterConfig, FulltextIndexConfig, IndexConfig, InvertedIndexConfig};
37use crate::error::{
38 CleanDirSnafu, DeleteIndexSnafu, DeleteIndexesSnafu, DeleteSstsSnafu, OpenDalSnafu, Result,
39};
40use crate::metrics::{COMPACTION_STAGE_ELAPSED, FLUSH_ELAPSED};
41use crate::read::FlatSource;
42use crate::region::options::IndexOptions;
43use crate::sst::file::{FileHandle, RegionFileId, RegionIndexId};
44use crate::sst::index::IndexerBuilderImpl;
45use crate::sst::index::intermediate::IntermediateManager;
46use crate::sst::index::puffin_manager::{PuffinManagerFactory, SstPuffinManager};
47use crate::sst::location::{self, region_dir_from_table_dir};
48use crate::sst::parquet::reader::ParquetReaderBuilder;
49use crate::sst::parquet::writer::ParquetWriter;
50use crate::sst::parquet::{SstInfo, WriteOptions};
51use crate::sst::{DEFAULT_WRITE_BUFFER_SIZE, DEFAULT_WRITE_CONCURRENCY, FormatType};
52
53pub type AccessLayerRef = Arc<AccessLayer>;
54pub type SstInfoArray = SmallVec<[SstInfo; 2]>;
56
57#[derive(Eq, PartialEq, Debug)]
59pub enum WriteType {
60 Flush,
62 Compaction,
64}
65
66#[derive(Debug)]
67pub struct Metrics {
68 pub(crate) write_type: WriteType,
69 pub(crate) iter_source: Duration,
70 pub(crate) write_batch: Duration,
71 pub(crate) update_index: Duration,
72 pub(crate) upload_parquet: Duration,
73 pub(crate) upload_puffin: Duration,
74 pub(crate) compact_memtable: Duration,
75}
76
77impl Metrics {
78 pub fn new(write_type: WriteType) -> Self {
79 Self {
80 write_type,
81 iter_source: Default::default(),
82 write_batch: Default::default(),
83 update_index: Default::default(),
84 upload_parquet: Default::default(),
85 upload_puffin: Default::default(),
86 compact_memtable: Default::default(),
87 }
88 }
89
90 pub(crate) fn merge(mut self, other: Self) -> Self {
91 assert_eq!(self.write_type, other.write_type);
92 self.iter_source += other.iter_source;
93 self.write_batch += other.write_batch;
94 self.update_index += other.update_index;
95 self.upload_parquet += other.upload_parquet;
96 self.upload_puffin += other.upload_puffin;
97 self.compact_memtable += other.compact_memtable;
98 self
99 }
100
101 pub(crate) fn observe(self) {
102 match self.write_type {
103 WriteType::Flush => {
104 FLUSH_ELAPSED
105 .with_label_values(&["iter_source"])
106 .observe(self.iter_source.as_secs_f64());
107 FLUSH_ELAPSED
108 .with_label_values(&["write_batch"])
109 .observe(self.write_batch.as_secs_f64());
110 FLUSH_ELAPSED
111 .with_label_values(&["update_index"])
112 .observe(self.update_index.as_secs_f64());
113 FLUSH_ELAPSED
114 .with_label_values(&["upload_parquet"])
115 .observe(self.upload_parquet.as_secs_f64());
116 FLUSH_ELAPSED
117 .with_label_values(&["upload_puffin"])
118 .observe(self.upload_puffin.as_secs_f64());
119 if !self.compact_memtable.is_zero() {
120 FLUSH_ELAPSED
121 .with_label_values(&["compact_memtable"])
122 .observe(self.upload_puffin.as_secs_f64());
123 }
124 }
125 WriteType::Compaction => {
126 COMPACTION_STAGE_ELAPSED
127 .with_label_values(&["iter_source"])
128 .observe(self.iter_source.as_secs_f64());
129 COMPACTION_STAGE_ELAPSED
130 .with_label_values(&["write_batch"])
131 .observe(self.write_batch.as_secs_f64());
132 COMPACTION_STAGE_ELAPSED
133 .with_label_values(&["update_index"])
134 .observe(self.update_index.as_secs_f64());
135 COMPACTION_STAGE_ELAPSED
136 .with_label_values(&["upload_parquet"])
137 .observe(self.upload_parquet.as_secs_f64());
138 COMPACTION_STAGE_ELAPSED
139 .with_label_values(&["upload_puffin"])
140 .observe(self.upload_puffin.as_secs_f64());
141 }
142 };
143 }
144}
145
146pub struct AccessLayer {
148 table_dir: String,
149 path_type: PathType,
151 object_store: ObjectStore,
153 puffin_manager_factory: PuffinManagerFactory,
155 intermediate_manager: IntermediateManager,
157}
158
159impl std::fmt::Debug for AccessLayer {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("AccessLayer")
162 .field("table_dir", &self.table_dir)
163 .finish()
164 }
165}
166
167impl AccessLayer {
168 pub fn new(
170 table_dir: impl Into<String>,
171 path_type: PathType,
172 object_store: ObjectStore,
173 puffin_manager_factory: PuffinManagerFactory,
174 intermediate_manager: IntermediateManager,
175 ) -> AccessLayer {
176 AccessLayer {
177 table_dir: table_dir.into(),
178 path_type,
179 object_store,
180 puffin_manager_factory,
181 intermediate_manager,
182 }
183 }
184
185 pub fn table_dir(&self) -> &str {
187 &self.table_dir
188 }
189
190 pub fn object_store(&self) -> &ObjectStore {
192 &self.object_store
193 }
194
195 pub fn path_type(&self) -> PathType {
197 self.path_type
198 }
199
200 pub fn puffin_manager_factory(&self) -> &PuffinManagerFactory {
202 &self.puffin_manager_factory
203 }
204
205 pub fn intermediate_manager(&self) -> &IntermediateManager {
207 &self.intermediate_manager
208 }
209
210 pub(crate) fn build_puffin_manager(&self) -> SstPuffinManager {
212 let store = self.object_store.clone();
213 let path_provider =
214 RegionFilePathFactory::new(self.table_dir().to_string(), self.path_type());
215 self.puffin_manager_factory.build(store, path_provider)
216 }
217
218 pub(crate) async fn delete_index(
219 &self,
220 index_file_id: RegionIndexId,
221 ) -> Result<(), crate::error::Error> {
222 let path = location::index_file_path(
223 &self.table_dir,
224 RegionIndexId::new(index_file_id.file_id, index_file_id.version),
225 self.path_type,
226 );
227 self.object_store
228 .delete(&path)
229 .await
230 .context(DeleteIndexSnafu {
231 file_id: index_file_id.file_id(),
232 })?;
233 Ok(())
234 }
235
236 pub(crate) async fn delete_ssts(
237 &self,
238 region_id: RegionId,
239 file_ids: &[FileId],
240 ) -> Result<(), crate::error::Error> {
241 if file_ids.is_empty() {
242 return Ok(());
243 }
244
245 let attempted_files = file_ids.to_vec();
246 let paths: Vec<_> = file_ids
247 .iter()
248 .map(|file_id| {
249 location::sst_file_path(
250 &self.table_dir,
251 RegionFileId::new(region_id, *file_id),
252 self.path_type,
253 )
254 })
255 .collect();
256
257 let mut deleter = self
258 .object_store
259 .deleter()
260 .await
261 .with_context(|_| DeleteSstsSnafu {
262 region_id,
263 file_ids: attempted_files.clone(),
264 })?;
265 deleter
266 .delete_iter(paths.iter().map(String::as_str))
267 .await
268 .with_context(|_| DeleteSstsSnafu {
269 region_id,
270 file_ids: attempted_files.clone(),
271 })?;
272 deleter.close().await.with_context(|_| DeleteSstsSnafu {
273 region_id,
274 file_ids: attempted_files,
275 })?;
276
277 Ok(())
278 }
279
280 pub(crate) async fn delete_indexes(
281 &self,
282 index_ids: &[RegionIndexId],
283 ) -> Result<(), crate::error::Error> {
284 if index_ids.is_empty() {
285 return Ok(());
286 }
287
288 let file_ids: Vec<_> = index_ids
289 .iter()
290 .map(|index_id| index_id.file_id())
291 .collect();
292 let paths: Vec<_> = index_ids
293 .iter()
294 .map(|index_id| location::index_file_path(&self.table_dir, *index_id, self.path_type))
295 .collect();
296
297 let mut deleter = self
298 .object_store
299 .deleter()
300 .await
301 .context(DeleteIndexesSnafu {
302 file_ids: file_ids.clone(),
303 })?;
304 deleter
305 .delete_iter(paths.iter().map(String::as_str))
306 .await
307 .context(DeleteIndexesSnafu {
308 file_ids: file_ids.clone(),
309 })?;
310 deleter
311 .close()
312 .await
313 .context(DeleteIndexesSnafu { file_ids })?;
314
315 Ok(())
316 }
317
318 pub fn build_region_dir(&self, region_id: RegionId) -> String {
320 region_dir_from_table_dir(&self.table_dir, region_id, self.path_type)
321 }
322
323 pub(crate) fn read_sst(&self, file: FileHandle) -> ParquetReaderBuilder {
325 ParquetReaderBuilder::new(
326 self.table_dir.clone(),
327 self.path_type,
328 file,
329 self.object_store.clone(),
330 )
331 }
332
333 pub async fn write_sst(
337 &self,
338 request: SstWriteRequest,
339 write_opts: &WriteOptions,
340 metrics: &mut Metrics,
341 ) -> Result<SstInfoArray> {
342 let region_id = request.metadata.region_id;
343 let region_metadata = request.metadata.clone();
344 let cache_manager = request.cache_manager.clone();
345 let override_sequence = if request.preserve_row_sequence {
346 None
347 } else {
348 request.max_sequence
349 };
350
351 let sst_info = if let Some(write_cache) = cache_manager.write_cache() {
352 write_cache
354 .write_and_upload_sst(
355 request,
356 SstUploadRequest {
357 dest_path_provider: RegionFilePathFactory::new(
358 self.table_dir.clone(),
359 self.path_type,
360 ),
361 remote_store: self.object_store.clone(),
362 },
363 write_opts,
364 metrics,
365 )
366 .await?
367 } else {
368 let store = self.object_store.clone();
370 let path_provider = RegionFilePathFactory::new(self.table_dir.clone(), self.path_type);
371 let indexer_builder = IndexerBuilderImpl {
372 build_type: request.op_type.into(),
373 metadata: request.metadata.clone(),
374 puffin_manager: self
375 .puffin_manager_factory
376 .build(store, path_provider.clone()),
377 write_cache_enabled: false,
378 intermediate_manager: self.intermediate_manager.clone(),
379 index_options: request.index_options,
380 inverted_index_config: request.inverted_index_config,
381 fulltext_index_config: request.fulltext_index_config,
382 bloom_filter_index_config: request.bloom_filter_index_config,
383 #[cfg(feature = "vector_index")]
384 vector_index_config: request.vector_index_config,
385 };
386 let cleaner = TempFileCleaner::new(region_id, self.object_store.clone());
390 let mut writer = ParquetWriter::new_with_object_store(
391 self.object_store.clone(),
392 request.metadata,
393 request.index_config,
394 indexer_builder,
395 path_provider,
396 metrics,
397 )
398 .await
399 .with_file_cleaner(cleaner);
400 match request.sst_write_format {
401 FormatType::PrimaryKey => {
402 writer
403 .write_all_flat_as_primary_key(
404 request.source,
405 override_sequence,
406 write_opts,
407 )
408 .await?
409 }
410 FormatType::Flat => {
411 writer
412 .write_all_flat(request.source, override_sequence, write_opts)
413 .await?
414 }
415 }
416 };
417
418 if !sst_info.is_empty() && cache_manager.sst_meta_cache_enabled() {
420 for sst in &sst_info {
421 if let Some(parquet_metadata) = &sst.file_metadata {
422 let file_id = RegionFileId::new(region_id, sst.file_id);
423 let file_path = format!(
424 "region_id={}, file_id={}",
425 file_id.region_id(),
426 file_id.file_id()
427 );
428 let page_index_policy = if parquet_metadata.offset_index().is_some() {
429 PageIndexPolicy::Optional
430 } else {
431 PageIndexPolicy::Skip
432 };
433 let parquet_metadata = parquet_metadata.clone();
434 let region_metadata = region_metadata.clone();
435 let cache_manager = cache_manager.clone();
436 common_runtime::spawn_blocking_global(move || {
440 match prepare_sst_meta_sync(
441 &file_path,
442 Arc::unwrap_or_clone(parquet_metadata),
443 Some(region_metadata),
444 page_index_policy,
445 ) {
446 Ok(SstMetaPreparation::Prepared(metadata)) => {
447 cache_manager.put_prepared_sst_meta(file_id, metadata, true);
448 }
449 Ok(SstMetaPreparation::DecodedOnly { encoding_error, .. }) => warn!(
450 encoding_error;
451 "Failed to encode parquet metadata for cache, file: {}",
452 file_path
453 ),
454 Err(err) => {
455 warn!(err; "Failed to cache parquet metadata for {}", file_path);
456 }
457 }
458 });
459 }
460 }
461 }
462
463 Ok(sst_info)
464 }
465
466 pub(crate) async fn put_sst(
468 &self,
469 data: &bytes::Bytes,
470 region_id: RegionId,
471 sst_info: &SstInfo,
472 cache_manager: &CacheManagerRef,
473 ) -> Result<Metrics> {
474 if let Some(write_cache) = cache_manager.write_cache() {
475 let upload_request = SstUploadRequest {
477 dest_path_provider: RegionFilePathFactory::new(
478 self.table_dir.clone(),
479 self.path_type,
480 ),
481 remote_store: self.object_store.clone(),
482 };
483 write_cache
484 .put_and_upload_sst(data, region_id, sst_info, upload_request)
485 .await
486 } else {
487 let start = Instant::now();
488 let cleaner = TempFileCleaner::new(region_id, self.object_store.clone());
489 let path_provider = RegionFilePathFactory::new(self.table_dir.clone(), self.path_type);
490 let sst_file_path =
491 path_provider.build_sst_file_path(RegionFileId::new(region_id, sst_info.file_id));
492 let mut writer = self
493 .object_store
494 .writer_with(&sst_file_path)
495 .chunk(DEFAULT_WRITE_BUFFER_SIZE.as_bytes() as usize)
496 .concurrent(DEFAULT_WRITE_CONCURRENCY)
497 .await
498 .context(OpenDalSnafu)?;
499 if let Err(err) = writer.write(data.clone()).await.context(OpenDalSnafu) {
500 cleaner.clean_by_file_id(sst_info.file_id).await;
501 return Err(err);
502 }
503 if let Err(err) = writer.close().await.context(OpenDalSnafu) {
504 cleaner.clean_by_file_id(sst_info.file_id).await;
505 return Err(err);
506 }
507 let mut metrics = Metrics::new(WriteType::Flush);
508 metrics.write_batch = start.elapsed();
509 Ok(metrics)
510 }
511 }
512
513 pub fn storage_sst_entries(&self) -> impl Stream<Item = Result<StorageSstEntry>> + use<> {
515 let object_store = self.object_store.clone();
516 let table_dir = self.table_dir.clone();
517
518 try_stream! {
519 let mut lister = object_store
520 .lister_with(table_dir.as_str())
521 .recursive(true)
522 .await
523 .context(OpenDalSnafu)?;
524
525 while let Some(entry) = lister.try_next().await.context(OpenDalSnafu)? {
526 let metadata = entry.metadata();
527 if metadata.is_dir() {
528 continue;
529 }
530
531 let path = entry.path();
532 if !path.ends_with(".parquet") && !path.ends_with(".puffin") {
533 continue;
534 }
535
536 let file_size = metadata.content_length();
537 let file_size = if file_size == 0 { None } else { Some(file_size) };
538 let last_modified_ms = metadata
539 .last_modified()
540 .map(|ts| Timestamp::new_millisecond(ts.into_inner().as_millisecond()));
541
542 let entry = StorageSstEntry {
543 file_path: path.to_string(),
544 file_size,
545 last_modified_ms,
546 node_id: None,
547 };
548
549 yield entry;
550 }
551 }
552 }
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
557pub enum OperationType {
558 Flush,
559 Compact,
560}
561
562pub struct SstWriteRequest {
564 pub op_type: OperationType,
565 pub metadata: RegionMetadataRef,
566 pub source: FlatSource,
567 pub cache_manager: CacheManagerRef,
568 #[allow(dead_code)]
569 pub storage: Option<String>,
570 pub max_sequence: Option<SequenceNumber>,
571 pub sst_write_format: FormatType,
572
573 pub preserve_row_sequence: bool,
574
575 pub index_options: IndexOptions,
577 pub index_config: IndexConfig,
578 pub inverted_index_config: InvertedIndexConfig,
579 pub fulltext_index_config: FulltextIndexConfig,
580 pub bloom_filter_index_config: BloomFilterConfig,
581 #[cfg(feature = "vector_index")]
582 pub vector_index_config: crate::config::VectorIndexConfig,
583}
584
585pub(crate) struct TempFileCleaner {
587 region_id: RegionId,
588 object_store: ObjectStore,
589}
590
591impl TempFileCleaner {
592 pub(crate) fn new(region_id: RegionId, object_store: ObjectStore) -> Self {
594 Self {
595 region_id,
596 object_store,
597 }
598 }
599
600 pub(crate) async fn clean_by_file_id(&self, file_id: FileId) {
603 let sst_key = IndexKey::new(self.region_id, file_id, FileType::Parquet).to_string();
604 let index_key = IndexKey::new(self.region_id, file_id, FileType::Puffin(0)).to_string();
605
606 Self::clean_atomic_dir_files(&self.object_store, &[&sst_key, &index_key]).await;
607 }
608
609 pub(crate) async fn clean_atomic_dir_files(
611 local_store: &ObjectStore,
612 names_to_remove: &[&str],
613 ) {
614 let Ok(entries) = local_store.list(ATOMIC_WRITE_DIR).await.inspect_err(|e| {
617 if e.kind() != ErrorKind::NotFound {
618 common_telemetry::error!(e; "Failed to list tmp files for {:?}", names_to_remove)
619 }
620 }) else {
621 return;
622 };
623
624 let actual_files: Vec<_> = entries
627 .into_iter()
628 .filter_map(|entry| {
629 if entry.metadata().is_dir() {
630 return None;
631 }
632
633 let should_remove = names_to_remove
635 .iter()
636 .any(|file| entry.name().starts_with(file));
637 if should_remove {
638 Some(entry.path().to_string())
639 } else {
640 None
641 }
642 })
643 .collect();
644
645 common_telemetry::warn!(
646 "Clean files {:?} under atomic write dir for {:?}",
647 actual_files,
648 names_to_remove
649 );
650
651 if let Err(e) = local_store.delete_iter(actual_files).await {
652 common_telemetry::error!(e; "Failed to delete tmp file for {:?}", names_to_remove);
653 }
654 }
655}
656
657pub(crate) async fn new_fs_cache_store(root: &str) -> Result<ObjectStore> {
658 let atomic_write_dir = join_dir(root, ATOMIC_WRITE_DIR);
659 clean_dir(&atomic_write_dir).await?;
660
661 let old_atomic_temp_dir = join_dir(root, OLD_ATOMIC_WRITE_DIR);
663 clean_dir(&old_atomic_temp_dir).await?;
664
665 let builder = Fs::default().root(root).atomic_write_dir(&atomic_write_dir);
666 let store = ObjectStore::new(builder).context(OpenDalSnafu)?;
667
668 Ok(with_instrument_layers(store, false))
669}
670
671async fn clean_dir(dir: &str) -> Result<()> {
673 if tokio::fs::try_exists(dir)
674 .await
675 .context(CleanDirSnafu { dir })?
676 {
677 tokio::fs::remove_dir_all(dir)
678 .await
679 .context(CleanDirSnafu { dir })?;
680 }
681
682 Ok(())
683}
684
685pub trait FilePathProvider: Send + Sync {
687 fn build_index_file_path(&self, file_id: RegionFileId) -> String;
689
690 fn build_index_file_path_with_version(&self, index_id: RegionIndexId) -> String;
692
693 fn build_sst_file_path(&self, file_id: RegionFileId) -> String;
695}
696
697#[derive(Clone)]
699pub(crate) struct WriteCachePathProvider {
700 file_cache: FileCacheRef,
701}
702
703impl WriteCachePathProvider {
704 pub fn new(file_cache: FileCacheRef) -> Self {
706 Self { file_cache }
707 }
708}
709
710impl FilePathProvider for WriteCachePathProvider {
711 fn build_index_file_path(&self, file_id: RegionFileId) -> String {
712 let puffin_key = IndexKey::new(file_id.region_id(), file_id.file_id(), FileType::Puffin(0));
713 self.file_cache.cache_file_path(puffin_key)
714 }
715
716 fn build_index_file_path_with_version(&self, index_id: RegionIndexId) -> String {
717 let puffin_key = IndexKey::new(
718 index_id.region_id(),
719 index_id.file_id(),
720 FileType::Puffin(index_id.version),
721 );
722 self.file_cache.cache_file_path(puffin_key)
723 }
724
725 fn build_sst_file_path(&self, file_id: RegionFileId) -> String {
726 let parquet_file_key =
727 IndexKey::new(file_id.region_id(), file_id.file_id(), FileType::Parquet);
728 self.file_cache.cache_file_path(parquet_file_key)
729 }
730}
731
732#[derive(Clone, Debug)]
734pub(crate) struct RegionFilePathFactory {
735 pub(crate) table_dir: String,
736 pub(crate) path_type: PathType,
737}
738
739impl RegionFilePathFactory {
740 pub fn new(table_dir: String, path_type: PathType) -> Self {
742 Self {
743 table_dir,
744 path_type,
745 }
746 }
747}
748
749impl FilePathProvider for RegionFilePathFactory {
750 fn build_index_file_path(&self, file_id: RegionFileId) -> String {
751 location::index_file_path_legacy(&self.table_dir, file_id, self.path_type)
752 }
753
754 fn build_index_file_path_with_version(&self, index_id: RegionIndexId) -> String {
755 location::index_file_path(&self.table_dir, index_id, self.path_type)
756 }
757
758 fn build_sst_file_path(&self, file_id: RegionFileId) -> String {
759 location::sst_file_path(&self.table_dir, file_id, self.path_type)
760 }
761}