Skip to content

Commit 17fb7b4

Browse files
authored
fix(query): fix Inverted/Vector index panic with Native Storage Format (#18932)
* fix(query): fix Inverted/Vector index panic with Native Storage Format * fix
1 parent 923eb29 commit 17fb7b4

File tree

9 files changed

+127
-29
lines changed

9 files changed

+127
-29
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/query/catalog/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ log = { workspace = true }
3434
parking_lot = { workspace = true }
3535
parquet = { workspace = true }
3636
rand = { workspace = true }
37+
roaring = { workspace = true }
3738
serde = { workspace = true }
3839
serde_json = { workspace = true }
3940
sha2 = { workspace = true }

src/query/catalog/src/plan/internal_column.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use databend_common_expression::SNAPSHOT_NAME_COLUMN_ID;
4545
use databend_common_expression::VECTOR_SCORE_COLUMN_ID;
4646
use databend_storages_common_table_meta::meta::try_extract_uuid_str_from_path;
4747
use databend_storages_common_table_meta::meta::NUM_BLOCK_ID_BITS;
48+
use roaring::RoaringTreemap;
4849

4950
// Segment and Block id Bits when generate internal column `_row_id`
5051
// Assumes that the max block count of a segment is 2 ^ NUM_BLOCK_ID_BITS
@@ -94,15 +95,15 @@ pub fn block_idx_in_segment(block_num: usize, block_id: usize) -> usize {
9495
}
9596

9697
// meta data for generate internal columns
97-
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq, Eq)]
98+
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, PartialEq)]
9899
pub struct InternalColumnMeta {
99100
pub segment_idx: usize,
100101
pub block_id: usize,
101102
pub block_location: String,
102103
pub segment_location: String,
103104
pub snapshot_location: Option<String>,
104105
/// The row offsets in the block.
105-
pub offsets: Option<Vec<usize>>,
106+
pub offsets: Option<RoaringTreemap>,
106107
pub base_block_ids: Option<Scalar>,
107108
pub inner: Option<BlockMetaInfoPtr>,
108109
// The search matched rows and optional scores in the block.
@@ -221,8 +222,8 @@ impl InternalColumn {
221222
let high_32bit = compute_row_id_prefix(seg_id, block_id);
222223
let mut row_ids = Vec::with_capacity(num_rows);
223224
if let Some(offsets) = &meta.offsets {
224-
for i in offsets {
225-
let row_id = compute_row_id(high_32bit, *i as u64);
225+
for i in offsets.iter() {
226+
let row_id = compute_row_id(high_32bit, i);
226227
row_ids.push(row_id);
227228
}
228229
} else {
@@ -256,8 +257,8 @@ impl InternalColumn {
256257
});
257258
let mut row_ids = Vec::with_capacity(num_rows);
258259
if let Some(offsets) = &meta.offsets {
259-
for i in offsets {
260-
let row_id = format!("{}{:06x}", uuid, *i);
260+
for i in offsets.iter() {
261+
let row_id = format!("{}{:06x}", uuid, i);
261262
row_ids.push(row_id);
262263
}
263264
} else {
@@ -282,6 +283,7 @@ impl InternalColumn {
282283

283284
let mut bitmap = MutableBitmap::from_len_zeroed(num_rows);
284285
for (idx, _) in matched_rows.iter() {
286+
debug_assert!(*idx < bitmap.len());
285287
bitmap.set(*idx, true);
286288
}
287289
Column::Boolean(bitmap.into()).into()
@@ -292,8 +294,9 @@ impl InternalColumn {
292294

293295
let mut scores = vec![F32::from(0_f32); num_rows];
294296
for (idx, score) in matched_rows.iter() {
297+
debug_assert!(*idx < scores.len());
295298
if let Some(val) = scores.get_mut(*idx) {
296-
assert!(score.is_some());
299+
debug_assert!(score.is_some());
297300
*val = F32::from(*score.unwrap());
298301
}
299302
}
@@ -307,6 +310,7 @@ impl InternalColumn {
307310
// Fill other rows with the maximum value and they will be filtered out.
308311
let mut scores = vec![F32::from(f32::MAX); num_rows];
309312
for (idx, score) in vector_scores.iter() {
313+
debug_assert!(*idx < scores.len());
310314
if let Some(val) = scores.get_mut(*idx) {
311315
*val = *score;
312316
}

src/query/storages/fuse/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ parking_lot = { workspace = true }
6666
parquet = { workspace = true }
6767
paste = { workspace = true }
6868
rand = { workspace = true }
69+
roaring = { workspace = true }
6970
serde = { workspace = true }
7071
serde_json = { workspace = true }
7172
sha2 = { workspace = true }

src/query/storages/fuse/src/operations/read/native_data_source_deserializer.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ use databend_common_pipeline_core::processors::OutputPort;
5757
use databend_common_pipeline_core::processors::Processor;
5858
use databend_common_pipeline_core::processors::ProcessorPtr;
5959
use databend_common_sql::IndexType;
60+
use roaring::RoaringTreemap;
6061
use xorf::BinaryFuse16;
6162

6263
use super::native_data_source::NativeDataSource;
@@ -797,15 +798,17 @@ impl NativeDeserializeDataTransform {
797798
let mut block = block.resort(&self.src_schema, &self.output_schema)?;
798799
let fuse_part = FuseBlockPartInfo::from_part(&self.parts[0])?;
799800
let offsets = if self.block_reader.query_internal_columns() {
800-
let offset = self.read_state.offset;
801+
let offset = self.read_state.offset as u64;
801802
let offsets = if let Some(count) = self.read_state.filtered_count {
802803
let filter_executor = self.filter_executor.as_mut().unwrap();
803-
filter_executor.mutable_true_selection()[0..count]
804-
.iter()
805-
.map(|idx| *idx as usize + offset)
806-
.collect::<Vec<_>>()
804+
RoaringTreemap::from_sorted_iter(
805+
filter_executor.true_selection()[0..count]
806+
.iter()
807+
.map(|idx| *idx as u64 + offset),
808+
)
809+
.unwrap()
807810
} else {
808-
(offset..offset + origin_num_rows).collect()
811+
RoaringTreemap::from_sorted_iter(offset..offset + origin_num_rows as u64).unwrap()
809812
};
810813
Some(offsets)
811814
} else {

src/query/storages/fuse/src/operations/read/parquet_data_source_deserializer.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use databend_common_pipeline_core::processors::OutputPort;
4343
use databend_common_pipeline_core::processors::Processor;
4444
use databend_common_pipeline_core::processors::ProcessorPtr;
4545
use databend_common_sql::IndexType;
46+
use roaring::RoaringTreemap;
4647
use xorf::BinaryFuse16;
4748

4849
use super::parquet_data_source::ParquetDataSource;
@@ -343,9 +344,12 @@ impl Processor for DeserializeDataTransform {
343344
// `TransformAddInternalColumns` will generate internal columns using `BlockMetaIndex` in next pipeline.
344345
let offsets = if self.block_reader.query_internal_columns() {
345346
filter.as_ref().map(|bitmap| {
346-
(0..origin_num_rows)
347-
.filter(|i| unsafe { bitmap.get_bit_unchecked(*i) })
348-
.collect()
347+
RoaringTreemap::from_sorted_iter(
348+
(0..origin_num_rows)
349+
.filter(|i| unsafe { bitmap.get_bit_unchecked(*i) })
350+
.map(|i| i as u64),
351+
)
352+
.unwrap()
349353
})
350354
} else {
351355
None

src/query/storages/fuse/src/operations/read/util.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use databend_common_exception::Result;
2222
use databend_common_expression::BlockMetaInfoPtr;
2323
use databend_common_expression::DataBlock;
2424
use databend_common_expression::Scalar;
25+
use roaring::RoaringTreemap;
2526

2627
use crate::operations::BlockMetaIndex;
2728
use crate::FuseBlockPartInfo;
@@ -40,7 +41,7 @@ pub fn need_reserve_block_info(ctx: Arc<dyn TableContext>, table_idx: usize) ->
4041
pub(crate) fn add_data_block_meta(
4142
block: DataBlock,
4243
fuse_part: &FuseBlockPartInfo,
43-
offsets: Option<Vec<usize>>,
44+
offsets: Option<RoaringTreemap>,
4445
base_block_ids: Option<Scalar>,
4546
update_stream_columns: bool,
4647
query_internal_columns: bool,
@@ -67,6 +68,33 @@ pub(crate) fn add_data_block_meta(
6768
if query_internal_columns {
6869
// Fill `BlockMetaInfoPtr` if query internal columns
6970
let block_meta = fuse_part.block_meta_index().unwrap();
71+
72+
// Transform matched_rows indices from block-level to page-level
73+
let matched_rows = block_meta.matched_rows.clone().map(|matched_rows| {
74+
if let Some(offsets) = &offsets {
75+
matched_rows
76+
.into_iter()
77+
.filter(|(idx, _)| offsets.contains(*idx as u64))
78+
.map(|(idx, score)| ((offsets.rank(idx as u64) - 1) as usize, score))
79+
.collect::<Vec<_>>()
80+
} else {
81+
matched_rows
82+
}
83+
});
84+
85+
// Transform vector_scores indices from block-level to page-level
86+
let vector_scores = block_meta.vector_scores.clone().map(|vector_scores| {
87+
if let Some(offsets) = &offsets {
88+
vector_scores
89+
.into_iter()
90+
.filter(|(idx, _)| offsets.contains(*idx as u64))
91+
.map(|(idx, score)| ((offsets.rank(idx as u64) - 1) as usize, score))
92+
.collect::<Vec<_>>()
93+
} else {
94+
vector_scores
95+
}
96+
});
97+
7098
let internal_column_meta = InternalColumnMeta {
7199
segment_idx: block_meta.segment_idx,
72100
block_id: block_meta.block_id,
@@ -76,8 +104,8 @@ pub(crate) fn add_data_block_meta(
76104
offsets,
77105
base_block_ids,
78106
inner: meta,
79-
matched_rows: block_meta.matched_rows.clone(),
80-
vector_scores: block_meta.vector_scores.clone(),
107+
matched_rows,
108+
vector_scores,
81109
};
82110
meta = Some(Box::new(internal_column_meta));
83111
}

tests/sqllogictests/suites/query/index/04_inverted_index/04_0000_inverted_index_base.test

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ query
3232
SELECT id, score(), content FROM t WHERE match(content, 'test')
3333
----
3434

35-
query IFT
35+
query
3636
SELECT id, score(), content FROM t WHERE match(content, 'the')
3737
----
3838

@@ -47,7 +47,7 @@ SELECT id, score(), content FROM t WHERE match(content, 'word')
4747
2 1.5948367 A picture is worth a thousand words
4848
4 1.6550698 Actions speak louder than words
4949

50-
query IFT
50+
query
5151
SELECT id, score(), content FROM t WHERE match(content, 'box')
5252
----
5353

@@ -133,25 +133,25 @@ SELECT id, score(), content FROM t WHERE match(content, '文化 博大精深') O
133133
15 2.063777 中国的茶文化源远流长,品茶已经成为一种生活方式。
134134
28 7.61753 中国的饮食文化博大精深,各地的美食各具特色,让人流连忘返。
135135

136-
query IFT
136+
query
137137
SELECT id, score(), content FROM t WHERE match(content, '化博') ORDER BY score()
138138
----
139139

140140

141-
query IFT
141+
query
142142
SELECT id, score(), content FROM t WHERE match(content, '。') ORDER BY score()
143143
----
144144

145145

146-
query IFT
146+
query
147147
SELECT id, score(), content FROM t WHERE match(content, '不存在') ORDER BY score()
148148
----
149149

150150

151151
statement error 1903
152152
SELECT id, score(), content FROM t WHERE match(content, '()')
153153

154-
query IFT
154+
query
155155
SELECT id, score(), content FROM t WHERE match(content, '()', 'lenient=true')
156156
----
157157

@@ -198,7 +198,7 @@ SELECT id, score(), content FROM t WHERE match(content, 'the')
198198
6 2.033073 Beauty is in the eye of the beholder
199199
10 2.033073 An apple a day keeps the doctor away
200200

201-
query IFT
201+
query
202202
SELECT id, score(), content FROM t WHERE match(content, 'fly')
203203
----
204204

@@ -306,7 +306,7 @@ SELECT id, score(), title FROM books WHERE query('title:python OR rust') ORDER B
306306
6 0.96639454 Flask Web开发:基于Python的Web应用开发实战(第2版)
307307
13 0.8931828 Learn AI-Assisted Python Programming: With GitHub Copilot and ChatGPT
308308

309-
query IFT
309+
query
310310
SELECT id, score(), title FROM books WHERE query('title:python AND rust') ORDER BY score() DESC
311311
----
312312

@@ -486,10 +486,32 @@ CREATE INVERTED INDEX IF NOT EXISTS idx ON t2(body) tokenizer = 'chinese'
486486
statement ok
487487
INSERT INTO t2 VALUES (1, null)
488488

489-
query IT
489+
query
490490
select * from t2 where query('body:test');
491491
----
492492

493+
statement ok
494+
CREATE TABLE t_native (id int, content string, INVERTED INDEX idx1 (content)) storage_format = 'native' row_per_page = 2;
495+
496+
statement ok
497+
INSERT INTO t_native VALUES
498+
(1, 'The quick brown fox jumps over the lazy dog'),
499+
(2, 'A picture is worth a thousand words'),
500+
(3, 'The early bird catches the worm'),
501+
(4, 'Actions speak louder than words'),
502+
(5, 'Time flies like an arrow; fruit flies like a banana'),
503+
(6, 'Beauty is in the eye of the beholder'),
504+
(7, 'When life gives you lemons, make lemonade'),
505+
(8, 'Put all your eggs in one basket'),
506+
(9, 'You can not judge a book by its cover'),
507+
(10, 'An apple a day keeps the doctor away')
508+
509+
query IT
510+
select * from t_native where query('content:book OR content:basket');
511+
----
512+
8 Put all your eggs in one basket
513+
9 You can not judge a book by its cover
514+
493515
statement ok
494516
use default
495517

tests/sqllogictests/suites/query/index/09_vector_index/09_0000_vector_index_base.test

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,43 @@ SELECT id, cosine_distance(embedding2, [0.50515236, 0.8561939, 0.87169914, 0.558
284284
----
285285
5 0.03522873
286286

287+
statement ok
288+
CREATE TABLE IF NOT EXISTS t_native(id Int, embedding Vector(8), VECTOR INDEX idx (embedding) m=10 ef_construct=40 distance='cosine') storage_format = 'native' row_per_page = 2
289+
290+
statement ok
291+
INSERT INTO t_native VALUES
292+
(1, [0.50515236, 0.8561939, 0.87169914, 0.55843271, 0.73689797, 0.49985862, 0.64527255, 0.29313098]),
293+
(2, [0.17790798, 0.0132427, 0.55352279, 0.49129727, 0.74246407, 0.97345777, 0.83489323, 0.86012174]),
294+
(3, [0.2703968, 0.26768266, 0.96587005, 0.04760408, 0.92289409, 0.15799311, 0.86381163, 0.2922287]),
295+
(4, [0.0810719, 0.27882267, 0.6015564, 0.34236571, 0.58889543, 0.83293431, 0.67012723, 0.76303241]),
296+
(5, [0.66399931, 0.35041433, 0.2159864, 0.89537508, 0.44577037, 0.57896497, 0.36630178, 0.33816571]),
297+
(6, [0.32052319, 0.38567453, 0.62853221, 0.84816365, 0.15853234, 0.33207714, 0.7673085, 0.69513879]),
298+
(7, [0.82590676, 0.35860656, 0.6277274, 0.95148122, 0.81893313, 0.91440945, 0.15803721, 0.5866869]),
299+
(8, [0.42135513, 0.05637937, 0.88864157, 0.59217909, 0.98435169, 0.39234101, 0.41490889, 0.02760555])
300+
301+
statement ok
302+
INSERT INTO t_native VALUES
303+
(9, [0.61418788, 0.34545306, 0.14638622, 0.53249639, 0.09139293, 0.84940919, 0.105433, 0.4156201]),
304+
(10, [0.21828953, 0.87852734, 0.64221122, 0.24536394, 0.81689593, 0.86341877, 0.7218334, 0.45028494]),
305+
(11, [0.43279006, 0.45523681, 0.76060274, 0.66284758, 0.19131476, 0.13564463, 0.88712212, 0.93279565]),
306+
(12, [0.79671359, 0.86079789, 0.94477631, 0.5116732, 0.29733205, 0.33645561, 0.41380333, 0.75909903]),
307+
(13, [0.94666755, 0.39522571, 0.39857241, 0.88080323, 0.53470771, 0.09486194, 0.17524627, 0.86497559]),
308+
(14, [0.8397819, 0.37221789, 0.32885295, 0.20470829, 0.49838217, 0.00736057, 0.45418757, 0.6956924 ]),
309+
(15, [0.13230447, 0.630588, 0.10812326, 0.21558228, 0.83768057, 0.48870546, 0.65021806, 0.31626541]),
310+
(16, [0.2667851, 0.01529589, 0.98994706, 0.31870983, 0.31783372, 0.34863699, 0.30254189, 0.84441678])
311+
312+
query IF
313+
SELECT id, cosine_distance(embedding, [0.50515236, 0.8561939, 0.87169914, 0.55843271, 0.73689797, 0.49985862, 0.64527255, 0.29313098]::vector(8)) AS similarity FROM t_native ORDER BY similarity ASC LIMIT 5;
314+
----
315+
1 0.015007019
316+
10 0.06970841
317+
12 0.09774226
318+
8 0.1490264
319+
15 0.150944
320+
287321
statement ok
288322
use default
289323

290324
statement ok
291325
drop database test_vector_index
292326

293-

0 commit comments

Comments
 (0)