This is an automated email from the ASF dual-hosted git repository.
HappenLee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new ff89892be25 [improvement](be) Prune exact scan keys by tablet bucket
(#68553)
ff89892be25 is described below
commit ff89892be2565b36b2d6fa3b43a8a53ac4e03dc8
Author: HappenLee <[email protected]>
AuthorDate: Mon Oct 12 11:52:05 2026 +0800
[improvement](be) Prune exact scan keys by tablet bucket (#68553)
### What problem does this PR solve?
Issue Number: None
Related PR: #68446
Ordinary IN scans currently send the complete point-key range list to
every selected tablet. FE tablet pruning reduces the tablet set but each
remaining tablet still probes keys belonging to other buckets. For
example, 55 keys across 43 selected tablets produce 2,365 tablet/range
associations.
This change groups final exact point ranges by the table's hash bucket
before creating scanners. FE attaches the full bucket count and ordinal
from the existing tablet map; BE uses the same raw-byte CRC32 as VARCHAR
distribution. Ordinary scanners and both parallel splitting strategies
consume bucket-local ranges, and empty groups skip scanner/segment
loading. Residual predicates and versioned read sources stay on the
normal scan path.
Initial scope: unpartitioned base indexes with one non-null VARCHAR
storage key equal to the sole HASH distribution column. Other schemas,
rollups, special scans, and non-point/coalesced ranges retain the
existing path. `enable_scan_key_bucket_prune` defaults to true and
provides a per-session A/B switch. Optional Thrift metadata preserves
mixed-version behavior. Two Profile counters expose tablet/range
associations before and after grouping.
### Performance validation
Same RELEASE binary, local single FE/BE on a shared 192-logical-CPU
host; 1M MOW rows, 128 buckets, eight insert batches, auto compaction
disabled, 4KB string payload. Each query selects address/chain/payload
for an IN list and `chain = 1`. Parallel scan enabled; short-circuit,
SQL cache and query cache disabled. Each cell has 50 samples; paired
OFF/ON order is randomized and all 1,000 timed query results match.
`doris_cache_cold` clears the dedicated BE's Doris caches before each
sample. OS caches remain uncontrolled; this does not model cold
object-storage reads. Compare OFF/ON within a row, not across cache
modes.
| Cache | IN keys | OFF p50 ms | ON p50 ms | p50 reduction | OFF p95 ms
| ON p95 ms |
|---|---:|---:|---:|---:|---:|---:|
| hot | 4 | 12.306 | 12.327 | -0.2% | 14.861 | 14.358 |
| hot | 9 | 11.956 | 11.738 | 1.8% | 15.550 | 14.778 |
| hot | 30 | 13.324 | 12.665 | 4.9% | 16.209 | 17.543 |
| hot | 55 | 16.221 | 13.691 | 15.6% | 17.982 | 14.849 |
| hot | 100 | 77.752 | 37.955 | 51.2% | 100.982 | 46.330 |
| doris_cache_cold | 4 | 8.263 | 8.257 | 0.1% | 9.886 | 9.912 |
| doris_cache_cold | 9 | 9.583 | 9.320 | 2.7% | 13.308 | 11.846 |
| doris_cache_cold | 30 | 13.207 | 11.947 | 9.5% | 15.255 | 13.696 |
| doris_cache_cold | 55 | 16.720 | 14.012 | 16.2% | 19.495 | 16.214 |
| doris_cache_cold | 100 | 94.023 | 44.950 | 52.2% | 225.355 | 108.331 |
For 55 keys, range associations drop from 2365 to 55 (97.7%); end-to-end
p50 drops about 16%. Separately captured warm profiles show
`GenerateRowRangeByKeysTime` 13.560 → 1.653 ms and cumulative
`ScannerCpuTime` 46.460 → 31.784 ms (single samples). Small lists show
little gain; hot 30-key p95 increased in this run, so this is not a
universal tail-latency improvement.
<details>
<summary>Workload construction and timing controls</summary>
```sql
CREATE TABLE scan_key_bucket_perf (
address VARCHAR(80) NOT NULL, chain INT NOT NULL, payload STRING
) UNIQUE KEY(address) DISTRIBUTED BY HASH(address) BUCKETS 128
PROPERTIES("replication_num"="1", "enable_unique_key_merge_on_write"="true",
"disable_auto_compaction"="true");
```
Insert eight batches of 125,000 rows from `numbers("number"="125000")`.
In batch `b`, let `i = number + b * 125000`; address is
`concat('address', lpad(cast(i as string), 9, '0'))`, chain is `i % 3`,
and payload is `repeat(sha2(cast(i as string), 256), 64)`.
For list size `n`, keys are generated with Python
`random.Random(68446+n).sample(range(1000000), n)` and the same address
formatting. Query all three columns using the generated IN list and
`chain = 1`. Set `max_scan_key_num=1024`,
`max_pushdown_conditions_per_column=1024`, `enable_parallel_scan=true`,
`enable_short_circuit_query=false`, `enable_sql_cache=false`, and
`enable_query_cache=false`. Toggle `enable_scan_key_bucket_prune` per
trial. Hot mode uses five warm-ups per setting; cold mode calls
`/api/clear_cache/all` on the dedicated BE before each sample. Fetch all
rows, measure with `perf_counter_ns`, and compare sorted result
checksums. Profiles are collected separately with both paths warmed.
</details>
### Release note
Improve eligible VARCHAR IN scans by pruning foreign-bucket point ranges
before scanner creation. The `enable_scan_key_bucket_prune` session
variable controls the optimization.
### Check List (For Author)
- Test
- [x] Regression test: generated and replayed
`query_p0/test_scan_key_bucket_prune`; 35 result blocks covering ON/OFF,
parallel ON/OFF, range coalescing, residual predicates, DUP rows, MOW
updates/deletes, and unsupported nullable/composite/partitioned layouts.
Eight setting combinations agree; generated results also match
independent Python references.
- [x] Unit Test: 10 BE tests under ASAN
(`ScanKeyBucketPrunerTest.*:ScannerLateArrivalRfTest.*`) and 14 FE tests
(`OlapScanNodeTest`).
- [x] Manual test: RELEASE BE/FE builds, FE checkstyle, clang-format
16.0.6, build/header hygiene, and the A/B measurements above.
- Behavior changed: Yes, eligible scanners receive bucket-local point
ranges; SQL results and storage formats are unchanged.
- Does this need documentation: No, internal scan optimization; switch
description is included in session-variable metadata.
Static analysis: the repository clang-tidy script was run with commands
expanded from the unity compilation database. The completed run exits
nonzero on an unmatched `NOLINTEND` in `be/src/core/types.h:576`,
confirmed present in the base commit. Other emitted diagnostics also
point to unchanged code. Comparing every emitted diagnostic with the
final diff found no diagnostics on changed lines; this is not reported
as a clean clang-tidy pass.
---
be/src/exec/CMakeLists.txt | 2 +
be/src/exec/operator/olap_scan_operator.cpp | 55 +-
be/src/exec/scan/parallel_scanner_builder.cpp | 22 +-
be/src/exec/scan/parallel_scanner_builder.h | 10 +-
be/src/exec/scan/scan_key_bucket_pruner.cpp | 60 ++
be/src/exec/scan/scan_key_bucket_pruner.h | 42 ++
be/test/exec/operator/olap_scan_operator_test.cpp | 3 +
be/test/exec/scan/scan_key_bucket_pruner_test.cpp | 115 +++
be/test/exec/scan/scanner_late_arrival_rf_test.cpp | 32 +-
.../org/apache/doris/planner/OlapScanNode.java | 30 +
.../java/org/apache/doris/qe/SessionVariable.java | 6 +
.../org/apache/doris/planner/OlapScanNodeTest.java | 88 +++
gensrc/thrift/PlanNodes.thrift | 9 +-
.../data/query_p0/test_scan_key_bucket_prune.out | 798 +++++++++++++++++++++
.../query_p0/test_scan_key_bucket_prune.groovy | 77 ++
15 files changed, 1318 insertions(+), 31 deletions(-)
diff --git a/be/src/exec/CMakeLists.txt b/be/src/exec/CMakeLists.txt
index 7ccbd60b5ea..099224c1576 100644
--- a/be/src/exec/CMakeLists.txt
+++ b/be/src/exec/CMakeLists.txt
@@ -51,6 +51,8 @@ set(EXEC_UNITY_SKIP
${CMAKE_CURRENT_SOURCE_DIR}/operator/operator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/operator/partitioned_aggregation_sink_operator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/operator/scan_operator.cpp
+ # Small standalone helper; preserve the existing scanner/writer unity
batches.
+ ${CMAKE_CURRENT_SOURCE_DIR}/scan/scan_key_bucket_pruner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sink/writer/vfile_result_writer.cpp)
doris_skip_unity_inclusion(${EXEC_UNITY_SKIP})
set_target_properties(Exec PROPERTIES UNITY_BUILD ${DORIS_UNITY_BUILD}
diff --git a/be/src/exec/operator/olap_scan_operator.cpp
b/be/src/exec/operator/olap_scan_operator.cpp
index c84909ff8ea..d925f827f65 100644
--- a/be/src/exec/operator/olap_scan_operator.cpp
+++ b/be/src/exec/operator/olap_scan_operator.cpp
@@ -34,6 +34,7 @@
#include "exec/runtime_filter/runtime_filter_consumer_helper.h"
#include "exec/scan/olap_scanner.h"
#include "exec/scan/parallel_scanner_builder.h"
+#include "exec/scan/scan_key_bucket_pruner.h"
#include "exprs/function/in.h"
#include "exprs/hybrid_set.h"
#include "exprs/score_runtime.h"
@@ -747,6 +748,37 @@ Status
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
}
}
+ // Keep the full-scan placeholder in the fallback list. An empty routed
list
+ // means this tablet has no matching keys and must not create a scanner.
+ std::vector<OlapScanRange*> key_ranges;
+ for (auto& range : _cond_ranges) {
+ key_ranges.push_back(range.get());
+ }
+ std::vector<const std::vector<OlapScanRange*>*>
tablet_key_ranges(_tablets.size(), &key_ranges);
+ ScanKeyBucketPruner bucket_pruner;
+ if (p._olap_scan_node.__isset.enable_scan_key_bucket_prune &&
+ p._olap_scan_node.enable_scan_key_bucket_prune) {
+ const int32_t bucket_num = _scan_ranges.front()->bucket_num;
+ DORIS_CHECK_GT(bucket_num, 0);
+ if (bucket_pruner.init(_cond_ranges, bucket_num)) {
+ int64_t routed_ranges = 0;
+ for (size_t i = 0; i < _scan_ranges.size(); ++i) {
+ const auto& range = *_scan_ranges[i];
+ DORIS_CHECK(range.__isset.bucket_seq &&
range.__isset.bucket_num);
+ DORIS_CHECK_EQ(range.bucket_num, bucket_num);
+ DORIS_CHECK_GE(range.bucket_seq, 0);
+ DORIS_CHECK_LT(range.bucket_seq, bucket_num);
+ tablet_key_ranges[i] =
&bucket_pruner.ranges_for_bucket(range.bucket_seq);
+ routed_ranges += tablet_key_ranges[i]->size();
+ }
+ COUNTER_SET(
+ ADD_COUNTER(custom_profile(),
"ScanKeyRangesBeforeBucketPrune", TUnit::UNIT),
+ static_cast<int64_t>(key_ranges.size() * _tablets.size()));
+ COUNTER_SET(ADD_COUNTER(custom_profile(),
"ScanKeyRangesAfterBucketPrune", TUnit::UNIT),
+ routed_ranges);
+ }
+ }
+
bool enable_parallel_scan = state()->enable_parallel_scan();
auto resolve_binlog_scan_type = [](const TPaloScanRange& scan_range) {
if (scan_range.__isset.binlog_scan_type) {
@@ -778,19 +810,9 @@ Status
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
(_storage_no_merge() || p._olap_scan_node.is_preaggregation)
// binlog<row> need to be read in order
&& !read_row_binlog && !has_tso_predicate) {
- // Filter out the "full scan" placeholder range (has_lower_bound ==
false)
- // so that only ranges with real key bounds are forwarded to the
parallel scanner.
- std::vector<OlapScanRange*> key_ranges;
- for (auto& range : _cond_ranges) {
- if (!range->has_lower_bound) {
- continue;
- }
- key_ranges.emplace_back(range.get());
- }
-
ParallelScannerBuilder scanner_builder(this, _tablets, _read_sources,
_scan_ranges,
- _scanner_profile, key_ranges,
state(), p._limit,
- true,
p._olap_scan_node.is_preaggregation);
+ _scanner_profile,
tablet_key_ranges, state(),
+ p._limit, true,
p._olap_scan_node.is_preaggregation);
int max_scanners_count = state()->parallel_scan_max_scanners_count();
@@ -835,6 +857,9 @@ Status
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
int scanners_per_tablet = std::max(1, 64 / (int)_scan_ranges.size());
for (size_t scan_range_idx = 0; scan_range_idx < _scan_ranges.size();
scan_range_idx++) {
+ if (tablet_key_ranges[scan_range_idx]->empty()) {
+ continue;
+ }
const auto& palo_scan_range = *_scan_ranges[scan_range_idx];
if (read_row_binlog &&
(palo_scan_range.__isset.start_tso ||
palo_scan_range.__isset.end_tso)) {
@@ -855,7 +880,7 @@ Status
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
int64_t version = 0;
std::from_chars(palo_scan_range.version.data(),
palo_scan_range.version.data() +
palo_scan_range.version.size(), version);
- std::vector<std::unique_ptr<doris::OlapScanRange>>* ranges =
&_cond_ranges;
+ const auto* ranges = tablet_key_ranges[scan_range_idx];
int size_based_scanners_per_tablet = 1;
if (config::doris_scan_range_max_mb > 0) {
@@ -869,12 +894,12 @@ Status
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
int64_t num_ranges = ranges->size();
for (int64_t i = 0; i < num_ranges;) {
std::vector<doris::OlapScanRange*> scanner_ranges;
- scanner_ranges.push_back((*ranges)[i].get());
+ scanner_ranges.push_back((*ranges)[i]);
++i;
for (int64_t j = 1; i < num_ranges && j < ranges_per_scanner &&
(*ranges)[i]->end_include == (*ranges)[i -
1]->end_include;
++j, ++i) {
- scanner_ranges.push_back((*ranges)[i].get());
+ scanner_ranges.push_back((*ranges)[i]);
}
COUNTER_UPDATE(_key_range_counter, scanner_ranges.size());
diff --git a/be/src/exec/scan/parallel_scanner_builder.cpp
b/be/src/exec/scan/parallel_scanner_builder.cpp
index 288c51e1802..e1650f09323 100644
--- a/be/src/exec/scan/parallel_scanner_builder.cpp
+++ b/be/src/exec/scan/parallel_scanner_builder.cpp
@@ -81,6 +81,10 @@ Status
ParallelScannerBuilder::_build_scanners_by_rowid(std::list<ScannerSPtr>&
DCHECK_GE(_rows_per_scanner, _min_rows_per_scanner);
for (size_t tablet_idx = 0; tablet_idx < _tablets.size(); ++tablet_idx) {
+ const auto& key_ranges = *_tablet_key_ranges[tablet_idx];
+ if (key_ranges.empty()) {
+ continue;
+ }
auto&& [tablet, version] = _tablets[tablet_idx];
const auto& scan_range = *_scan_ranges[tablet_idx];
DCHECK(_all_read_sources.contains(tablet->tablet_id()));
@@ -144,7 +148,7 @@ Status
ParallelScannerBuilder::_build_scanners_by_rowid(std::list<ScannerSPtr>&
partitial_read_source.rs_splits.emplace_back(std::move(split));
scanners.emplace_back(_build_scanner(
- tablet, version, _key_ranges, scan_range,
+ tablet, version, key_ranges, scan_range,
{.rs_splits =
std::move(partitial_read_source.rs_splits),
.delete_predicates =
entire_read_source.delete_predicates,
.delete_bitmap =
entire_read_source.delete_bitmap},
@@ -191,7 +195,7 @@ Status
ParallelScannerBuilder::_build_scanners_by_rowid(std::list<ScannerSPtr>&
}
#endif
scanners.emplace_back(
- _build_scanner(tablet, version, _key_ranges, scan_range,
+ _build_scanner(tablet, version, key_ranges, scan_range,
{.rs_splits =
std::move(partitial_read_source.rs_splits),
.delete_predicates =
entire_read_source.delete_predicates,
.delete_bitmap =
entire_read_source.delete_bitmap},
@@ -211,6 +215,10 @@ Status
ParallelScannerBuilder::_build_scanners_by_per_segment(std::list<ScannerS
DCHECK_GE(_rows_per_scanner, _min_rows_per_scanner);
for (size_t tablet_idx = 0; tablet_idx < _tablets.size(); ++tablet_idx) {
+ const auto& key_ranges = *_tablet_key_ranges[tablet_idx];
+ if (key_ranges.empty()) {
+ continue;
+ }
auto&& [tablet, version] = _tablets[tablet_idx];
const auto& scan_range = *_scan_ranges[tablet_idx];
DCHECK(_all_read_sources.contains(tablet->tablet_id()));
@@ -244,7 +252,7 @@ Status
ParallelScannerBuilder::_build_scanners_by_per_segment(std::list<ScannerS
partitial_read_source.rs_splits.emplace_back(std::move(split));
scanners.emplace_back(_build_scanner(
- tablet, version, _key_ranges, scan_range,
+ tablet, version, key_ranges, scan_range,
{.rs_splits =
std::move(partitial_read_source.rs_splits),
.delete_predicates =
entire_read_source.delete_predicates,
.delete_bitmap = entire_read_source.delete_bitmap},
@@ -262,11 +270,14 @@ Status
ParallelScannerBuilder::_build_scanners_by_per_segment(std::list<ScannerS
*/
Status ParallelScannerBuilder::_load() {
_total_rows = 0;
- size_t idx = 0;
bool enable_segment_cache =
_state->query_options().__isset.enable_segment_cache
?
_state->query_options().enable_segment_cache
: true;
- for (auto&& [tablet, version] : _tablets) {
+ for (size_t idx = 0; idx < _tablets.size(); ++idx) {
+ if (_tablet_key_ranges[idx]->empty()) {
+ continue;
+ }
+ auto&& [tablet, version] = _tablets[idx];
const auto tablet_id = tablet->tablet_id();
_all_read_sources[tablet_id] = _read_sources[idx];
const auto& read_source = _all_read_sources[tablet_id];
@@ -288,7 +299,6 @@ Status ParallelScannerBuilder::_load() {
}
_total_rows += rowset->num_rows();
}
- idx++;
}
_rows_per_scanner = _total_rows / _max_scanners_count;
diff --git a/be/src/exec/scan/parallel_scanner_builder.h
b/be/src/exec/scan/parallel_scanner_builder.h
index 60ee95fb267..7a8a704c488 100644
--- a/be/src/exec/scan/parallel_scanner_builder.h
+++ b/be/src/exec/scan/parallel_scanner_builder.h
@@ -45,8 +45,9 @@ public:
std::vector<TabletReadSource>& read_sources,
const std::vector<std::unique_ptr<TPaloScanRange>>&
scan_ranges,
const std::shared_ptr<RuntimeProfile>& profile,
- const std::vector<OlapScanRange*>& key_ranges,
RuntimeState* state,
- int64_t limit, bool is_dup_mow_key, bool
is_preaggregation)
+ const std::vector<const
std::vector<OlapScanRange*>*>& tablet_key_ranges,
+ RuntimeState* state, int64_t limit, bool
is_dup_mow_key,
+ bool is_preaggregation)
: _parent(parent),
_scanner_profile(profile),
_state(state),
@@ -54,10 +55,11 @@ public:
_is_dup_mow_key(is_dup_mow_key),
_is_preaggregation(is_preaggregation),
_tablets(tablets.cbegin(), tablets.cend()),
- _key_ranges(key_ranges.cbegin(), key_ranges.cend()),
+ _tablet_key_ranges(tablet_key_ranges),
_scan_ranges(scan_ranges),
_read_sources(read_sources) {
DORIS_CHECK_EQ(_tablets.size(), scan_ranges.size());
+ DORIS_CHECK_EQ(_tablets.size(), tablet_key_ranges.size());
for (size_t i = 0; i < _tablets.size(); ++i) {
DORIS_CHECK(scan_ranges[i] != nullptr);
DORIS_CHECK_EQ(_tablets[i].tablet->tablet_id(),
scan_ranges[i]->tablet_id);
@@ -119,7 +121,7 @@ private:
// PreAgg OFF: The storage layer must complete pre-aggregation and return
fully aggregated data. (Slow data reading)
bool _is_preaggregation;
std::vector<TabletWithVersion> _tablets;
- std::vector<OlapScanRange*> _key_ranges;
+ const std::vector<const std::vector<OlapScanRange*>*>& _tablet_key_ranges;
const std::vector<std::unique_ptr<TPaloScanRange>>& _scan_ranges;
std::unordered_map<int64_t, TabletReadSource> _all_read_sources;
std::vector<TabletReadSource>& _read_sources;
diff --git a/be/src/exec/scan/scan_key_bucket_pruner.cpp
b/be/src/exec/scan/scan_key_bucket_pruner.cpp
new file mode 100644
index 00000000000..8dc059bea84
--- /dev/null
+++ b/be/src/exec/scan/scan_key_bucket_pruner.cpp
@@ -0,0 +1,60 @@
+// 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.
+
+#include "exec/scan/scan_key_bucket_pruner.h"
+
+#include "storage/olap_utils.h"
+#include "util/hash_util.hpp"
+
+namespace doris {
+
+bool ScanKeyBucketPruner::init(const
std::vector<std::unique_ptr<OlapScanRange>>& ranges,
+ int32_t bucket_num) {
+ DORIS_CHECK_GT(bucket_num, 0);
+ _bucket_ranges.clear();
+ for (const auto& range : ranges) {
+ if (!range->has_lower_bound || !range->has_upper_bound ||
!range->begin_include ||
+ !range->end_include || range->begin_scan_range.size() != 1 ||
+ range->end_scan_range.size() != 1) {
+ return false;
+ }
+ const auto& begin = range->begin_scan_range.get_field(0);
+ const auto& end = range->end_scan_range.get_field(0);
+ if (begin.get_type() != TYPE_VARCHAR || end.get_type() != TYPE_VARCHAR
|| begin != end) {
+ return false;
+ }
+ }
+ _bucket_ranges.resize(bucket_num);
+ for (const auto& range : ranges) {
+ const auto& value =
range->begin_scan_range.get_field(0).get<TYPE_VARCHAR>();
+ // Identical to ColumnString::update_crcs_with_value for a single
non-null
+ // VARCHAR distribution column: raw bytes, seed zero, and the full
bucket count.
+ const uint32_t hash =
+ HashUtil::zlib_crc_hash(value.data(),
static_cast<uint32_t>(value.size()), 0);
+ _bucket_ranges[hash % bucket_num].push_back(range.get());
+ }
+ return true;
+}
+
+const std::vector<OlapScanRange*>& ScanKeyBucketPruner::ranges_for_bucket(
+ int32_t bucket_seq) const {
+ DCHECK_GE(bucket_seq, 0);
+ DCHECK_LT(bucket_seq, _bucket_ranges.size());
+ return _bucket_ranges[bucket_seq];
+}
+
+} // namespace doris
diff --git a/be/src/exec/scan/scan_key_bucket_pruner.h
b/be/src/exec/scan/scan_key_bucket_pruner.h
new file mode 100644
index 00000000000..dd1430e0645
--- /dev/null
+++ b/be/src/exec/scan/scan_key_bucket_pruner.h
@@ -0,0 +1,42 @@
+// 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.
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace doris {
+
+struct OlapScanRange;
+
+// Groups exact, single-VARCHAR scan keys by the table's HASH distribution
bucket.
+// The caller must establish that this is the table's sole non-null storage and
+// distribution key. Ranges remain owned by the scan local state.
+class ScanKeyBucketPruner {
+public:
+ // Returns false for non-point ranges, including ranges coalesced by
max_scan_key_num.
+ bool init(const std::vector<std::unique_ptr<OlapScanRange>>& ranges,
int32_t bucket_num);
+
+ const std::vector<OlapScanRange*>& ranges_for_bucket(int32_t bucket_seq)
const;
+
+private:
+ std::vector<std::vector<OlapScanRange*>> _bucket_ranges;
+};
+
+} // namespace doris
diff --git a/be/test/exec/operator/olap_scan_operator_test.cpp
b/be/test/exec/operator/olap_scan_operator_test.cpp
index 4cc86c526ee..97aafe54af0 100644
--- a/be/test/exec/operator/olap_scan_operator_test.cpp
+++ b/be/test/exec/operator/olap_scan_operator_test.cpp
@@ -99,6 +99,9 @@ class OlapScanOperatorTsoPruningTest : public
OlapScanOperatorBinlogPushDownTest
protected:
void SetUp() override {
OlapScanOperatorBinlogPushDownTest::SetUp();
+ // Match prepare()'s tablet slots, but leave the tablets null so the
empty-source
+ // tests still verify that TSO pruning finishes without accessing a
tablet.
+ _local_state->_tablets.resize(_local_state->_scan_ranges.size());
_parent->_olap_scan_node.__set_read_row_binlog(true);
_profile = std::make_unique<RuntimeProfile>("TsoPruningTest");
_local_state->_scanner_init_timer = ADD_TIMER(_profile,
"ScannerInitTime");
diff --git a/be/test/exec/scan/scan_key_bucket_pruner_test.cpp
b/be/test/exec/scan/scan_key_bucket_pruner_test.cpp
new file mode 100644
index 00000000000..62cc938473f
--- /dev/null
+++ b/be/test/exec/scan/scan_key_bucket_pruner_test.cpp
@@ -0,0 +1,115 @@
+// 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.
+
+#include "exec/scan/scan_key_bucket_pruner.h"
+
+#include <gtest/gtest.h>
+
+#include "core/column/column_string.h"
+#include "storage/olap_utils.h"
+
+namespace doris {
+namespace {
+
+std::unique_ptr<OlapScanRange> varchar_point(const std::string& value) {
+ auto range = std::make_unique<OlapScanRange>();
+ range->has_lower_bound = range->has_upper_bound = true;
+
range->begin_scan_range.add_field(Field::create_field<TYPE_VARCHAR>(value));
+ range->end_scan_range.add_field(Field::create_field<TYPE_VARCHAR>(value));
+ return range;
+}
+
+} // namespace
+
+TEST(ScanKeyBucketPrunerTest,
MatchesDistributionHashAndPreservesRangeIdentity) {
+ const std::vector<std::string> values {"", "a", "address000001",
std::string("a\0b", 3),
+ "中文"};
+ // Python zlib.crc32(bytes) % 128, independent of the routing
implementation.
+ const std::vector<uint32_t> expected_buckets {0, 67, 76, 113, 55};
+ auto column = ColumnString::create();
+ std::vector<std::unique_ptr<OlapScanRange>> ranges;
+ for (const auto& value : values) {
+ ranges.push_back(varchar_point(value));
+ column->insert_data(value.data(), value.size());
+ }
+ std::vector<uint32_t> hashes(values.size(), 0);
+ column->update_crcs_with_value(hashes.data(), TYPE_VARCHAR,
+ static_cast<uint32_t>(values.size()), 0,
nullptr);
+ ScanKeyBucketPruner pruner;
+ ASSERT_TRUE(pruner.init(ranges, 128));
+ size_t routed_count = 0;
+ for (int bucket = 0; bucket < 128; ++bucket) {
+ routed_count += pruner.ranges_for_bucket(bucket).size();
+ }
+ EXPECT_EQ(routed_count, ranges.size());
+ for (size_t i = 0; i < ranges.size(); ++i) {
+ EXPECT_EQ(hashes[i] % 128, expected_buckets[i]);
+ const auto& routed = pruner.ranges_for_bucket(expected_buckets[i]);
+ ASSERT_EQ(routed.size(), 1);
+ EXPECT_EQ(routed.front(), ranges[i].get());
+ }
+ EXPECT_TRUE(pruner.ranges_for_bucket(1).empty());
+}
+
+TEST(ScanKeyBucketPrunerTest, KeepsDuplicatesAndBucketLocalOrder) {
+ std::vector<std::unique_ptr<OlapScanRange>> ranges;
+ ranges.push_back(varchar_point("a"));
+ ranges.push_back(varchar_point("a"));
+ ScanKeyBucketPruner pruner;
+ ASSERT_TRUE(pruner.init(ranges, 128));
+ EXPECT_EQ(pruner.ranges_for_bucket(67),
+ (std::vector<OlapScanRange*> {ranges[0].get(),
ranges[1].get()}));
+}
+
+TEST(ScanKeyBucketPrunerTest, RejectsIntervalsAndUnsupportedKeys) {
+ ScanKeyBucketPruner pruner;
+ std::vector<std::unique_ptr<OlapScanRange>> ranges;
+ ranges.push_back(varchar_point("a"));
+ auto check_fallback = [&](std::unique_ptr<OlapScanRange> range) {
+ ASSERT_TRUE(pruner.init(ranges, 128));
+ ranges.push_back(std::move(range));
+ EXPECT_FALSE(pruner.init(ranges, 128));
+ ranges.pop_back();
+ };
+ check_fallback(std::make_unique<OlapScanRange>());
+ auto range = varchar_point("b");
+ range->end_scan_range.get_field(0) =
Field::create_field<TYPE_VARCHAR>("z");
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->begin_include = false;
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->end_include = false;
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->has_upper_bound = false;
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->begin_scan_range.add_null();
+ range->end_scan_range.add_null();
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->begin_scan_range.get_field(0) = Field(TYPE_NULL);
+ range->end_scan_range.get_field(0) = Field(TYPE_NULL);
+ check_fallback(std::move(range));
+ range = varchar_point("b");
+ range->begin_scan_range.get_field(0) = Field::create_field<TYPE_INT>(1);
+ range->end_scan_range.get_field(0) = Field::create_field<TYPE_INT>(1);
+ check_fallback(std::move(range));
+}
+
+} // namespace doris
diff --git a/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
b/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
index 8428a3d6290..a21e7f47bda 100644
--- a/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
+++ b/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
@@ -583,8 +583,10 @@ TEST_F(ScannerLateArrivalRfTest,
parallel_scanner_factory_preserves_bucket_ident
scan_range->__set_bucket_num(bucket_num);
scan_ranges.push_back(std::move(scan_range));
auto profile = std::make_shared<RuntimeProfile>("parallel scanner bucket
identity");
+ const std::vector<OlapScanRange*> key_ranges;
+ const std::vector<const std::vector<OlapScanRange*>*> tablet_key_ranges(1,
&key_ranges);
ParallelScannerBuilder builder(local_state.get(), tablets, read_sources,
scan_ranges, profile,
- {}, state, -1, true, true);
+ tablet_key_ranges, state, -1, true, true);
auto scanner = builder._build_scanner(tablet, 1, {}, *scan_ranges.front(),
TabletReadSource {},
io::FileCacheStatistics {});
@@ -593,6 +595,28 @@ TEST_F(ScannerLateArrivalRfTest,
parallel_scanner_factory_preserves_bucket_ident
EXPECT_TRUE(scanner->is_pruned_by_runtime_filter());
}
+TEST_F(ScannerLateArrivalRfTest,
empty_bucket_keys_skip_loading_and_both_scanner_split_paths) {
+ auto tablet = std::make_shared<FakeTablet>(1, 20);
+ std::vector<TabletWithVersion> tablets {{tablet, 1}};
+ std::vector<TabletReadSource> read_sources(1);
+ std::vector<std::unique_ptr<TPaloScanRange>> scan_ranges;
+ auto scan_range = std::make_unique<TPaloScanRange>();
+ scan_range->__set_tablet_id(20);
+ scan_ranges.push_back(std::move(scan_range));
+ const std::vector<OlapScanRange*> empty_keys;
+ const std::vector<const std::vector<OlapScanRange*>*> tablet_key_ranges(1,
&empty_keys);
+ auto profile = std::make_shared<RuntimeProfile>("empty bucket");
+ for (bool per_segment : {false, true}) {
+ ParallelScannerBuilder builder(nullptr, tablets, read_sources,
scan_ranges, profile,
+ tablet_key_ranges,
_runtime_states[0].get(), -1, true, true);
+ builder.set_scan_parallelism_by_per_segment(per_segment);
+ std::list<ScannerSPtr> scanners;
+ ASSERT_TRUE(builder.build_scanners(scanners).ok());
+ EXPECT_TRUE(scanners.empty());
+ EXPECT_TRUE(builder._all_read_sources.empty());
+ }
+}
+
TEST_F(ScannerLateArrivalRfTest,
high_cardinality_ineligible_parallel_builder_reuses_scan_ranges) {
constexpr size_t scan_range_count = 20'000;
constexpr int64_t first_tablet_id = 100;
@@ -612,8 +636,10 @@ TEST_F(ScannerLateArrivalRfTest,
high_cardinality_ineligible_parallel_builder_re
std::vector<OlapScanRange*> key_ranges;
std::shared_ptr<RuntimeProfile> profile;
- ParallelScannerBuilder builder(nullptr, tablets, read_sources,
scan_ranges, profile, key_ranges,
- nullptr, -1, true, true);
+ const std::vector<const std::vector<OlapScanRange*>*>
tablet_key_ranges(scan_range_count,
+
&key_ranges);
+ ParallelScannerBuilder builder(nullptr, tablets, read_sources,
scan_ranges, profile,
+ tablet_key_ranges, nullptr, -1, true, true);
EXPECT_EQ(&builder._scan_ranges, &scan_ranges);
EXPECT_EQ(builder._scan_ranges.size(), scan_range_count);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
index fcbd9151002..fce942267a0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
@@ -1528,6 +1528,7 @@ public class OlapScanNode extends ScanNode {
if (hasRfDrivingBucketPruning()) {
setRuntimeFilterBucketPruneParameters();
}
+ setScanKeyBucketPruneParameters(msg.olap_scan_node);
super.toThrift(msg);
}
@@ -1587,6 +1588,35 @@ public class OlapScanNode extends ScanNode {
return false;
}
+ @VisibleForTesting
+ synchronized void setScanKeyBucketPruneParameters(TOlapScanNode node) {
+ ConnectContext context = ConnectContext.get();
+ if (context == null ||
!context.getSessionVariable().enableScanKeyBucketPrune
+ || isPointQuery() || tableSample != null || scanParams != null
+ || olapTable instanceof OlapTableWrapper
+ || selectedIndexId != olapTable.getBaseIndexId()
+ || olapTable.getPartitionInfo().getType() !=
PartitionType.UNPARTITIONED
+ || !(olapTable.getDefaultDistributionInfo() instanceof
HashDistributionInfo)
+ ||
olapTable.getBaseSchema().stream().anyMatch(Column::isClusterKey)) {
+ return;
+ }
+ List<Column> keys = olapTable.getKeyColumnsByIndexId(selectedIndexId);
+ List<Column> distributionColumns =
+ ((HashDistributionInfo)
olapTable.getDefaultDistributionInfo()).getDistributionColumns();
+ if (keys.size() != 1 || distributionColumns.size() != 1
+ || !keys.get(0).equals(distributionColumns.get(0))
+ || !keys.get(0).getType().isVarchar() ||
keys.get(0).isAllowNull()) {
+ return;
+ }
+ setRuntimeFilterBucketPruneParameters();
+ // Missing metadata can deliberately disable attachment (e.g. the
debug point).
+ // Do not attest eligibility unless every range has its full bucket
ordinal/count.
+ if (scanRangeLocations.stream().allMatch(locations ->
+ locations.getScanRange().getPaloScanRange().isSetBucketNum()))
{
+ node.setEnableScanKeyBucketPrune(true);
+ }
+ }
+
@VisibleForTesting
synchronized void setRuntimeFilterBucketPruneParameters() {
if (runtimeFilterBucketPruneParametersSet) {
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 b3421d02dfd..bc086c7596e 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
@@ -473,6 +473,8 @@ public class SessionVariable implements Serializable,
Writable {
public static final String ENABLE_RUNTIME_FILTER_BUCKET_PRUNE =
"enable_runtime_filter_bucket_prune";
+ public static final String ENABLE_SCAN_KEY_BUCKET_PRUNE =
"enable_scan_key_bucket_prune";
+
public static final String ENABLE_PRUNE_NESTED_COLUMN =
"enable_prune_nested_column";
static final String SESSION_CONTEXT = "session_context";
@@ -2191,6 +2193,10 @@ public class SessionVariable implements Serializable,
Writable {
@VarAttrDef.VarAttr(name = ENABLE_RUNTIME_FILTER_BUCKET_PRUNE, needForward
= true, fuzzy = true)
public boolean enableRuntimeFilterBucketPrune = true;
+ @VarAttrDef.VarAttr(name = ENABLE_SCAN_KEY_BUCKET_PRUNE, needForward =
true, fuzzy = true,
+ description = "Prune exact VARCHAR scan keys by their HASH
distribution bucket.")
+ public boolean enableScanKeyBucketPrune = true;
+
/**
* The client can pass some special information by setting this session
variable in the format: "k1:v1;k2:v2".
* For example, trace_id can be passed to trace the query request sent by
the user.
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/planner/OlapScanNodeTest.java
b/fe/fe-core/src/test/java/org/apache/doris/planner/OlapScanNodeTest.java
index 44bf4d8215a..e5ea5ab740e 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/planner/OlapScanNodeTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/planner/OlapScanNodeTest.java
@@ -30,6 +30,7 @@ import org.apache.doris.analysis.TupleDescriptor;
import org.apache.doris.analysis.TupleId;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DiskInfo;
+import org.apache.doris.catalog.HashDistributionInfo;
import org.apache.doris.catalog.LocalReplica;
import org.apache.doris.catalog.LocalTablet;
import org.apache.doris.catalog.MaterializedIndex;
@@ -40,7 +41,9 @@ import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.catalog.RangePartitionInfo;
import org.apache.doris.catalog.RangePartitionItem;
import org.apache.doris.catalog.Replica.ReplicaState;
+import org.apache.doris.catalog.SinglePartitionInfo;
import org.apache.doris.catalog.Tablet;
+import org.apache.doris.catalog.Type;
import org.apache.doris.catalog.info.TableNameInfo;
import org.apache.doris.cloud.catalog.CloudPartition;
import org.apache.doris.common.AnalysisException;
@@ -49,6 +52,8 @@ import org.apache.doris.common.util.DebugPointUtil;
import org.apache.doris.common.util.TimeUtils;
import org.apache.doris.datasource.InternalCatalog;
import org.apache.doris.nereids.exceptions.ParseException;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.SessionVariable;
import org.apache.doris.system.Backend;
import org.apache.doris.thrift.TOlapScanNode;
import org.apache.doris.thrift.TPaloScanRange;
@@ -400,6 +405,89 @@ public class OlapScanNodeTest {
}
}
+ @Test
+ public void testScanKeyBucketPruneEligibilityAndFullBucketOrdinals()
throws Exception {
+ ConnectContext context = Mockito.mock(ConnectContext.class,
Mockito.RETURNS_DEEP_STUBS);
+ SessionVariable session = new SessionVariable();
+ Mockito.when(context.getSessionVariable()).thenReturn(session);
+ try (MockedStatic<ConnectContext> contexts =
Mockito.mockStatic(ConnectContext.class)) {
+ contexts.when(ConnectContext::get).thenReturn(context);
+ OlapScanNode scanNode = newBucketPruneScanNode(10L, 11L);
+ OlapTable table = scanNode.getOlapTable();
+ Column key = new Column("address", Type.VARCHAR, true, null,
false, null, "");
+ Mockito.when(table.getBaseIndexId()).thenReturn(1L);
+ Mockito.when(table.getPartitionInfo()).thenReturn(new
SinglePartitionInfo());
+
Mockito.when(table.getBaseSchema()).thenReturn(Lists.newArrayList(key));
+
Mockito.when(table.getKeyColumnsByIndexId(1L)).thenReturn(Lists.newArrayList(key));
+ Mockito.when(table.getDefaultDistributionInfo())
+ .thenReturn(new HashDistributionInfo(128,
Lists.newArrayList(key)));
+ scanNode.setSelectedIndexInfo(1L, true, "");
+ getBucketInfo(scanNode).put(10L, ((long) 128 << Integer.SIZE) |
113L);
+ getBucketInfo(scanNode).put(11L, ((long) 128 << Integer.SIZE) |
67L);
+
+ TOlapScanNode node = new TOlapScanNode();
+ scanNode.setScanKeyBucketPruneParameters(node);
+ Assertions.assertTrue(node.isEnableScanKeyBucketPrune());
+ Assertions.assertEquals(128, scanNode.scanRangeLocations.get(0)
+ .getScanRange().getPaloScanRange().getBucketNum());
+ Assertions.assertEquals(113, scanNode.scanRangeLocations.get(0)
+ .getScanRange().getPaloScanRange().getBucketSeq());
+
+ session.enableScanKeyBucketPrune = false;
+ assertScanKeyBucketPruneDisabled(scanNode);
+ session.enableScanKeyBucketPrune = true;
+ key.setIsAllowNull(true);
+ assertScanKeyBucketPruneDisabled(scanNode);
+ key.setIsAllowNull(false);
+ scanNode.setSelectedIndexInfo(2L, true, "");
+ assertScanKeyBucketPruneDisabled(scanNode);
+ scanNode.setSelectedIndexInfo(1L, true, "");
+
Mockito.when(table.getKeyColumnsByIndexId(1L)).thenReturn(Lists.newArrayList(key,
key));
+ assertScanKeyBucketPruneDisabled(scanNode);
+
Mockito.when(table.getKeyColumnsByIndexId(1L)).thenReturn(Lists.newArrayList(key));
+ Column other = new Column("chain", Type.INT);
+ Mockito.when(table.getDefaultDistributionInfo())
+ .thenReturn(new HashDistributionInfo(128,
Lists.newArrayList(other)));
+ assertScanKeyBucketPruneDisabled(scanNode);
+ Mockito.when(table.getDefaultDistributionInfo())
+ .thenReturn(new HashDistributionInfo(128,
Lists.newArrayList(key, other)));
+ assertScanKeyBucketPruneDisabled(scanNode);
+ Mockito.when(table.getDefaultDistributionInfo())
+ .thenReturn(new HashDistributionInfo(128,
Lists.newArrayList(key)));
+ Mockito.when(table.getPartitionInfo()).thenReturn(new
RangePartitionInfo(Lists.newArrayList(key)));
+ assertScanKeyBucketPruneDisabled(scanNode);
+ }
+ }
+
+ @Test
+ public void testScanKeyBucketPruneRequiresAllTabletMetadata() throws
Exception {
+ ConnectContext context = Mockito.mock(ConnectContext.class,
Mockito.RETURNS_DEEP_STUBS);
+ Mockito.when(context.getSessionVariable()).thenReturn(new
SessionVariable());
+ try (MockedStatic<ConnectContext> contexts =
Mockito.mockStatic(ConnectContext.class)) {
+ contexts.when(ConnectContext::get).thenReturn(context);
+ OlapScanNode scanNode = newBucketPruneScanNode(10L, 11L);
+ OlapTable table = scanNode.getOlapTable();
+ Column key = new Column("address", Type.VARCHAR, true, null,
false, null, "");
+ Mockito.when(table.getBaseIndexId()).thenReturn(1L);
+ Mockito.when(table.getPartitionInfo()).thenReturn(new
SinglePartitionInfo());
+
Mockito.when(table.getBaseSchema()).thenReturn(Lists.newArrayList(key));
+
Mockito.when(table.getKeyColumnsByIndexId(1L)).thenReturn(Lists.newArrayList(key));
+ Mockito.when(table.getDefaultDistributionInfo())
+ .thenReturn(new HashDistributionInfo(128,
Lists.newArrayList(key)));
+ scanNode.setSelectedIndexInfo(1L, true, "");
+ getBucketInfo(scanNode).put(10L, ((long) 128 << Integer.SIZE) |
113L);
+ assertScanKeyBucketPruneDisabled(scanNode);
+ Assertions.assertFalse(scanNode.scanRangeLocations.get(0)
+ .getScanRange().getPaloScanRange().isSetBucketNum());
+ }
+ }
+
+ private void assertScanKeyBucketPruneDisabled(OlapScanNode scanNode) {
+ TOlapScanNode node = new TOlapScanNode();
+ scanNode.setScanKeyBucketPruneParameters(node);
+ Assertions.assertFalse(node.isSetEnableScanKeyBucketPrune());
+ }
+
private OlapScanNode newBucketPruneScanNode(long... tabletIds) {
OlapTable table = Mockito.mock(OlapTable.class);
Mockito.when(table.getName()).thenReturn("rf_bucket_fact");
diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift
index 661bd293463..9699414e9f5 100644
--- a/gensrc/thrift/PlanNodes.thrift
+++ b/gensrc/thrift/PlanNodes.thrift
@@ -102,9 +102,8 @@ struct TPaloScanRange {
10: optional i64 start_tso
11: optional i64 end_tso
12: optional TBinlogScanType binlog_scan_type
- // Bucket metadata for BE-side runtime-filter bucket pruning. These fields
- // are populated only when the scan has an eligible single-column HASH
- // distribution runtime-filter target.
+ // Full distribution bucket ordinal/count for runtime-filter bucket pruning
+ // and exact scan-key bucket pruning. The count is not the selected tablet
count.
13: optional i32 bucket_seq
14: optional i32 bucket_num
}
@@ -1098,6 +1097,10 @@ struct TOlapScanNode {
27: optional list<TPartitionBoundary> partition_boundaries
// Slot ids of extra storage key columns used only to align the scan tuple
with storage schema.
28: optional set<i32> extra_key_column_slot_ids
+ // FE attests: unpartitioned base index, sole non-null VARCHAR storage key
+ // equals the sole HASH distribution key, and all scan ranges carry bucket
metadata.
+ // BE may prune only final inclusive point ranges; absent means disabled.
+ 29: optional bool enable_scan_key_bucket_prune
}
struct TEqJoinCondition {
diff --git a/regression-test/data/query_p0/test_scan_key_bucket_prune.out
b/regression-test/data/query_p0/test_scan_key_bucket_prune.out
new file mode 100644
index 00000000000..0eb76f6af60
--- /dev/null
+++ b/regression-test/data/query_p0/test_scan_key_bucket_prune.out
@@ -0,0 +1,798 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !dup_false_false_1 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_false_false_1 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_false_false_1 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_false_false_1 --
+1003 1003
+
+-- !dup_false_false_1024 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_false_false_1024 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_false_false_1024 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_false_false_1024 --
+1003 1003
+
+-- !dup_false_true_1 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_false_true_1 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_false_true_1 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_false_true_1 --
+1003 1003
+
+-- !dup_false_true_1024 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_false_true_1024 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_false_true_1024 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_false_true_1024 --
+1003 1003
+
+-- !dup_true_false_1 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_true_false_1 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_true_false_1 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_true_false_1 --
+1003 1003
+
+-- !dup_true_false_1024 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_true_false_1024 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_true_false_1024 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_true_false_1024 --
+1003 1003
+
+-- !dup_true_true_1 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_true_true_1 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_true_true_1 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_true_true_1 --
+1003 1003
+
+-- !dup_true_true_1024 --
+ 1 empty
+address000001 1 payload1
+address000001 2 duplicate
+address000002 2 payload2
+address000004 1 payload4
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !mow_true_true_1024 --
+ 1 empty
+address000001 2 updated
+address000002 1 updated2
+address000017 2 payload17
+address000034 1 payload34
+address000068 2 payload68
+address000085 1 payload85
+address000119 2 payload119
+address000136 1 payload136
+address000170 2 payload170
+address000187 1 payload187
+address000221 2 payload221
+address000238 1 payload238
+address000272 2 payload272
+address000289 1 payload289
+address000323 2 payload323
+address000340 1 payload340
+address000374 2 payload374
+address000391 1 payload391
+address000425 2 payload425
+address000442 1 payload442
+address000476 2 payload476
+address000493 1 payload493
+address000527 2 payload527
+address000544 1 payload544
+address000578 2 payload578
+address000595 1 payload595
+address000629 2 payload629
+address000646 1 payload646
+address000680 2 payload680
+address000697 1 payload697
+address000731 2 payload731
+address000748 1 payload748
+address000782 2 payload782
+address000799 1 payload799
+address000833 2 payload833
+address000850 1 payload850
+address000884 2 payload884
+address000901 1 payload901
+中文 1 unicode
+
+-- !range_true_true_1024 --
+address000003 0 payload3
+address000004 1 payload4
+address000005 2 payload5
+address000006 0 payload6
+address000007 1 payload7
+address000008 2 payload8
+address000009 0 payload9
+
+-- !full_true_true_1024 --
+1003 1003
+
+-- !nullable --
+\N 0
+a 1
+b 2
+
+-- !composite --
+a 1 1
+b 3 3
+
+-- !partitioned --
+a 1
+c 3
+
diff --git a/regression-test/suites/query_p0/test_scan_key_bucket_prune.groovy
b/regression-test/suites/query_p0/test_scan_key_bucket_prune.groovy
new file mode 100644
index 00000000000..8507bd16436
--- /dev/null
+++ b/regression-test/suites/query_p0/test_scan_key_bucket_prune.groovy
@@ -0,0 +1,77 @@
+// 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.
+
+suite("test_scan_key_bucket_prune") {
+ sql "DROP TABLE IF EXISTS scan_key_bucket_dup"
+ sql """CREATE TABLE scan_key_bucket_dup (
+ address VARCHAR(80) NOT NULL, chain INT NOT NULL, payload STRING
+ ) DUPLICATE KEY(address) DISTRIBUTED BY HASH(address) BUCKETS 128
+ PROPERTIES("replication_num" = "1")"""
+ sql """INSERT INTO scan_key_bucket_dup
+ SELECT concat('address', lpad(cast(number AS STRING), 6, '0')), number
% 3,
+ concat('payload', cast(number AS STRING)) FROM numbers("number"
= "1000")"""
+ sql "INSERT INTO scan_key_bucket_dup VALUES ('', 1, 'empty'), ('中文', 1,
'unicode'), ('address000001', 2, 'duplicate')"
+
+ sql "DROP TABLE IF EXISTS scan_key_bucket_mow"
+ sql """CREATE TABLE scan_key_bucket_mow (
+ address VARCHAR(80) NOT NULL, chain INT NOT NULL, payload STRING
+ ) UNIQUE KEY(address) DISTRIBUTED BY HASH(address) BUCKETS 128
+ PROPERTIES("replication_num" = "1", "enable_unique_key_merge_on_write" =
"true")"""
+ sql "INSERT INTO scan_key_bucket_mow SELECT * FROM scan_key_bucket_dup
WHERE payload != 'duplicate'"
+ sql "INSERT INTO scan_key_bucket_mow VALUES ('address000001', 2,
'updated'), ('address000002', 1, 'updated2')"
+ sql "DELETE FROM scan_key_bucket_mow WHERE address = 'address000004'"
+
+ def keys = (0..<55).collect { "'address${String.format('%06d', it * 17)}'"
}.join(',')
+ keys += ", '', '中文', 'missing', NULL, 'address000001', 'address000002',
'address000004'"
+ [false, true].each { prune ->
+ sql "SET enable_scan_key_bucket_prune = ${prune}"
+ [false, true].each { parallel ->
+ sql "SET enable_parallel_scan = ${parallel}"
+ [1, 1024].each { cap ->
+ // cap=1 coalesces IN keys to a range: routing must fall back.
+ sql "SET max_scan_key_num = ${cap}"
+ def label = "${prune}_${parallel}_${cap}"
+ "order_qt_dup_${label}"("SELECT * FROM scan_key_bucket_dup
WHERE address IN (${keys}) AND chain IN (1, 2)")
+ "order_qt_mow_${label}"("SELECT * FROM scan_key_bucket_mow
WHERE address IN (${keys}) AND chain IN (1, 2)")
+ "order_qt_range_${label}"("SELECT * FROM scan_key_bucket_dup
WHERE address > 'address000002' AND address < 'address000010'")
+ "qt_full_${label}"("SELECT count(*), sum(chain) FROM
scan_key_bucket_dup")
+ }
+ }
+ }
+ sql "SET enable_scan_key_bucket_prune = true"
+ sql "SET max_scan_key_num = 1024"
+ sql "DROP TABLE IF EXISTS scan_key_bucket_nullable"
+ sql """CREATE TABLE scan_key_bucket_nullable (address VARCHAR(80) NULL,
chain INT)
+ DUPLICATE KEY(address) DISTRIBUTED BY HASH(address) BUCKETS 8
+ PROPERTIES("replication_num" = "1")"""
+ sql "INSERT INTO scan_key_bucket_nullable VALUES (NULL, 0), ('a', 1),
('b', 2)"
+ order_qt_nullable "SELECT * FROM scan_key_bucket_nullable WHERE address IN
('a', 'b', NULL) OR address IS NULL"
+
+ sql "DROP TABLE IF EXISTS scan_key_bucket_composite"
+ sql """CREATE TABLE scan_key_bucket_composite (address VARCHAR(80) NOT
NULL, chain INT NOT NULL, v INT)
+ DUPLICATE KEY(address, chain) DISTRIBUTED BY HASH(address, chain)
BUCKETS 8
+ PROPERTIES("replication_num" = "1")"""
+ sql "INSERT INTO scan_key_bucket_composite VALUES ('a', 1, 1), ('a', 2,
2), ('b', 3, 3)"
+ order_qt_composite "SELECT * FROM scan_key_bucket_composite WHERE address
IN ('a', 'b') AND chain IN (1, 3)"
+
+ sql "DROP TABLE IF EXISTS scan_key_bucket_partitioned"
+ sql """CREATE TABLE scan_key_bucket_partitioned (address VARCHAR(80) NOT
NULL, chain INT NOT NULL)
+ DUPLICATE KEY(address) PARTITION BY RANGE(chain) (PARTITION p1 VALUES
LESS THAN ('2'), PARTITION p2 VALUES LESS THAN (MAXVALUE))
+ DISTRIBUTED BY HASH(address) BUCKETS 8 PROPERTIES("replication_num" =
"1")"""
+ sql "INSERT INTO scan_key_bucket_partitioned VALUES ('a', 1), ('b', 2),
('c', 3)"
+ order_qt_partitioned "SELECT * FROM scan_key_bucket_partitioned WHERE
address IN ('a', 'b', 'c') AND chain != 2"
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]