1use std::collections::HashMap;
18use std::fmt;
19use std::fmt::{Debug, Formatter};
20use std::num::NonZeroU64;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, RwLock};
23
24use base64::prelude::{BASE64_STANDARD, Engine};
25use bytes::Bytes;
26use common_base::readable_size::ReadableSize;
27use common_telemetry::{debug, error, warn};
28use common_time::Timestamp;
29use datatypes::schema::FulltextBackend;
30use partition::expr::PartitionExpr;
31use serde::{Deserialize, Serialize};
32use smallvec::SmallVec;
33use store_api::metadata::ColumnMetadata;
34use store_api::region_request::PathType;
35use store_api::storage::{ColumnId, FileId, IndexVersion, RegionId};
36
37use crate::access_layer::AccessLayerRef;
38use crate::cache::CacheManagerRef;
39use crate::cache::file_cache::{FileType, IndexKey};
40use crate::region::options::IndexOptions;
41use crate::sst::file_purger::FilePurgerRef;
42use crate::sst::file_ref::FileReferenceManagerRef;
43use crate::sst::index::fulltext_index::FULLTEXT_BLOOM_VERSION;
44use crate::sst::location;
45use crate::sst::parquet::SstInfo;
46use crate::sst::primary_key::PrimaryKeyRangeMapper;
47
48fn serialize_bytes_option<S>(bytes: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
50where
51 S: serde::Serializer,
52{
53 match bytes {
54 None => serializer.serialize_none(),
55 Some(b) => serializer.serialize_some(&BASE64_STANDARD.encode(b)),
56 }
57}
58
59fn deserialize_bytes_option<'de, D>(deserializer: D) -> Result<Option<Bytes>, D::Error>
60where
61 D: serde::Deserializer<'de>,
62{
63 let opt: Option<String> = Option::deserialize(deserializer)?;
64 match opt {
65 None => Ok(None),
66 Some(s) => {
67 let decoded = BASE64_STANDARD
68 .decode(&s)
69 .map_err(serde::de::Error::custom)?;
70 Ok(Some(Bytes::from(decoded)))
71 }
72 }
73}
74
75fn serialize_partition_expr<S>(
77 partition_expr: &Option<PartitionExpr>,
78 serializer: S,
79) -> Result<S::Ok, S::Error>
80where
81 S: serde::Serializer,
82{
83 use serde::ser::Error;
84
85 match partition_expr {
86 None => serializer.serialize_none(),
87 Some(expr) => {
88 let json_str = expr.as_json_str().map_err(S::Error::custom)?;
89 serializer.serialize_some(&json_str)
90 }
91 }
92}
93
94fn deserialize_partition_expr<'de, D>(deserializer: D) -> Result<Option<PartitionExpr>, D::Error>
95where
96 D: serde::Deserializer<'de>,
97{
98 use serde::de::Error;
99
100 let opt_json_str: Option<String> = Option::deserialize(deserializer)?;
101 match opt_json_str {
102 None => Ok(None),
103 Some(json_str) => {
104 if json_str.is_empty() {
105 Ok(None)
107 } else {
108 PartitionExpr::from_json_str(&json_str).map_err(D::Error::custom)
110 }
111 }
112 }
113}
114
115pub type Level = u8;
117pub const MAX_LEVEL: Level = 2;
119pub type IndexTypes = SmallVec<[IndexType; 4]>;
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub struct RegionFileId {
127 region_id: RegionId,
129 file_id: FileId,
131}
132
133impl RegionFileId {
134 pub fn new(region_id: RegionId, file_id: FileId) -> Self {
136 Self { region_id, file_id }
137 }
138
139 pub fn region_id(&self) -> RegionId {
141 self.region_id
142 }
143
144 pub fn file_id(&self) -> FileId {
146 self.file_id
147 }
148}
149
150impl fmt::Display for RegionFileId {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 write!(f, "{}/{}", self.region_id, self.file_id)
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
158pub struct RegionIndexId {
159 pub file_id: RegionFileId,
160 pub version: IndexVersion,
161}
162
163impl RegionIndexId {
164 pub fn new(file_id: RegionFileId, version: IndexVersion) -> Self {
165 Self { file_id, version }
166 }
167
168 pub fn region_id(&self) -> RegionId {
169 self.file_id.region_id
170 }
171
172 pub fn file_id(&self) -> FileId {
173 self.file_id.file_id
174 }
175}
176
177impl fmt::Display for RegionIndexId {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 if self.version == 0 {
180 write!(f, "{}/{}", self.file_id.region_id, self.file_id.file_id)
181 } else {
182 write!(
183 f,
184 "{}/{}.{}",
185 self.file_id.region_id, self.file_id.file_id, self.version
186 )
187 }
188 }
189}
190
191pub type FileTimeRange = (Timestamp, Timestamp);
194
195pub(crate) fn overlaps(l: &FileTimeRange, r: &FileTimeRange) -> bool {
197 let (l, r) = if l.0 <= r.0 { (l, r) } else { (r, l) };
198 let (_, l_end) = l;
199 let (r_start, _) = r;
200
201 r_start <= l_end
202}
203
204#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
206#[serde(default)]
207pub struct FileMeta {
208 pub region_id: RegionId,
210 pub file_id: FileId,
212 pub time_range: FileTimeRange,
215 pub level: Level,
217 pub file_size: u64,
219 pub max_row_group_uncompressed_size: u64,
221 pub available_indexes: IndexTypes,
223 pub indexes: Vec<ColumnIndexMetadata>,
233 pub index_file_size: u64,
235 pub index_version: u64,
239 pub num_rows: u64,
245 pub num_row_groups: u64,
251 pub sequence: Option<NonZeroU64>,
259 #[serde(
267 serialize_with = "serialize_partition_expr",
268 deserialize_with = "deserialize_partition_expr"
269 )]
270 pub partition_expr: Option<PartitionExpr>,
271 pub num_series: u64,
275 #[serde(
278 default,
279 skip_serializing_if = "Option::is_none",
280 serialize_with = "serialize_bytes_option",
281 deserialize_with = "deserialize_bytes_option"
282 )]
283 pub primary_key_min: Option<Bytes>,
284 #[serde(
287 default,
288 skip_serializing_if = "Option::is_none",
289 serialize_with = "serialize_bytes_option",
290 deserialize_with = "deserialize_bytes_option"
291 )]
292 pub primary_key_max: Option<Bytes>,
293 #[serde(default, skip_serializing_if = "is_false")]
298 pub preserve_row_sequence: bool,
299}
300
301fn is_false(value: &bool) -> bool {
302 !*value
303}
304
305struct DebugFmt<F: Fn(&mut Formatter<'_>) -> fmt::Result>(F);
308
309impl<F: Fn(&mut Formatter<'_>) -> fmt::Result> std::fmt::Debug for DebugFmt<F> {
310 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
311 (self.0)(f)
312 }
313}
314
315impl Debug for FileMeta {
316 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
317 let mut debug_struct = f.debug_struct("FileMeta");
318 debug_struct
319 .field("region_id", &self.region_id)
320 .field("file_id", &DebugFmt(|f| write!(f, "{} ", self.file_id)))
321 .field(
322 "time_range",
323 &DebugFmt(|f| {
324 write!(
325 f,
326 "({}, {}) ",
327 self.time_range.0.to_iso8601_string(),
328 self.time_range.1.to_iso8601_string()
329 )
330 }),
331 )
332 .field("level", &self.level)
333 .field("file_size", &ReadableSize(self.file_size))
334 .field(
335 "max_row_group_uncompressed_size",
336 &ReadableSize(self.max_row_group_uncompressed_size),
337 );
338 if !self.available_indexes.is_empty() {
339 debug_struct
340 .field("available_indexes", &self.available_indexes)
341 .field("indexes", &self.indexes)
342 .field("index_file_size", &ReadableSize(self.index_file_size));
343 }
344 debug_struct
345 .field("num_rows", &self.num_rows)
346 .field("num_row_groups", &self.num_row_groups)
347 .field(
348 "sequence",
349 &DebugFmt(|f| match self.sequence {
350 None => {
351 write!(f, "None")
352 }
353 Some(seq) => {
354 write!(f, "{}", seq)
355 }
356 }),
357 )
358 .field("partition_expr", &self.partition_expr)
359 .field("num_series", &self.num_series);
360 if self.primary_key_min.is_some() || self.primary_key_max.is_some() {
361 debug_struct
362 .field(
363 "primary_key_min",
364 &self.primary_key_min.as_ref().map(|b| b.len()),
365 )
366 .field(
367 "primary_key_max",
368 &self.primary_key_max.as_ref().map(|b| b.len()),
369 );
370 }
371 debug_struct.finish()
372 }
373}
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
377pub enum IndexType {
378 InvertedIndex,
380 FulltextIndex,
382 BloomFilterIndex,
384 VectorIndex,
386}
387
388#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
398#[serde(default)]
399pub struct ColumnIndexMetadata {
400 pub column_id: ColumnId,
402 pub created_indexes: IndexTypes,
404 #[serde(skip_serializing_if = "is_zero")]
408 pub fulltext_bloom_version: u8,
409}
410
411fn is_zero(version: &u8) -> bool {
412 *version == 0
413}
414
415impl FileMeta {
416 pub(crate) fn is_effective_target_sequence_trusted(&self, target_region_id: RegionId) -> bool {
421 if self.region_id != target_region_id {
422 self.sequence.is_some()
423 } else {
424 self.preserve_row_sequence
425 }
426 }
427
428 pub fn primary_key_range(&self) -> Option<(Bytes, Bytes)> {
430 match (&self.primary_key_min, &self.primary_key_max) {
431 (Some(min), Some(max)) => Some((min.clone(), max.clone())),
432 _ => None,
433 }
434 }
435
436 pub fn exists_index(&self) -> bool {
437 !self.available_indexes.is_empty()
438 }
439
440 pub fn index_version(&self) -> Option<IndexVersion> {
441 if self.exists_index() {
442 Some(self.index_version)
443 } else {
444 None
445 }
446 }
447
448 pub fn is_index_up_to_date(&self, other: &FileMeta) -> bool {
450 self.exists_index() && other.exists_index() && self.index_version >= other.index_version
451 }
452
453 pub fn inverted_index_available(&self) -> bool {
455 self.available_indexes.contains(&IndexType::InvertedIndex)
456 }
457
458 pub fn fulltext_index_available(&self) -> bool {
460 self.available_indexes.contains(&IndexType::FulltextIndex)
461 }
462
463 pub fn bloom_filter_index_available(&self) -> bool {
465 self.available_indexes
466 .contains(&IndexType::BloomFilterIndex)
467 }
468
469 pub fn index_file_size(&self) -> u64 {
470 self.index_file_size
471 }
472
473 pub fn is_index_consistent_with_region(
476 &self,
477 metadata: &[ColumnMetadata],
478 index_options: &IndexOptions,
479 ) -> bool {
480 let id_to_indexes = self
481 .indexes
482 .iter()
483 .map(|index| (index.column_id, index))
484 .collect::<std::collections::HashMap<_, _>>();
485 let inverted_options = &index_options.inverted_index;
486 for column in metadata {
487 let schema = &column.column_schema;
488 let expects_inverted = schema.is_inverted_indexed()
491 && inverted_options.segment_row_count > 0
492 && !inverted_options
493 .ignore_column_ids
494 .contains(&column.column_id);
495 let expects_fulltext = schema.is_fulltext_indexed();
496 let expects_skipping = schema.is_skipping_indexed();
497 if !expects_inverted && !expects_fulltext && !expects_skipping {
498 continue;
499 }
500 let missing_index = |index_type: IndexType| {
501 debug!(
502 "File index is missing, region_id: {}, file_id: {}, column_id: {}, index_type: {:?}",
503 self.region_id, self.file_id, column.column_id, index_type
504 );
505 false
506 };
507 let Some(index) = id_to_indexes.get(&column.column_id) else {
508 debug!(
509 "File has no index for column, region_id: {}, file_id: {}, column_id: {}",
510 self.region_id, self.file_id, column.column_id
511 );
512 return false;
513 };
514 let indexes = &index.created_indexes;
515 if expects_inverted && !indexes.contains(&IndexType::InvertedIndex) {
516 return missing_index(IndexType::InvertedIndex);
517 }
518 if expects_fulltext && !indexes.contains(&IndexType::FulltextIndex) {
519 return missing_index(IndexType::FulltextIndex);
520 }
521 if schema
522 .fulltext_options()
523 .ok()
524 .flatten()
525 .is_some_and(|options| options.enable && options.backend == FulltextBackend::Bloom)
526 && index.fulltext_bloom_version < FULLTEXT_BLOOM_VERSION
527 {
528 debug!(
529 "Fulltext bloom index is outdated, region_id: {}, file_id: {}, column_id: {}, version: {}, current: {}",
530 self.region_id,
531 self.file_id,
532 column.column_id,
533 index.fulltext_bloom_version,
534 FULLTEXT_BLOOM_VERSION
535 );
536 return false;
537 }
538 if expects_skipping && !indexes.contains(&IndexType::BloomFilterIndex) {
539 return missing_index(IndexType::BloomFilterIndex);
540 }
541 }
542 true
543 }
544
545 pub fn file_id(&self) -> RegionFileId {
547 RegionFileId::new(self.region_id, self.file_id)
548 }
549
550 pub fn index_id(&self) -> RegionIndexId {
552 RegionIndexId::new(self.file_id(), self.index_version)
553 }
554}
555
556#[derive(Clone)]
558pub struct FileHandle {
559 inner: Arc<FileHandleInner>,
560}
561
562impl fmt::Debug for FileHandle {
563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564 f.debug_struct("FileHandle")
565 .field("meta", self.meta_ref())
566 .field("compacting", &self.compacting())
567 .field("deleted", &self.is_deleted())
568 .finish()
569 }
570}
571
572pub(crate) fn known_max_input_sequence<'a>(
578 inputs: impl IntoIterator<Item = &'a FileMeta>,
579) -> Option<NonZeroU64> {
580 let mut max: Option<NonZeroU64> = None;
581 for input in inputs {
582 let sequence = input.sequence?;
583 max = Some(max.map_or(sequence, |current| current.max(sequence)));
584 }
585 max
586}
587
588impl FileHandle {
589 pub fn new(meta: FileMeta, file_purger: FilePurgerRef) -> FileHandle {
591 let pk_range = meta.primary_key_range();
592 FileHandle {
593 inner: Arc::new(FileHandleInner::new(meta, file_purger, pk_range)),
594 }
595 }
596
597 #[cfg(test)]
598 pub fn new_with_primary_key_range(
599 meta: FileMeta,
600 file_purger: FilePurgerRef,
601 primary_key_range: Option<(Bytes, Bytes)>,
602 ) -> FileHandle {
603 FileHandle {
604 inner: Arc::new(FileHandleInner::new(meta, file_purger, primary_key_range)),
605 }
606 }
607
608 pub(crate) fn with_updated_index(&self, meta: FileMeta) -> Self {
610 debug_assert_eq!(self.file_id(), meta.file_id());
611 let primary_key_range = meta
612 .primary_key_range()
613 .or_else(|| self.raw_primary_key_range());
614 let lifetime = self.inner.lifetime.clone();
615 *lifetime.meta.lock().unwrap() = meta.clone();
616 Self {
617 inner: Arc::new(FileHandleInner::with_lifetime(
618 meta,
619 lifetime,
620 primary_key_range,
621 )),
622 }
623 }
624
625 pub fn region_id(&self) -> RegionId {
627 self.inner.meta.region_id
628 }
629
630 pub(crate) fn is_effective_target_sequence_trusted(&self, target_region_id: RegionId) -> bool {
635 self.meta_ref()
636 .is_effective_target_sequence_trusted(target_region_id)
637 }
638
639 pub fn file_id(&self) -> RegionFileId {
641 RegionFileId::new(self.inner.meta.region_id, self.inner.meta.file_id)
642 }
643
644 pub fn index_id(&self) -> RegionIndexId {
646 RegionIndexId::new(self.file_id(), self.inner.meta.index_version)
647 }
648
649 pub fn file_path(&self, table_dir: &str, path_type: PathType) -> String {
651 location::sst_file_path(table_dir, self.file_id(), path_type)
652 }
653
654 pub fn time_range(&self) -> FileTimeRange {
656 self.inner.meta.time_range
657 }
658
659 pub fn mark_deleted(&self) {
661 if !self.inner.lifetime.deleted.swap(true, Ordering::Relaxed) {
662 self.inner
663 .lifetime
664 .file_purger
665 .invalidate_file(&self.inner.meta);
666 }
667 }
668
669 pub fn compacting(&self) -> bool {
670 self.inner.compacting.load(Ordering::Relaxed)
671 }
672
673 pub fn set_compacting(&self, compacting: bool) {
674 self.inner.compacting.store(compacting, Ordering::Relaxed);
675 }
676
677 pub fn try_set_compacting(&self) -> bool {
679 self.inner
680 .compacting
681 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
682 .is_ok()
683 }
684
685 pub fn meta_ref(&self) -> &FileMeta {
687 &self.inner.meta
688 }
689
690 pub fn file_purger(&self) -> FilePurgerRef {
691 self.inner.lifetime.file_purger.clone()
692 }
693
694 pub fn size(&self) -> u64 {
695 self.inner.meta.file_size
696 }
697
698 pub fn index_size(&self) -> u64 {
699 self.inner.meta.index_file_size
700 }
701
702 pub fn num_rows(&self) -> usize {
703 self.inner.meta.num_rows as usize
704 }
705
706 pub fn level(&self) -> Level {
707 self.inner.meta.level
708 }
709
710 pub fn is_deleted(&self) -> bool {
711 self.inner.lifetime.deleted.load(Ordering::Relaxed)
712 }
713
714 pub(crate) fn primary_key_range(
717 &self,
718 mapper: &PrimaryKeyRangeMapper,
719 ) -> Option<(Bytes, Bytes)> {
720 debug_assert_eq!(self.region_id().table_id(), mapper.region_id().table_id());
721 if let Some(range) = self
722 .inner
723 .primary_key_range
724 .read()
725 .unwrap()
726 .aligned(mapper.schema_version())
727 {
728 return range.clone();
729 }
730 let aligned = self.inner.primary_key_range.write().unwrap().align(mapper);
732 match aligned {
733 Ok(range) => range,
734 Err(err) => {
735 warn!(err; "Invalid SST primary key range; using unknown bounds, region: {}, file: {}, schema version: {}",
736 self.region_id(), self.file_id(), mapper.schema_version());
737 None
738 }
739 }
740 }
741
742 pub fn raw_primary_key_range(&self) -> Option<(Bytes, Bytes)> {
744 self.inner.primary_key_range.read().unwrap().raw().cloned()
745 }
746
747 pub(crate) fn set_primary_key_range(&self, primary_key_range: (Bytes, Bytes)) {
748 let mut range = self.inner.primary_key_range.write().unwrap();
751 if matches!(*range, PrimaryKeyRange::Missing) {
752 *range = PrimaryKeyRange::Raw(primary_key_range);
753 }
754 }
755}
756
757type PrimaryKeyBounds = (Bytes, Bytes);
758
759enum PrimaryKeyRange {
762 Missing,
763 Raw(PrimaryKeyBounds),
764 Aligned {
765 raw: PrimaryKeyBounds,
766 schema_version: u64,
767 bounds: Option<PrimaryKeyBounds>,
768 },
769}
770
771impl PrimaryKeyRange {
772 fn raw(&self) -> Option<&PrimaryKeyBounds> {
773 match self {
774 Self::Missing => None,
775 Self::Raw(raw) | Self::Aligned { raw, .. } => Some(raw),
776 }
777 }
778
779 fn aligned(&self, target_version: u64) -> Option<&Option<PrimaryKeyBounds>> {
780 match self {
781 Self::Aligned {
782 schema_version,
783 bounds,
784 ..
785 } if *schema_version == target_version => Some(bounds),
786 _ => None,
787 }
788 }
789
790 fn align(
791 &mut self,
792 mapper: &PrimaryKeyRangeMapper,
793 ) -> crate::error::Result<Option<PrimaryKeyBounds>> {
794 if let Some(bounds) = self.aligned(mapper.schema_version()) {
795 return Ok(bounds.clone());
796 }
797 let Some(raw) = self.raw().cloned() else {
798 return Ok(None);
799 };
800 let aligned = mapper.map(raw.clone());
801 *self = Self::Aligned {
803 raw,
804 schema_version: mapper.schema_version(),
805 bounds: aligned.as_ref().ok().cloned().flatten(),
806 };
807 aligned
808 }
809}
810
811struct FileHandleInner {
815 meta: FileMeta,
816 compacting: AtomicBool,
817 primary_key_range: RwLock<PrimaryKeyRange>,
818 lifetime: Arc<SstLifetime>,
819}
820
821impl Drop for FileHandleInner {
822 fn drop(&mut self) {
823 if let Some(manager) = &self.lifetime.file_ref_manager {
824 manager.remove_file(&self.meta);
825 }
826 }
827}
828
829impl FileHandleInner {
830 fn new(
831 meta: FileMeta,
832 file_purger: FilePurgerRef,
833 primary_key_range: Option<(Bytes, Bytes)>,
834 ) -> Self {
835 let file_ref_manager = file_purger.file_ref_manager();
836 let lifetime = Arc::new(SstLifetime {
837 meta: Mutex::new(meta.clone()),
838 deleted: AtomicBool::new(false),
839 file_purger,
840 file_ref_manager,
841 });
842 Self::with_lifetime(meta, lifetime, primary_key_range)
843 }
844
845 fn with_lifetime(
846 meta: FileMeta,
847 lifetime: Arc<SstLifetime>,
848 primary_key_range: Option<(Bytes, Bytes)>,
849 ) -> Self {
850 if let Some(manager) = &lifetime.file_ref_manager {
851 manager.register_file(&meta);
852 }
853 Self {
854 meta,
855 compacting: AtomicBool::new(false),
856 primary_key_range: RwLock::new(
857 primary_key_range.map_or(PrimaryKeyRange::Missing, PrimaryKeyRange::Raw),
858 ),
859 lifetime,
860 }
861 }
862}
863
864struct SstLifetime {
867 meta: Mutex<FileMeta>,
869 deleted: AtomicBool,
870 file_purger: FilePurgerRef,
871 file_ref_manager: Option<FileReferenceManagerRef>,
873}
874
875impl Drop for SstLifetime {
876 fn drop(&mut self) {
877 if self.deleted.load(Ordering::Acquire) {
878 self.file_purger
879 .purge_file(self.meta.get_mut().unwrap().clone());
880 }
881 }
882}
883
884pub async fn delete_files(
891 region_id: RegionId,
892 file_ids: &[(FileId, u64)],
893 delete_index: bool,
894 access_layer: &AccessLayerRef,
895 cache_manager: &Option<CacheManagerRef>,
896) -> crate::error::Result<()> {
897 if let Some(cache) = &cache_manager {
899 for (file_id, _) in file_ids {
900 cache.invalidate_range_results(*file_id);
901 cache
902 .remove_parquet_meta_data(RegionFileId::new(region_id, *file_id))
903 .await;
904 }
905 }
906 let mut attempted_files = Vec::with_capacity(file_ids.len());
907 let mut index_ids = Vec::new();
908
909 for (file_id, index_version) in file_ids {
910 let region_file_id = RegionFileId::new(region_id, *file_id);
911 attempted_files.push(*file_id);
912 index_ids.extend(
913 (0..=*index_version).map(|version| RegionIndexId::new(region_file_id, version)),
914 );
915 }
916
917 access_layer
918 .delete_ssts(region_id, &attempted_files)
919 .await?;
920 access_layer.delete_indexes(&index_ids).await?;
921
922 debug!(
923 "Attempted to delete {} files for region {}: {:?}",
924 attempted_files.len(),
925 region_id,
926 attempted_files
927 );
928
929 for (file_id, index_version) in file_ids {
930 purge_index_cache_stager(
931 region_id,
932 delete_index,
933 access_layer,
934 cache_manager,
935 *file_id,
936 *index_version,
937 )
938 .await;
939 }
940 Ok(())
941}
942
943#[derive(Clone)]
948pub(crate) struct UncommittedSsts {
949 region_id: RegionId,
950 files: Arc<Mutex<HashMap<FileId, (u64, bool)>>>,
951 access_layer: AccessLayerRef,
952 cache_manager: Option<CacheManagerRef>,
953}
954
955impl UncommittedSsts {
956 pub(crate) fn new(
957 region_id: RegionId,
958 access_layer: AccessLayerRef,
959 cache_manager: Option<CacheManagerRef>,
960 ) -> Self {
961 Self {
962 region_id,
963 files: Arc::new(Mutex::new(HashMap::new())),
964 access_layer,
965 cache_manager,
966 }
967 }
968
969 pub(crate) fn track(&self, ssts: &[SstInfo]) {
971 let mut files = self.files.lock().unwrap();
972 for sst in ssts {
973 files.insert(
974 sst.file_id,
975 (sst.index_metadata.version, sst.index_metadata.file_size > 0),
976 );
977 }
978 }
979
980 pub(crate) fn track_before_upload(&self, file_id: FileId, with_index: bool) {
982 self.files.lock().unwrap().insert(file_id, (0, with_index));
983 }
984
985 pub(crate) fn disarm_cleanup(&self) {
990 self.files.lock().unwrap().clear();
991 }
992
993 #[cfg(test)]
994 pub(crate) fn num_tracked_files(&self) -> usize {
995 self.files.lock().unwrap().len()
996 }
997
998 pub(crate) async fn cleanup(&self) {
1000 if let Err(err) = self.try_cleanup().await {
1001 error!(err; "Failed to clean uncommitted SSTs for region {}", self.region_id);
1002 }
1003 }
1004
1005 async fn try_cleanup(&self) -> crate::error::Result<()> {
1006 let files = std::mem::take(&mut *self.files.lock().unwrap());
1007 if files.is_empty() {
1008 return Ok(());
1009 }
1010
1011 let delete_index = files.values().any(|(_, exists_index)| *exists_index);
1012 let file_ids = files
1013 .into_iter()
1014 .map(|(file_id, (index_version, _))| (file_id, index_version))
1015 .collect::<Vec<_>>();
1016 delete_files(
1017 self.region_id,
1018 &file_ids,
1019 delete_index,
1020 &self.access_layer,
1021 &self.cache_manager,
1022 )
1023 .await
1024 }
1025
1026 #[cfg(test)]
1027 pub(crate) async fn cleanup_for_test(&self) -> crate::error::Result<()> {
1028 self.try_cleanup().await
1029 }
1030}
1031
1032pub async fn delete_index(
1033 region_index_id: RegionIndexId,
1034 access_layer: &AccessLayerRef,
1035 cache_manager: &Option<CacheManagerRef>,
1036) -> crate::error::Result<()> {
1037 delete_index_and_purge(region_index_id, access_layer, cache_manager).await?;
1038
1039 Ok(())
1040}
1041
1042pub async fn delete_indexes(
1043 index_ids: &[RegionIndexId],
1044 access_layer: &AccessLayerRef,
1045 cache_manager: &Option<CacheManagerRef>,
1046) -> crate::error::Result<()> {
1047 if index_ids.is_empty() {
1048 return Ok(());
1049 }
1050
1051 if let Err(e) = access_layer.delete_indexes(index_ids).await {
1052 error!(e; "Failed to batch delete index files");
1053
1054 for index_id in index_ids {
1055 delete_index_and_purge(*index_id, access_layer, cache_manager).await?;
1056 }
1057
1058 return Ok(());
1059 }
1060
1061 purge_indexes(index_ids, access_layer, cache_manager).await;
1062
1063 Ok(())
1064}
1065
1066async fn delete_index_and_purge(
1067 index_id: RegionIndexId,
1068 access_layer: &AccessLayerRef,
1069 cache_manager: &Option<CacheManagerRef>,
1070) -> crate::error::Result<()> {
1071 access_layer.delete_index(index_id).await?;
1072 purge_index_cache_stager(
1073 index_id.region_id(),
1074 true,
1075 access_layer,
1076 cache_manager,
1077 index_id.file_id(),
1078 index_id.version,
1079 )
1080 .await;
1081 Ok(())
1082}
1083
1084async fn purge_indexes(
1085 index_ids: &[RegionIndexId],
1086 access_layer: &AccessLayerRef,
1087 cache_manager: &Option<CacheManagerRef>,
1088) {
1089 for index_id in index_ids {
1090 purge_index_cache_stager(
1091 index_id.region_id(),
1092 true,
1093 access_layer,
1094 cache_manager,
1095 index_id.file_id(),
1096 index_id.version,
1097 )
1098 .await;
1099 }
1100}
1101
1102async fn purge_index_cache_stager(
1103 region_id: RegionId,
1104 delete_index: bool,
1105 access_layer: &AccessLayerRef,
1106 cache_manager: &Option<CacheManagerRef>,
1107 file_id: FileId,
1108 index_version: u64,
1109) {
1110 if let Some(write_cache) = cache_manager.as_ref().and_then(|cache| cache.write_cache()) {
1111 if delete_index {
1113 write_cache
1114 .remove(IndexKey::new(
1115 region_id,
1116 file_id,
1117 FileType::Puffin(index_version),
1118 ))
1119 .await;
1120 }
1121
1122 write_cache
1124 .remove(IndexKey::new(region_id, file_id, FileType::Parquet))
1125 .await;
1126 }
1127
1128 if let Err(e) = access_layer
1130 .puffin_manager_factory()
1131 .purge_stager(RegionIndexId::new(
1132 RegionFileId::new(region_id, file_id),
1133 index_version,
1134 ))
1135 .await
1136 {
1137 error!(e; "Failed to purge stager with index file, file_id: {}, index_version: {}, region: {}",
1138 file_id, index_version, region_id);
1139 }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144 use std::str::FromStr;
1145
1146 use datatypes::prelude::ConcreteDataType;
1147 use datatypes::schema::{
1148 ColumnSchema, FulltextAnalyzer, FulltextBackend, FulltextOptions, SkippingIndexOptions,
1149 };
1150 use datatypes::value::Value;
1151 use partition::expr::{PartitionExpr, col};
1152
1153 use super::*;
1154
1155 fn create_file_meta(file_id: FileId, level: Level) -> FileMeta {
1156 FileMeta {
1157 region_id: 0.into(),
1158 file_id,
1159 time_range: FileTimeRange::default(),
1160 level,
1161 file_size: 0,
1162 max_row_group_uncompressed_size: 0,
1163 available_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1164 indexes: vec![ColumnIndexMetadata {
1165 column_id: 0,
1166 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1167 ..Default::default()
1168 }],
1169 index_file_size: 0,
1170 index_version: 0,
1171 num_rows: 0,
1172 num_row_groups: 0,
1173 sequence: None,
1174 partition_expr: None,
1175 num_series: 0,
1176 ..Default::default()
1177 }
1178 }
1179
1180 #[test]
1181 fn test_deserialize_file_meta() {
1182 let file_meta = create_file_meta(FileId::random(), 0);
1183 let serialized_file_meta = serde_json::to_string(&file_meta).unwrap();
1184 let deserialized_file_meta = serde_json::from_str(&serialized_file_meta);
1185 assert_eq!(file_meta, deserialized_file_meta.unwrap());
1186 }
1187
1188 #[test]
1189 fn test_deserialize_from_string() {
1190 let json_file_meta = "{\"region_id\":0,\"file_id\":\"bc5896ec-e4d8-4017-a80d-f2de73188d55\",\
1191 \"time_range\":[{\"value\":0,\"unit\":\"Millisecond\"},{\"value\":0,\"unit\":\"Millisecond\"}],\
1192 \"available_indexes\":[\"InvertedIndex\"],\"indexes\":[{\"column_id\": 0, \"created_indexes\": [\"InvertedIndex\"]}],\"level\":0}";
1193 let file_meta = create_file_meta(
1194 FileId::from_str("bc5896ec-e4d8-4017-a80d-f2de73188d55").unwrap(),
1195 0,
1196 );
1197 let deserialized_file_meta: FileMeta = serde_json::from_str(json_file_meta).unwrap();
1198 assert_eq!(file_meta, deserialized_file_meta);
1199 }
1200
1201 #[test]
1202 fn test_deserialize_legacy_vector_index() {
1203 let json = r#"{
1204 "region_id": 0,
1205 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1206 "time_range": [{"value":0,"unit":"Millisecond"},{"value":0,"unit":"Millisecond"}],
1207 "available_indexes": ["VectorIndex", "InvertedIndex"],
1208 "indexes": [{"column_id": 1, "created_indexes": ["VectorIndex"]}],
1209 "level": 0
1210 }"#;
1211 let meta: FileMeta = serde_json::from_str(json).unwrap();
1212 assert!(meta.inverted_index_available());
1213 assert!(!meta.fulltext_index_available());
1214 assert!(!meta.bloom_filter_index_available());
1215 let encoded = serde_json::to_value(&meta).unwrap();
1216 assert_eq!(
1217 encoded["available_indexes"],
1218 serde_json::json!(["VectorIndex", "InvertedIndex"])
1219 );
1220 assert_eq!(
1221 encoded["indexes"][0]["created_indexes"],
1222 serde_json::json!(["VectorIndex"])
1223 );
1224 }
1225
1226 #[test]
1227 fn test_file_meta_with_partition_expr() {
1228 let file_id = FileId::random();
1229 let partition_expr = PartitionExpr::new(
1230 col("a"),
1231 partition::expr::RestrictedOp::GtEq,
1232 Value::UInt32(10).into(),
1233 );
1234
1235 let file_meta_with_partition = FileMeta {
1236 region_id: 0.into(),
1237 file_id,
1238 time_range: FileTimeRange::default(),
1239 level: 0,
1240 file_size: 0,
1241 max_row_group_uncompressed_size: 0,
1242 available_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1243 indexes: vec![ColumnIndexMetadata {
1244 column_id: 0,
1245 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1246 ..Default::default()
1247 }],
1248 index_file_size: 0,
1249 index_version: 0,
1250 num_rows: 0,
1251 num_row_groups: 0,
1252 sequence: None,
1253 partition_expr: Some(partition_expr.clone()),
1254 num_series: 0,
1255 ..Default::default()
1256 };
1257
1258 let serialized = serde_json::to_string(&file_meta_with_partition).unwrap();
1260 let deserialized: FileMeta = serde_json::from_str(&serialized).unwrap();
1261 assert_eq!(file_meta_with_partition, deserialized);
1262
1263 let serialized_value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
1265 assert!(serialized_value["partition_expr"].as_str().is_some());
1266 let partition_expr_json = serialized_value["partition_expr"].as_str().unwrap();
1267 assert!(partition_expr_json.contains("\"Column\":\"a\""));
1268 assert!(partition_expr_json.contains("\"op\":\"GtEq\""));
1269
1270 let file_meta_none = FileMeta {
1272 partition_expr: None,
1273 ..file_meta_with_partition.clone()
1274 };
1275 let serialized_none = serde_json::to_string(&file_meta_none).unwrap();
1276 let deserialized_none: FileMeta = serde_json::from_str(&serialized_none).unwrap();
1277 assert_eq!(file_meta_none, deserialized_none);
1278 }
1279
1280 #[test]
1281 fn test_file_meta_partition_expr_backward_compatibility() {
1282 let json_with_partition_expr = r#"{
1284 "region_id": 0,
1285 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1286 "time_range": [
1287 {"value": 0, "unit": "Millisecond"},
1288 {"value": 0, "unit": "Millisecond"}
1289 ],
1290 "level": 0,
1291 "file_size": 0,
1292 "available_indexes": ["InvertedIndex"],
1293 "index_file_size": 0,
1294 "num_rows": 0,
1295 "num_row_groups": 0,
1296 "sequence": null,
1297 "partition_expr": "{\"Expr\":{\"lhs\":{\"Column\":\"a\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"UInt32\":10}}}}"
1298 }"#;
1299
1300 let file_meta: FileMeta = serde_json::from_str(json_with_partition_expr).unwrap();
1301 assert!(file_meta.partition_expr.is_some());
1302 let expr = file_meta.partition_expr.unwrap();
1303 assert_eq!(format!("{}", expr), "a >= 10");
1304
1305 let json_with_empty_expr = r#"{
1307 "region_id": 0,
1308 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1309 "time_range": [
1310 {"value": 0, "unit": "Millisecond"},
1311 {"value": 0, "unit": "Millisecond"}
1312 ],
1313 "level": 0,
1314 "file_size": 0,
1315 "available_indexes": [],
1316 "index_file_size": 0,
1317 "num_rows": 0,
1318 "num_row_groups": 0,
1319 "sequence": null,
1320 "partition_expr": ""
1321 }"#;
1322
1323 let file_meta_empty: FileMeta = serde_json::from_str(json_with_empty_expr).unwrap();
1324 assert!(file_meta_empty.partition_expr.is_none());
1325
1326 let json_with_null_expr = r#"{
1328 "region_id": 0,
1329 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1330 "time_range": [
1331 {"value": 0, "unit": "Millisecond"},
1332 {"value": 0, "unit": "Millisecond"}
1333 ],
1334 "level": 0,
1335 "file_size": 0,
1336 "available_indexes": [],
1337 "index_file_size": 0,
1338 "num_rows": 0,
1339 "num_row_groups": 0,
1340 "sequence": null,
1341 "partition_expr": null
1342 }"#;
1343
1344 let file_meta_null: FileMeta = serde_json::from_str(json_with_null_expr).unwrap();
1345 assert!(file_meta_null.partition_expr.is_none());
1346
1347 let json_with_empty_expr = r#"{
1349 "region_id": 0,
1350 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1351 "time_range": [
1352 {"value": 0, "unit": "Millisecond"},
1353 {"value": 0, "unit": "Millisecond"}
1354 ],
1355 "level": 0,
1356 "file_size": 0,
1357 "available_indexes": [],
1358 "index_file_size": 0,
1359 "num_rows": 0,
1360 "num_row_groups": 0,
1361 "sequence": null
1362 }"#;
1363
1364 let file_meta_empty: FileMeta = serde_json::from_str(json_with_empty_expr).unwrap();
1365 assert!(file_meta_empty.partition_expr.is_none());
1366 }
1367
1368 #[test]
1369 fn test_file_meta_indexes_backward_compatibility() {
1370 let json_old_file_meta = r#"{
1372 "region_id": 0,
1373 "file_id": "bc5896ec-e4d8-4017-a80d-f2de73188d55",
1374 "time_range": [
1375 {"value": 0, "unit": "Millisecond"},
1376 {"value": 0, "unit": "Millisecond"}
1377 ],
1378 "available_indexes": ["InvertedIndex"],
1379 "level": 0,
1380 "file_size": 0,
1381 "index_file_size": 0,
1382 "num_rows": 0,
1383 "num_row_groups": 0
1384 }"#;
1385
1386 let deserialized_file_meta: FileMeta = serde_json::from_str(json_old_file_meta).unwrap();
1387
1388 assert_eq!(deserialized_file_meta.indexes, vec![]);
1390
1391 let expected_indexes: IndexTypes = SmallVec::from_iter([IndexType::InvertedIndex]);
1392 assert_eq!(deserialized_file_meta.available_indexes, expected_indexes);
1393
1394 assert_eq!(
1395 deserialized_file_meta.file_id,
1396 FileId::from_str("bc5896ec-e4d8-4017-a80d-f2de73188d55").unwrap()
1397 );
1398 assert!(!deserialized_file_meta.preserve_row_sequence);
1399 }
1400
1401 #[test]
1402 fn test_file_meta_preserve_row_sequence_serde() {
1403 let file_meta = FileMeta {
1404 preserve_row_sequence: true,
1405 ..Default::default()
1406 };
1407
1408 let serialized = serde_json::to_string(&file_meta).unwrap();
1409 let value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
1410 assert_eq!(value["preserve_row_sequence"], true);
1411
1412 let deserialized: FileMeta = serde_json::from_str(&serialized).unwrap();
1413 assert_eq!(file_meta, deserialized);
1414
1415 let file_meta_false = FileMeta {
1416 preserve_row_sequence: false,
1417 ..file_meta.clone()
1418 };
1419 let serialized_false = serde_json::to_string(&file_meta_false).unwrap();
1420 let value_false: serde_json::Value = serde_json::from_str(&serialized_false).unwrap();
1421 assert!(value_false.get("preserve_row_sequence").is_none());
1422 }
1423 #[test]
1424 fn test_is_index_consistent_with_region() {
1425 fn new_column_meta(
1426 id: ColumnId,
1427 name: &str,
1428 inverted: bool,
1429 fulltext: bool,
1430 skipping: bool,
1431 ) -> ColumnMetadata {
1432 let mut column_schema =
1433 ColumnSchema::new(name, ConcreteDataType::string_datatype(), true);
1434 if inverted {
1435 column_schema = column_schema.with_inverted_index(true);
1436 }
1437 if fulltext {
1438 column_schema = column_schema
1439 .with_fulltext_options(FulltextOptions::new_unchecked(
1440 true,
1441 FulltextAnalyzer::English,
1442 false,
1443 FulltextBackend::Bloom,
1444 1000,
1445 0.01,
1446 ))
1447 .unwrap();
1448 }
1449 if skipping {
1450 column_schema = column_schema
1451 .with_skipping_options(SkippingIndexOptions::new_unchecked(
1452 1024,
1453 0.01,
1454 datatypes::schema::SkippingIndexType::BloomFilter,
1455 ))
1456 .unwrap();
1457 }
1458
1459 ColumnMetadata {
1460 column_schema,
1461 semantic_type: api::v1::SemanticType::Tag,
1462 column_id: id,
1463 }
1464 }
1465
1466 let mut file_meta = FileMeta {
1468 indexes: vec![ColumnIndexMetadata {
1469 column_id: 1,
1470 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1471 ..Default::default()
1472 }],
1473 ..Default::default()
1474 };
1475 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1476 assert!(file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1477
1478 file_meta.indexes = vec![ColumnIndexMetadata {
1480 column_id: 1,
1481 created_indexes: SmallVec::from_iter([
1482 IndexType::InvertedIndex,
1483 IndexType::BloomFilterIndex,
1484 ]),
1485 ..Default::default()
1486 }];
1487 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1488 assert!(file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1489
1490 file_meta.indexes = vec![ColumnIndexMetadata {
1492 column_id: 1,
1493 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1494 ..Default::default()
1495 }];
1496 let region_meta = vec![new_column_meta(1, "tag1", true, true, false)]; assert!(!file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1498
1499 file_meta.indexes = vec![ColumnIndexMetadata {
1501 column_id: 2, created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1503 ..Default::default()
1504 }];
1505 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)]; assert!(!file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1507
1508 file_meta.indexes = vec![ColumnIndexMetadata {
1510 column_id: 1,
1511 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1512 ..Default::default()
1513 }];
1514 let region_meta = vec![new_column_meta(1, "tag1", false, false, false)]; assert!(file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1516
1517 file_meta.indexes = vec![];
1519 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1520 assert!(!file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1521
1522 file_meta.indexes = vec![
1524 ColumnIndexMetadata {
1525 column_id: 1,
1526 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
1527 ..Default::default()
1528 },
1529 ColumnIndexMetadata {
1530 column_id: 2, created_indexes: SmallVec::from_iter([IndexType::FulltextIndex]),
1532 ..Default::default()
1533 },
1534 ];
1535 let region_meta = vec![
1536 new_column_meta(1, "tag1", true, false, false),
1537 new_column_meta(2, "tag2", false, true, true), ];
1539 assert!(!file_meta.is_index_consistent_with_region(®ion_meta, &IndexOptions::default()));
1540
1541 file_meta.indexes = vec![];
1543 let region_meta = vec![new_column_meta(1, "tag1", true, false, false)];
1544 let mut index_options = IndexOptions::default();
1545 index_options.inverted_index.ignore_column_ids = vec![1];
1546 assert!(file_meta.is_index_consistent_with_region(®ion_meta, &index_options));
1547 let mut index_options = IndexOptions::default();
1548 index_options.inverted_index.segment_row_count = 0;
1549 assert!(file_meta.is_index_consistent_with_region(®ion_meta, &index_options));
1550 }
1551
1552 #[test]
1553 fn test_index_consistency_requires_current_fulltext_bloom_version() {
1554 let fulltext_column = |backend| {
1555 let column_schema = ColumnSchema::new("msg", ConcreteDataType::string_datatype(), true)
1556 .with_fulltext_options(FulltextOptions::new_unchecked(
1557 true,
1558 FulltextAnalyzer::English,
1559 false,
1560 backend,
1561 1000,
1562 0.01,
1563 ))
1564 .unwrap();
1565 vec![ColumnMetadata {
1566 column_schema,
1567 semantic_type: api::v1::SemanticType::Field,
1568 column_id: 1,
1569 }]
1570 };
1571 let file_meta = |fulltext_bloom_version| FileMeta {
1572 indexes: vec![ColumnIndexMetadata {
1573 column_id: 1,
1574 created_indexes: SmallVec::from_iter([IndexType::FulltextIndex]),
1575 fulltext_bloom_version,
1576 }],
1577 ..Default::default()
1578 };
1579
1580 let bloom = fulltext_column(FulltextBackend::Bloom);
1581 assert!(!file_meta(0).is_index_consistent_with_region(&bloom, &IndexOptions::default()));
1583 assert!(
1584 file_meta(FULLTEXT_BLOOM_VERSION)
1585 .is_index_consistent_with_region(&bloom, &IndexOptions::default())
1586 );
1587 let tantivy = fulltext_column(FulltextBackend::Tantivy);
1589 assert!(file_meta(0).is_index_consistent_with_region(&tantivy, &IndexOptions::default()));
1590 }
1591}