1pub mod builder;
16
17use std::collections::BTreeMap;
18use std::sync::Arc;
19use std::time::Instant;
20
21use common_base::range_read::RangeReader;
22use common_telemetry::warn;
23use datatypes::data_type::ConcreteDataType;
24use index::inverted_index::format::reader::{InvertedIndexBlobReader, InvertedIndexReadMetrics};
25use index::inverted_index::search::index_apply::{
26 ApplyOutput, IndexApplier, IndexNotFoundStrategy, PredicatesIndexApplier, SearchContext,
27};
28use index::inverted_index::search::predicate::Predicate;
29use index::target::IndexTarget;
30use object_store::ObjectStore;
31use puffin::puffin_manager::cache::PuffinMetadataCacheRef;
32use puffin::puffin_manager::{PuffinManager, PuffinReader};
33use snafu::ResultExt;
34use store_api::metadata::RegionMetadataRef;
35use store_api::region_request::PathType;
36use store_api::storage::ColumnId;
37
38use crate::access_layer::{RegionFilePathFactory, WriteCachePathProvider};
39use crate::cache::file_cache::{FileCacheRef, FileType, IndexKey};
40use crate::cache::index::inverted_index::{CachedInvertedIndexBlobReader, InvertedIndexCacheRef};
41use crate::cache::index::result_cache::PredicateKey;
42use crate::error::{
43 ApplyInvertedIndexSnafu, BuildIndexApplierSnafu, MetadataSnafu, PuffinBuildReaderSnafu,
44 PuffinReadBlobSnafu, Result,
45};
46use crate::metrics::{INDEX_APPLY_ELAPSED, INDEX_APPLY_MEMORY_USAGE};
47use crate::sst::file::RegionIndexId;
48use crate::sst::index::inverted_index::INDEX_BLOB_TYPE;
49use crate::sst::index::puffin_manager::{BlobReader, PuffinManagerFactory};
50use crate::sst::index::{TYPE_INVERTED_INDEX, trigger_index_background_download};
51
52#[derive(Default, Clone)]
54pub struct InvertedIndexApplyMetrics {
55 pub apply_elapsed: std::time::Duration,
57 pub blob_cache_miss: usize,
59 pub blob_read_bytes: u64,
61 pub inverted_index_read_metrics: InvertedIndexReadMetrics,
63}
64
65impl std::fmt::Debug for InvertedIndexApplyMetrics {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 let Self {
68 apply_elapsed,
69 blob_cache_miss,
70 blob_read_bytes,
71 inverted_index_read_metrics,
72 } = self;
73
74 if self.is_empty() {
75 return write!(f, "{{}}");
76 }
77 write!(f, "{{")?;
78
79 write!(f, "\"apply_elapsed\":\"{:?}\"", apply_elapsed)?;
80
81 if *blob_cache_miss > 0 {
82 write!(f, ", \"blob_cache_miss\":{}", blob_cache_miss)?;
83 }
84 if *blob_read_bytes > 0 {
85 write!(f, ", \"blob_read_bytes\":{}", blob_read_bytes)?;
86 }
87 write!(
88 f,
89 ", \"inverted_index_read_metrics\":{:?}",
90 inverted_index_read_metrics
91 )?;
92
93 write!(f, "}}")
94 }
95}
96
97impl InvertedIndexApplyMetrics {
98 pub fn is_empty(&self) -> bool {
100 self.apply_elapsed.is_zero()
101 }
102
103 pub fn merge_from(&mut self, other: &Self) {
105 self.apply_elapsed += other.apply_elapsed;
106 self.blob_cache_miss += other.blob_cache_miss;
107 self.blob_read_bytes += other.blob_read_bytes;
108 self.inverted_index_read_metrics
109 .merge_from(&other.inverted_index_read_metrics);
110 }
111}
112
113pub(crate) struct InvertedIndexApplier {
116 table_dir: String,
118
119 path_type: PathType,
121
122 store: ObjectStore,
124
125 file_cache: Option<FileCacheRef>,
127
128 puffin_manager_factory: PuffinManagerFactory,
130
131 inverted_index_cache: Option<InvertedIndexCacheRef>,
133
134 puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
136
137 predicates: BTreeMap<ColumnId, Vec<Predicate>>,
139
140 default_plan: SstApplyPlan,
142
143 expected_predicate_col_types: BTreeMap<ColumnId, ConcreteDataType>,
145}
146
147pub(crate) type InvertedIndexApplierRef = Arc<InvertedIndexApplier>;
148
149#[derive(Clone)]
150pub(crate) struct SstApplyPlan {
151 pub predicate_key: PredicateKey,
152 pub index_applier: Arc<PredicatesIndexApplier>,
153}
154
155impl InvertedIndexApplier {
156 pub fn new(
158 table_dir: String,
159 path_type: PathType,
160 store: ObjectStore,
161 puffin_manager_factory: PuffinManagerFactory,
162 predicates: BTreeMap<ColumnId, Vec<Predicate>>,
163 expected_predicate_col_types: BTreeMap<ColumnId, ConcreteDataType>,
164 ) -> Result<Self> {
165 let default_plan = Self::build_apply_plan(&predicates)?;
166 INDEX_APPLY_MEMORY_USAGE.add(default_plan.index_applier.memory_usage() as i64);
167
168 Ok(Self {
169 table_dir,
170 path_type,
171 store,
172 file_cache: None,
173 puffin_manager_factory,
174 inverted_index_cache: None,
175 puffin_metadata_cache: None,
176 predicates,
177 default_plan,
178 expected_predicate_col_types,
179 })
180 }
181
182 pub fn with_file_cache(mut self, file_cache: Option<FileCacheRef>) -> Self {
184 self.file_cache = file_cache;
185 self
186 }
187
188 pub fn with_index_cache(mut self, index_cache: Option<InvertedIndexCacheRef>) -> Self {
190 self.inverted_index_cache = index_cache;
191 self
192 }
193
194 pub fn with_puffin_metadata_cache(
196 mut self,
197 puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
198 ) -> Self {
199 self.puffin_metadata_cache = puffin_metadata_cache;
200 self
201 }
202
203 #[tracing::instrument(
211 skip_all,
212 fields(file_id = %file_id)
213 )]
214 pub async fn apply(
215 &self,
216 file_id: RegionIndexId,
217 file_size_hint: Option<u64>,
218 index_applier: &PredicatesIndexApplier,
219 mut metrics: Option<&mut InvertedIndexApplyMetrics>,
220 ) -> Result<ApplyOutput> {
221 let start = Instant::now();
222
223 let context = SearchContext {
224 index_not_found_strategy: IndexNotFoundStrategy::Ignore,
227 };
228
229 let mut cache_miss = 0;
230 let blob = match self.cached_blob_reader(file_id, file_size_hint).await {
231 Ok(Some(puffin_reader)) => puffin_reader,
232 other => {
233 cache_miss += 1;
234 if let Err(err) = other {
235 warn!(err; "An unexpected error occurred while reading the cached index file. Fallback to remote index file.")
236 }
237 self.remote_blob_reader(file_id, file_size_hint).await?
238 }
239 };
240
241 let blob_size = blob.metadata().await.context(MetadataSnafu)?.content_length;
242
243 let result = if let Some(index_cache) = &self.inverted_index_cache {
244 let mut index_reader = CachedInvertedIndexBlobReader::new(
245 file_id.file_id(),
246 file_id.version,
247 blob_size,
248 InvertedIndexBlobReader::new(blob),
249 index_cache.clone(),
250 );
251 index_applier
252 .apply(
253 context,
254 &mut index_reader,
255 metrics
256 .as_deref_mut()
257 .map(|m| &mut m.inverted_index_read_metrics),
258 )
259 .await
260 .context(ApplyInvertedIndexSnafu)
261 } else {
262 let mut index_reader = InvertedIndexBlobReader::new(blob);
263 index_applier
264 .apply(
265 context,
266 &mut index_reader,
267 metrics
268 .as_deref_mut()
269 .map(|m| &mut m.inverted_index_read_metrics),
270 )
271 .await
272 .context(ApplyInvertedIndexSnafu)
273 };
274
275 let elapsed = start.elapsed();
277 INDEX_APPLY_ELAPSED
278 .with_label_values(&[TYPE_INVERTED_INDEX])
279 .observe(elapsed.as_secs_f64());
280
281 if let Some(metrics) = metrics {
282 metrics.apply_elapsed = elapsed;
283 metrics.blob_cache_miss = cache_miss;
284 metrics.blob_read_bytes = blob_size;
285 }
286
287 result
288 }
289
290 async fn cached_blob_reader(
292 &self,
293 file_id: RegionIndexId,
294 file_size_hint: Option<u64>,
295 ) -> Result<Option<BlobReader>> {
296 let Some(file_cache) = &self.file_cache else {
297 return Ok(None);
298 };
299
300 let index_key = IndexKey::new(
301 file_id.region_id(),
302 file_id.file_id(),
303 FileType::Puffin(file_id.version),
304 );
305 if file_cache.get(index_key).await.is_none() {
306 return Ok(None);
307 };
308
309 let puffin_manager = self.puffin_manager_factory.build(
310 file_cache.local_store(),
311 WriteCachePathProvider::new(file_cache.clone()),
312 );
313
314 let reader = puffin_manager
316 .reader(&file_id)
317 .await
318 .context(PuffinBuildReaderSnafu)?
319 .with_file_size_hint(file_size_hint)
320 .blob(INDEX_BLOB_TYPE)
321 .await
322 .context(PuffinReadBlobSnafu)?
323 .reader()
324 .await
325 .context(PuffinBuildReaderSnafu)?;
326 Ok(Some(reader))
327 }
328
329 async fn remote_blob_reader(
331 &self,
332 file_id: RegionIndexId,
333 file_size_hint: Option<u64>,
334 ) -> Result<BlobReader> {
335 let path_factory = RegionFilePathFactory::new(self.table_dir.clone(), self.path_type);
336
337 trigger_index_background_download(
339 self.file_cache.as_ref(),
340 &file_id,
341 file_size_hint,
342 &path_factory,
343 &self.store,
344 );
345
346 let puffin_manager = self
347 .puffin_manager_factory
348 .build(self.store.clone(), path_factory)
349 .with_puffin_metadata_cache(self.puffin_metadata_cache.clone());
350
351 puffin_manager
352 .reader(&file_id)
353 .await
354 .context(PuffinBuildReaderSnafu)?
355 .with_file_size_hint(file_size_hint)
356 .blob(INDEX_BLOB_TYPE)
357 .await
358 .context(PuffinReadBlobSnafu)?
359 .reader()
360 .await
361 .context(PuffinBuildReaderSnafu)
362 }
363
364 pub fn plan_for_sst(&self, sst_metadata: &RegionMetadataRef) -> Result<Option<SstApplyPlan>> {
368 let mut compatible_predicates = BTreeMap::new();
369 let mut has_type_mismatch = false;
370
371 for (col_id, expected) in &self.expected_predicate_col_types {
372 if let Some(sst_col) = sst_metadata.column_by_id(*col_id)
373 && sst_col.column_schema.data_type != *expected
374 {
375 has_type_mismatch = true;
376 continue;
377 }
378
379 if let Some(predicates) = self.predicates.get(col_id) {
380 compatible_predicates.insert(*col_id, predicates.clone());
381 }
382 }
383
384 if compatible_predicates.is_empty() {
385 return Ok(None);
386 }
387
388 if !has_type_mismatch {
389 return Ok(Some(self.default_plan.clone()));
390 }
391
392 let plan = Self::build_apply_plan(&compatible_predicates)?;
393 Ok(Some(plan))
394 }
395
396 fn build_apply_plan(
397 predicates_by_col: &BTreeMap<ColumnId, Vec<Predicate>>,
398 ) -> Result<SstApplyPlan> {
399 let predicates = predicates_by_col
400 .iter()
401 .map(|(col_id, preds)| (format!("{}", IndexTarget::ColumnId(*col_id)), preds.clone()))
402 .collect();
403
404 let index_applier =
405 PredicatesIndexApplier::try_from(predicates).context(BuildIndexApplierSnafu)?;
406
407 let predicate_key = PredicateKey::new_inverted(Arc::new(predicates_by_col.clone()));
408 Ok(SstApplyPlan {
409 predicate_key,
410 index_applier: Arc::new(index_applier),
411 })
412 }
413}
414
415impl Drop for InvertedIndexApplier {
416 fn drop(&mut self) {
417 INDEX_APPLY_MEMORY_USAGE.sub(self.default_plan.index_applier.memory_usage() as i64);
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use api::v1::SemanticType;
424 use datatypes::data_type::ConcreteDataType;
425 use datatypes::schema::ColumnSchema;
426 use futures::io::Cursor;
427 use index::inverted_index::search::predicate::RegexMatchPredicate;
428 use object_store::services::Memory;
429 use puffin::puffin_manager::PuffinWriter;
430 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
431 use store_api::storage::{FileId, RegionId};
432
433 use super::*;
434 use crate::sst::index::RegionFileId;
435
436 #[tokio::test]
437 async fn test_plan_for_sst() {
438 let (_d, puffin_manager_factory) =
439 PuffinManagerFactory::new_for_test_async("test_plan_for_sst_basic_").await;
440 let object_store = ObjectStore::new(Memory::default()).unwrap();
441 let table_dir = "table_dir".to_string();
442
443 let mut predicates = BTreeMap::new();
444 predicates.insert(
445 1,
446 vec![Predicate::RegexMatch(RegexMatchPredicate {
447 pattern: "foo".to_string(),
448 })],
449 );
450 let expected_predicate_col_types =
451 BTreeMap::from_iter([(1, ConcreteDataType::string_datatype())]);
452
453 let sst_index_applier = InvertedIndexApplier::new(
454 table_dir,
455 PathType::Bare,
456 object_store,
457 puffin_manager_factory,
458 predicates,
459 expected_predicate_col_types,
460 )
461 .unwrap();
462 let plan = sst_index_applier
463 .plan_for_sst(&mock_region_metadata())
464 .unwrap();
465 assert!(plan.is_some());
466 }
467
468 #[tokio::test]
469 async fn test_plan_for_sst_type_mismatch() {
470 let (_d, puffin_manager_factory) =
471 PuffinManagerFactory::new_for_test_async("test_plan_for_sst_type_mismatch_").await;
472 let object_store = ObjectStore::new(Memory::default()).unwrap();
473 let table_dir = "table_dir".to_string();
474
475 let mut predicates = BTreeMap::new();
476 predicates.insert(
477 1,
478 vec![Predicate::RegexMatch(RegexMatchPredicate {
479 pattern: "foo".to_string(),
480 })],
481 );
482 let expected_predicate_col_types =
484 BTreeMap::from_iter([(1, ConcreteDataType::int64_datatype())]);
485
486 let sst_index_applier = InvertedIndexApplier::new(
487 table_dir,
488 PathType::Bare,
489 object_store,
490 puffin_manager_factory,
491 predicates,
492 expected_predicate_col_types,
493 )
494 .unwrap();
495 let plan = sst_index_applier
496 .plan_for_sst(&mock_region_metadata())
497 .unwrap();
498 assert!(plan.is_none());
499 }
500
501 #[tokio::test]
502 async fn test_index_applier_apply_invalid_blob_type() {
503 let (_d, puffin_manager_factory) =
504 PuffinManagerFactory::new_for_test_async("test_index_applier_apply_invalid_blob_type_")
505 .await;
506 let object_store = ObjectStore::new(Memory::default()).unwrap();
507 let file_id = RegionFileId::new(0.into(), FileId::random());
508 let index_id = RegionIndexId::new(file_id, 0);
509 let table_dir = "table_dir".to_string();
510
511 let puffin_manager = puffin_manager_factory.build(
512 object_store.clone(),
513 RegionFilePathFactory::new(table_dir.clone(), PathType::Bare),
514 );
515 let mut writer = puffin_manager.writer(&index_id).await.unwrap();
516 writer
517 .put_blob(
518 "invalid_blob_type",
519 Cursor::new(vec![]),
520 Default::default(),
521 Default::default(),
522 )
523 .await
524 .unwrap();
525 writer.finish().await.unwrap();
526
527 let mut predicates = BTreeMap::new();
528 predicates.insert(
529 1,
530 vec![Predicate::RegexMatch(RegexMatchPredicate {
531 pattern: "foo".to_string(),
532 })],
533 );
534 let expected_predicate_col_types =
535 BTreeMap::from_iter([(1, ConcreteDataType::string_datatype())]);
536 let sst_index_applier = InvertedIndexApplier::new(
537 table_dir.clone(),
538 PathType::Bare,
539 object_store,
540 puffin_manager_factory,
541 predicates,
542 expected_predicate_col_types,
543 )
544 .unwrap();
545 let plan = sst_index_applier
546 .plan_for_sst(&mock_region_metadata())
547 .unwrap()
548 .unwrap();
549 let res = sst_index_applier
550 .apply(index_id, None, &plan.index_applier, None)
551 .await;
552 assert!(format!("{:?}", res.unwrap_err()).contains("Blob not found"));
553 }
554
555 fn mock_region_metadata() -> RegionMetadataRef {
556 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 1));
557 builder
558 .push_column_metadata(ColumnMetadata {
559 column_schema: ColumnSchema::new("tag", ConcreteDataType::string_datatype(), false),
560 semantic_type: SemanticType::Tag,
561 column_id: 1,
562 })
563 .push_column_metadata(ColumnMetadata {
564 column_schema: ColumnSchema::new(
565 "ts",
566 ConcreteDataType::timestamp_millisecond_datatype(),
567 false,
568 ),
569 semantic_type: SemanticType::Timestamp,
570 column_id: 2,
571 })
572 .primary_key(vec![1]);
573 Arc::new(builder.build().unwrap())
574 }
575}