[Opt](func) Improve AggFunc Percentile performance (#62520)

### Key Optimizations
- Reworked exact percentile state into a simpler `values +
PercentileLevels + inited_flag` layout.
- Removed the old Counts-based exact percentile path and replaced it
with a lightweight `PercentileLevels` metadata structure.
- Reduced add-path overhead by directly appending values into a
contiguous buffer and initializing `percentile levels` only once.
- Optimized multi-quantile finalize(`percentile_array`) by sorting
quantiles through permutation once, then reusing incremental
`nth_element` selection.

### Performance
before:
```text
Doris> select percentile(FUniqID, 0.91) from hits_100m;
+---------------------------+
| percentile(FUniqID, 0.91) |
+---------------------------+
|     8.749975206154981e+18 |
+---------------------------+
1 row in set (6.149 sec)

Doris> select percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56]) from hits_100m;
+------------------------------------------------------------------------------------------------+
| percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56])                                      |
+------------------------------------------------------------------------------------------------+
| [0, 5.097819197233503e+18, 5.68906185719365e+18, 6.281280528073601e+18, 6.869583632113269e+18] |
+------------------------------------------------------------------------------------------------+
1 row in set (28.984 sec)

Doris> select percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56, 0.67, 0.78, 0.89, 0.91]) from hits_100m;
ERROR 1105 (HY000): errCode = 2, detailMessage = (127.0.0.1)[E-3113]string column length is too large: total_length=7200000149, element_number=0, rows=0
```

now
```text
Doris> select percentile(FUniqID, 0.91) from hits_100m;
+---------------------------+
| percentile(FUniqID, 0.91) |
+---------------------------+
|     8.749975206154981e+18 |
+---------------------------+
1 row in set (2.107 sec)

Doris> select percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56]) from hits_100m;
+------------------------------------------------------------------------------------------------+
| percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56])                                      |
+------------------------------------------------------------------------------------------------+
| [0, 5.097819197233503e+18, 5.68906185719365e+18, 6.281280528073601e+18, 6.869583632113269e+18] |
+------------------------------------------------------------------------------------------------+
1 row in set (5.903 sec)

Doris> select percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56, 0.67, 0.78, 0.89, 0.91]) from hits_100m;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| percentile_array(FUniqID, [0.12, 0.23, 0.34, 0.45, 0.56, 0.67, 0.78, 0.89, 0.91])                                                                                                          |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [0, 5.097819197233503e+18, 5.68906185719365e+18, 6.281280528073601e+18, 6.869583632113269e+18, 7.459044216605808e+18, 8.044902013692362e+18, 8.640523572825436e+18, 8.749975206154981e+18] |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (8.866 sec)
```
This commit is contained in:
linrrarity authored and GitHub committed 2026-04-24 11:52:07 +08:00
1 parent 7dbb507e37
commit ac6d282671
15 files changed
+821 -94

No files matched your search

@@ -46,6 +46,7 @@ struct AggregateFunctionAttr {
bool is_window_function {false};
bool is_foreach {false};
bool enable_aggregate_function_null_v2 {false};
bool new_version_percentile {false};
std::vector<std::string> column_names;
};
@@ -63,9 +63,13 @@ void register_aggregate_function_percentile(AggregateFunctionSimpleFactory& fact
using creator = creator_with_type_list<TYPE_TINYINT, TYPE_SMALLINT, TYPE_INT, TYPE_BIGINT,
TYPE_LARGEINT, TYPE_FLOAT, TYPE_DOUBLE>;
factory.register_function_both("percentile", creator::creator<AggregateFunctionPercentile>);
factory.register_alias("percentile", "percentile_cont");
factory.register_function_both("percentile_array",
creator::creator<AggregateFunctionPercentileArray>);
factory.register_function_both("percentile_v2",
creator::creator<AggregateFunctionPercentileV2>);
factory.register_function_both("percentile_array_v2",
creator::creator<AggregateFunctionPercentileArrayV2>);
factory.register_alias("percentile", "percentile_cont");
}
void register_percentile_approx_old_function(AggregateFunctionSimpleFactory& factory) {
@@ -87,4 +91,4 @@ void register_aggregate_function_percentile_approx(AggregateFunctionSimpleFactor
register_percentile_approx_old_function(factory);
}
} // namespace doris
} // namespace doris
@@ -36,10 +36,11 @@
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/pod_array.h"
#include "core/pod_array_fwd.h"
#include "core/types.h"
#include "exprs/aggregate/aggregate_function.h"
#include "util/counts.h"
#include "util/percentile_util.h"
#include "util/tdigest.h"
namespace doris {
@@ -47,14 +48,6 @@ namespace doris {
class Arena;
class BufferReadable;
inline void check_quantile(double quantile) {
if (quantile < 0 || quantile > 1) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"quantile in func percentile should in [0, 1], but real data is:" +
std::to_string(quantile));
}
}
struct PercentileApproxState {
static constexpr double INIT_QUANTILE = -1.0;
PercentileApproxState() = default;
@@ -436,6 +429,193 @@ struct PercentileState {
}
};
template <PrimitiveType T>
struct PercentileExactState {
using ValueType = typename PrimitiveTypeTraits<T>::CppType;
static constexpr size_t bytes_in_arena = 64 - sizeof(PODArray<ValueType>);
using Array = PODArrayWithStackMemory<ValueType, bytes_in_arena>;
void add_single_range(const ValueType* data, size_t count, double quantile) {
if (!inited_flag) {
_set_single_level(quantile);
inited_flag = true;
}
_append(data, count);
}
void add_many_range(const ValueType* data, size_t count,
const PaddedPODArray<Float64>& quantiles_data, const NullMap& null_maps,
size_t start, int64_t arg_size) {
if (!inited_flag) {
_set_many_levels(quantiles_data, null_maps, start, arg_size);
inited_flag = true;
}
if (levels.empty()) {
return;
}
_append(data, count);
}
void write(BufferWritable& buf) const {
buf.write_binary(inited_flag);
if (!inited_flag) {
return;
}
levels.write(buf);
size_t size = values.size();
buf.write_binary(size);
if (size > 0) {
buf.write(reinterpret_cast<const char*>(values.data()), sizeof(ValueType) * size);
}
}
void read(BufferReadable& buf) {
reset();
buf.read_binary(inited_flag);
if (!inited_flag) {
return;
}
levels.read(buf);
size_t size = 0;
buf.read_binary(size);
values.resize(size);
if (size > 0) {
auto raw = buf.read(sizeof(ValueType) * size);
memcpy(values.data(), raw.data, raw.size);
}
}
void merge(const PercentileExactState& rhs) {
if (!rhs.inited_flag) {
return;
}
if (!inited_flag) {
levels = rhs.levels;
inited_flag = true;
} else {
levels.merge(rhs.levels);
}
_append(rhs.values.data(), rhs.values.size());
}
void reset() {
values.clear();
levels.clear();
inited_flag = false;
}
double get() const {
if (!inited_flag || levels.empty() || values.empty()) {
return 0.0;
}
DCHECK_EQ(levels.quantiles.size(), 1);
return _get_result(levels.quantiles[0]);
}
void insert_result_into(IColumn& to) const {
auto& column_data = assert_cast<ColumnFloat64&>(to).get_data();
if (!inited_flag || levels.empty() || values.empty()) {
return;
}
size_t old_size = column_data.size();
size_t size = levels.quantiles.size();
column_data.resize(old_size + size);
auto* result = column_data.data() + old_size;
if (values.size() == 1) {
for (size_t i = 0; i < size; ++i) {
result[i] = static_cast<double>(values.front());
}
return;
}
size_t prev_index = 0;
const auto& quantiles = levels.quantiles;
const auto& permutation = levels.get_permutation();
for (size_t i = 0; i < size; ++i) {
auto level_index = permutation[i];
auto level = quantiles[level_index];
double u = static_cast<double>(values.size() - 1) * level;
auto index = static_cast<size_t>(u);
if (index + 1 >= values.size()) {
result[level_index] =
static_cast<double>(*std::max_element(values.begin(), values.end()));
} else {
std::nth_element(values.begin() + prev_index, values.begin() + index, values.end());
auto* nth_elem = std::min_element(values.begin() + index + 1, values.end());
result[level_index] =
static_cast<double>(values[index]) +
(u - static_cast<double>(index)) * (static_cast<double>(*nth_elem) -
static_cast<double>(values[index]));
prev_index = index;
}
}
}
private:
void _set_single_level(double quantile) {
DCHECK(levels.empty());
check_quantile(quantile);
levels.quantiles.push_back(quantile);
levels.permutation.push_back(0);
}
void _set_many_levels(const PaddedPODArray<Float64>& quantiles_data, const NullMap& null_maps,
size_t start, int64_t arg_size) {
DCHECK(levels.empty());
size_t size = cast_set<size_t>(arg_size);
levels.quantiles.resize(size);
levels.permutation.resize(size);
for (size_t i = 0; i < size; ++i) {
if (null_maps[start + i]) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"quantiles in func percentile_array should not have null");
}
check_quantile(quantiles_data[start + i]);
levels.quantiles[i] = quantiles_data[start + i];
levels.permutation[i] = i;
}
}
void _append(const ValueType* data, size_t count) {
if (count == 0) {
return;
}
values.reserve(values.size() + count);
values.insert_assume_reserved(data, data + count);
}
double _get_result(double quantile) const {
if (values.size() == 1) {
return static_cast<double>(values.front());
}
double u = static_cast<double>(values.size() - 1) * quantile;
auto index = static_cast<size_t>(u);
if (index + 1 >= values.size()) {
return static_cast<double>(*std::max_element(values.begin(), values.end()));
}
std::nth_element(values.begin(), values.begin() + index, values.end());
auto* nth_elem = std::min_element(values.begin() + index + 1, values.end());
return static_cast<double>(values[index]) +
(u - static_cast<double>(index)) *
(static_cast<double>(*nth_elem) - static_cast<double>(values[index]));
}
mutable Array values;
mutable PercentileLevels levels;
bool inited_flag = false;
};
template <PrimitiveType T>
class AggregateFunctionPercentile final
: public IAggregateFunctionDataHelper<PercentileState<T>, AggregateFunctionPercentile<T>>,
@@ -568,4 +748,194 @@ public:
}
};
} // namespace doris
template <PrimitiveType T>
class AggregateFunctionPercentileV2 final
: public IAggregateFunctionDataHelper<PercentileExactState<T>,
AggregateFunctionPercentileV2<T>>,
MultiExpression,
NullableAggregateFunction {
public:
using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
using Base =
IAggregateFunctionDataHelper<PercentileExactState<T>, AggregateFunctionPercentileV2<T>>;
AggregateFunctionPercentileV2(const DataTypes& argument_types_) : Base(argument_types_) {}
String get_name() const override { return "percentile_v2"; }
DataTypePtr get_return_type() const override { return std::make_shared<DataTypeFloat64>(); }
void add(AggregateDataPtr __restrict place, const IColumn** columns, ssize_t row_num,
Arena&) const override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(*columns[1]);
AggregateFunctionPercentileV2::data(place).add_single_range(&sources.get_data()[row_num], 1,
quantile.get_data()[0]);
}
void add_batch_single_place(size_t batch_size, AggregateDataPtr place, const IColumn** columns,
Arena&) const override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(*columns[1]);
DCHECK_EQ(sources.get_data().size(), batch_size);
AggregateFunctionPercentileV2::data(place).add_single_range(
sources.get_data().data(), batch_size, quantile.get_data()[0]);
}
void add_batch_range(size_t batch_begin, size_t batch_end, AggregateDataPtr place,
const IColumn** columns, Arena&, bool has_null) override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(*columns[1]);
DCHECK(!has_null);
AggregateFunctionPercentileV2::data(place).add_single_range(
sources.get_data().data() + batch_begin, batch_end - batch_begin + 1,
quantile.get_data()[0]);
}
void reset(AggregateDataPtr __restrict place) const override {
AggregateFunctionPercentileV2::data(place).reset();
}
void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs,
Arena&) const override {
AggregateFunctionPercentileV2::data(place).merge(AggregateFunctionPercentileV2::data(rhs));
}
void serialize(ConstAggregateDataPtr __restrict place, BufferWritable& buf) const override {
AggregateFunctionPercentileV2::data(place).write(buf);
}
void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf,
Arena&) const override {
AggregateFunctionPercentileV2::data(place).read(buf);
}
void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn& to) const override {
auto& col = assert_cast<ColumnFloat64&>(to);
col.insert_value(AggregateFunctionPercentileV2::data(place).get());
}
};
template <PrimitiveType T>
class AggregateFunctionPercentileArrayV2 final
: public IAggregateFunctionDataHelper<PercentileExactState<T>,
AggregateFunctionPercentileArrayV2<T>>,
MultiExpression,
NotNullableAggregateFunction {
public:
using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
using Base = IAggregateFunctionDataHelper<PercentileExactState<T>,
AggregateFunctionPercentileArrayV2<T>>;
AggregateFunctionPercentileArrayV2(const DataTypes& argument_types_) : Base(argument_types_) {}
String get_name() const override { return "percentile_array_v2"; }
DataTypePtr get_return_type() const override {
return std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()));
}
void add(AggregateDataPtr __restrict place, const IColumn** columns, ssize_t row_num,
Arena&) const override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile_array =
assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[1]);
const auto& offset_column_data = quantile_array.get_offsets();
const auto& null_maps = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_null_map_data();
const auto& nested_column = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_nested_column();
const auto& nested_column_data =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(nested_column);
size_t start = row_num == 0 ? 0 : offset_column_data[row_num - 1];
AggregateFunctionPercentileArrayV2::data(place).add_many_range(
&sources.get_data()[row_num], 1, nested_column_data.get_data(), null_maps, start,
cast_set<int64_t>(offset_column_data[row_num] - start));
}
void add_batch_single_place(size_t batch_size, AggregateDataPtr place, const IColumn** columns,
Arena&) const override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile_array =
assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[1]);
const auto& offset_column_data = quantile_array.get_offsets();
const auto& null_maps = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_null_map_data();
const auto& nested_column = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_nested_column();
const auto& nested_column_data =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(nested_column);
DCHECK_EQ(sources.get_data().size(), batch_size);
AggregateFunctionPercentileArrayV2::data(place).add_many_range(
sources.get_data().data(), batch_size, nested_column_data.get_data(), null_maps, 0,
cast_set<int64_t>(offset_column_data[0]));
}
void add_batch_range(size_t batch_begin, size_t batch_end, AggregateDataPtr place,
const IColumn** columns, Arena&, bool has_null) override {
const auto& sources =
assert_cast<const ColVecType&, TypeCheckOnRelease::DISABLE>(*columns[0]);
const auto& quantile_array =
assert_cast<const ColumnArray&, TypeCheckOnRelease::DISABLE>(*columns[1]);
const auto& offset_column_data = quantile_array.get_offsets();
const auto& null_maps = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_null_map_data();
const auto& nested_column = assert_cast<const ColumnNullable&, TypeCheckOnRelease::DISABLE>(
quantile_array.get_data())
.get_nested_column();
const auto& nested_column_data =
assert_cast<const ColumnFloat64&, TypeCheckOnRelease::DISABLE>(nested_column);
DCHECK(!has_null);
size_t start = batch_begin == 0 ? 0 : offset_column_data[batch_begin - 1];
AggregateFunctionPercentileArrayV2::data(place).add_many_range(
sources.get_data().data() + batch_begin, batch_end - batch_begin + 1,
nested_column_data.get_data(), null_maps, start,
cast_set<int64_t>(offset_column_data[batch_begin] - start));
}
void reset(AggregateDataPtr __restrict place) const override {
AggregateFunctionPercentileArrayV2::data(place).reset();
}
void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs,
Arena&) const override {
AggregateFunctionPercentileArrayV2::data(place).merge(
AggregateFunctionPercentileArrayV2::data(rhs));
}
void serialize(ConstAggregateDataPtr __restrict place, BufferWritable& buf) const override {
AggregateFunctionPercentileArrayV2::data(place).write(buf);
}
void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf,
Arena&) const override {
AggregateFunctionPercentileArrayV2::data(place).read(buf);
}
void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn& to) const override {
auto& to_arr = assert_cast<ColumnArray&>(to);
auto& to_nested_col = to_arr.get_data();
if (to_nested_col.is_nullable()) {
auto* col_null = reinterpret_cast<ColumnNullable*>(&to_nested_col);
AggregateFunctionPercentileArrayV2::data(place).insert_result_into(
col_null->get_nested_column());
col_null->get_null_map_data().resize_fill(col_null->get_nested_column().size(), 0);
} else {
AggregateFunctionPercentileArrayV2::data(place).insert_result_into(to_nested_col);
}
to_arr.get_offsets().push_back(to_nested_col.size());
}
};
} // namespace doris
@@ -135,6 +135,15 @@ public:
if (function_alias.contains(name)) {
name_str = function_alias[name];
}
if (attr.new_version_percentile) {
if (name_str == "percentile" || name_str == "percentile_cont") {
name_str = "percentile_v2";
} else if (name_str == "percentile_array") {
name_str = "percentile_array_v2";
}
}
if (nullable) {
return nullable_aggregate_functions.find(name_str) == nullable_aggregate_functions.end()
? nullptr
+6
View File
@@ -229,6 +229,9 @@ Status AggFnEvaluator::prepare(RuntimeState* state, const RowDescriptor& desc,
.is_foreach = is_foreach,
.enable_aggregate_function_null_v2 =
state->enable_aggregate_function_null_v2(),
.new_version_percentile =
state->query_options().__isset.new_version_percentile &&
state->query_options().new_version_percentile,
.column_names = std::move(column_names)});
} else {
_function = AggregateFunctionSimpleFactory::instance().get(
@@ -238,6 +241,9 @@ Status AggFnEvaluator::prepare(RuntimeState* state, const RowDescriptor& desc,
.is_foreach = is_foreach,
.enable_aggregate_function_null_v2 =
state->enable_aggregate_function_null_v2(),
.new_version_percentile =
state->query_options().__isset.new_version_percentile &&
state->query_options().new_version_percentile,
.column_names = std::move(column_names)});
}
}
@@ -21,13 +21,27 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <queue>
#include <string>
#include <vector>
#include "common/cast_set.h"
#include "common/exception.h"
#include "core/column/column_nullable.h"
#include "core/pod_array.h"
#include "core/string_buffer.hpp"
namespace doris {
inline void check_quantile(double quantile) {
if (quantile < 0 || quantile > 1) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"quantile in func percentile should in [0, 1], but real data is:" +
std::to_string(quantile));
}
}
template <typename Ty>
class Counts {
public:
@@ -223,4 +237,64 @@ private:
std::vector<PODArray<Ty>> _sorted_nums_vec;
};
class PercentileLevels {
public:
void merge(const PercentileLevels& rhs) {
if (rhs.empty()) {
return;
}
if (empty()) {
quantiles = rhs.quantiles;
permutation = rhs.permutation;
return;
}
DCHECK_EQ(quantiles.size(), rhs.quantiles.size());
for (size_t i = 0; i < quantiles.size(); ++i) {
DCHECK_EQ(quantiles[i], rhs.quantiles[i]);
}
}
void write(BufferWritable& buf) const {
int size_num = cast_set<int>(quantiles.size());
buf.write_binary(size_num);
for (const auto& quantile : quantiles) {
buf.write_binary(quantile);
}
}
void read(BufferReadable& buf) {
int size_num = 0;
buf.read_binary(size_num);
quantiles.resize(size_num);
permutation.resize(size_num);
for (int i = 0; i < size_num; ++i) {
buf.read_binary(quantiles[i]);
permutation[i] = cast_set<size_t>(i);
}
}
void clear() {
quantiles.clear();
permutation.clear();
}
bool empty() const { return quantiles.empty(); }
const std::vector<size_t>& get_permutation() const {
sort_permutation();
return permutation;
}
void sort_permutation() const {
pdqsort(permutation.begin(), permutation.end(),
[this](size_t lhs, size_t rhs) { return quantiles[lhs] < quantiles[rhs]; });
}
std::vector<double> quantiles;
mutable std::vector<size_t> permutation;
};
} // namespace doris
+49
View File
@@ -28,6 +28,7 @@
#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
#include "core/column/column_vector.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_date.h"
#include "core/data_type/data_type_date_time.h"
#include "core/data_type/data_type_nullable.h"
@@ -49,6 +50,7 @@ void register_aggregate_function_sum(AggregateFunctionSimpleFactory& factory);
void register_aggregate_function_topn(AggregateFunctionSimpleFactory& factory);
void register_aggregate_function_bit(AggregateFunctionSimpleFactory& factory);
void register_aggregate_function_minmax(AggregateFunctionSimpleFactory& factory);
void register_aggregate_function_percentile(AggregateFunctionSimpleFactory& factory);
void register_aggregate_function_replace_reader_load(AggregateFunctionSimpleFactory& factory);
TEST(AggTest, basic_test) {
@@ -136,6 +138,53 @@ TEST(AggTest, window_function_test) {
EXPECT_EQ(size2, 1);
}
TEST(AggTest, percentile_query_option_routes_default_names_to_v2) {
AggregateFunctionSimpleFactory factory;
register_aggregate_function_percentile(factory);
int be_version = BeExecVersionManager::get_newest_version();
DataTypes percentile_types = {std::make_shared<DataTypeInt64>(),
std::make_shared<DataTypeFloat64>()};
auto percentile_result_type = std::make_shared<DataTypeFloat64>();
auto percentile_v1 =
factory.get("percentile", percentile_types, percentile_result_type, false, be_version);
ASSERT_NE(percentile_v1, nullptr);
EXPECT_EQ(percentile_v1->get_name(), "percentile");
auto percentile_v2 =
factory.get("percentile", percentile_types, percentile_result_type, false, be_version,
{.new_version_percentile = true, .column_names = {}});
ASSERT_NE(percentile_v2, nullptr);
EXPECT_EQ(percentile_v2->get_name(), "percentile_v2");
auto percentile_cont_v1 = factory.get("percentile_cont", percentile_types,
percentile_result_type, false, be_version);
ASSERT_NE(percentile_cont_v1, nullptr);
EXPECT_EQ(percentile_cont_v1->get_name(), "percentile");
auto percentile_cont_v2 =
factory.get("percentile_cont", percentile_types, percentile_result_type, false,
be_version, {.new_version_percentile = true, .column_names = {}});
ASSERT_NE(percentile_cont_v2, nullptr);
EXPECT_EQ(percentile_cont_v2->get_name(), "percentile_v2");
DataTypes percentile_array_types = {
std::make_shared<DataTypeInt64>(),
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()))};
auto percentile_array_result_type =
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeFloat64>()));
auto percentile_array_v1 = factory.get("percentile_array", percentile_array_types,
percentile_array_result_type, false, be_version);
ASSERT_NE(percentile_array_v1, nullptr);
EXPECT_EQ(percentile_array_v1->get_name(), "percentile_array");
auto percentile_array_v2 =
factory.get("percentile_array", percentile_array_types, percentile_array_result_type,
false, be_version, {.new_version_percentile = true, .column_names = {}});
ASSERT_NE(percentile_array_v2, nullptr);
EXPECT_EQ(percentile_array_v2->get_name(), "percentile_array_v2");
}
TEST(AggTest, window_function_test2) {
AggregateFunctionSimpleFactory factory;
register_aggregate_function_sum(factory);
-82
View File
@@ -1,82 +0,0 @@
// 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 "util/counts.h"
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
#include <cstdint>
#include "gtest/gtest_pred_impl.h"
namespace doris {
class TCountsTest : public testing::Test {};
TEST_F(TCountsTest, TotalTest) {
Counts<int64_t> counts;
// 1 1 1 2 5 7 7 9 9 19
// >>> import numpy as np
// >>> a = np.array([1,1,1,2,5,7,7,9,9,19])
// >>> p = np.percentile(a, 20)
counts.increment(1, 3);
counts.increment(5, 1);
counts.increment(2, 1);
counts.increment(9, 1);
counts.increment(9, 1);
counts.increment(19, 1);
counts.increment(7, 2);
double result = counts.terminate(0.2);
EXPECT_EQ(1, result);
auto cs = ColumnString::create();
BufferWritable bw(*cs);
counts.serialize(bw);
bw.commit();
Counts<int64_t> other;
StringRef res(cs->get_chars().data(), cs->get_chars().size());
BufferReadable br(res);
other.unserialize(br);
double result1 = other.terminate(0.2);
EXPECT_EQ(result, result1);
Counts<int64_t> other1;
other1.increment(1, 1);
other1.increment(100, 3);
other1.increment(50, 3);
other1.increment(10, 1);
other1.increment(99, 2);
// deserialize other1
cs->clear();
other1.serialize(bw);
bw.commit();
Counts<int64_t> other1_deserialized;
BufferReadable br1(res);
other1_deserialized.unserialize(br1);
Counts<int64_t> merge_res;
merge_res.merge(&other);
merge_res.merge(&other1_deserialized);
// 1 1 1 1 2 5 7 7 9 9 10 19 50 50 50 99 99 100 100 100
EXPECT_EQ(merge_res.terminate(0.3), 6.4);
}
} // namespace doris
+290
View File
@@ -0,0 +1,290 @@
// 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 "util/percentile_util.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <cstdint>
#include <vector>
namespace doris {
class PercentileUtilTest : public testing::Test {};
TEST_F(PercentileUtilTest, CountsTotalTest) {
Counts<int64_t> counts;
// 1 1 1 2 5 7 7 9 9 19
// >>> import numpy as np
// >>> a = np.array([1,1,1,2,5,7,7,9,9,19])
// >>> p = np.percentile(a, 20)
counts.increment(1, 3);
counts.increment(5, 1);
counts.increment(2, 1);
counts.increment(9, 1);
counts.increment(9, 1);
counts.increment(19, 1);
counts.increment(7, 2);
double result = counts.terminate(0.2);
EXPECT_EQ(1, result);
auto cs = ColumnString::create();
BufferWritable bw(*cs);
counts.serialize(bw);
bw.commit();
Counts<int64_t> other;
StringRef res(cs->get_chars().data(), cs->get_chars().size());
BufferReadable br(res);
other.unserialize(br);
double result1 = other.terminate(0.2);
EXPECT_EQ(result, result1);
Counts<int64_t> other1;
other1.increment(1, 1);
other1.increment(100, 3);
other1.increment(50, 3);
other1.increment(10, 1);
other1.increment(99, 2);
// deserialize other1
cs->clear();
other1.serialize(bw);
bw.commit();
Counts<int64_t> other1_deserialized;
BufferReadable br1(res);
other1_deserialized.unserialize(br1);
Counts<int64_t> merge_res;
merge_res.merge(&other);
merge_res.merge(&other1_deserialized);
// 1 1 1 1 2 5 7 7 9 9 10 19 50 50 50 99 99 100 100 100
EXPECT_EQ(merge_res.terminate(0.3), 6.4);
}
TEST_F(PercentileUtilTest, CountsBoundaryBehavior) {
Counts<int64_t> empty_counts;
EXPECT_DOUBLE_EQ(0.0, empty_counts.terminate(0.5));
Counts<int64_t> single_counts;
single_counts.increment(42);
EXPECT_DOUBLE_EQ(42.0, single_counts.terminate(0.0));
EXPECT_DOUBLE_EQ(42.0, single_counts.terminate(0.5));
EXPECT_DOUBLE_EQ(42.0, single_counts.terminate(1.0));
}
TEST_F(PercentileUtilTest, CountsSerializeMergedState) {
Counts<int64_t> left;
left.increment(5);
left.increment(1);
auto col = ColumnString::create();
BufferWritable writer(*col);
left.serialize(writer);
writer.commit();
StringRef left_data(col->get_chars().data(), col->get_chars().size());
BufferReadable left_reader(left_data);
Counts<int64_t> left_sorted;
left_sorted.unserialize(left_reader);
Counts<int64_t> right;
right.increment(9);
right.increment(7);
col->clear();
right.serialize(writer);
writer.commit();
StringRef right_data(col->get_chars().data(), col->get_chars().size());
BufferReadable right_reader(right_data);
Counts<int64_t> right_sorted;
right_sorted.unserialize(right_reader);
Counts<int64_t> merged;
merged.merge(&left_sorted);
merged.merge(&right_sorted);
col->clear();
merged.serialize(writer);
writer.commit();
StringRef merged_data(col->get_chars().data(), col->get_chars().size());
BufferReadable merged_reader(merged_data);
Counts<int64_t> restored;
restored.unserialize(merged_reader);
EXPECT_DOUBLE_EQ(1.0, restored.terminate(0.0));
EXPECT_DOUBLE_EQ(6.0, restored.terminate(0.5));
EXPECT_DOUBLE_EQ(9.0, restored.terminate(1.0));
}
TEST_F(PercentileUtilTest, CheckQuantileBoundary) {
EXPECT_NO_THROW(check_quantile(0.0));
EXPECT_NO_THROW(check_quantile(0.5));
EXPECT_NO_THROW(check_quantile(1.0));
EXPECT_THROW(check_quantile(-0.0001), Exception);
EXPECT_THROW(check_quantile(1.0001), Exception);
}
TEST_F(PercentileUtilTest, EmptyLevelsState) {
PercentileLevels levels;
EXPECT_TRUE(levels.empty());
EXPECT_TRUE(levels.quantiles.empty());
EXPECT_TRUE(levels.permutation.empty());
auto col = ColumnString::create();
BufferWritable writer(*col);
levels.write(writer);
writer.commit();
StringRef data(col->get_chars().data(), col->get_chars().size());
BufferReadable reader(data);
PercentileLevels restored;
restored.read(reader);
EXPECT_TRUE(restored.empty());
EXPECT_TRUE(restored.quantiles.empty());
EXPECT_TRUE(restored.permutation.empty());
}
TEST_F(PercentileUtilTest, SortPermutationKeepsQuantiles) {
PercentileLevels levels;
levels.quantiles = {0.56, 0.12, 0.45, 0.23};
levels.permutation = {0, 1, 2, 3};
const auto& permutation = levels.get_permutation();
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), permutation);
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), levels.permutation);
EXPECT_EQ((std::vector<double> {0.56, 0.12, 0.45, 0.23}), levels.quantiles);
}
TEST_F(PercentileUtilTest, SortPermutationWithDuplicateQuantiles) {
PercentileLevels levels;
levels.quantiles = {0.7, 0.2, 0.2, 0.9, 0.7};
levels.permutation = {0, 1, 2, 3, 4};
const auto& permutation = levels.get_permutation();
ASSERT_EQ(levels.quantiles.size(), permutation.size());
std::vector<size_t> sorted_perm = permutation;
std::sort(sorted_perm.begin(), sorted_perm.end());
EXPECT_EQ((std::vector<size_t> {0, 1, 2, 3, 4}), sorted_perm);
for (size_t i = 1; i < permutation.size(); ++i) {
EXPECT_LE(levels.quantiles[permutation[i - 1]], levels.quantiles[permutation[i]]);
}
}
TEST_F(PercentileUtilTest, WriteReadDefersPermutationSort) {
PercentileLevels levels;
levels.quantiles = {0.56, 0.12, 0.45, 0.23};
levels.permutation = {0, 1, 2, 3};
auto col = ColumnString::create();
BufferWritable writer(*col);
levels.write(writer);
writer.commit();
StringRef data(col->get_chars().data(), col->get_chars().size());
BufferReadable reader(data);
PercentileLevels restored;
restored.read(reader);
EXPECT_EQ(levels.quantiles, restored.quantiles);
EXPECT_EQ((std::vector<size_t> {0, 1, 2, 3}), restored.permutation);
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), restored.get_permutation());
}
TEST_F(PercentileUtilTest, ReadBuildsPermutationFromUnsortedQuantiles) {
auto col = ColumnString::create();
BufferWritable writer(*col);
writer.write_binary(4);
writer.write_binary(0.56);
writer.write_binary(0.12);
writer.write_binary(0.45);
writer.write_binary(0.23);
writer.commit();
StringRef data(col->get_chars().data(), col->get_chars().size());
BufferReadable reader(data);
PercentileLevels levels;
levels.read(reader);
EXPECT_EQ((std::vector<double> {0.56, 0.12, 0.45, 0.23}), levels.quantiles);
EXPECT_EQ((std::vector<size_t> {0, 1, 2, 3}), levels.permutation);
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), levels.get_permutation());
}
TEST_F(PercentileUtilTest, MergeFromEmptyCopiesState) {
PercentileLevels src;
src.quantiles = {0.56, 0.12, 0.45, 0.23};
src.permutation = {0, 1, 2, 3};
PercentileLevels dst;
dst.merge(src);
EXPECT_EQ(src.quantiles, dst.quantiles);
EXPECT_EQ(src.permutation, dst.permutation);
}
TEST_F(PercentileUtilTest, MergeEmptyRightKeepsLeft) {
PercentileLevels lhs;
lhs.quantiles = {0.56, 0.12, 0.45, 0.23};
lhs.permutation = {0, 1, 2, 3};
PercentileLevels rhs;
lhs.merge(rhs);
EXPECT_EQ((std::vector<double> {0.56, 0.12, 0.45, 0.23}), lhs.quantiles);
EXPECT_EQ((std::vector<size_t> {0, 1, 2, 3}), lhs.permutation);
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), lhs.get_permutation());
}
TEST_F(PercentileUtilTest, MergeSameStateKeepsState) {
PercentileLevels lhs;
lhs.quantiles = {0.56, 0.12, 0.45, 0.23};
lhs.permutation = {0, 1, 2, 3};
PercentileLevels rhs;
rhs.quantiles = lhs.quantiles;
rhs.permutation = lhs.permutation;
lhs.merge(rhs);
EXPECT_EQ((std::vector<double> {0.56, 0.12, 0.45, 0.23}), lhs.quantiles);
EXPECT_EQ((std::vector<size_t> {0, 1, 2, 3}), lhs.permutation);
EXPECT_EQ((std::vector<size_t> {1, 3, 2, 0}), lhs.get_permutation());
}
TEST_F(PercentileUtilTest, ClearResetsState) {
PercentileLevels levels;
levels.quantiles = {0.56, 0.12, 0.45, 0.23};
levels.permutation = {1, 3, 2, 0};
levels.clear();
EXPECT_TRUE(levels.empty());
EXPECT_TRUE(levels.quantiles.empty());
EXPECT_TRUE(levels.permutation.empty());
}
} // namespace doris
@@ -318,6 +318,7 @@ public class NereidsStreamLoadPlanner {
: false;
queryOptions.setEnableMemtableOnSinkNode(enableMemtableOnSinkNode);
queryOptions.setNewVersionUnixTimestamp(true);
queryOptions.setNewVersionPercentile(true);
params.setQueryOptions(queryOptions);
TQueryGlobals queryGlobals = new TQueryGlobals();
queryGlobals.setNowString(TimeUtils.getDatetimeFormatWithTimeZone().format(LocalDateTime.now()));
@@ -321,6 +321,7 @@ public class FoldConstantRuleOnBE implements ExpressionPatternRuleFactory {
tQueryOptions.setBeExecVersion(Config.be_exec_version);
tQueryOptions.setEnableDecimal256(context.getSessionVariable().isEnableDecimal256());
tQueryOptions.setNewVersionUnixTimestamp(true);
tQueryOptions.setNewVersionPercentile(true);
tQueryOptions.setEnableStrictCast(SessionVariable.enableStrictCast());
TFoldConstantParams tParams = new TFoldConstantParams(paramMap, queryGlobals);
@@ -309,6 +309,7 @@ public class CoordinatorContext {
queryOptions.setProfileLevel(2);
queryOptions.setBeExecVersion(Config.be_exec_version);
queryOptions.setNewVersionUnixTimestamp(true);
queryOptions.setNewVersionPercentile(true);
TQueryGlobals queryGlobals = new TQueryGlobals();
queryGlobals.setNowString(TimeUtils.getDatetimeFormatWithTimeZone().format(LocalDateTime.now()));
@@ -492,6 +492,7 @@ public class NereidsCoordinator extends Coordinator {
// Set this field to true to avoid data entering the normal cache LRU queue
this.coordinatorContext.queryOptions.setDisableFileCache(true);
this.coordinatorContext.queryOptions.setNewVersionUnixTimestamp(true);
this.coordinatorContext.queryOptions.setNewVersionPercentile(true);
}
private void setForQuery() {
@@ -5597,6 +5597,7 @@ public class SessionVariable implements Serializable, Writable {
tResult.setEnableStrictCast(enableStrictCast());
tResult.setEnableInsertStrict(enableInsertStrict);
tResult.setNewVersionUnixTimestamp(true); // once FE upgraded, always use new version
tResult.setNewVersionPercentile(true);
tResult.setHnswEfSearch(hnswEFSearch);
tResult.setHnswCheckRelativeDistance(hnswCheckRelativeDistance);
+1
View File
@@ -482,6 +482,7 @@ struct TQueryOptions {
// Use Rust-based Lance reader for FORMAT_LANCE scan ranges
216: optional bool enable_rust_lance_reader = false;
217: optional bool new_version_percentile = false
// 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.