This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new aa564ea61dd [feature](ann-index) Add ann topn small candidate fallback
session va… (#64555)
aa564ea61dd is described below
commit aa564ea61dd7faaee3a33d7cc3a2ba420c091a77
Author: Qi Chen <[email protected]>
AuthorDate: Wed Jun 17 17:27:50 2026 +0800
[feature](ann-index) Add ann topn small candidate fallback session va…
(#64555)
### What problem does this PR solve?
Issue Number: close #xxx
Related PR: #xxx
Problem Summary:
### Release note
Cherry-pick #64243
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/src/exec/operator/olap_scan_operator.cpp | 8 +
be/src/exec/operator/olap_scan_operator.h | 4 +
be/src/exec/scan/olap_scanner.cpp | 8 +
be/src/exec/scan/vector_search_user_params.cpp | 24 ++-
be/src/exec/scan/vector_search_user_params.h | 7 +
be/src/exprs/vectorized_fn_call.cpp | 17 +-
be/src/exprs/vectorized_fn_call.h | 3 +-
be/src/exprs/vexpr.cpp | 2 +-
be/src/exprs/vexpr.h | 4 +-
be/src/exprs/vexpr_context.cpp | 6 +-
be/src/exprs/vexpr_context.h | 4 +-
be/src/exprs/virtual_slot_ref.cpp | 8 +-
be/src/exprs/virtual_slot_ref.h | 3 +-
be/src/runtime/runtime_state.h | 8 +
be/src/storage/index/ann/ann_search_params.h | 12 +-
be/src/storage/index/ann/ann_topn_runtime.h | 2 +
be/src/storage/olap_common.h | 4 +
be/src/storage/segment/segment_iterator.cpp | 20 +-
.../storage/index/ann/ann_index_edge_case_test.cpp | 60 ++++++
.../storage/index/ann/ann_range_search_test.cpp | 27 +--
.../java/org/apache/doris/qe/SessionVariable.java | 38 ++++
gensrc/thrift/PaloInternalService.thrift | 5 +
.../ann_topn_small_candidate_fallback.groovy | 231 +++++++++++++++++++++
23 files changed, 467 insertions(+), 38 deletions(-)
diff --git a/be/src/exec/operator/olap_scan_operator.cpp
b/be/src/exec/operator/olap_scan_operator.cpp
index 12aabf2d457..da855aff32d 100644
--- a/be/src/exec/operator/olap_scan_operator.cpp
+++ b/be/src/exec/operator/olap_scan_operator.cpp
@@ -374,6 +374,14 @@ Status OlapScanLocalState::_init_profile() {
"AnnIndexRangeResultPostProcessCosts");
_ann_fallback_brute_force_cnt =
ADD_COUNTER(_segment_profile, "AnnIndexFallbackBruteForceCnt",
TUnit::UNIT);
+ _ann_topn_fallback_by_small_candidate_cnt =
+ ADD_COUNTER(_segment_profile,
"AnnIndexTopNFallbackBySmallCandidateCnt", TUnit::UNIT);
+ _ann_topn_fallback_small_candidate_rows =
+ ADD_COUNTER(_segment_profile,
"AnnIndexTopNFallbackSmallCandidateRows", TUnit::UNIT);
+ _ann_range_fallback_by_small_candidate_cnt =
+ ADD_COUNTER(_segment_profile,
"AnnIndexRangeFallbackBySmallCandidateCnt", TUnit::UNIT);
+ _ann_range_fallback_small_candidate_rows =
+ ADD_COUNTER(_segment_profile,
"AnnIndexRangeFallbackSmallCandidateRows", TUnit::UNIT);
_variant_scan_sparse_column_timer = ADD_TIMER(_segment_profile,
"VariantScanSparseColumnTimer");
_variant_scan_sparse_column_bytes =
ADD_COUNTER(_segment_profile, "VariantScanSparseColumnBytes",
TUnit::BYTES);
diff --git a/be/src/exec/operator/olap_scan_operator.h
b/be/src/exec/operator/olap_scan_operator.h
index 5bf32f7b870..cfc16bd65d0 100644
--- a/be/src/exec/operator/olap_scan_operator.h
+++ b/be/src/exec/operator/olap_scan_operator.h
@@ -258,6 +258,10 @@ private:
RuntimeProfile::Counter* _ann_range_result_convert_costs = nullptr;
RuntimeProfile::Counter* _ann_fallback_brute_force_cnt = nullptr;
+ RuntimeProfile::Counter* _ann_topn_fallback_by_small_candidate_cnt =
nullptr;
+ RuntimeProfile::Counter* _ann_topn_fallback_small_candidate_rows = nullptr;
+ RuntimeProfile::Counter* _ann_range_fallback_by_small_candidate_cnt =
nullptr;
+ RuntimeProfile::Counter* _ann_range_fallback_small_candidate_rows =
nullptr;
RuntimeProfile::Counter* _output_index_result_column_timer = nullptr;
diff --git a/be/src/exec/scan/olap_scanner.cpp
b/be/src/exec/scan/olap_scanner.cpp
index c2d3a945cca..aa21a657fba 100644
--- a/be/src/exec/scan/olap_scanner.cpp
+++ b/be/src/exec/scan/olap_scanner.cpp
@@ -910,6 +910,14 @@ void OlapScanner::_collect_profile_before_close() {
stats.ann_index_topn_result_process_ns);
COUNTER_UPDATE(local_state->_ann_fallback_brute_force_cnt,
stats.ann_fall_back_brute_force_cnt);
+ COUNTER_UPDATE(local_state->_ann_topn_fallback_by_small_candidate_cnt,
+ stats.ann_topn_fallback_by_small_candidate_cnt);
+ COUNTER_UPDATE(local_state->_ann_topn_fallback_small_candidate_rows,
+ stats.ann_topn_fallback_small_candidate_rows);
+ COUNTER_UPDATE(local_state->_ann_range_fallback_by_small_candidate_cnt,
+ stats.ann_range_fallback_by_small_candidate_cnt);
+ COUNTER_UPDATE(local_state->_ann_range_fallback_small_candidate_rows,
+ stats.ann_range_fallback_small_candidate_rows);
// Overhead counter removed; precise instrumentation is reported via
engine_prepare above.
}
diff --git a/be/src/exec/scan/vector_search_user_params.cpp
b/be/src/exec/scan/vector_search_user_params.cpp
index b8e679936ab..c5e969172a4 100644
--- a/be/src/exec/scan/vector_search_user_params.cpp
+++ b/be/src/exec/scan/vector_search_user_params.cpp
@@ -24,13 +24,31 @@ namespace doris {
bool VectorSearchUserParams::operator==(const VectorSearchUserParams& other)
const {
return hnsw_ef_search == other.hnsw_ef_search &&
hnsw_check_relative_distance == other.hnsw_check_relative_distance
&&
- hnsw_bounded_queue == other.hnsw_bounded_queue && ivf_nprobe ==
other.ivf_nprobe;
+ hnsw_bounded_queue == other.hnsw_bounded_queue && ivf_nprobe ==
other.ivf_nprobe &&
+ ann_index_candidate_rows_threshold ==
other.ann_index_candidate_rows_threshold &&
+ ann_index_candidate_rows_percent_threshold ==
+ other.ann_index_candidate_rows_percent_threshold;
+}
+
+bool VectorSearchUserParams::should_fallback_ann_index_by_small_candidate(
+ size_t candidate_rows, size_t rows_of_segment) const {
+ bool reach_absolute_threshold =
+ ann_index_candidate_rows_threshold > 0 &&
+ candidate_rows <
static_cast<size_t>(ann_index_candidate_rows_threshold);
+ bool reach_percent_threshold = ann_index_candidate_rows_percent_threshold
> 0 &&
+ static_cast<double>(candidate_rows) <
+
static_cast<double>(rows_of_segment) *
+
ann_index_candidate_rows_percent_threshold;
+ return reach_absolute_threshold || reach_percent_threshold;
}
std::string VectorSearchUserParams::to_string() const {
return fmt::format(
"hnsw_ef_search: {}, hnsw_check_relative_distance: {}, "
- "hnsw_bounded_queue: {}, ivf_nprobe: {}",
- hnsw_ef_search, hnsw_check_relative_distance, hnsw_bounded_queue,
ivf_nprobe);
+ "hnsw_bounded_queue: {}, ivf_nprobe: {}, "
+ "ann_index_candidate_rows_threshold: {}, "
+ "ann_index_candidate_rows_percent_threshold: {}",
+ hnsw_ef_search, hnsw_check_relative_distance, hnsw_bounded_queue,
ivf_nprobe,
+ ann_index_candidate_rows_threshold,
ann_index_candidate_rows_percent_threshold);
}
} // namespace doris
diff --git a/be/src/exec/scan/vector_search_user_params.h
b/be/src/exec/scan/vector_search_user_params.h
index dfe1bf89880..bef9ff30aaf 100644
--- a/be/src/exec/scan/vector_search_user_params.h
+++ b/be/src/exec/scan/vector_search_user_params.h
@@ -17,6 +17,8 @@
#pragma once
+#include <cstddef>
+#include <cstdint>
#include <string>
namespace doris {
@@ -27,9 +29,14 @@ struct VectorSearchUserParams {
bool hnsw_check_relative_distance = true;
bool hnsw_bounded_queue = true;
int ivf_nprobe = 32;
+ int64_t ann_index_candidate_rows_threshold = 0;
+ double ann_index_candidate_rows_percent_threshold = 0.3;
bool operator==(const VectorSearchUserParams& other) const;
+ bool should_fallback_ann_index_by_small_candidate(size_t candidate_rows,
+ size_t rows_of_segment)
const;
+
std::string to_string() const;
};
#include "common/compile_check_end.h"
diff --git a/be/src/exprs/vectorized_fn_call.cpp
b/be/src/exprs/vectorized_fn_call.cpp
index 40aef9a0e4f..542eaf98512 100644
--- a/be/src/exprs/vectorized_fn_call.cpp
+++ b/be/src/exprs/vectorized_fn_call.cpp
@@ -556,7 +556,8 @@ Status VectorizedFnCall::evaluate_ann_range_search(
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
cid_to_index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats,
AnnRangeSearchEvaluationResult& evaluation_result) {
evaluation_result = {};
if (range_search_runtime.is_ann_range_search == false) {
@@ -611,6 +612,20 @@ Status VectorizedFnCall::evaluate_ann_range_search(
range_search_runtime.dim, index_dim);
}
+ const auto& user_params = range_search_runtime.user_params;
+ if (user_params.should_fallback_ann_index_by_small_candidate(origin_num,
rows_of_segment)) {
+ VLOG_DEBUG << fmt::format(
+ "Ann range search input rows {} reach small candidate
threshold, "
+ "rows_of_segment: {}, absolute_threshold: {},
percent_threshold: {}, "
+ "will not use ann index to filter",
+ origin_num, rows_of_segment,
user_params.ann_index_candidate_rows_threshold,
+ user_params.ann_index_candidate_rows_percent_threshold);
+ ann_index_stats.fall_back_brute_force_cnt += 1;
+ ann_index_stats.range_fallback_by_small_candidate_cnt += 1;
+ ann_index_stats.range_fallback_small_candidate_rows += origin_num;
+ return Status::OK();
+ }
+
auto stats = std::make_unique<segment_v2::AnnIndexStats>();
// Track load index timing
{
diff --git a/be/src/exprs/vectorized_fn_call.h
b/be/src/exprs/vectorized_fn_call.h
index f90206c6d81..61034b34d75 100644
--- a/be/src/exprs/vectorized_fn_call.h
+++ b/be/src/exprs/vectorized_fn_call.h
@@ -92,7 +92,8 @@ public:
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
cid_to_index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats,
AnnRangeSearchEvaluationResult& result) override;
void prepare_ann_range_search(const doris::VectorSearchUserParams& params,
diff --git a/be/src/exprs/vexpr.cpp b/be/src/exprs/vexpr.cpp
index 5c80aecede0..d26d209cb55 100644
--- a/be/src/exprs/vexpr.cpp
+++ b/be/src/exprs/vexpr.cpp
@@ -1017,7 +1017,7 @@ Status VExpr::evaluate_ann_range_search(
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, AnnIndexStats& ann_index_stats,
+ size_t rows_of_segment, roaring::Roaring& row_bitmap, AnnIndexStats&
ann_index_stats,
AnnRangeSearchEvaluationResult& result) {
result = {};
return Status::OK();
diff --git a/be/src/exprs/vexpr.h b/be/src/exprs/vexpr.h
index 196ffd1b082..9ab63b6e31c 100644
--- a/be/src/exprs/vexpr.h
+++ b/be/src/exprs/vexpr.h
@@ -352,8 +352,8 @@ public:
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
cid_to_index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
- AnnRangeSearchEvaluationResult& result);
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats,
AnnRangeSearchEvaluationResult& result);
// Prepare the runtime for ANN range search.
// AnnRangeSearchRuntime is used to store the runtime information of ann
range search.
diff --git a/be/src/exprs/vexpr_context.cpp b/be/src/exprs/vexpr_context.cpp
index 29e0b25cf49..084d466a61d 100644
--- a/be/src/exprs/vexpr_context.cpp
+++ b/be/src/exprs/vexpr_context.cpp
@@ -433,8 +433,8 @@ Status VExprContext::evaluate_ann_range_search(
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
const std::unordered_map<VExprContext*, std::unordered_map<ColumnId,
VExpr*>>&
common_expr_to_slotref_map,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
- bool* ann_range_search_executed) {
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats, bool*
ann_range_search_executed) {
if (ann_range_search_executed != nullptr) {
*ann_range_search_executed = false;
}
@@ -445,7 +445,7 @@ Status VExprContext::evaluate_ann_range_search(
AnnRangeSearchEvaluationResult evaluation_result;
RETURN_IF_ERROR(_root->evaluate_ann_range_search(
_ann_range_search_runtime, cid_to_index_iterators, idx_to_cid,
column_iterators,
- row_bitmap, ann_index_stats, evaluation_result));
+ rows_of_segment, row_bitmap, ann_index_stats, evaluation_result));
if (!evaluation_result.executed) {
return Status::OK();
diff --git a/be/src/exprs/vexpr_context.h b/be/src/exprs/vexpr_context.h
index ccd385d3a93..b07027ef162 100644
--- a/be/src/exprs/vexpr_context.h
+++ b/be/src/exprs/vexpr_context.h
@@ -395,8 +395,8 @@ public:
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
const std::unordered_map<VExprContext*,
std::unordered_map<ColumnId, VExpr*>>&
common_expr_to_slotref_map,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
- bool* ann_range_search_executed);
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats, bool*
ann_range_search_executed);
uint64_t get_digest(uint64_t seed) const;
diff --git a/be/src/exprs/virtual_slot_ref.cpp
b/be/src/exprs/virtual_slot_ref.cpp
index 66448b39af1..01f8f708cab 100644
--- a/be/src/exprs/virtual_slot_ref.cpp
+++ b/be/src/exprs/virtual_slot_ref.cpp
@@ -234,11 +234,11 @@ Status VirtualSlotRef::evaluate_ann_range_search(
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
cid_to_index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
- AnnRangeSearchEvaluationResult& result) {
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats,
AnnRangeSearchEvaluationResult& result) {
return _virtual_column_expr->evaluate_ann_range_search(
- range_search_runtime, cid_to_index_iterators, idx_to_cid,
column_iterators, row_bitmap,
- ann_index_stats, result);
+ range_search_runtime, cid_to_index_iterators, idx_to_cid,
column_iterators,
+ rows_of_segment, row_bitmap, ann_index_stats, result);
}
#include "common/compile_check_end.h"
} // namespace doris
diff --git a/be/src/exprs/virtual_slot_ref.h b/be/src/exprs/virtual_slot_ref.h
index 382a3b45e34..8fbf2567719 100644
--- a/be/src/exprs/virtual_slot_ref.h
+++ b/be/src/exprs/virtual_slot_ref.h
@@ -107,7 +107,8 @@ public:
const std::vector<std::unique_ptr<segment_v2::IndexIterator>>&
cid_to_index_iterators,
const std::vector<ColumnId>& idx_to_cid,
const std::vector<std::unique_ptr<segment_v2::ColumnIterator>>&
column_iterators,
- roaring::Roaring& row_bitmap, segment_v2::AnnIndexStats&
ann_index_stats,
+ size_t rows_of_segment, roaring::Roaring& row_bitmap,
+ segment_v2::AnnIndexStats& ann_index_stats,
AnnRangeSearchEvaluationResult& result) override;
#ifdef BE_TEST
diff --git a/be/src/runtime/runtime_state.h b/be/src/runtime/runtime_state.h
index b8a85d097f9..13e4b7b6355 100644
--- a/be/src/runtime/runtime_state.h
+++ b/be/src/runtime/runtime_state.h
@@ -845,6 +845,14 @@ public:
params.hnsw_check_relative_distance =
_query_options.hnsw_check_relative_distance;
params.hnsw_bounded_queue = _query_options.hnsw_bounded_queue;
params.ivf_nprobe = _query_options.ivf_nprobe;
+ params.ann_index_candidate_rows_threshold =
+ _query_options.__isset.ann_index_candidate_rows_threshold
+ ? _query_options.ann_index_candidate_rows_threshold
+ : 0;
+ params.ann_index_candidate_rows_percent_threshold =
+
_query_options.__isset.ann_index_candidate_rows_percent_threshold
+ ?
_query_options.ann_index_candidate_rows_percent_threshold
+ : 0.3;
return params;
}
diff --git a/be/src/storage/index/ann/ann_search_params.h
b/be/src/storage/index/ann/ann_search_params.h
index 95d8d11dd83..9ad0f9d30c5 100644
--- a/be/src/storage/index/ann/ann_search_params.h
+++ b/be/src/storage/index/ann/ann_search_params.h
@@ -61,7 +61,9 @@ struct AnnIndexStats {
ivf_on_disk_search_cnt(TUnit::UNIT, 0),
ivf_on_disk_cache_hit_cnt(TUnit::UNIT, 0),
ivf_on_disk_cache_miss_cnt(TUnit::UNIT, 0),
- fall_back_brute_force_cnt(0) {}
+ fall_back_brute_force_cnt(0),
+ range_fallback_by_small_candidate_cnt(0),
+ range_fallback_small_candidate_rows(0) {}
AnnIndexStats(const AnnIndexStats& other)
: search_costs_ns(TUnit::TIME_NS, other.search_costs_ns.value()),
@@ -76,7 +78,9 @@ struct AnnIndexStats {
ivf_on_disk_search_cnt(TUnit::UNIT,
other.ivf_on_disk_search_cnt.value()),
ivf_on_disk_cache_hit_cnt(TUnit::UNIT,
other.ivf_on_disk_cache_hit_cnt.value()),
ivf_on_disk_cache_miss_cnt(TUnit::UNIT,
other.ivf_on_disk_cache_miss_cnt.value()),
- fall_back_brute_force_cnt(other.fall_back_brute_force_cnt) {}
+ fall_back_brute_force_cnt(other.fall_back_brute_force_cnt),
+
range_fallback_by_small_candidate_cnt(other.range_fallback_by_small_candidate_cnt),
+
range_fallback_small_candidate_rows(other.range_fallback_small_candidate_rows)
{}
AnnIndexStats& operator=(const AnnIndexStats& other) {
if (this != &other) {
@@ -92,6 +96,8 @@ struct AnnIndexStats {
ivf_on_disk_cache_hit_cnt.set(other.ivf_on_disk_cache_hit_cnt.value());
ivf_on_disk_cache_miss_cnt.set(other.ivf_on_disk_cache_miss_cnt.value());
fall_back_brute_force_cnt = other.fall_back_brute_force_cnt;
+ range_fallback_by_small_candidate_cnt =
other.range_fallback_by_small_candidate_cnt;
+ range_fallback_small_candidate_rows =
other.range_fallback_small_candidate_rows;
}
return *this;
}
@@ -109,6 +115,8 @@ struct AnnIndexStats {
RuntimeProfile::Counter ivf_on_disk_cache_hit_cnt; // IVF_ON_DISK cache
hit count
RuntimeProfile::Counter ivf_on_disk_cache_miss_cnt; // IVF_ON_DISK cache
miss count
int64_t fall_back_brute_force_cnt; // fallback count when ANN range search
is bypassed
+ int64_t range_fallback_by_small_candidate_cnt;
+ int64_t range_fallback_small_candidate_rows;
};
struct AnnTopNParam {
diff --git a/be/src/storage/index/ann/ann_topn_runtime.h
b/be/src/storage/index/ann/ann_topn_runtime.h
index 63e04cc30b6..05167f76317 100644
--- a/be/src/storage/index/ann/ann_topn_runtime.h
+++ b/be/src/storage/index/ann/ann_topn_runtime.h
@@ -151,6 +151,8 @@ public:
*/
bool is_asc() const { return _asc; }
+ const doris::VectorSearchUserParams& user_params() const { return
_user_params; }
+
private:
// Core configuration
const bool _asc; ///< Sort order for results
diff --git a/be/src/storage/olap_common.h b/be/src/storage/olap_common.h
index 8219acf28b8..4c9a4fd8352 100644
--- a/be/src/storage/olap_common.h
+++ b/be/src/storage/olap_common.h
@@ -400,6 +400,10 @@ struct OlapReaderStatistics {
int64_t ann_range_engine_convert_ns = 0; // time spent on FAISS-side
conversions (Range)
int64_t rows_ann_index_range_filtered = 0;
int64_t ann_fall_back_brute_force_cnt = 0;
+ int64_t ann_topn_fallback_by_small_candidate_cnt = 0;
+ int64_t ann_topn_fallback_small_candidate_rows = 0;
+ int64_t ann_range_fallback_by_small_candidate_cnt = 0;
+ int64_t ann_range_fallback_small_candidate_rows = 0;
int64_t output_index_result_column_timer = 0;
// number of segment filtered by column stat when creating seg iterator
diff --git a/be/src/storage/segment/segment_iterator.cpp
b/be/src/storage/segment/segment_iterator.cpp
index d5e9b68437e..8c9ccb7424d 100644
--- a/be/src/storage/segment/segment_iterator.cpp
+++ b/be/src/storage/segment/segment_iterator.cpp
@@ -933,15 +933,19 @@ Status SegmentIterator::_apply_ann_topn_predicate() {
size_t pre_size = _row_bitmap.cardinality();
size_t rows_of_segment = _segment->num_rows();
- if (static_cast<double>(pre_size) < static_cast<double>(rows_of_segment) *
0.3) {
+ const auto& user_params = _ann_topn_runtime->user_params();
+ if (user_params.should_fallback_ann_index_by_small_candidate(pre_size,
rows_of_segment)) {
VLOG_DEBUG << fmt::format(
- "Ann topn predicate input rows {} < 30% of segment rows {},
will not use ann index "
- "to "
- "filter",
- pre_size, rows_of_segment);
+ "Ann topn predicate input rows {} reach small candidate
threshold, "
+ "rows_of_segment: {}, absolute_threshold: {},
percent_threshold: {}, "
+ "will not use ann index to filter",
+ pre_size, rows_of_segment,
user_params.ann_index_candidate_rows_threshold,
+ user_params.ann_index_candidate_rows_percent_threshold);
// Disable index-only scan on ann indexed column.
_need_read_data_indices[src_cid] = true;
_opts.stats->ann_fall_back_brute_force_cnt += 1;
+ _opts.stats->ann_topn_fallback_by_small_candidate_cnt += 1;
+ _opts.stats->ann_topn_fallback_small_candidate_rows += pre_size;
return Status::OK();
}
IColumn::MutablePtr result_column;
@@ -1238,7 +1242,7 @@ Status SegmentIterator::_apply_index_expr() {
bool ann_range_search_executed = false;
RETURN_IF_ERROR(expr_ctx->evaluate_ann_range_search(
_index_iterators, _schema->column_ids(), _column_iterators,
- _common_expr_to_slotref_map, _row_bitmap, ann_index_stats,
+ _common_expr_to_slotref_map, num_rows(), _row_bitmap,
ann_index_stats,
&ann_range_search_executed));
if (ann_range_search_executed) {
_opts.stats->ann_index_range_search_cnt++;
@@ -1256,6 +1260,10 @@ Status SegmentIterator::_apply_index_expr() {
_opts.stats->ann_range_engine_convert_ns +=
ann_index_stats.engine_convert_ns.value();
_opts.stats->ann_range_pre_process_ns +=
ann_index_stats.engine_prepare_ns.value();
_opts.stats->ann_fall_back_brute_force_cnt +=
ann_index_stats.fall_back_brute_force_cnt;
+ _opts.stats->ann_range_fallback_by_small_candidate_cnt +=
+ ann_index_stats.range_fallback_by_small_candidate_cnt;
+ _opts.stats->ann_range_fallback_small_candidate_rows +=
+ ann_index_stats.range_fallback_small_candidate_rows;
}
return Status::OK();
diff --git a/be/test/storage/index/ann/ann_index_edge_case_test.cpp
b/be/test/storage/index/ann/ann_index_edge_case_test.cpp
index aa6d7637dd4..e4ab446c928 100644
--- a/be/test/storage/index/ann/ann_index_edge_case_test.cpp
+++ b/be/test/storage/index/ann/ann_index_edge_case_test.cpp
@@ -22,11 +22,13 @@
#include <string>
#include <vector>
+#include "runtime/runtime_state.h"
#include "storage/index/ann/ann_index_iterator.h"
#include "storage/index/ann/ann_index_reader.h"
#include "storage/index/ann/ann_index_writer.h"
#include "storage/index/ann/faiss_ann_index.h"
#include "storage/index/ann/vector_search_utils.h"
+#include "testutil/mock/mock_query_context.h"
using namespace doris::vector_search_utils;
@@ -38,24 +40,34 @@ TEST_F(VectorSearchTest, TestAnnIndexStatsInitialization) {
// Test initial values
EXPECT_EQ(stats.search_costs_ns.value(), 0);
EXPECT_EQ(stats.load_index_costs_ns.value(), 0);
+ EXPECT_EQ(stats.range_fallback_by_small_candidate_cnt, 0);
+ EXPECT_EQ(stats.range_fallback_small_candidate_rows, 0);
// Test setting values
stats.search_costs_ns.set(1000L);
stats.load_index_costs_ns.set(2000L);
+ stats.range_fallback_by_small_candidate_cnt = 1;
+ stats.range_fallback_small_candidate_rows = 3;
EXPECT_EQ(stats.search_costs_ns.value(), 1000);
EXPECT_EQ(stats.load_index_costs_ns.value(), 2000);
+ EXPECT_EQ(stats.range_fallback_by_small_candidate_cnt, 1);
+ EXPECT_EQ(stats.range_fallback_small_candidate_rows, 3);
}
TEST_F(VectorSearchTest, TestAnnIndexStatsCopyConstructor) {
doris::segment_v2::AnnIndexStats original;
original.search_costs_ns.set(1500L);
original.load_index_costs_ns.set(2500L);
+ original.range_fallback_by_small_candidate_cnt = 1;
+ original.range_fallback_small_candidate_rows = 3;
doris::segment_v2::AnnIndexStats copied(original);
EXPECT_EQ(copied.search_costs_ns.value(), 1500);
EXPECT_EQ(copied.load_index_costs_ns.value(), 2500);
+ EXPECT_EQ(copied.range_fallback_by_small_candidate_cnt, 1);
+ EXPECT_EQ(copied.range_fallback_small_candidate_rows, 3);
}
TEST_F(VectorSearchTest, TestAnnRangeSearchParamsToString) {
@@ -119,6 +131,25 @@ TEST_F(VectorSearchTest,
TestVectorSearchUserParamsDefaultValues) {
EXPECT_EQ(params.hnsw_ef_search, 32);
EXPECT_EQ(params.hnsw_check_relative_distance, true);
EXPECT_EQ(params.hnsw_bounded_queue, true);
+ EXPECT_EQ(params.ivf_nprobe, 32);
+ EXPECT_EQ(params.ann_index_candidate_rows_threshold, 0);
+ EXPECT_EQ(params.ann_index_candidate_rows_percent_threshold, 0.3);
+}
+
+TEST_F(VectorSearchTest, TestVectorSearchUserParamsSmallCandidateFallback) {
+ doris::VectorSearchUserParams params;
+ params.ann_index_candidate_rows_percent_threshold = 0;
+
+ EXPECT_FALSE(params.should_fallback_ann_index_by_small_candidate(3, 10));
+
+ params.ann_index_candidate_rows_threshold = 4;
+ EXPECT_TRUE(params.should_fallback_ann_index_by_small_candidate(3, 10));
+ EXPECT_FALSE(params.should_fallback_ann_index_by_small_candidate(4, 10));
+
+ params.ann_index_candidate_rows_threshold = 0;
+ params.ann_index_candidate_rows_percent_threshold = 0.3;
+ EXPECT_TRUE(params.should_fallback_ann_index_by_small_candidate(2, 10));
+ EXPECT_FALSE(params.should_fallback_ann_index_by_small_candidate(3, 10));
}
TEST_F(VectorSearchTest, TestVectorSearchUserParamsEquality) {
@@ -137,6 +168,35 @@ TEST_F(VectorSearchTest,
TestVectorSearchUserParamsEquality) {
// Test inequality
params2.hnsw_ef_search = 50;
EXPECT_NE(params1, params2);
+
+ params2.hnsw_ef_search = 100;
+ params2.ann_index_candidate_rows_threshold = 10;
+ EXPECT_NE(params1, params2);
+
+ params2.ann_index_candidate_rows_threshold = 0;
+ params2.ann_index_candidate_rows_percent_threshold = 0.1;
+ EXPECT_NE(params1, params2);
+}
+
+TEST_F(VectorSearchTest, TestRuntimeStateVectorSearchUserParams) {
+ TQueryOptions query_options;
+ query_options.__set_hnsw_ef_search(64);
+ query_options.__set_hnsw_check_relative_distance(false);
+ query_options.__set_hnsw_bounded_queue(false);
+ query_options.__set_ivf_nprobe(8);
+ query_options.__set_ann_index_candidate_rows_threshold(100);
+ query_options.__set_ann_index_candidate_rows_percent_threshold(0.2);
+
+ auto query_ctx = MockQueryContext::create();
+ TQueryGlobals query_globals;
+ RuntimeState state(TUniqueId(), 0, query_options, query_globals, nullptr,
query_ctx.get());
+ auto params = state.get_vector_search_params();
+ EXPECT_EQ(params.hnsw_ef_search, 64);
+ EXPECT_EQ(params.hnsw_check_relative_distance, false);
+ EXPECT_EQ(params.hnsw_bounded_queue, false);
+ EXPECT_EQ(params.ivf_nprobe, 8);
+ EXPECT_EQ(params.ann_index_candidate_rows_threshold, 100);
+ EXPECT_EQ(params.ann_index_candidate_rows_percent_threshold, 0.2);
}
TEST_F(VectorSearchTest, TestIndexSearchResultInitialization) {
diff --git a/be/test/storage/index/ann/ann_range_search_test.cpp
b/be/test/storage/index/ann/ann_range_search_test.cpp
index 400e822695c..1beab6a9559 100644
--- a/be/test/storage/index/ann/ann_range_search_test.cpp
+++ b/be/test/storage/index/ann/ann_range_search_test.cpp
@@ -188,7 +188,8 @@ TEST_F(VectorSearchTest, TestEvaluateAnnRangeSearch) {
ASSERT_TRUE(range_search_ctx
->evaluate_ann_range_search(cid_to_index_iterators,
idx_to_cid,
column_iterators,
common_expr_to_slotref_map,
- row_bitmap, stats,
&ann_range_search_executed)
+ row_bitmap.cardinality(),
row_bitmap, stats,
+ &ann_range_search_executed)
.ok());
EXPECT_TRUE(ann_range_search_executed);
@@ -291,7 +292,8 @@ TEST_F(VectorSearchTest, TestEvaluateAnnRangeSearch2) {
ASSERT_TRUE(range_search_ctx
->evaluate_ann_range_search(cid_to_index_iterators,
idx_to_cid,
column_iterators,
common_expr_to_slotref_map,
- row_bitmap, stats,
&ann_range_search_executed)
+ row_bitmap.cardinality(),
row_bitmap, stats,
+ &ann_range_search_executed)
.ok());
EXPECT_TRUE(ann_range_search_executed);
@@ -372,10 +374,10 @@ TEST_F(VectorSearchTest,
TestEvaluateAnnRangeSearchStateDoesNotLeakAcrossClones)
common_expr_to_slotref_map;
bool ann_range_search_executed = false;
ASSERT_TRUE(segment_with_ann_ctx
- ->evaluate_ann_range_search(ann_index_iterators,
idx_to_cid,
- ann_column_iterators,
-
common_expr_to_slotref_map, ann_row_bitmap,
- ann_stats,
&ann_range_search_executed)
+ ->evaluate_ann_range_search(
+ ann_index_iterators, idx_to_cid,
ann_column_iterators,
+ common_expr_to_slotref_map,
ann_row_bitmap.cardinality(),
+ ann_row_bitmap, ann_stats,
&ann_range_search_executed)
.ok());
EXPECT_TRUE(ann_range_search_executed);
const auto* ann_result =
segment_with_ann_ctx->get_index_context()->get_index_result_for_expr(
@@ -396,10 +398,10 @@ TEST_F(VectorSearchTest,
TestEvaluateAnnRangeSearchStateDoesNotLeakAcrossClones)
segment_v2::AnnIndexStats no_ann_stats;
bool no_ann_range_search_executed = true;
ASSERT_TRUE(segment_without_ann_ctx
- ->evaluate_ann_range_search(no_ann_index_iterators,
idx_to_cid,
- no_ann_column_iterators,
-
common_expr_to_slotref_map, no_ann_row_bitmap,
- no_ann_stats,
&no_ann_range_search_executed)
+ ->evaluate_ann_range_search(
+ no_ann_index_iterators, idx_to_cid,
no_ann_column_iterators,
+ common_expr_to_slotref_map,
no_ann_row_bitmap.cardinality(),
+ no_ann_row_bitmap, no_ann_stats,
&no_ann_range_search_executed)
.ok());
EXPECT_FALSE(no_ann_range_search_executed);
EXPECT_FALSE(segment_without_ann_ctx->get_index_context()->has_index_result_for_expr(
@@ -477,7 +479,8 @@ TEST_F(VectorSearchTest,
TestEvaluateAnnRangeSearchUsesSourceColumnIndexForSlotM
ASSERT_TRUE(range_search_ctx
->evaluate_ann_range_search(cid_to_index_iterators,
idx_to_cid,
column_iterators,
common_expr_to_slotref_map,
- row_bitmap, stats,
&ann_range_search_executed)
+ row_bitmap.cardinality(),
row_bitmap, stats,
+ &ann_range_search_executed)
.ok());
EXPECT_TRUE(ann_range_search_executed);
EXPECT_TRUE(common_expr_index_status[5][range_search_ctx->root().get()]);
@@ -868,7 +871,7 @@ TEST_F(VectorSearchTest,
TestEvaluateAnnRangeSearch_DimensionMismatch) {
auto st = range_search_ctx->evaluate_ann_range_search(
cid_to_index_iterators, idx_to_cid, column_iterators,
common_expr_to_slotref_map,
- row_bitmap, stats, nullptr);
+ row_bitmap.cardinality(), row_bitmap, stats, nullptr);
EXPECT_FALSE(st.ok());
EXPECT_TRUE(st.is<doris::ErrorCode::INVALID_ARGUMENT>());
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index dfae86f84f3..9242527f155 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -959,6 +959,10 @@ public class SessionVariable implements Serializable,
Writable {
public static final String HNSW_CHECK_RELATIVE_DISTANCE =
"hnsw_check_relative_distance";
public static final String HNSW_BOUNDED_QUEUE = "hnsw_bounded_queue";
public static final String IVF_NPROBE = "ivf_nprobe";
+ public static final String ANN_INDEX_CANDIDATE_ROWS_THRESHOLD =
+ "ann_index_candidate_rows_threshold";
+ public static final String ANN_INDEX_CANDIDATE_ROWS_PERCENT_THRESHOLD =
+ "ann_index_candidate_rows_percent_threshold";
public static final String DEFAULT_VARIANT_MAX_SUBCOLUMNS_COUNT =
"default_variant_max_subcolumns_count";
@@ -3459,6 +3463,38 @@ public class SessionVariable implements Serializable,
Writable {
"IVF index nprobe parameter, controls the number of
clusters to search"})
public int ivfNprobe = 32;
+ @VariableMgr.VarAttr(name = ANN_INDEX_CANDIDATE_ROWS_THRESHOLD,
needForward = true,
+ checker = "checkAnnIndexCandidateRowsThreshold",
+ description = {"Skip ANN index when candidate rows before ANN
search are less "
+ + "than this threshold. 0 disables the absolute row
threshold",
+ "Skip ANN index when candidate rows before ANN search are
less "
+ + "than this threshold. 0 disables the absolute
row threshold"})
+ public long annIndexCandidateRowsThreshold = 0;
+
+ @VariableMgr.VarAttr(name = ANN_INDEX_CANDIDATE_ROWS_PERCENT_THRESHOLD,
needForward = true,
+ checker = "checkAnnIndexCandidateRowsPercentThreshold",
+ description = {"Skip ANN index when candidate row ratio before ANN
search is less "
+ + "than this threshold",
+ "Skip ANN index when candidate row ratio before ANN search
is less "
+ + "than this threshold"})
+ public double annIndexCandidateRowsPercentThreshold = 0.3;
+
+ public void checkAnnIndexCandidateRowsThreshold(String value) {
+ long threshold = Long.parseLong(value);
+ if (threshold < 0) {
+ throw new InvalidParameterException(
+ ANN_INDEX_CANDIDATE_ROWS_THRESHOLD + " should be greater
than or equal to 0");
+ }
+ }
+
+ public void checkAnnIndexCandidateRowsPercentThreshold(String value) {
+ double threshold = Double.parseDouble(value);
+ if (Double.isNaN(threshold) || Double.isInfinite(threshold) ||
threshold < 0 || threshold > 1) {
+ throw new InvalidParameterException(
+ ANN_INDEX_CANDIDATE_ROWS_PERCENT_THRESHOLD + " should be
between 0 and 1");
+ }
+ }
+
@VariableMgr.VarAttr(
name = DEFAULT_VARIANT_MAX_SUBCOLUMNS_COUNT,
needForward = true,
@@ -5477,6 +5513,8 @@ public class SessionVariable implements Serializable,
Writable {
tResult.setHnswCheckRelativeDistance(hnswCheckRelativeDistance);
tResult.setHnswBoundedQueue(hnswBoundedQueue);
tResult.setIvfNprobe(ivfNprobe);
+
tResult.setAnnIndexCandidateRowsThreshold(annIndexCandidateRowsThreshold);
+
tResult.setAnnIndexCandidateRowsPercentThreshold(annIndexCandidateRowsPercentThreshold);
tResult.setMergeReadSliceSize(mergeReadSliceSizeBytes);
tResult.setEnableExtendedRegex(enableExtendedRegex);
diff --git a/gensrc/thrift/PaloInternalService.thrift
b/gensrc/thrift/PaloInternalService.thrift
index db2835d81ed..71b318f6f0c 100644
--- a/gensrc/thrift/PaloInternalService.thrift
+++ b/gensrc/thrift/PaloInternalService.thrift
@@ -491,6 +491,11 @@ struct TQueryOptions {
// Default 8MB. Sent by FE session variable preferred_block_size_bytes.
218: optional i64 preferred_block_size_bytes = 8388608
+ // ANN search falls back to exact vector distance evaluation when candidate
rows
+ // before ANN search are less than this value. 0 disables the absolute
threshold.
+ 219: optional i64 ann_index_candidate_rows_threshold = 0
+ // Candidate row ratio threshold against segment rows. Existing default is
0.3.
+ 220: optional double ann_index_candidate_rows_percent_threshold = 0.3
// For cloud, to control if the content would be written into file cache
// In write path, to control if the content would be written into file cache.
// In read path, read from file cache or remote storage when execute query.
diff --git
a/regression-test/suites/ann_index_p0/ann_topn_small_candidate_fallback.groovy
b/regression-test/suites/ann_index_p0/ann_topn_small_candidate_fallback.groovy
new file mode 100644
index 00000000000..b4f0e29085b
--- /dev/null
+++
b/regression-test/suites/ann_index_p0/ann_topn_small_candidate_fallback.groovy
@@ -0,0 +1,231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import groovy.json.JsonSlurper
+
+def getProfileList = {
+ def dst = "http://" + context.config.feHttpAddress
+ def conn = new URL(dst + "/rest/v1/query_profile").openConnection()
+ conn.setRequestMethod("GET")
+ def encoding =
Base64.getEncoder().encodeToString((context.config.feHttpUser + ":" +
+ (context.config.feHttpPassword == null ? "" :
context.config.feHttpPassword))
+ .getBytes("UTF-8"))
+ conn.setRequestProperty("Authorization", "Basic ${encoding}")
+ return conn.getInputStream().getText()
+}
+
+def getProfile = { id ->
+ def dst = "http://" + context.config.feHttpAddress
+ def conn = new URL(dst +
"/api/profile/text/?query_id=$id").openConnection()
+ conn.setRequestMethod("GET")
+ def encoding =
Base64.getEncoder().encodeToString((context.config.feHttpUser + ":" +
+ (context.config.feHttpPassword == null ? "" :
context.config.feHttpPassword))
+ .getBytes("UTF-8"))
+ conn.setRequestProperty("Authorization", "Basic ${encoding}")
+ return conn.getInputStream().getText()
+}
+
+def extractCounterValue = { String profileText, String counterName ->
+ for (def line : profileText.split("\n")) {
+ if (line.contains(counterName + ":")) {
+ def m = (line =~
/${java.util.regex.Pattern.quote(counterName)}:\s*([0-9]+(?:\.[0-9]+)?)/)
+ if (m.find()) {
+ return m.group(1)
+ }
+ }
+ }
+ return null
+}
+
+suite("ann_topn_small_candidate_fallback", "nonConcurrent") {
+ def getProfileWithToken = { token ->
+ String profileId = ""
+ int attempts = 0
+ while (attempts < 10 && (profileId == null || profileId == "")) {
+ List profileData = new
JsonSlurper().parseText(getProfileList()).data.rows
+ for (def profileItem in profileData) {
+ if (profileItem["Sql Statement"].toString().contains(token)) {
+ profileId = profileItem["Profile ID"].toString()
+ break
+ }
+ }
+ if (profileId == null || profileId == "") {
+ Thread.sleep(300)
+ }
+ attempts++
+ }
+ assertTrue(profileId != null && profileId != "")
+ Thread.sleep(800)
+ return getProfile(profileId).toString()
+ }
+
+ sql "unset variable all;"
+ sql "set enable_common_expr_pushdown=true;"
+ sql "set experimental_enable_virtual_slot_for_cse=true;"
+ sql "set enable_no_need_read_data_opt=true;"
+ sql "set enable_profile=true;"
+ sql "set profile_level=2;"
+ sql "set parallel_pipeline_task_num=1;"
+ sql "set enable_sql_cache=false;"
+ sql "set enable_condition_cache=false;"
+ sql "set ann_index_candidate_rows_percent_threshold=0;"
+
+ sql "drop table if exists ann_topn_small_candidate_fallback"
+ sql """
+ create table ann_topn_small_candidate_fallback (
+ id int not null,
+ embedding array<float> not null,
+ comment string not null,
+ index idx_comment(`comment`) using inverted properties("parser" =
"english"),
+ index ann_embedding(`embedding`) using ann properties(
+ "index_type"="hnsw",
+ "metric_type"="l2_distance",
+ "dim"="3"
+ )
+ ) duplicate key(id)
+ distributed by hash(id) buckets 1
+ properties("replication_num"="1");
+ """
+
+ sql """
+ insert into ann_topn_small_candidate_fallback values
+ (1, [0.0, 0.0, 0.0], 'small candidate'),
+ (2, [0.2, 0.0, 0.0], 'small candidate'),
+ (3, [0.4, 0.0, 0.0], 'small candidate'),
+ (4, [10.0, 0.0, 0.0], 'large candidate'),
+ (5, [11.0, 0.0, 0.0], 'large candidate'),
+ (6, [12.0, 0.0, 0.0], 'large candidate'),
+ (7, [13.0, 0.0, 0.0], 'large candidate'),
+ (8, [14.0, 0.0, 0.0], 'large candidate'),
+ (9, [15.0, 0.0, 0.0], 'large candidate'),
+ (10, [16.0, 0.0, 0.0], 'large candidate');
+ """
+
+ def defaultRows = sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where comment match_any 'small'
+ order by l2_distance_approximate(embedding, [0.0, 0.0, 0.0])
+ limit 2;
+ """
+ assertEquals([1, 2], defaultRows.collect { it[0] })
+
+ sql "set ann_index_candidate_rows_threshold=4;"
+ def fallbackRows = sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where comment match_any 'small'
+ order by l2_distance_approximate(embedding, [0.0, 0.0, 0.0])
+ limit 2;
+ """
+ assertEquals([1, 2], fallbackRows.collect { it[0] })
+
+ def tokenFallback = UUID.randomUUID().toString()
+ sql """
+ select id, "${tokenFallback}"
+ from ann_topn_small_candidate_fallback
+ where comment match_any 'small'
+ order by l2_distance_approximate(embedding, [0.0, 0.0, 0.0])
+ limit 2;
+ """
+ def fallbackProfile = getProfileWithToken(tokenFallback)
+ def smallCandidateFallbackCnt =
+ extractCounterValue(fallbackProfile,
"AnnIndexTopNFallbackBySmallCandidateCnt")
+ def smallCandidateRows =
+ extractCounterValue(fallbackProfile,
"AnnIndexTopNFallbackSmallCandidateRows")
+ logger.info("small candidate fallback count=${smallCandidateFallbackCnt},
rows=${smallCandidateRows}")
+ assertEquals("1", smallCandidateFallbackCnt)
+ assertEquals("3", smallCandidateRows)
+
+ sql "set ann_index_candidate_rows_threshold=11;"
+ def tokenRangeFallback = UUID.randomUUID().toString()
+ sql """
+ select id, "${tokenRangeFallback}"
+ from ann_topn_small_candidate_fallback
+ where l2_distance_approximate(embedding, [0.0, 0.0, 0.0]) < 1.0
+ order by id;
+ """
+ def rangeFallbackProfile = getProfileWithToken(tokenRangeFallback)
+ def rangeSmallCandidateFallbackCnt =
+ extractCounterValue(rangeFallbackProfile,
"AnnIndexRangeFallbackBySmallCandidateCnt")
+ def rangeSmallCandidateRows =
+ extractCounterValue(rangeFallbackProfile,
"AnnIndexRangeFallbackSmallCandidateRows")
+ logger.info("range small candidate fallback
count=${rangeSmallCandidateFallbackCnt}, " +
+ "rows=${rangeSmallCandidateRows}")
+ assertEquals("1", rangeSmallCandidateFallbackCnt)
+ assertEquals("10", rangeSmallCandidateRows)
+
+ try {
+ GetDebugPoint().enableDebugPointForAllBEs(
+ "segment_iterator._read_columns_by_index", [column_name:
"embedding"])
+
+ sql "set ann_index_candidate_rows_threshold=4;"
+ test {
+ sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where comment match_any 'small'
+ order by l2_distance_approximate(embedding, [0.0, 0.0, 0.0])
+ limit 2;
+ """
+ exception "does not need to read data"
+ }
+
+ sql "set ann_index_candidate_rows_threshold=11;"
+ test {
+ sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where l2_distance_approximate(embedding, [0.0, 0.0, 0.0]) < 1.0
+ order by id;
+ """
+ exception "does not need to read data"
+ }
+
+ sql "set ann_index_candidate_rows_threshold=0;"
+ sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where comment match_any 'small'
+ order by l2_distance_approximate(embedding, [0.0, 0.0, 0.0])
+ limit 2;
+ """
+ sql """
+ select id
+ from ann_topn_small_candidate_fallback
+ where l2_distance_approximate(embedding, [0.0, 0.0, 0.0]) < 1.0
+ order by id;
+ """
+ } finally {
+
GetDebugPoint().disableDebugPointForAllBEs("segment_iterator._read_columns_by_index")
+ }
+
+ test {
+ sql "set ann_index_candidate_rows_threshold=-1;"
+ exception "ann_index_candidate_rows_threshold should be greater than
or equal to 0"
+ }
+
+ test {
+ sql "set ann_index_candidate_rows_percent_threshold=1.1;"
+ exception "ann_index_candidate_rows_percent_threshold should be
between 0 and 1"
+ }
+
+ test {
+ sql "set ann_index_candidate_rows_percent_threshold=NaN;"
+ exception "ann_index_candidate_rows_percent_threshold should be
between 0 and 1"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]