1use core::ops::Range;
16use std::sync::Arc;
17use std::time::Instant;
18
19use api::v1::index::InvertedIndexMetas;
20use async_trait::async_trait;
21use bytes::Bytes;
22use index::inverted_index::error::{CommonIoSnafu, Error, Result, SharedReadSnafu};
23use index::inverted_index::format::reader::{InvertedIndexReadMetrics, InvertedIndexReader};
24use prost::Message;
25use snafu::IntoError;
26use store_api::storage::{FileId, IndexVersion};
27
28use crate::cache::index::{INDEX_METADATA_TYPE, IndexCache};
29use crate::metrics::{CACHE_HIT, CACHE_MISS};
30
31const INDEX_TYPE_INVERTED_INDEX: &str = "inverted_index";
32
33pub type InvertedIndexCache = IndexCache<(FileId, IndexVersion), InvertedIndexMetas>;
35pub type InvertedIndexCacheRef = Arc<InvertedIndexCache>;
36
37impl InvertedIndexCache {
38 pub fn new(index_metadata_cap: u64, index_content_cap: u64, page_size: u64) -> Self {
40 Self::new_with_weighter(
41 index_metadata_cap,
42 index_content_cap,
43 page_size,
44 INDEX_TYPE_INVERTED_INDEX,
45 "inverted_index_metadata",
46 inverted_index_metadata_weight,
47 inverted_index_content_weight,
48 )
49 }
50
51 pub fn invalidate_file(&self, file_id: FileId) {
53 self.invalidate_if(move |key| key.0 == file_id);
54 }
55}
56
57fn inverted_index_metadata_weight(k: &(FileId, IndexVersion), v: &Arc<InvertedIndexMetas>) -> u32 {
59 (k.0.as_bytes().len() + size_of::<IndexVersion>() + v.encoded_len()) as u32
60}
61
62fn inverted_index_content_weight((k, _): &((FileId, IndexVersion), u64), v: &Bytes) -> u32 {
64 (k.0.as_bytes().len() + size_of::<IndexVersion>() + v.len()) as u32
65}
66
67pub struct CachedInvertedIndexBlobReader<R> {
69 file_id: FileId,
70 index_version: IndexVersion,
71 blob_size: u64,
72 inner: R,
73 cache: InvertedIndexCacheRef,
74}
75
76impl<R> CachedInvertedIndexBlobReader<R> {
77 pub fn new(
79 file_id: FileId,
80 index_version: IndexVersion,
81 blob_size: u64,
82 inner: R,
83 cache: InvertedIndexCacheRef,
84 ) -> Self {
85 Self {
86 file_id,
87 index_version,
88 blob_size,
89 inner,
90 cache,
91 }
92 }
93}
94
95#[async_trait]
96impl<R: InvertedIndexReader> InvertedIndexReader for CachedInvertedIndexBlobReader<R> {
97 async fn range_read<'a>(
98 &self,
99 offset: u64,
100 size: u32,
101 metrics: Option<&'a mut InvertedIndexReadMetrics>,
102 ) -> Result<Vec<u8>> {
103 let start = metrics.as_ref().map(|_| Instant::now());
104
105 let inner = &self.inner;
106 let (result, cache_metrics) = self
107 .cache
108 .get_or_load(
109 (self.file_id, self.index_version),
110 self.blob_size,
111 offset,
112 size,
113 move |ranges| async move { inner.read_vec(&ranges, None).await },
114 )
115 .await
116 .map_err(map_page_error)?;
117
118 if let Some(m) = metrics {
119 m.total_bytes += cache_metrics.page_bytes;
120 m.total_ranges += cache_metrics.num_pages;
121 m.cache_hit += cache_metrics.cache_hit;
122 m.cache_miss += cache_metrics.cache_miss;
123 m.fetch_elapsed += start.unwrap().elapsed();
124 }
125
126 Ok(result.to_vec())
127 }
128
129 async fn read_vec<'a>(
130 &self,
131 ranges: &[Range<u64>],
132 metrics: Option<&'a mut InvertedIndexReadMetrics>,
133 ) -> Result<Vec<Bytes>> {
134 let start = metrics.as_ref().map(|_| Instant::now());
135
136 let inner = &self.inner;
137 let (pages, total_cache_metrics) = self
138 .cache
139 .get_or_load_vec(
140 (self.file_id, self.index_version),
141 self.blob_size,
142 ranges,
143 move |ranges| async move { inner.read_vec(&ranges, None).await },
144 )
145 .await
146 .map_err(map_page_error)?;
147 if let Some(m) = metrics {
148 m.total_bytes += total_cache_metrics.page_bytes;
149 m.total_ranges += total_cache_metrics.num_pages;
150 m.cache_hit += total_cache_metrics.cache_hit;
151 m.cache_miss += total_cache_metrics.cache_miss;
152 m.fetch_elapsed += start.unwrap().elapsed();
153 }
154
155 Ok(pages)
156 }
157
158 async fn metadata<'a>(
159 &self,
160 metrics: Option<&'a mut InvertedIndexReadMetrics>,
161 ) -> Result<Arc<InvertedIndexMetas>> {
162 let key = (self.file_id, self.index_version);
163 if let Some(cached) = self.cache.get_metadata(key).await {
164 CACHE_HIT.with_label_values(&[INDEX_METADATA_TYPE]).inc();
165 if let Some(m) = metrics {
166 m.cache_hit += 1;
167 }
168 return Ok(cached);
169 }
170
171 let start = metrics.as_ref().map(|_| Instant::now());
172 let collect_metrics = metrics.is_some();
173 let mut loaded = InvertedIndexReadMetrics::default();
174 let result = self
175 .cache
176 .get_or_load_metadata(key, async {
177 self.inner
178 .metadata(collect_metrics.then_some(&mut loaded))
179 .await
180 })
181 .await;
182 if let Some(m) = metrics {
183 m.total_bytes += loaded.total_bytes;
187 m.total_ranges += loaded.total_ranges;
188 m.cache_hit += loaded.cache_hit;
189 m.cache_miss += loaded.cache_miss;
190 if let Some(start) = start {
191 m.fetch_elapsed += start.elapsed();
192 }
193 }
194 let meta = result.map_err(|source| SharedReadSnafu.into_error(source))?;
195 CACHE_MISS.with_label_values(&[INDEX_METADATA_TYPE]).inc();
196 Ok(meta)
197 }
198}
199
200fn map_page_error(error: Arc<std::io::Error>) -> Error {
202 if let Some(source) = crate::cache::find_shared_page_source::<Error>(&error) {
203 SharedReadSnafu.into_error(source)
204 } else {
205 CommonIoSnafu.into_error(std::io::Error::other(error))
206 }
207}
208
209#[cfg(test)]
210mod test {
211 use std::num::NonZeroUsize;
212
213 use futures::stream;
214 use index::Bytes;
215 use index::bitmap::{Bitmap, BitmapType};
216 use index::inverted_index::format::reader::{InvertedIndexBlobReader, InvertedIndexReader};
217 use index::inverted_index::format::writer::{InvertedIndexBlobWriter, InvertedIndexWriter};
218 use prometheus::register_int_counter_vec;
219 use rand::{Rng, RngCore};
220
221 use super::*;
222 use crate::sst::index::store::InstrumentedStore;
223 use crate::test_util::TestEnv;
224
225 const FUZZ_REPEAT_TIMES: usize = 100;
227
228 struct MetadataReader {
229 release: tokio::sync::Notify,
230 fail: std::sync::atomic::AtomicBool,
231 }
232
233 #[async_trait]
234 impl InvertedIndexReader for MetadataReader {
235 async fn range_read<'a>(
236 &self,
237 _: u64,
238 _: u32,
239 _: Option<&'a mut InvertedIndexReadMetrics>,
240 ) -> Result<Vec<u8>> {
241 unreachable!("metadata test does not read content pages")
242 }
243
244 async fn read_vec<'a>(
245 &self,
246 _: &[Range<u64>],
247 _: Option<&'a mut InvertedIndexReadMetrics>,
248 ) -> Result<Vec<bytes::Bytes>> {
249 unreachable!("metadata test does not read content pages")
250 }
251
252 async fn metadata<'a>(
253 &self,
254 metrics: Option<&'a mut InvertedIndexReadMetrics>,
255 ) -> Result<Arc<InvertedIndexMetas>> {
256 self.release.notified().await;
257 if let Some(metrics) = metrics {
258 metrics.total_bytes += 8192;
259 metrics.total_ranges += 1;
260 metrics.fetch_elapsed += std::time::Duration::from_secs(3600);
262 }
263 if self.fail.load(std::sync::atomic::Ordering::Relaxed) {
264 return Err(CommonIoSnafu.into_error(std::io::Error::other("metadata failed")));
265 }
266 Ok(Arc::new(InvertedIndexMetas::default()))
267 }
268 }
269
270 #[tokio::test]
271 async fn metadata_waiters_do_not_duplicate_io_metrics() {
272 for fail in [false, true] {
273 let cache = Arc::new(InvertedIndexCache::new(4096, 4096, 1024));
274 let reader = CachedInvertedIndexBlobReader::new(
275 FileId::random(),
276 0,
277 8192,
278 MetadataReader {
279 release: tokio::sync::Notify::new(),
280 fail: std::sync::atomic::AtomicBool::new(fail),
281 },
282 cache,
283 );
284 let mut owner_metrics = InvertedIndexReadMetrics::default();
285 let mut waiter_metrics = InvertedIndexReadMetrics::default();
286 let mut owner = Box::pin(reader.metadata(Some(&mut owner_metrics)));
287 assert!(futures::poll!(&mut owner).is_pending());
288 let mut waiter = Box::pin(reader.metadata(Some(&mut waiter_metrics)));
289 assert!(futures::poll!(&mut waiter).is_pending());
290 reader.inner.release.notify_one();
291 let owner_result = owner.await;
292 let waiter_result = waiter.await;
293 assert_eq!(owner_result.is_err(), fail);
294 assert_eq!(waiter_result.is_err(), fail);
295 assert_eq!(
296 (owner_metrics.total_bytes, owner_metrics.total_ranges),
297 (8192, 1)
298 );
299 assert_eq!(
300 (waiter_metrics.total_bytes, waiter_metrics.total_ranges),
301 (0, 0)
302 );
303 assert_eq!((owner_metrics.cache_hit, waiter_metrics.cache_hit), (0, 0));
304 for metrics in [&owner_metrics, &waiter_metrics] {
305 assert!(metrics.fetch_elapsed > std::time::Duration::ZERO);
308 assert!(metrics.fetch_elapsed < std::time::Duration::from_secs(3600));
309 }
310 if fail {
311 let Error::SharedRead { source: owner, .. } = owner_result.unwrap_err() else {
312 panic!("owner must preserve the typed shared read error");
313 };
314 let Error::SharedRead { source: waiter, .. } = waiter_result.unwrap_err() else {
315 panic!("waiter must preserve the typed shared read error");
316 };
317 assert!(Arc::ptr_eq(&owner, &waiter));
318 } else {
319 let mut hot_metrics = InvertedIndexReadMetrics::default();
320 reader.metadata(Some(&mut hot_metrics)).await.unwrap();
321 assert_eq!(hot_metrics.cache_hit, 1);
322 assert_eq!(hot_metrics.total_bytes, 0);
323 }
324 }
325 }
326
327 #[test]
329 fn fuzz_index_calculation() {
330 use common_cache::page::PageLayout;
331 use std::num::NonZeroU64;
332
333 let mut rng = rand::rng();
334 let mut data = vec![0u8; 1024 * 1024];
335 rng.fill_bytes(&mut data);
336 for _ in 0..FUZZ_REPEAT_TIMES {
337 let offset = rng.random_range(0..data.len() as u64);
338 let size = rng.random_range(0..data.len() as u64 - offset);
339 let page_size = NonZeroU64::new(rng.random_range(1_u64..1024)).unwrap();
340 let layout = PageLayout::new(page_size, data.len() as u64);
341 let requested = offset..offset + size;
342 let mut read = Vec::with_capacity(size as usize);
343 for id in layout.page_ids(requested.clone()).unwrap() {
344 let range = layout.page_range(id).unwrap();
345 let page = &data[range.start as usize..range.end as usize];
346 let slice = layout
347 .request_slice(id, requested.clone())
348 .unwrap()
349 .unwrap();
350 read.extend_from_slice(&page[slice]);
351 }
352 assert_eq!(read, data[offset as usize..(offset + size) as usize]);
353 }
354 }
355
356 fn unpack(fst_value: u64) -> [u32; 2] {
357 bytemuck::cast::<u64, [u32; 2]>(fst_value)
358 }
359
360 async fn create_inverted_index_blob() -> Vec<u8> {
361 let mut blob = Vec::new();
362 let mut writer = InvertedIndexBlobWriter::new(&mut blob);
363 writer
364 .add_index(
365 "tag0".to_string(),
366 Bitmap::from_lsb0_bytes(&[0b0000_0001, 0b0000_0000], BitmapType::Roaring),
367 Box::new(stream::iter(vec![
368 Ok((
369 Bytes::from("a"),
370 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring),
371 )),
372 Ok((
373 Bytes::from("b"),
374 Bitmap::from_lsb0_bytes(&[0b0010_0000], BitmapType::Roaring),
375 )),
376 Ok((
377 Bytes::from("c"),
378 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring),
379 )),
380 ])),
381 index::bitmap::BitmapType::Roaring,
382 )
383 .await
384 .unwrap();
385 writer
386 .add_index(
387 "tag1".to_string(),
388 Bitmap::from_lsb0_bytes(&[0b0000_0001, 0b0000_0000], BitmapType::Roaring),
389 Box::new(stream::iter(vec![
390 Ok((
391 Bytes::from("x"),
392 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring),
393 )),
394 Ok((
395 Bytes::from("y"),
396 Bitmap::from_lsb0_bytes(&[0b0010_0000], BitmapType::Roaring),
397 )),
398 Ok((
399 Bytes::from("z"),
400 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring),
401 )),
402 ])),
403 index::bitmap::BitmapType::Roaring,
404 )
405 .await
406 .unwrap();
407 writer
408 .finish(8, NonZeroUsize::new(1).unwrap())
409 .await
410 .unwrap();
411
412 blob
413 }
414
415 #[tokio::test]
416 async fn test_inverted_index_cache() {
417 let blob = create_inverted_index_blob().await;
418
419 let mut env = TestEnv::new().await;
421 let file_size = blob.len() as u64;
422 let index_version = 0;
423 let store = env.init_object_store_manager();
424 let temp_path = "data";
425 store.write(temp_path, blob).await.unwrap();
426 let store = InstrumentedStore::new(store);
427 let metric =
428 register_int_counter_vec!("test_bytes", "a counter for test", &["test"]).unwrap();
429 let counter = metric.with_label_values(&["test"]);
430 let range_reader = store
431 .range_reader("data", &counter, &counter)
432 .await
433 .unwrap();
434
435 let reader = InvertedIndexBlobReader::new(range_reader);
436 let cached_reader = CachedInvertedIndexBlobReader::new(
437 FileId::random(),
438 index_version,
439 file_size,
440 reader,
441 Arc::new(InvertedIndexCache::new(8192, 8192, 50)),
442 );
443 let metadata = cached_reader.metadata(None).await.unwrap();
444 assert_eq!(metadata.total_row_count, 8);
445 assert_eq!(metadata.segment_row_count, 1);
446 assert_eq!(metadata.metas.len(), 2);
447 let tag0 = metadata.metas.get("tag0").unwrap();
449 let stats0 = tag0.stats.as_ref().unwrap();
450 assert_eq!(stats0.distinct_count, 3);
451 assert_eq!(stats0.null_count, 1);
452 assert_eq!(stats0.min_value, Bytes::from("a"));
453 assert_eq!(stats0.max_value, Bytes::from("c"));
454 let fst0 = cached_reader
455 .fst(
456 tag0.base_offset + tag0.relative_fst_offset as u64,
457 tag0.fst_size,
458 None,
459 )
460 .await
461 .unwrap();
462 assert_eq!(fst0.len(), 3);
463 let [offset, size] = unpack(fst0.get(b"a").unwrap());
464 let bitmap = cached_reader
465 .bitmap(
466 tag0.base_offset + offset as u64,
467 size,
468 BitmapType::Roaring,
469 None,
470 )
471 .await
472 .unwrap();
473 assert_eq!(
474 bitmap,
475 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring)
476 );
477 let [offset, size] = unpack(fst0.get(b"b").unwrap());
478 let bitmap = cached_reader
479 .bitmap(
480 tag0.base_offset + offset as u64,
481 size,
482 BitmapType::Roaring,
483 None,
484 )
485 .await
486 .unwrap();
487 assert_eq!(
488 bitmap,
489 Bitmap::from_lsb0_bytes(&[0b0010_0000], BitmapType::Roaring)
490 );
491 let [offset, size] = unpack(fst0.get(b"c").unwrap());
492 let bitmap = cached_reader
493 .bitmap(
494 tag0.base_offset + offset as u64,
495 size,
496 BitmapType::Roaring,
497 None,
498 )
499 .await
500 .unwrap();
501 assert_eq!(
502 bitmap,
503 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring)
504 );
505
506 let tag1 = metadata.metas.get("tag1").unwrap();
508 let stats1 = tag1.stats.as_ref().unwrap();
509 assert_eq!(stats1.distinct_count, 3);
510 assert_eq!(stats1.null_count, 1);
511 assert_eq!(stats1.min_value, Bytes::from("x"));
512 assert_eq!(stats1.max_value, Bytes::from("z"));
513 let fst1 = cached_reader
514 .fst(
515 tag1.base_offset + tag1.relative_fst_offset as u64,
516 tag1.fst_size,
517 None,
518 )
519 .await
520 .unwrap();
521 assert_eq!(fst1.len(), 3);
522 let [offset, size] = unpack(fst1.get(b"x").unwrap());
523 let bitmap = cached_reader
524 .bitmap(
525 tag1.base_offset + offset as u64,
526 size,
527 BitmapType::Roaring,
528 None,
529 )
530 .await
531 .unwrap();
532 assert_eq!(
533 bitmap,
534 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring)
535 );
536 let [offset, size] = unpack(fst1.get(b"y").unwrap());
537 let bitmap = cached_reader
538 .bitmap(
539 tag1.base_offset + offset as u64,
540 size,
541 BitmapType::Roaring,
542 None,
543 )
544 .await
545 .unwrap();
546 assert_eq!(
547 bitmap,
548 Bitmap::from_lsb0_bytes(&[0b0010_0000], BitmapType::Roaring)
549 );
550 let [offset, size] = unpack(fst1.get(b"z").unwrap());
551 let bitmap = cached_reader
552 .bitmap(
553 tag1.base_offset + offset as u64,
554 size,
555 BitmapType::Roaring,
556 None,
557 )
558 .await
559 .unwrap();
560 assert_eq!(
561 bitmap,
562 Bitmap::from_lsb0_bytes(&[0b0000_0001], BitmapType::Roaring)
563 );
564
565 let mut rng = rand::rng();
567 for _ in 0..FUZZ_REPEAT_TIMES {
568 let offset = rng.random_range(0..file_size);
569 let size = rng.random_range(0..file_size as u32 - offset as u32);
570 let expected = cached_reader.range_read(offset, size, None).await.unwrap();
571 let inner = &cached_reader.inner;
572 let (read, _cache_metrics) = cached_reader
573 .cache
574 .get_or_load(
575 (cached_reader.file_id, cached_reader.index_version),
576 file_size,
577 offset,
578 size,
579 |ranges| async move { inner.read_vec(&ranges, None).await },
580 )
581 .await
582 .unwrap();
583 assert_eq!(read.as_ref(), expected.as_slice());
584 }
585 }
586
587 #[tokio::test]
588 async fn test_get_or_load_vec_loads_missing_pages_once() {
589 use std::sync::atomic::{AtomicUsize, Ordering};
590
591 let mut rng = rand::rng();
592 let mut data = vec![0u8; 64 * 1024];
593 rng.fill_bytes(&mut data);
594 let file_size = data.len() as u64;
595 let cache = InvertedIndexCache::new(1024 * 1024, 1024 * 1024, 1000);
596 let key = (FileId::random(), 0);
597
598 for _ in 0..FUZZ_REPEAT_TIMES {
599 cache.invalidate_file(key.0);
600 let ranges = (0..rng.random_range(1..20))
601 .map(|_| {
602 let start = rng.random_range(0..file_size);
603 start..rng.random_range(start..=file_size.min(start + 5000))
604 })
605 .collect::<Vec<_>>();
606 let expected = ranges
607 .iter()
608 .map(|r| bytes::Bytes::copy_from_slice(&data[r.start as usize..r.end as usize]))
609 .collect::<Vec<_>>();
610
611 let loads = AtomicUsize::new(0);
612 let physical_ranges = std::sync::Mutex::new(Vec::new());
613 let load = |pages: Vec<Range<u64>>| {
614 loads.fetch_add(1, Ordering::Relaxed);
615 physical_ranges
616 .lock()
617 .unwrap()
618 .extend(pages.iter().cloned());
619 let data = &data;
620 async move {
621 Ok::<_, std::io::Error>(
622 pages
623 .iter()
624 .map(|r| {
625 bytes::Bytes::copy_from_slice(
626 &data[r.start as usize..r.end as usize],
627 )
628 })
629 .collect(),
630 )
631 }
632 };
633 let (cold, cold_metrics) = cache
634 .get_or_load_vec(key, file_size, &ranges, load)
635 .await
636 .unwrap();
637 assert_eq!(cold, expected);
638 let cold_loads = loads.load(Ordering::Relaxed);
639 assert_eq!(cold_loads, usize::from(cold_metrics.cache_miss > 0));
640 let mut observed = physical_ranges.lock().unwrap().clone();
641 observed.sort_by_key(|r| r.start);
642 assert!(observed.windows(2).all(|w| w[0].end <= w[1].start));
643 assert_eq!(
644 observed.iter().map(|r| r.end - r.start).sum::<u64>(),
645 cold_metrics.page_bytes
646 );
647
648 let (warm, metrics) = cache
649 .get_or_load_vec(key, file_size, &ranges, load)
650 .await
651 .unwrap();
652 assert_eq!(warm, expected);
653 assert_eq!(metrics.cache_miss, 0);
654 assert_eq!(loads.load(Ordering::Relaxed), cold_loads);
655 }
656 }
657}