Skip to content

Commit f41563e

Browse files
author
zhaixiangli
committed
[opt](scan) Extend sequence mapping candidate scan to NGRAM bloom filters
Issue Number: close #66879 Related PR: #66881 Problem Summary: Allow positive LIKE predicates backed by NGRAM_BF indexes to drive sequence-mapping candidate scans. Reuse key-prefix ranges during candidate collection while retaining the full-key fallback, and skip synthetic whole-value key ranges. Add bloom-filter profile accounting and regression coverage for matching, stale versions, short patterns, NOT LIKE, disabled inverted-index queries, and composite key prefixes.
1 parent 87a42d8 commit f41563e

8 files changed

Lines changed: 212 additions & 18 deletions

File tree

be/src/exec/operator/olap_scan_operator.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ Status OlapScanLocalState::_init_profile() {
304304
ADD_COUNTER(_segment_profile, "SeqMapCandidateScanBytes", TUnit::BYTES);
305305
_seq_map_candidate_index_filtered_rows_counter =
306306
ADD_COUNTER(_segment_profile, "SeqMapCandidateIndexFilteredRows", TUnit::UNIT);
307+
_seq_map_candidate_bloom_filter_filtered_rows_counter =
308+
ADD_COUNTER(_segment_profile, "SeqMapCandidateBloomFilterFilteredRows", TUnit::UNIT);
307309
_seq_map_candidate_index_downgrades_counter =
308310
ADD_COUNTER(_segment_profile, "SeqMapCandidateIndexDowngrades", TUnit::UNIT);
309311
_seq_map_candidate_index_lookup_timer =
@@ -1232,6 +1234,10 @@ Status OlapScanLocalState::_build_key_ranges_and_filters() {
12321234
}
12331235
DCHECK(_slot_id_to_predicates.count(iter->first) > 0);
12341236
const auto& value_range = iter->second;
1237+
if (std::visit([](const auto& range) { return range.is_whole_value_range(); },
1238+
value_range)) {
1239+
break;
1240+
}
12351241

12361242
std::optional<int> key_to_erase;
12371243
bool is_fixed_value_range = false;

be/src/exec/operator/olap_scan_operator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ class OlapScanLocalState final : public ScanLocalState<OlapScanLocalState> {
265265
RuntimeProfile::Counter* _seq_map_candidate_scan_rows_counter = nullptr;
266266
RuntimeProfile::Counter* _seq_map_candidate_scan_bytes_counter = nullptr;
267267
RuntimeProfile::Counter* _seq_map_candidate_index_filtered_rows_counter = nullptr;
268+
RuntimeProfile::Counter* _seq_map_candidate_bloom_filter_filtered_rows_counter = nullptr;
268269
RuntimeProfile::Counter* _seq_map_candidate_index_downgrades_counter = nullptr;
269270
RuntimeProfile::Counter* _seq_map_candidate_index_lookup_timer = nullptr;
270271
RuntimeProfile::Counter* _seq_map_candidate_cache_local_bytes_counter = nullptr;

be/src/exec/scan/olap_scanner.cpp

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#include "storage/index/inverted/inverted_index_profile.h"
6666
#include "storage/index/inverted/similarity/collection_statistics.h"
6767
#include "storage/iterator/block_reader.h"
68+
#include "storage/itoken_extractor.h"
6869
#include "storage/olap_common.h"
6970
#include "storage/olap_tuple.h"
7071
#include "storage/olap_utils.h"
@@ -344,6 +345,7 @@ void OlapScanner::_merge_seq_map_candidate_stats(const OlapReaderStatistics& can
344345
total_stats->seq_map_candidate_scan_bytes += candidate_stats.uncompressed_bytes_read;
345346
total_stats->seq_map_candidate_index_filtered_rows +=
346347
candidate_stats.rows_inverted_index_filtered;
348+
total_stats->seq_map_candidate_bloom_filter_filtered_rows += candidate_stats.rows_bf_filtered;
347349
total_stats->seq_map_candidate_index_downgrades +=
348350
candidate_stats.inverted_index_downgrade_count;
349351
total_stats->seq_map_candidate_index_lookup_ns += candidate_stats.inverted_index_lookup_timer;
@@ -363,6 +365,7 @@ void OlapScanner::_merge_seq_map_candidate_stats(const OlapReaderStatistics& can
363365

364366
Status OlapScanner::_collect_seq_map_candidate_keys(
365367
const std::vector<std::shared_ptr<ColumnPredicate>>& driver_predicates,
368+
const std::vector<FunctionFilter>& driver_function_filters,
366369
const std::vector<std::shared_ptr<ColumnPredicate>>& key_predicates,
367370
int64_t previous_candidate_scan_rows, bool price_point_lookups, int64_t max_candidate_keys,
368371
size_t max_candidate_bytes, size_t candidate_workspace_bytes,
@@ -391,7 +394,7 @@ Status OlapScanner::_collect_seq_map_candidate_keys(
391394
for (const auto& predicate : driver_predicates) {
392395
candidate_params.predicates.emplace_back(predicate->clone(predicate->column_id()));
393396
}
394-
candidate_params.function_filters.clear();
397+
candidate_params.function_filters = driver_function_filters;
395398
candidate_params.all_access_paths.clear();
396399
candidate_params.predicate_access_paths.clear();
397400
candidate_params.output_columns.clear();
@@ -422,7 +425,7 @@ Status OlapScanner::_collect_seq_map_candidate_keys(
422425

423426
std::vector<ColumnId> candidate_columns;
424427
candidate_columns.reserve(_tablet_reader_params.tablet_schema->num_key_columns() +
425-
driver_predicates.size());
428+
driver_predicates.size() + driver_function_filters.size());
426429
for (uint32_t cid = 0; cid < _tablet_reader_params.tablet_schema->num_key_columns(); ++cid) {
427430
candidate_columns.push_back(cid);
428431
}
@@ -432,6 +435,14 @@ Status OlapScanner::_collect_seq_map_candidate_keys(
432435
candidate_columns.push_back(predicate->column_id());
433436
}
434437
}
438+
for (const auto& filter : driver_function_filters) {
439+
const int32_t cid = candidate_params.tablet_schema->field_index(filter._col_name);
440+
DORIS_CHECK_GE(cid, 0);
441+
if (std::find(candidate_columns.begin(), candidate_columns.end(), cid) ==
442+
candidate_columns.end()) {
443+
candidate_columns.push_back(cid);
444+
}
445+
}
435446
candidate_params.return_columns = candidate_columns;
436447
candidate_params.origin_return_columns = &candidate_columns;
437448
candidate_params.tablet_columns_convert_to_null_set = nullptr;
@@ -534,9 +545,18 @@ void OlapScanner::_record_seq_map_candidate_fallback_reason(RuntimeProfile* prof
534545
profile->add_info_string("SeqMapCandidateFallbackReason." + fallback_reason, fallback_reason);
535546
}
536547

548+
bool OlapScanner::_has_usable_ngram_bf_pattern(const FunctionFilter& filter, size_t gram_size) {
549+
NgramTokenExtractor extractor(gram_size);
550+
size_t position = 0;
551+
std::string token;
552+
return extractor.next_in_string_like(filter._string_param.data, filter._string_param.size,
553+
&position, token);
554+
}
555+
537556
Status OlapScanner::_build_seq_map_candidate_keys(
538557
const std::vector<std::shared_ptr<ColumnPredicate>>& key_predicates,
539558
const std::map<uint32_t, std::vector<std::shared_ptr<ColumnPredicate>>>& group_drivers,
559+
const std::map<uint32_t, std::vector<FunctionFilter>>& group_function_drivers,
540560
int64_t max_candidate_keys, const CandidateMemoryBudget& memory_budget,
541561
const CandidateScanCostLimit& cost_limit) {
542562
auto& params = _tablet_reader_params;
@@ -560,6 +580,11 @@ Status OlapScanner::_build_seq_map_candidate_keys(
560580
size_t final_key_bytes = 0;
561581
bool first_group = true;
562582
for (const auto& [seq_col, predicates] : group_drivers) {
583+
const auto function_it = group_function_drivers.find(seq_col);
584+
const std::vector<FunctionFilter> empty_function_filters;
585+
const auto& function_filters = function_it == group_function_drivers.end()
586+
? empty_function_filters
587+
: function_it->second;
563588
CandidateKeyMap group_keys;
564589
size_t group_key_bytes = 0;
565590
bool limit_exceeded = false;
@@ -568,7 +593,7 @@ Status OlapScanner::_build_seq_map_candidate_keys(
568593
bool cost_exceeded = false;
569594
const size_t remaining_candidate_bytes = memory_budget.key_bytes - final_key_bytes;
570595
RETURN_IF_ERROR(_collect_seq_map_candidate_keys(
571-
predicates, key_predicates, stats->seq_map_candidate_scan_rows,
596+
predicates, function_filters, key_predicates, stats->seq_map_candidate_scan_rows,
572597
group_drivers.size() == 1, max_candidate_keys, remaining_candidate_bytes,
573598
memory_budget.workspace_bytes, cost_limit, &group_keys, &group_key_bytes,
574599
&limit_exceeded, &bytes_exceeded, &reservation_exceeded, &cost_exceeded));
@@ -671,14 +696,10 @@ Status OlapScanner::_prepare_seq_map_candidate_keys() {
671696
_seq_map_candidate_fallback_reason = "candidate_key_bytes_limit";
672697
return Status::OK();
673698
}
674-
if (!query_options.enable_inverted_index_query) {
675-
++stats->seq_map_candidate_fallbacks;
676-
_seq_map_candidate_fallback_reason = "inverted_index_query_disabled";
677-
return Status::OK();
678-
}
679-
680699
std::vector<std::shared_ptr<ColumnPredicate>> key_predicates;
681700
std::map<uint32_t, std::vector<std::shared_ptr<ColumnPredicate>>> group_drivers;
701+
std::map<uint32_t, std::vector<FunctionFilter>> group_function_drivers;
702+
bool has_disabled_inverted_driver = false;
682703
const auto& value_to_seq = schema->value_col_idx_to_seq_col_idx();
683704
for (const auto& predicate : params.predicates) {
684705
const auto cid = predicate->column_id();
@@ -697,17 +718,55 @@ Status OlapScanner::_prepare_seq_map_candidate_keys() {
697718
if (!positive_driver || schema->inverted_indexs(column).empty()) {
698719
continue;
699720
}
721+
if (!query_options.enable_inverted_index_query) {
722+
has_disabled_inverted_driver = true;
723+
continue;
724+
}
700725
group_drivers[seq_it->second].push_back(predicate);
701726
++stats->seq_map_candidate_driver_predicates;
702727
}
703728

729+
for (const auto& filter : params.function_filters) {
730+
if (filter._opposite || !config::enable_query_like_bloom_filter) {
731+
continue;
732+
}
733+
const int32_t cid = schema->field_index(filter._col_name);
734+
DORIS_CHECK_GE(cid, 0);
735+
const auto& column = schema->column(cid);
736+
if (column.is_key()) {
737+
continue;
738+
}
739+
const auto seq_it = value_to_seq.find(cid);
740+
if (seq_it == value_to_seq.end()) {
741+
continue;
742+
}
743+
const auto* ngram_index = schema->get_ngram_bf_index(column.unique_id());
744+
if (ngram_index == nullptr ||
745+
!_has_usable_ngram_bf_pattern(filter, ngram_index->get_gram_size())) {
746+
continue;
747+
}
748+
group_drivers.try_emplace(seq_it->second);
749+
group_function_drivers[seq_it->second].push_back(filter);
750+
++stats->seq_map_candidate_driver_predicates;
751+
}
752+
704753
if (group_drivers.empty()) {
705754
++stats->seq_map_candidate_fallbacks;
706-
_seq_map_candidate_fallback_reason = "no_indexed_positive_driver";
755+
_seq_map_candidate_fallback_reason = has_disabled_inverted_driver
756+
? "inverted_index_query_disabled"
757+
: "no_indexed_positive_driver";
707758
return Status::OK();
708759
}
709760
stats->seq_map_candidate_driver_groups = group_drivers.size();
710-
if (!params.start_key.empty() || !params.end_key.empty()) {
761+
// Full-key ranges are already selective. Prefix ranges such as advertiser_id = ?
762+
// still leave enough rows for a value-column candidate scan to be useful.
763+
const auto range_uses_full_key =
764+
[key_column_count = schema->num_key_columns()](const std::vector<OlapTuple>& keys) {
765+
return std::ranges::any_of(keys, [key_column_count](const OlapTuple& key) {
766+
return key.size() >= key_column_count;
767+
});
768+
};
769+
if (range_uses_full_key(params.start_key) || range_uses_full_key(params.end_key)) {
711770
++stats->seq_map_candidate_fallbacks;
712771
_seq_map_candidate_fallback_reason = "key_range_present";
713772
return Status::OK();
@@ -725,8 +784,9 @@ Status OlapScanner::_prepare_seq_map_candidate_keys() {
725784
try {
726785
++enable_thread_catch_bad_alloc;
727786
Defer restore_bad_alloc_catch {[&] { --enable_thread_catch_bad_alloc; }};
728-
build_status = _build_seq_map_candidate_keys(key_predicates, group_drivers,
729-
max_candidate_keys, memory_budget, cost_limit);
787+
build_status =
788+
_build_seq_map_candidate_keys(key_predicates, group_drivers, group_function_drivers,
789+
max_candidate_keys, memory_budget, cost_limit);
730790
} catch (const Exception& exception) {
731791
build_status = exception.code() == ErrorCode::MEM_ALLOC_FAILED
732792
? Status::MemoryLimitExceeded(exception.to_string())
@@ -1635,6 +1695,8 @@ void OlapScanner::_collect_profile_before_close() {
16351695
stats.seq_map_candidate_scan_bytes);
16361696
COUNTER_UPDATE(local_state->_seq_map_candidate_index_filtered_rows_counter,
16371697
stats.seq_map_candidate_index_filtered_rows);
1698+
COUNTER_UPDATE(local_state->_seq_map_candidate_bloom_filter_filtered_rows_counter,
1699+
stats.seq_map_candidate_bloom_filter_filtered_rows);
16381700
COUNTER_UPDATE(local_state->_seq_map_candidate_index_downgrades_counter,
16391701
stats.seq_map_candidate_index_downgrades);
16401702
COUNTER_UPDATE(local_state->_seq_map_candidate_index_lookup_timer,

be/src/exec/scan/olap_scanner.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,12 @@ class OlapScanner : public Scanner {
147147
[[nodiscard]] Status _build_seq_map_candidate_keys(
148148
const std::vector<std::shared_ptr<ColumnPredicate>>& key_predicates,
149149
const std::map<uint32_t, std::vector<std::shared_ptr<ColumnPredicate>>>& group_drivers,
150+
const std::map<uint32_t, std::vector<FunctionFilter>>& group_function_drivers,
150151
int64_t max_candidate_keys, const CandidateMemoryBudget& memory_budget,
151152
const CandidateScanCostLimit& cost_limit);
152153
[[nodiscard]] Status _collect_seq_map_candidate_keys(
153154
const std::vector<std::shared_ptr<ColumnPredicate>>& driver_predicates,
155+
const std::vector<FunctionFilter>& driver_function_filters,
154156
const std::vector<std::shared_ptr<ColumnPredicate>>& key_predicates,
155157
int64_t previous_candidate_scan_rows, bool price_point_lookups,
156158
int64_t max_candidate_keys, size_t max_candidate_bytes,
@@ -176,6 +178,8 @@ class OlapScanner : public Scanner {
176178
[[nodiscard]] static bool _is_candidate_memory_failure(const Status& status);
177179
static void _record_seq_map_candidate_fallback_reason(RuntimeProfile* profile,
178180
const std::string& fallback_reason);
181+
[[nodiscard]] static bool _has_usable_ngram_bf_pattern(const FunctionFilter& filter,
182+
size_t gram_size);
179183
[[nodiscard]] static size_t _estimate_candidate_key_bytes(const std::string& encoded_key,
180184
size_t key_column_count);
181185
[[nodiscard]] size_t _estimate_candidate_map_bytes(const CandidateKeyMap& candidate_keys) const;

be/src/storage/olap_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ struct OlapReaderStatistics {
295295
int64_t seq_map_candidate_scan_rows = 0;
296296
int64_t seq_map_candidate_scan_bytes = 0;
297297
int64_t seq_map_candidate_index_filtered_rows = 0;
298+
int64_t seq_map_candidate_bloom_filter_filtered_rows = 0;
298299
int64_t seq_map_candidate_index_downgrades = 0;
299300
int64_t seq_map_candidate_index_lookup_ns = 0;
300301
int64_t seq_map_candidate_cache_local_bytes = 0;

be/test/exec/scan/olap_scanner_test.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include "cloud/config.h"
2626
#include "common/config.h"
27+
#include "exprs/function_filter.h"
2728
#include "io/io_common.h"
2829
#include "runtime/runtime_profile.h"
2930
#include "testutil/mock/mock_runtime_state.h"
@@ -250,6 +251,16 @@ TEST(OlapScannerTest, CandidateMemoryFailuresAreClassifiedForFallback) {
250251
EXPECT_FALSE(OlapScanner::_is_candidate_memory_failure(Status::Cancelled("query cancelled")));
251252
}
252253

254+
TEST(OlapScannerTest, CandidateNgramPatternMustContainCompleteGram) {
255+
FunctionFilter usable(false, "title", nullptr, StringRef("%have%"));
256+
FunctionFilter too_short(false, "title", nullptr, StringRef("%ha%"));
257+
FunctionFilter split_by_wildcard(false, "title", nullptr, StringRef("%h_ve%"));
258+
259+
EXPECT_TRUE(OlapScanner::_has_usable_ngram_bf_pattern(usable, 3));
260+
EXPECT_FALSE(OlapScanner::_has_usable_ngram_bf_pattern(too_short, 3));
261+
EXPECT_FALSE(OlapScanner::_has_usable_ngram_bf_pattern(split_by_wildcard, 3));
262+
}
263+
253264
TEST(OlapScannerTest, CandidateFallbackReasonsDoNotOverwriteEachOther) {
254265
RuntimeProfile profile("candidate fallback reasons");
255266

@@ -271,6 +282,7 @@ TEST(OlapScannerTest, CandidateStatsMergeFullFileCacheAccounting) {
271282
candidate_stats.raw_rows_read = 11;
272283
candidate_stats.uncompressed_bytes_read = 12;
273284
candidate_stats.rows_inverted_index_filtered = 13;
285+
candidate_stats.rows_bf_filtered = 14;
274286
candidate_stats.inverted_index_downgrade_count = 14;
275287
candidate_stats.inverted_index_lookup_timer = 15;
276288
candidate_stats.io_ns = 16;
@@ -293,6 +305,7 @@ TEST(OlapScannerTest, CandidateStatsMergeFullFileCacheAccounting) {
293305
EXPECT_EQ(11, total_stats.seq_map_candidate_scan_rows);
294306
EXPECT_EQ(12, total_stats.seq_map_candidate_scan_bytes);
295307
EXPECT_EQ(13, total_stats.seq_map_candidate_index_filtered_rows);
308+
EXPECT_EQ(14, total_stats.seq_map_candidate_bloom_filter_filtered_rows);
296309
EXPECT_EQ(14, total_stats.seq_map_candidate_index_downgrades);
297310
EXPECT_EQ(15, total_stats.seq_map_candidate_index_lookup_ns);
298311
EXPECT_EQ(20, total_stats.seq_map_candidate_cache_local_bytes);

regression-test/data/unique_seq_map_p0/test_seq_map_candidate_key_scan.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,22 @@
2424
1
2525
3
2626

27+
-- !candidate_ngram_like --
28+
1
29+
30+
-- !candidate_ngram_short_pattern --
31+
1
32+
33+
-- !candidate_ngram_not_like --
34+
5002
35+
2736
-- !candidate_index_disabled --
2837
1
2938
3
3039

40+
-- !candidate_ngram_with_inverted_disabled --
41+
1
42+
3143
-- !candidate_no_driver --
3244
2
3345

0 commit comments

Comments
 (0)