This is an automated email from the ASF dual-hosted git repository. eldenmoon 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 e24ab70e921 [improve](tablet schema) opt TabletIndex mem (#48273) e24ab70e921 is described below commit e24ab70e921d2076559c3d406381efbaf8f41fe0 Author: lihangyu <lihan...@selectdb.com> AuthorDate: Wed Mar 5 15:11:09 2025 +0800 [improve](tablet schema) opt TabletIndex mem (#48273) Reuse TabletIndex like TabletColumn related PR #42448 --- be/src/olap/tablet_column_object_pool.cpp | 73 ++++++++--- be/src/olap/tablet_column_object_pool.h | 17 ++- be/src/olap/tablet_schema.cpp | 80 +++++++----- be/src/olap/tablet_schema.h | 18 +-- be/src/olap/tablet_schema_cache.cpp | 2 +- be/test/olap/tablet_column_object_pool_test.cpp | 166 ++++++++++++++++++++++++ 6 files changed, 291 insertions(+), 65 deletions(-) diff --git a/be/src/olap/tablet_column_object_pool.cpp b/be/src/olap/tablet_column_object_pool.cpp index 6e07fb4e831..db9a20639c9 100644 --- a/be/src/olap/tablet_column_object_pool.cpp +++ b/be/src/olap/tablet_column_object_pool.cpp @@ -26,32 +26,67 @@ namespace doris { bvar::Adder<int64_t> g_tablet_column_cache_count("tablet_column_cache_count"); bvar::Adder<int64_t> g_tablet_column_cache_hit_count("tablet_column_cache_hit_count"); +bvar::Adder<int64_t> g_tablet_index_cache_count("tablet_index_cache_count"); +bvar::Adder<int64_t> g_tablet_index_cache_hit_count("tablet_index_cache_hit_count"); + +template <typename T, typename CacheValueType> +std::pair<Cache::Handle*, std::shared_ptr<T>> insert_impl( + TabletColumnObjectPool* pool, const std::string& key, const char* type_name, + bvar::Adder<int64_t>& cache_counter, bvar::Adder<int64_t>& hit_counter, + std::function<void(std::shared_ptr<T>, const std::string&)> init_from_pb) { + auto* lru_handle = pool->lookup(key); + std::shared_ptr<T> obj_ptr; -std::pair<Cache::Handle*, TabletColumnPtr> TabletColumnObjectPool::insert(const std::string& key) { - auto* lru_handle = lookup(key); - TabletColumnPtr tablet_column_ptr; if (lru_handle) { - auto* value = (CacheValue*)LRUCachePolicy::value(lru_handle); - tablet_column_ptr = value->tablet_column; - VLOG_DEBUG << "reuse column "; - g_tablet_column_cache_hit_count << 1; + auto* value = reinterpret_cast<CacheValueType*>(pool->value(lru_handle)); + obj_ptr = value->value; + VLOG_DEBUG << "reuse " << type_name; + hit_counter << 1; } else { - auto* value = new CacheValue; - tablet_column_ptr = std::make_shared<TabletColumn>(); - ColumnPB pb; - pb.ParseFromString(key); - tablet_column_ptr->init_from_pb(pb); - VLOG_DEBUG << "create column "; - value->tablet_column = tablet_column_ptr; - lru_handle = LRUCachePolicy::insert(key, value, 1, 0, CachePriority::NORMAL); - g_tablet_column_cache_count << 1; + auto* value = new CacheValueType; + obj_ptr = std::make_shared<T>(); + init_from_pb(obj_ptr, key); + VLOG_DEBUG << "create " << type_name; + value->value = obj_ptr; + lru_handle = pool->LRUCachePolicy::insert(key, value, 1, 0, CachePriority::NORMAL); + cache_counter << 1; } + DCHECK(lru_handle != nullptr); - return {lru_handle, tablet_column_ptr}; + return {lru_handle, obj_ptr}; +} + +std::pair<Cache::Handle*, TabletColumnPtr> TabletColumnObjectPool::insert(const std::string& key) { + return insert_impl<TabletColumn, CacheValue>(this, key, "column", g_tablet_column_cache_count, + g_tablet_column_cache_hit_count, + [](auto&& column, auto&& key) { + ColumnPB pb; + pb.ParseFromString(key); + column->init_from_pb(pb); + }); } -TabletColumnObjectPool::CacheValue::~CacheValue() { - g_tablet_column_cache_count << -1; +std::pair<Cache::Handle*, TabletIndexPtr> TabletColumnObjectPool::insert_index( + const std::string& key) { + return insert_impl<TabletIndex, IndexCacheValue>(this, key, "index", g_tablet_index_cache_count, + g_tablet_index_cache_hit_count, + [](auto&& index, auto&& key) { + TabletIndexPB index_pb; + index_pb.ParseFromString(key); + index->init_from_pb(index_pb); + }); } +template <typename T> +TabletColumnObjectPool::BaseCacheValue<T>::BaseCacheValue::~BaseCacheValue() { + if constexpr (std::is_same_v<T, TabletColumn>) { + g_tablet_column_cache_count << -1; + } else { + g_tablet_index_cache_count << -1; + } +} + +template struct TabletColumnObjectPool::BaseCacheValue<TabletColumn>; +template struct TabletColumnObjectPool::BaseCacheValue<TabletIndex>; + } // namespace doris diff --git a/be/src/olap/tablet_column_object_pool.h b/be/src/olap/tablet_column_object_pool.h index 1eead6a25c9..998ba7a9aa4 100644 --- a/be/src/olap/tablet_column_object_pool.h +++ b/be/src/olap/tablet_column_object_pool.h @@ -24,8 +24,8 @@ namespace doris { -// TabletColumnObjectPool is a cache for TabletColumn objects. It is used to reduce memory consumption -// when there are a large number of identical TabletColumns in the cluster, which usually occurs +// TabletColumnObjectPool is a cache for TabletColumn/TabletIndex objects. It is used to reduce memory consumption +// when there are a large number of identical TabletColumns/TabletIndex in the cluster, which usually occurs // when VARIANT type columns are modified and added, each Rowset has an individual TabletSchema. // Excessive TabletSchemas can lead to significant memory overhead. Reusing memory for identical // TabletColumns would greatly reduce this memory consumption. @@ -47,12 +47,17 @@ public: std::pair<Cache::Handle*, TabletColumnPtr> insert(const std::string& key); + std::pair<Cache::Handle*, TabletIndexPtr> insert_index(const std::string& key); + private: - class CacheValue : public LRUCacheValueBase { - public: - ~CacheValue() override; - TabletColumnPtr tablet_column; + template <typename T> + struct BaseCacheValue : public LRUCacheValueBase { + ~BaseCacheValue() override; + std::shared_ptr<T> value; }; + + using CacheValue = BaseCacheValue<TabletColumn>; + using IndexCacheValue = BaseCacheValue<TabletIndex>; }; } // namespace doris diff --git a/be/src/olap/tablet_schema.cpp b/be/src/olap/tablet_schema.cpp index 51e5a8dd179..716711589ea 100644 --- a/be/src/olap/tablet_schema.cpp +++ b/be/src/olap/tablet_schema.cpp @@ -573,7 +573,7 @@ void TabletColumn::init_from_pb(const ColumnPB& column) { // set path info for variant root column, to prevent from missing _column_path = std::make_shared<vectorized::PathInData>(_col_name_lower_case); } - for (auto& column_pb : column.sparse_columns()) { + for (const auto& column_pb : column.sparse_columns()) { TabletColumn column; column.init_from_pb(column_pb); _sparse_cols.emplace_back(std::make_shared<TabletColumn>(std::move(column))); @@ -816,7 +816,7 @@ void TabletIndex::init_from_pb(const TabletIndexPB& index) { _col_unique_ids.push_back(col_unique_id); } _index_type = index.index_type(); - for (auto& kv : index.properties()) { + for (const auto& kv : index.properties()) { _properties[kv.first] = kv.second; } _escaped_index_suffix_path = index.index_suffix_name(); @@ -905,18 +905,18 @@ void TabletColumn::append_sparse_column(TabletColumn column) { } void TabletSchema::append_index(TabletIndex&& index) { - _indexes.push_back(std::move(index)); + _indexes.push_back(std::make_shared<TabletIndex>(index)); } void TabletSchema::update_index(const TabletColumn& col, const IndexType& index_type, TabletIndex&& index) { int32_t col_unique_id = col.is_extracted_column() ? col.parent_unique_id() : col.unique_id(); const std::string& suffix_path = escape_for_path_name(col.suffix_path()); - for (size_t i = 0; i < _indexes.size(); i++) { - for (int32_t id : _indexes[i].col_unique_ids()) { - if (_indexes[i].index_type() == index_type && id == col_unique_id && - _indexes[i].get_index_suffix() == suffix_path) { - _indexes[i] = std::move(index); + for (auto& _indexe : _indexes) { + for (int32_t id : _indexe->col_unique_ids()) { + if (_indexe->index_type() == index_type && id == col_unique_id && + _indexe->get_index_suffix() == suffix_path) { + _indexe = std::make_shared<TabletIndex>(std::move(index)); break; } } @@ -928,14 +928,22 @@ void TabletSchema::replace_column(size_t pos, TabletColumn new_col) { _cols[pos] = std::make_shared<TabletColumn>(std::move(new_col)); } +void TabletSchema::clear_index_cache_handlers() { + for (auto* handle : _index_cache_handlers) { + TabletColumnObjectPool::instance()->release(handle); + } + _index_cache_handlers.clear(); +} + void TabletSchema::clear_index() { + clear_index_cache_handlers(); _indexes.clear(); } void TabletSchema::remove_index(int64_t index_id) { - std::vector<TabletIndex> indexes; + std::vector<TabletIndexPtr> indexes; for (auto index : _indexes) { - if (index.index_id() == index_id) { + if (index->index_id() == index_id) { continue; } indexes.emplace_back(std::move(index)); @@ -975,6 +983,7 @@ void TabletSchema::init_from_pb(const TabletSchemaPB& schema, bool ignore_extrac _field_id_to_index.clear(); _cluster_key_uids.clear(); clear_column_cache_handlers(); + clear_index_cache_handlers(); for (const auto& i : schema.cluster_key_uids()) { _cluster_key_uids.push_back(i); } @@ -1012,9 +1021,17 @@ void TabletSchema::init_from_pb(const TabletSchemaPB& schema, bool ignore_extrac } _num_columns++; } - for (auto& index_pb : schema.index()) { - TabletIndex index; - index.init_from_pb(index_pb); + for (const auto& index_pb : schema.index()) { + TabletIndexPtr index; + if (reuse_cache_column) { + auto pair = TabletColumnObjectPool::instance()->insert_index( + deterministic_string_serialize(index_pb)); + index = pair.second; + _index_cache_handlers.push_back(pair.first); + } else { + index = std::make_shared<TabletIndex>(); + index->init_from_pb(index_pb); + } _indexes.emplace_back(std::move(index)); } _num_short_key_columns = schema.num_short_key_columns(); @@ -1173,7 +1190,7 @@ void TabletSchema::build_current_tablet_schema(int64_t index_id, int32_t version } for (auto& i : index->indexes) { - _indexes.emplace_back(*i); + _indexes.emplace_back(std::make_shared<TabletIndex>(*i)); } if (has_bf_columns) { @@ -1260,7 +1277,7 @@ void TabletSchema::to_schema_pb(TabletSchemaPB* tablet_schema_pb) const { } for (const auto& index : _indexes) { auto* index_pb = tablet_schema_pb->add_index(); - index.to_schema_pb(index_pb); + index->to_schema_pb(index_pb); } tablet_schema_pb->set_num_short_key_columns(_num_short_key_columns); tablet_schema_pb->set_num_rows_per_row_block(_num_rows_per_row_block); @@ -1347,11 +1364,11 @@ TabletColumn& TabletSchema::mutable_column(size_t ordinal) { } void TabletSchema::update_indexes_from_thrift(const std::vector<doris::TOlapTableIndex>& tindexes) { - std::vector<TabletIndex> indexes; - for (auto& tindex : tindexes) { + std::vector<TabletIndexPtr> indexes; + for (const auto& tindex : tindexes) { TabletIndex index; index.init_from_thrift(tindex, *this); - indexes.emplace_back(std::move(index)); + indexes.emplace_back(std::make_shared<TabletIndex>(std::move(index))); } _indexes = std::move(indexes); } @@ -1398,7 +1415,8 @@ void TabletSchema::update_tablet_columns(const TabletSchema& tablet_schema, bool TabletSchema::has_inverted_index_with_index_id(int64_t index_id) const { for (size_t i = 0; i < _indexes.size(); i++) { - if (_indexes[i].index_type() == IndexType::INVERTED && _indexes[i].index_id() == index_id) { + if (_indexes[i]->index_type() == IndexType::INVERTED && + _indexes[i]->index_id() == index_id) { return true; } } @@ -1408,11 +1426,11 @@ bool TabletSchema::has_inverted_index_with_index_id(int64_t index_id) const { const TabletIndex* TabletSchema::inverted_index(int32_t col_unique_id, const std::string& suffix_path) const { const std::string escaped_suffix = escape_for_path_name(suffix_path); - for (size_t i = 0; i < _indexes.size(); i++) { - if (_indexes[i].index_type() == IndexType::INVERTED) { - for (int32_t id : _indexes[i].col_unique_ids()) { - if (id == col_unique_id && _indexes[i].get_index_suffix() == escaped_suffix) { - return &(_indexes[i]); + for (const auto& _index : _indexes) { + if (_index->index_type() == IndexType::INVERTED) { + for (int32_t id : _index->col_unique_ids()) { + if (id == col_unique_id && _index->get_index_suffix() == escaped_suffix) { + return _index.get(); } } } @@ -1433,9 +1451,9 @@ const TabletIndex* TabletSchema::inverted_index(const TabletColumn& col) const { bool TabletSchema::has_ngram_bf_index(int32_t col_unique_id) const { // TODO use more efficient impl - for (size_t i = 0; i < _indexes.size(); i++) { - if (_indexes[i].index_type() == IndexType::NGRAM_BF) { - for (int32_t id : _indexes[i].col_unique_ids()) { + for (const auto& _index : _indexes) { + if (_index->index_type() == IndexType::NGRAM_BF) { + for (int32_t id : _index->col_unique_ids()) { if (id == col_unique_id) { return true; } @@ -1448,11 +1466,11 @@ bool TabletSchema::has_ngram_bf_index(int32_t col_unique_id) const { const TabletIndex* TabletSchema::get_ngram_bf_index(int32_t col_unique_id) const { // TODO use more efficient impl - for (size_t i = 0; i < _indexes.size(); i++) { - if (_indexes[i].index_type() == IndexType::NGRAM_BF) { - for (int32_t id : _indexes[i].col_unique_ids()) { + for (const auto& _index : _indexes) { + if (_index->index_type() == IndexType::NGRAM_BF) { + for (int32_t id : _index->col_unique_ids()) { if (id == col_unique_id) { - return &(_indexes[i]); + return _index.get(); } } } diff --git a/be/src/olap/tablet_schema.h b/be/src/olap/tablet_schema.h index fbb135188a0..957b9adb2b9 100644 --- a/be/src/olap/tablet_schema.h +++ b/be/src/olap/tablet_schema.h @@ -253,8 +253,6 @@ private: bool operator==(const TabletColumn& a, const TabletColumn& b); bool operator!=(const TabletColumn& a, const TabletColumn& b); -class TabletSchema; - class TabletIndex : public MetadataAdder<TabletIndex> { public: TabletIndex() = default; @@ -297,6 +295,8 @@ private: std::map<string, string> _properties; }; +using TabletIndexPtr = std::shared_ptr<TabletIndex>; + class TabletSchema : public MetadataAdder<TabletSchema> { public: enum ColumnType { NORMAL = 0, DROPPED = 1, VARIANT = 2 }; @@ -402,8 +402,8 @@ public: const std::vector<const TabletIndex*> inverted_indexes() const { std::vector<const TabletIndex*> inverted_indexes; for (const auto& index : _indexes) { - if (index.index_type() == IndexType::INVERTED) { - inverted_indexes.emplace_back(&index); + if (index->index_type() == IndexType::INVERTED) { + inverted_indexes.emplace_back(index.get()); } } return inverted_indexes; @@ -411,14 +411,14 @@ public: bool has_inverted_index() const { for (const auto& index : _indexes) { DBUG_EXECUTE_IF("tablet_schema::has_inverted_index", { - if (index.col_unique_ids().empty()) { + if (index->col_unique_ids().empty()) { throw Exception(Status::InternalError("col unique ids cannot be empty")); } }); - if (index.index_type() == IndexType::INVERTED) { + if (index->index_type() == IndexType::INVERTED) { //if index_id == -1, ignore it. - if (!index.col_unique_ids().empty() && index.col_unique_ids()[0] >= 0) { + if (!index->col_unique_ids().empty() && index->col_unique_ids()[0] >= 0) { return true; } } @@ -547,6 +547,7 @@ private: TabletSchema(const TabletSchema&) = default; void clear_column_cache_handlers(); + void clear_index_cache_handlers(); KeysType _keys_type = DUP_KEYS; SortType _sort_type = SortType::LEXICAL; @@ -554,7 +555,8 @@ private: std::vector<TabletColumnPtr> _cols; std::vector<Cache::Handle*> _column_cache_handlers; - std::vector<TabletIndex> _indexes; + std::vector<TabletIndexPtr> _indexes; + std::vector<Cache::Handle*> _index_cache_handlers; std::unordered_map<StringRef, int32_t, StringRefHash> _field_name_to_index; std::unordered_map<int32_t, int32_t> _field_id_to_index; std::unordered_map<vectorized::PathInDataRef, int32_t, vectorized::PathInDataRef::Hash> diff --git a/be/src/olap/tablet_schema_cache.cpp b/be/src/olap/tablet_schema_cache.cpp index e044ef9c042..b39326551a9 100644 --- a/be/src/olap/tablet_schema_cache.cpp +++ b/be/src/olap/tablet_schema_cache.cpp @@ -52,7 +52,7 @@ std::pair<Cache::Handle*, TabletSchemaSPtr> TabletSchemaCache::insert(const std: tablet_schema_ptr = std::make_shared<TabletSchema>(); TabletSchemaPB pb; pb.ParseFromString(key); - // We should reuse the memory of the same TabletColumn object, set reuse_cached_column to true + // We should reuse the memory of the same TabletColumn/TabletIndex object, set reuse_cached_column to true tablet_schema_ptr->init_from_pb(pb, false, true); value->tablet_schema = tablet_schema_ptr; lru_handle = LRUCachePolicy::insert(key_signature, value, tablet_schema_ptr->num_columns(), diff --git a/be/test/olap/tablet_column_object_pool_test.cpp b/be/test/olap/tablet_column_object_pool_test.cpp new file mode 100644 index 00000000000..b28bd843377 --- /dev/null +++ b/be/test/olap/tablet_column_object_pool_test.cpp @@ -0,0 +1,166 @@ +// 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 "olap/tablet_column_object_pool.h" + +#include <gen_cpp/olap_file.pb.h> +#include <gtest/gtest.h> + +#include <memory> + +namespace doris { + +// Declare external variables +extern bvar::Adder<int64_t> g_tablet_column_cache_count; +extern bvar::Adder<int64_t> g_tablet_column_cache_hit_count; +extern bvar::Adder<int64_t> g_tablet_index_cache_count; +extern bvar::Adder<int64_t> g_tablet_index_cache_hit_count; + +class TabletColumnObjectPoolTest : public testing::Test { +protected: + void SetUp() override { + // reset all counters to 0 + g_tablet_column_cache_count.reset(); + g_tablet_column_cache_hit_count.reset(); + g_tablet_index_cache_count.reset(); + g_tablet_index_cache_hit_count.reset(); + + _pool = std::make_unique<TabletColumnObjectPool>(1024); + } + + // Helper function to create a column PB + std::string create_column_pb() { + ColumnPB pb; + pb.set_unique_id(123); + pb.set_name("test_column"); + pb.set_type("TINYINT"); + std::string serialized; + pb.SerializeToString(&serialized); + return serialized; + } + + // Helper function to create an index PB + std::string create_index_pb() { + TabletIndexPB pb; + pb.set_index_id(456); + pb.set_index_name("test_index"); + std::string serialized; + pb.SerializeToString(&serialized); + return serialized; + } + + std::unique_ptr<TabletColumnObjectPool> _pool; +}; + +// Test column cache insertion and hit +TEST_F(TabletColumnObjectPoolTest, TestColumnCacheInsertAndHit) { + std::string key = create_column_pb(); + + // First insertion + int64_t initial_count = g_tablet_column_cache_count.get_value(); + int64_t initial_hit_count = g_tablet_column_cache_hit_count.get_value(); + + auto [handle1, ptr1] = _pool->insert(key); + ASSERT_NE(nullptr, handle1); + ASSERT_NE(nullptr, ptr1); + ASSERT_EQ(initial_count + 1, g_tablet_column_cache_count.get_value()); + + // Second insertion with same key should hit cache + auto [handle2, ptr2] = _pool->insert(key); + ASSERT_NE(nullptr, handle2); + ASSERT_NE(nullptr, ptr2); + ASSERT_EQ(ptr1, ptr2); // Should return same object + ASSERT_EQ(initial_hit_count + 1, g_tablet_column_cache_hit_count.get_value()); + + _pool->release(handle1); + _pool->release(handle2); +} + +// Test index cache insertion and hit +TEST_F(TabletColumnObjectPoolTest, TestIndexCacheInsertAndHit) { + std::string key = create_index_pb(); + + // First insertion + int64_t initial_count = g_tablet_index_cache_count.get_value(); + int64_t initial_hit_count = g_tablet_index_cache_hit_count.get_value(); + + auto [handle1, ptr1] = _pool->insert_index(key); + ASSERT_NE(nullptr, handle1); + ASSERT_NE(nullptr, ptr1); + ASSERT_EQ(initial_count + 1, g_tablet_index_cache_count.get_value()); + + // Second insertion with same key should hit cache + auto [handle2, ptr2] = _pool->insert_index(key); + ASSERT_NE(nullptr, handle2); + ASSERT_NE(nullptr, ptr2); + ASSERT_EQ(ptr1, ptr2); // Should return same object + ASSERT_EQ(initial_hit_count + 1, g_tablet_index_cache_hit_count.get_value()); + + _pool->release(handle1); + _pool->release(handle2); +} + +// Test cache eviction +TEST_F(TabletColumnObjectPoolTest, TestCacheEviction) { + // Create a small cache + TabletColumnObjectPool small_pool(2); // Only 2 entries + + std::string key1 = create_column_pb(); + std::string key2 = create_column_pb(); + std::string key3 = create_column_pb(); + + // Insert first two entries + auto [handle1, ptr1] = small_pool.insert(key1); + auto [handle2, ptr2] = small_pool.insert(key2); + + // Release handle1 to allow eviction + small_pool.release(handle1); + + // Insert third entry should evict first entry + auto [handle3, ptr3] = small_pool.insert(key3); + + // Try to get first entry again - should be a new object + auto [handle1_new, ptr1_new] = small_pool.insert(key1); + ASSERT_NE(ptr1, ptr1_new); // Should be different objects + + small_pool.release(handle2); + small_pool.release(handle3); + small_pool.release(handle1_new); +} + +// Test destructor behavior +TEST_F(TabletColumnObjectPoolTest, TestDestructor) { + { + TabletColumnObjectPool temp_pool(10); + std::string key = create_column_pb(); + + int64_t initial_count = g_tablet_column_cache_count.get_value(); + auto [handle, ptr] = temp_pool.insert(key); + ASSERT_EQ(initial_count + 1, g_tablet_column_cache_count.get_value()); + + temp_pool.release(handle); + } // temp_pool destructor should be called here + // Verify counter was decremented + ASSERT_EQ(g_tablet_column_cache_count.get_value(), 0); +} + +} // namespace doris + +// int main(int argc, char** argv) { +// ::testing::InitGoogleTest(&argc, argv); +// return RUN_ALL_TESTS(); +// } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org