This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new 2c0021a2f51 branch-3.1: [opt](segment) SegmentFooterPB cache #48924 
(#53517)
2c0021a2f51 is described below

commit 2c0021a2f51e2e28e6b52928d1871bcd608fbd45
Author: lihangyu <[email protected]>
AuthorDate: Fri Jul 18 16:49:21 2025 +0800

    branch-3.1: [opt](segment) SegmentFooterPB cache #48924 (#53517)
    
    cherry-pick #48924
    
    Co-authored-by: zhiqiang <[email protected]>
---
 be/src/olap/page_cache.cpp                     |  85 +++++++++--
 be/src/olap/page_cache.h                       |  78 +++++++---
 be/src/olap/rowset/segment_v2/page_io.cpp      |   2 +
 be/src/olap/rowset/segment_v2/segment.cpp      |  80 ++++++++--
 be/src/olap/rowset/segment_v2/segment.h        |  11 +-
 be/test/olap/date_bloom_filter_test.cpp        |  11 +-
 be/test/olap/delete_bitmap_calculator_test.cpp | 113 +++++++-------
 be/test/olap/segment_footer_cache_test.cpp     | 195 +++++++++++++++++++++++++
 8 files changed, 467 insertions(+), 108 deletions(-)

diff --git a/be/src/olap/page_cache.cpp b/be/src/olap/page_cache.cpp
index b386da4d7c6..7e1129ba4de 100644
--- a/be/src/olap/page_cache.cpp
+++ b/be/src/olap/page_cache.cpp
@@ -17,6 +17,7 @@
 
 #include "olap/page_cache.h"
 
+#include <gen_cpp/segment_v2.pb.h>
 #include <glog/logging.h>
 
 #include <ostream>
@@ -24,32 +25,51 @@
 #include "runtime/exec_env.h"
 
 namespace doris {
-template <typename TAllocator>
-PageBase<TAllocator>::PageBase(size_t b, bool use_cache, 
segment_v2::PageTypePB page_type)
-        : LRUCacheValueBase(), _size(b), _capacity(b) {
+
+template <typename T>
+MemoryTrackedPageBase<T>::MemoryTrackedPageBase(size_t size, bool use_cache,
+                                                segment_v2::PageTypePB 
page_type)
+        : _size(size) {
     if (use_cache) {
         _mem_tracker_by_allocator = 
StoragePageCache::instance()->mem_tracker(page_type);
     } else {
         _mem_tracker_by_allocator =
                 
thread_context()->thread_mem_tracker_mgr->limiter_mem_tracker_sptr();
     }
+}
+
+MemoryTrackedPageWithPageEntity::MemoryTrackedPageWithPageEntity(size_t size, 
bool use_cache,
+                                                                 
segment_v2::PageTypePB page_type)
+        : MemoryTrackedPageBase<char*>(size, use_cache, page_type), 
_capacity(size) {
     {
-        SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_mem_tracker_by_allocator);
-        _data = reinterpret_cast<char*>(TAllocator::alloc(_capacity, 
ALLOCATOR_ALIGNMENT_16));
+        
SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(this->_mem_tracker_by_allocator);
+        this->_data = reinterpret_cast<char*>(
+                Allocator<false>::alloc(this->_capacity, 
ALLOCATOR_ALIGNMENT_16));
     }
 }
 
-template <typename TAllocator>
-PageBase<TAllocator>::~PageBase() {
-    if (_data != nullptr) {
-        DCHECK(_capacity != 0 && _size != 0);
-        SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_mem_tracker_by_allocator);
-        TAllocator::free(_data, _capacity);
+MemoryTrackedPageWithPageEntity::~MemoryTrackedPageWithPageEntity() {
+    if (this->_data != nullptr) {
+        DCHECK(this->_capacity != 0 && this->_size != 0);
+        
SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(this->_mem_tracker_by_allocator);
+        Allocator<false>::free(this->_data, this->_capacity);
     }
 }
 
-template class PageBase<Allocator<true>>;
-template class PageBase<Allocator<false>>;
+template <typename T>
+MemoryTrackedPageWithPagePtr<T>::MemoryTrackedPageWithPagePtr(size_t size,
+                                                              
segment_v2::PageTypePB page_type)
+        : MemoryTrackedPageBase<std::shared_ptr<T>>(size, true, page_type) {
+    DCHECK(this->_size > 0);
+    this->_size = size;
+    this->_mem_tracker_by_allocator->consume(this->_size);
+}
+
+template <typename T>
+MemoryTrackedPageWithPagePtr<T>::~MemoryTrackedPageWithPagePtr() {
+    DCHECK(this->_size > 0);
+    this->_mem_tracker_by_allocator->release(this->_size);
+}
 
 StoragePageCache* StoragePageCache::create_global_cache(size_t capacity,
                                                         int32_t 
index_cache_percentage,
@@ -98,7 +118,46 @@ void StoragePageCache::insert(const CacheKey& key, 
DataPage* data, PageCacheHand
 
     auto* cache = _get_page_cache(page_type);
     auto* lru_handle = cache->insert(key.encode(), data, data->capacity(), 0, 
priority);
+    DCHECK(lru_handle != nullptr);
+    *handle = PageCacheHandle(cache, lru_handle);
+}
+
+template <typename T>
+void StoragePageCache::insert(const CacheKey& key, T data, size_t size, 
PageCacheHandle* handle,
+                              segment_v2::PageTypePB page_type, bool 
in_memory) {
+    static_assert(std::is_same<typename std::remove_cv<T>::type,
+                               std::shared_ptr<typename 
T::element_type>>::value,
+                  "Second argument must be a std::shared_ptr");
+    using ValueType = typename T::element_type; // Type that shared_ptr points 
to
+
+    CachePriority priority = CachePriority::NORMAL;
+    if (in_memory) {
+        priority = CachePriority::DURABLE;
+    }
+
+    auto* cache = _get_page_cache(page_type);
+    // Lify cycle of page will be managed by StoragePageCache
+    auto page = 
std::make_unique<MemoryTrackedPageWithPagePtr<ValueType>>(size, page_type);
+    // Lify cycle of data will be managed by StoragePageCache and user at the 
same time.
+    page->set_data(data);
+
+    auto* lru_handle = cache->insert(key.encode(), page.get(), size, 0, 
priority);
+    DCHECK(lru_handle != nullptr);
     *handle = PageCacheHandle(cache, lru_handle);
+    // Now page is managed by StoragePageCache.
+    page.release();
+}
+
+Slice PageCacheHandle::data() const {
+    auto* cache_value = (DataPage*)_cache->value(_handle);
+    return {cache_value->data(), cache_value->size()};
 }
 
+template void StoragePageCache::insert(const CacheKey& key,
+                                       
std::shared_ptr<segment_v2::SegmentFooterPB> data,
+                                       size_t size, PageCacheHandle* handle,
+                                       segment_v2::PageTypePB page_type, bool 
in_memory);
+
+template class MemoryTrackedPageWithPagePtr<segment_v2::SegmentFooterPB>;
+
 } // namespace doris
diff --git a/be/src/olap/page_cache.h b/be/src/olap/page_cache.h
index 32b6683e782..73b20f1d5f2 100644
--- a/be/src/olap/page_cache.h
+++ b/be/src/olap/page_cache.h
@@ -37,33 +37,54 @@ namespace doris {
 
 class PageCacheHandle;
 
-template <typename TAllocator>
-class PageBase : private TAllocator, public LRUCacheValueBase {
+template <typename T>
+class MemoryTrackedPageBase : public LRUCacheValueBase {
 public:
-    PageBase() = default;
-    PageBase(size_t b, bool use_cache, segment_v2::PageTypePB page_type);
-    PageBase(const PageBase&) = delete;
-    PageBase& operator=(const PageBase&) = delete;
-    ~PageBase() override;
+    MemoryTrackedPageBase() = default;
+    MemoryTrackedPageBase(size_t b, bool use_cache, segment_v2::PageTypePB 
page_type);
 
-    char* data() { return _data; }
+    MemoryTrackedPageBase(const MemoryTrackedPageBase&) = delete;
+    MemoryTrackedPageBase& operator=(const MemoryTrackedPageBase&) = delete;
+    ~MemoryTrackedPageBase() = default;
+
+    T data() { return _data; }
     size_t size() { return _size; }
-    size_t capacity() { return _capacity; }
+
+protected:
+    T _data;
+    size_t _size = 0;
+    std::shared_ptr<MemTrackerLimiter> _mem_tracker_by_allocator;
+};
+
+class MemoryTrackedPageWithPageEntity : Allocator<false>, public 
MemoryTrackedPageBase<char*> {
+public:
+    MemoryTrackedPageWithPageEntity(size_t b, bool use_cache, 
segment_v2::PageTypePB page_type);
+
+    size_t capacity() { return this->_capacity; }
+
+    ~MemoryTrackedPageWithPageEntity() override;
 
     void reset_size(size_t n) {
-        DCHECK(n <= _capacity);
-        _size = n;
+        DCHECK(n <= this->_capacity);
+        this->_size = n;
     }
 
 private:
-    char* _data = nullptr;
-    // Effective size, smaller than capacity, such as data page remove 
checksum suffix.
-    size_t _size = 0;
     size_t _capacity = 0;
-    std::shared_ptr<MemTrackerLimiter> _mem_tracker_by_allocator;
 };
 
-using DataPage = PageBase<Allocator<false>>;
+template <typename T>
+class MemoryTrackedPageWithPagePtr : public 
MemoryTrackedPageBase<std::shared_ptr<T>> {
+public:
+    MemoryTrackedPageWithPagePtr(size_t b, segment_v2::PageTypePB page_type);
+
+    ~MemoryTrackedPageWithPagePtr() override;
+
+    void set_data(std::shared_ptr<T> data) { this->_data = data; }
+};
+
+using SemgnetFooterPBPage = 
MemoryTrackedPageWithPagePtr<segment_v2::SegmentFooterPB>;
+using DataPage = MemoryTrackedPageWithPageEntity;
 
 // Wrapper around Cache, and used for cache page of column data
 // in Segment.
@@ -149,6 +170,17 @@ public:
     void insert(const CacheKey& key, DataPage* data, PageCacheHandle* handle,
                 segment_v2::PageTypePB page_type, bool in_memory = false);
 
+    // Insert a std::share_ptr which points to a page into this cache.
+    // size should be the size of the page instead of shared_ptr.
+    // Internal implementation will wrap shared_ptr with 
MemoryTrackedPageWithPagePtr
+    // Since we are using std::shared_ptr, so lify cycle of the page is not 
managed by
+    // this cache alone.
+    // User could store a weak_ptr to the page, and lock it when needed.
+    // See Segment::_get_segment_footer for example.
+    template <typename T>
+    void insert(const CacheKey& key, T data, size_t size, PageCacheHandle* 
handle,
+                segment_v2::PageTypePB page_type, bool in_memory = false);
+
     std::shared_ptr<MemTrackerLimiter> mem_tracker(segment_v2::PageTypePB 
page_type) {
         return _get_page_cache(page_type)->mem_tracker();
     }
@@ -211,9 +243,17 @@ public:
     }
 
     LRUCachePolicy* cache() const { return _cache; }
-    Slice data() const {
-        auto* cache_value = (DataPage*)_cache->value(_handle);
-        return {cache_value->data(), cache_value->size()};
+    Slice data() const;
+
+    template <typename T>
+    T get() const {
+        static_assert(std::is_same<typename std::remove_cv<T>::type,
+                                   std::shared_ptr<typename 
T::element_type>>::value,
+                      "T must be a std::shared_ptr");
+        using ValueType = typename T::element_type; // Type that shared_ptr 
points to
+        MemoryTrackedPageWithPagePtr<ValueType>* page =
+                
(MemoryTrackedPageWithPagePtr<ValueType>*)_cache->value(_handle);
+        return page->data();
     }
 
 private:
diff --git a/be/src/olap/rowset/segment_v2/page_io.cpp 
b/be/src/olap/rowset/segment_v2/page_io.cpp
index 0e3d891aa07..4e1069888dc 100644
--- a/be/src/olap/rowset/segment_v2/page_io.cpp
+++ b/be/src/olap/rowset/segment_v2/page_io.cpp
@@ -134,6 +134,8 @@ Status PageIO::read_and_decompress_page_(const 
PageReadOptions& opts, PageHandle
     PageCacheHandle cache_handle;
     StoragePageCache::CacheKey cache_key(opts.file_reader->path().native(),
                                          opts.file_reader->size(), 
opts.page_pointer.offset);
+    VLOG_DEBUG << fmt::format("Reading page {}:{}:{}", cache_key.fname, 
cache_key.fsize,
+                              cache_key.offset);
     if (opts.use_page_cache && cache && cache->lookup(cache_key, 
&cache_handle, opts.type)) {
         // we find page in cache, use it
         *handle = PageHandle(std::move(cache_handle));
diff --git a/be/src/olap/rowset/segment_v2/segment.cpp 
b/be/src/olap/rowset/segment_v2/segment.cpp
index 2b8e19c7ce3..c9a60bee032 100644
--- a/be/src/olap/rowset/segment_v2/segment.cpp
+++ b/be/src/olap/rowset/segment_v2/segment.cpp
@@ -167,8 +167,9 @@ io::UInt128Wrapper Segment::file_cache_key(std::string_view 
rowset_id, uint32_t
 }
 
 int64_t Segment::get_metadata_size() const {
-    return sizeof(Segment) + (_footer_pb ? _footer_pb->ByteSizeLong() : 0) +
-           (_pk_index_meta ? _pk_index_meta->ByteSizeLong() : 0);
+    std::shared_ptr<SegmentFooterPB> footer_pb_shared = _footer_pb.lock();
+    return sizeof(Segment) + (_pk_index_meta ? _pk_index_meta->ByteSizeLong() 
: 0) +
+           (footer_pb_shared ? footer_pb_shared->ByteSizeLong() : 0);
 }
 
 void Segment::update_metadata_size() {
@@ -178,18 +179,20 @@ void Segment::update_metadata_size() {
 }
 
 Status Segment::_open(OlapReaderStatistics* stats) {
-    _footer_pb = std::make_unique<SegmentFooterPB>();
-    RETURN_IF_ERROR(_parse_footer(_footer_pb.get(), stats));
-    _pk_index_meta.reset(_footer_pb->has_primary_key_index_meta()
-                                 ? new 
PrimaryKeyIndexMetaPB(_footer_pb->primary_key_index_meta())
-                                 : nullptr);
+    std::shared_ptr<SegmentFooterPB> footer_pb_shared;
+    RETURN_IF_ERROR(_get_segment_footer(footer_pb_shared, stats));
+
+    _pk_index_meta.reset(
+            footer_pb_shared->has_primary_key_index_meta()
+                    ? new 
PrimaryKeyIndexMetaPB(footer_pb_shared->primary_key_index_meta())
+                    : nullptr);
     // delete_bitmap_calculator_test.cpp
     // DCHECK(footer.has_short_key_index_page());
-    _sk_index_page = _footer_pb->short_key_index_page();
-    _num_rows = _footer_pb->num_rows();
+    _sk_index_page = footer_pb_shared->short_key_index_page();
+    _num_rows = footer_pb_shared->num_rows();
 
     // An estimated memory usage of a segment
-    _meta_mem_usage += _footer_pb->ByteSizeLong();
+    _meta_mem_usage += footer_pb_shared->ByteSizeLong();
     if (_pk_index_meta != nullptr) {
         _meta_mem_usage += _pk_index_meta->ByteSizeLong();
     }
@@ -392,7 +395,8 @@ Status Segment::_write_error_file(size_t file_size, size_t 
offset, size_t bytes_
     return Status::OK(); // already exists
 };
 
-Status Segment::_parse_footer(SegmentFooterPB* footer, OlapReaderStatistics* 
stats) {
+Status Segment::_parse_footer(std::shared_ptr<SegmentFooterPB>& footer,
+                              OlapReaderStatistics* stats) {
     // Footer := SegmentFooterPB, FooterPBSize(4), FooterPBChecksum(4), 
MagicNumber(4)
     auto file_size = _file_reader->size();
     if (file_size < 12) {
@@ -459,6 +463,7 @@ Status Segment::_parse_footer(SegmentFooterPB* footer, 
OlapReaderStatistics* sta
     }
 
     // deserialize footer PB
+    footer = std::make_shared<SegmentFooterPB>();
     if (!footer->ParseFromString(footer_buf)) {
         Status st = _write_error_file(file_size, file_size - 12 - 
footer_length, bytes_read,
                                       footer_buf.data(), io_ctx);
@@ -470,6 +475,9 @@ Status Segment::_parse_footer(SegmentFooterPB* footer, 
OlapReaderStatistics* sta
                 _file_reader->path().native(), file_size,
                 file_cache_key_str(_file_reader->path().native()));
     }
+
+    VLOG_DEBUG << fmt::format("Loading segment footer from {} finished",
+                              _file_reader->path().native());
     return Status::OK();
 }
 
@@ -637,9 +645,9 @@ Status 
Segment::_create_column_readers_once(OlapReaderStatistics* stats) {
         SCOPED_RAW_TIMER(&stats->segment_create_column_readers_timer_ns);
     }
     return _create_column_readers_once_call.call([&] {
-        DCHECK(_footer_pb);
-        Defer defer([&]() { _footer_pb.reset(); });
-        return _create_column_readers(*_footer_pb);
+        std::shared_ptr<SegmentFooterPB> footer_pb_shared;
+        RETURN_IF_ERROR(_get_segment_footer(footer_pb_shared, stats));
+        return _create_column_readers(*footer_pb_shared);
     });
 }
 
@@ -1015,4 +1023,48 @@ Status Segment::seek_and_read_by_rowid(const 
TabletSchema& schema, SlotDescripto
     return Status::OK();
 }
 
+Status Segment::_get_segment_footer(std::shared_ptr<SegmentFooterPB>& 
footer_pb,
+                                    OlapReaderStatistics* stats) {
+    std::shared_ptr<SegmentFooterPB> footer_pb_shared = _footer_pb.lock();
+    if (footer_pb_shared != nullptr) {
+        footer_pb = footer_pb_shared;
+        return Status::OK();
+    }
+
+    VLOG_DEBUG << fmt::format("Segment footer of {}:{}:{} is missing, try to 
load it",
+                              _file_reader->path().native(), 
_file_reader->size(),
+                              _file_reader->size() - 12);
+
+    StoragePageCache* segment_footer_cache = 
ExecEnv::GetInstance()->get_storage_page_cache();
+    DCHECK(segment_footer_cache != nullptr);
+
+    auto cache_key = get_segment_footer_cache_key();
+
+    PageCacheHandle cache_handle;
+
+    if (!segment_footer_cache->lookup(cache_key, &cache_handle,
+                                      segment_v2::PageTypePB::DATA_PAGE)) {
+        RETURN_IF_ERROR(_parse_footer(footer_pb_shared, stats));
+        segment_footer_cache->insert(cache_key, footer_pb_shared, 
footer_pb_shared->ByteSizeLong(),
+                                     &cache_handle, 
segment_v2::PageTypePB::DATA_PAGE);
+    } else {
+        VLOG_DEBUG << fmt::format("Segment footer of {}:{}:{} is found in 
cache",
+                                  _file_reader->path().native(), 
_file_reader->size(),
+                                  _file_reader->size() - 12);
+    }
+    footer_pb_shared = cache_handle.get<std::shared_ptr<SegmentFooterPB>>();
+    _footer_pb = footer_pb_shared;
+    footer_pb = footer_pb_shared;
+    return Status::OK();
+}
+
+StoragePageCache::CacheKey Segment::get_segment_footer_cache_key() const {
+    DCHECK(_file_reader != nullptr);
+    // The footer is always at the end of the segment file.
+    // The size of footer is 12.
+    // So we use the size of file minus 12 as the cache key, which is unique 
for each segment file.
+    return StoragePageCache::CacheKey(_file_reader->path().native(), 
_file_reader->size(),
+                                      _file_reader->size() - 12);
+}
+
 } // namespace doris::segment_v2
diff --git a/be/src/olap/rowset/segment_v2/segment.h 
b/be/src/olap/rowset/segment_v2/segment.h
index b3b1a72c345..27237e71a63 100644
--- a/be/src/olap/rowset/segment_v2/segment.h
+++ b/be/src/olap/rowset/segment_v2/segment.h
@@ -35,6 +35,7 @@
 #include "io/fs/file_system.h"
 #include "olap/field.h"
 #include "olap/olap_common.h"
+#include "olap/page_cache.h"
 #include "olap/rowset/segment_v2/column_reader.h" // ColumnReader
 #include "olap/rowset/segment_v2/page_handle.h"
 #include "olap/schema.h"
@@ -219,7 +220,8 @@ private:
                         OlapReaderStatistics* stats = nullptr);
     // open segment file and read the minimum amount of necessary information 
(footer)
     Status _open(OlapReaderStatistics* stats);
-    Status _parse_footer(SegmentFooterPB* footer, OlapReaderStatistics* stats 
= nullptr);
+    Status _parse_footer(std::shared_ptr<SegmentFooterPB>& footer,
+                         OlapReaderStatistics* stats = nullptr);
     Status _create_column_readers(const SegmentFooterPB& footer);
     Status _load_pk_bloom_filter(OlapReaderStatistics* stats);
     ColumnReader* _get_column_reader(const TabletColumn& col);
@@ -231,7 +233,10 @@ private:
 
     Status _create_column_readers_once(OlapReaderStatistics* stats);
 
-private:
+    Status _get_segment_footer(std::shared_ptr<SegmentFooterPB>&, 
OlapReaderStatistics* stats);
+
+    StoragePageCache::CacheKey get_segment_footer_cache_key() const;
+
     friend class SegmentIterator;
     io::FileSystemSPtr _fs;
     io::FileReaderSPtr _file_reader;
@@ -268,7 +273,7 @@ private:
 
     DorisCallOnce<Status> _create_column_readers_once_call;
 
-    std::unique_ptr<SegmentFooterPB> _footer_pb;
+    std::weak_ptr<SegmentFooterPB> _footer_pb;
 
     // used to hold short key index page in memory
     PageHandle _sk_index_handle;
diff --git a/be/test/olap/date_bloom_filter_test.cpp 
b/be/test/olap/date_bloom_filter_test.cpp
index c528862c87b..04a0eaaefd0 100644
--- a/be/test/olap/date_bloom_filter_test.cpp
+++ b/be/test/olap/date_bloom_filter_test.cpp
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <gen_cpp/segment_v2.pb.h>
 #include <gtest/gtest.h>
 
 #include "olap/comparison_predicate.h"
@@ -150,7 +151,10 @@ TEST_F(DateBloomFilterTest, query_index_test) {
 
     segment_v2::SegmentSharedPtr segment;
     EXPECT_TRUE(((BetaRowset*)rowset.get())->load_segment(0, &segment).ok());
-    auto st = segment->_create_column_readers(*(segment->_footer_pb));
+    std::shared_ptr<SegmentFooterPB> footer_pb_shared;
+    auto st = segment->_get_segment_footer(footer_pb_shared, nullptr);
+    EXPECT_TRUE(st.ok());
+    st = segment->_create_column_readers(*footer_pb_shared);
     EXPECT_TRUE(st.ok());
 
     // date
@@ -227,7 +231,10 @@ TEST_F(DateBloomFilterTest, in_list_predicate_test) {
 
     segment_v2::SegmentSharedPtr segment;
     EXPECT_TRUE(((BetaRowset*)rowset.get())->load_segment(0, &segment).ok());
-    auto st = segment->_create_column_readers(*(segment->_footer_pb));
+    std::shared_ptr<SegmentFooterPB> footer_pb_shared;
+    auto st = segment->_get_segment_footer(footer_pb_shared, nullptr);
+    EXPECT_TRUE(st.ok());
+    st = segment->_create_column_readers(*(footer_pb_shared));
     EXPECT_TRUE(st.ok());
 
     // Test DATE column with IN predicate
diff --git a/be/test/olap/delete_bitmap_calculator_test.cpp 
b/be/test/olap/delete_bitmap_calculator_test.cpp
index ee54a061363..42e1d279f0f 100644
--- a/be/test/olap/delete_bitmap_calculator_test.cpp
+++ b/be/test/olap/delete_bitmap_calculator_test.cpp
@@ -50,9 +50,9 @@ static RowsetId rowset_id {0};
 
 using Generator = std::function<void(size_t rid, int cid, RowCursorCell& 
cell)>;
 
-static TabletColumnPtr create_int_sequence_value(int32_t id, bool is_nullable 
= true,
-                                                 bool is_bf_column = false,
-                                                 bool has_bitmap_index = 
false) {
+TabletColumnPtr create_int_sequence_value(int32_t id, bool is_nullable = true,
+                                          bool is_bf_column = false,
+                                          bool has_bitmap_index = false) {
     TabletColumnPtr column = std::make_shared<TabletColumn>();
     column->_unique_id = id;
     column->_col_name = std::to_string(id);
@@ -67,6 +67,58 @@ static TabletColumnPtr create_int_sequence_value(int32_t id, 
bool is_nullable =
     return column;
 }
 
+void build_segment(SegmentWriterOptions opts, TabletSchemaSPtr build_schema, 
size_t segment_id,
+                   TabletSchemaSPtr query_schema, size_t nrows, Generator 
generator,
+                   std::shared_ptr<Segment>* res, std::string segment_dir) {
+    std::string filename = fmt::format("{}_{}.dat", rowset_id.to_string(), 
segment_id);
+    std::string path = fmt::format("{}/{}", segment_dir, filename);
+    auto fs = io::global_local_filesystem();
+
+    io::FileWriterPtr file_writer;
+    Status st = fs->create_file(path, &file_writer);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+    SegmentWriter writer(file_writer.get(), segment_id, build_schema, nullptr, 
nullptr, opts,
+                         nullptr);
+    st = writer.init();
+    EXPECT_TRUE(st.ok());
+
+    RowCursor row;
+    auto olap_st = row.init(build_schema);
+    EXPECT_EQ(Status::OK(), olap_st);
+
+    for (size_t rid = 0; rid < nrows; ++rid) {
+        for (int cid = 0; cid < build_schema->num_columns(); ++cid) {
+            RowCursorCell cell = row.cell(cid);
+            generator(rid, cid, cell);
+        }
+        EXPECT_TRUE(writer.append_row(row).ok());
+    }
+
+    uint64_t file_size, index_size;
+    st = writer.finalize(&file_size, &index_size);
+    EXPECT_TRUE(st.ok());
+    EXPECT_TRUE(file_writer->close().ok());
+
+    EXPECT_NE("", writer.min_encoded_key().to_string());
+    EXPECT_NE("", writer.max_encoded_key().to_string());
+
+    int64_t tablet_id = 100;
+    st = segment_v2::Segment::open(fs, path, tablet_id, segment_id, rowset_id, 
query_schema,
+                                   io::FileReaderOptions {}, res);
+    EXPECT_TRUE(st.ok());
+    EXPECT_EQ(nrows, (*res)->num_rows());
+}
+
+TabletSchemaSPtr create_schema(const std::vector<TabletColumnPtr>& columns,
+                               KeysType keys_type = UNIQUE_KEYS) {
+    TabletSchemaSPtr res = std::make_shared<TabletSchema>();
+
+    for (auto& col : columns) {
+        res->append_column(*col);
+    }
+    res->_keys_type = keys_type;
+    return res;
+}
 class DeleteBitmapCalculatorTest : public testing::Test {
 public:
     void SetUp() override {
@@ -82,59 +134,6 @@ public:
         
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(kSegmentDir).ok());
     }
 
-    TabletSchemaSPtr create_schema(const std::vector<TabletColumnPtr>& columns,
-                                   KeysType keys_type = UNIQUE_KEYS) {
-        TabletSchemaSPtr res = std::make_shared<TabletSchema>();
-
-        for (auto& col : columns) {
-            res->append_column(*col);
-        }
-        res->_keys_type = keys_type;
-        return res;
-    }
-
-    void build_segment(SegmentWriterOptions opts, TabletSchemaSPtr 
build_schema, size_t segment_id,
-                       TabletSchemaSPtr query_schema, size_t nrows, Generator 
generator,
-                       std::shared_ptr<Segment>* res) {
-        std::string filename = fmt::format("{}_{}.dat", rowset_id.to_string(), 
segment_id);
-        std::string path = fmt::format("{}/{}", kSegmentDir, filename);
-        auto fs = io::global_local_filesystem();
-
-        io::FileWriterPtr file_writer;
-        Status st = fs->create_file(path, &file_writer);
-        EXPECT_TRUE(st.ok());
-        SegmentWriter writer(file_writer.get(), segment_id, build_schema, 
nullptr, nullptr, opts,
-                             nullptr);
-        st = writer.init();
-        EXPECT_TRUE(st.ok());
-
-        RowCursor row;
-        auto olap_st = row.init(build_schema);
-        EXPECT_EQ(Status::OK(), olap_st);
-
-        for (size_t rid = 0; rid < nrows; ++rid) {
-            for (int cid = 0; cid < build_schema->num_columns(); ++cid) {
-                RowCursorCell cell = row.cell(cid);
-                generator(rid, cid, cell);
-            }
-            EXPECT_TRUE(writer.append_row(row).ok());
-        }
-
-        uint64_t file_size, index_size;
-        st = writer.finalize(&file_size, &index_size);
-        EXPECT_TRUE(st.ok());
-        EXPECT_TRUE(file_writer->close().ok());
-
-        EXPECT_NE("", writer.min_encoded_key().to_string());
-        EXPECT_NE("", writer.max_encoded_key().to_string());
-
-        int64_t tablet_id = 100;
-        st = segment_v2::Segment::open(fs, path, tablet_id, segment_id, 
rowset_id, query_schema,
-                                       io::FileReaderOptions {}, res);
-        EXPECT_TRUE(st.ok());
-        EXPECT_EQ(nrows, (*res)->num_rows());
-    }
-
     void run_test(size_t const num_segments, size_t const max_rows_per_segment,
                   size_t const num_key_columns, bool has_sequence_col,
                   size_t const num_value_columns, int const random_seed, int 
const min_value,
@@ -213,7 +212,7 @@ public:
                 *(int*)cell.mutable_cell_ptr() = data_map[{sid, rid}][cid];
             };
             build_segment(opts, tablet_schema, sid, tablet_schema, 
datas[sid].size(), generator,
-                          &segment);
+                          &segment, kSegmentDir);
         }
 
         // find the location of rows to be deleted using 
`MergeIndexDeleteBitmapCalculator`
diff --git a/be/test/olap/segment_footer_cache_test.cpp 
b/be/test/olap/segment_footer_cache_test.cpp
new file mode 100644
index 00000000000..670df87424c
--- /dev/null
+++ b/be/test/olap/segment_footer_cache_test.cpp
@@ -0,0 +1,195 @@
+// 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 <gtest/gtest.h>
+
+#include <random>
+
+#include "olap/page_cache.h"
+#include "olap/rowset/segment_v2/segment.h"
+#include "olap/rowset/segment_v2/segment_writer.h"
+#include "olap/storage_engine.h"
+#include "olap/tablet_schema_helper.h"
+
+namespace doris {
+
+TabletColumnPtr create_int_sequence_value(int32_t id, bool is_nullable = true,
+                                          bool is_bf_column = false, bool 
has_bitmap_index = false);
+
+TabletSchemaSPtr create_schema(const std::vector<TabletColumnPtr>& columns,
+                               KeysType keys_type = UNIQUE_KEYS);
+
+using Generator = std::function<void(size_t rid, int cid, RowCursorCell& 
cell)>;
+
+void build_segment(SegmentWriterOptions opts, TabletSchemaSPtr build_schema, 
size_t segment_id,
+                   TabletSchemaSPtr query_schema, size_t nrows, Generator 
generator,
+                   std::shared_ptr<Segment>* res, std::string segment_dir);
+
+static std::string segment_footer_cache_test_segment_dir = 
"./ut_dir/segment_footer_cache_test";
+
+class SegmentFooterCacheTest : public ::testing::Test {
+    using Segments = std::vector<std::shared_ptr<segment_v2::Segment>>;
+    Segments create(size_t const num_segments, size_t const 
max_rows_per_segment,
+                    size_t const num_key_columns, bool has_sequence_col,
+                    size_t const num_value_columns, int const random_seed, int 
const min_value,
+                    int const max_value) {
+        Segments segments(num_segments);
+        segment_v2::SegmentWriterOptions opts;
+        opts.enable_unique_key_merge_on_write = true;
+
+        size_t const num_columns = num_key_columns + has_sequence_col + 
num_value_columns;
+        size_t const seq_col_idx = has_sequence_col ? num_key_columns : -1;
+
+        std::vector<TabletColumnPtr> columns;
+
+        for (int i = 0; i < num_key_columns; ++i) {
+            columns.emplace_back(create_int_key(i));
+        }
+        if (has_sequence_col) {
+            columns.emplace_back(create_int_sequence_value(num_key_columns));
+        }
+        for (int i = 0; i < num_value_columns; ++i) {
+            columns.emplace_back(create_int_value(num_key_columns + 
has_sequence_col));
+        }
+
+        TabletSchemaSPtr tablet_schema = create_schema(columns, UNIQUE_KEYS);
+
+        std::mt19937 rng(random_seed);
+        std::uniform_int_distribution<int> gen(min_value, max_value);
+
+        std::vector<std::vector<std::vector<int>>> datas(num_segments);
+        std::map<std::pair<size_t, size_t>, std::vector<int>> data_map;
+        // each flat_data of data will be a tuple of (column1, column2, ..., 
segment_id, row_id)
+        std::vector<std::vector<int>> flat_data;
+        size_t seq_counter = 0;
+
+        // Generate random data, ensuring that there are no identical keys 
within each segment
+        // and the keys within each segment are ordered.
+        // Also, ensure that the sequence values are not equal.
+        for (size_t sid = 0; sid < num_segments; ++sid) {
+            auto& segment_data = datas[sid];
+            for (size_t rid = 0; rid < max_rows_per_segment; ++rid) {
+                std::vector<int> row;
+                for (size_t cid = 0; cid < num_columns; ++cid) {
+                    if (cid == seq_col_idx) {
+                        row.emplace_back(++seq_counter);
+                    } else {
+                        row.emplace_back(gen(rng));
+                    }
+                }
+                segment_data.emplace_back(row);
+            }
+            std::sort(segment_data.begin(), segment_data.end());
+            segment_data.erase(
+                    std::unique(segment_data.begin(), segment_data.end(),
+                                [&](std::vector<int> const& lhs, 
std::vector<int> const& rhs) {
+                                    return std::vector<int>(lhs.begin(),
+                                                            lhs.begin() + 
num_key_columns) ==
+                                           std::vector<int>(rhs.begin(),
+                                                            rhs.begin() + 
num_key_columns);
+                                }),
+                    segment_data.end());
+            for (size_t rid = 0; rid < segment_data.size(); ++rid) {
+                data_map[{sid, rid}] = segment_data[rid];
+                auto row = segment_data[rid];
+                row.emplace_back(sid);
+                row.emplace_back(rid);
+                flat_data.emplace_back(row);
+            }
+        }
+
+        // Construct segments using the data generated before.
+        for (size_t sid = 0; sid < num_segments; ++sid) {
+            auto& segment = segments[sid];
+            std::vector<int> row_data;
+            auto generator = [&](size_t rid, int cid, RowCursorCell& cell) {
+                cell.set_not_null();
+                *(int*)cell.mutable_cell_ptr() = data_map[{sid, rid}][cid];
+            };
+            build_segment(opts, tablet_schema, sid, tablet_schema, 
datas[sid].size(), generator,
+                          &segment, segment_footer_cache_test_segment_dir);
+        }
+
+        return segments;
+    }
+
+    void SetUp() override {
+        auto st = io::global_local_filesystem()->delete_directory(
+                segment_footer_cache_test_segment_dir);
+        ASSERT_TRUE(st.ok()) << st;
+        st = 
io::global_local_filesystem()->create_directory(segment_footer_cache_test_segment_dir);
+        ASSERT_TRUE(st.ok()) << st;
+        ExecEnv::GetInstance()->set_storage_engine(
+                std::make_unique<StorageEngine>(EngineOptions {}));
+        _segments = create(2, 10, 2, false, 1, 4933, 1, 3);
+    }
+
+    void TearDown() override {
+        EXPECT_TRUE(io::global_local_filesystem()
+                            
->delete_directory(segment_footer_cache_test_segment_dir)
+                            .ok());
+    }
+
+private:
+    Segments _segments;
+};
+
+TEST_F(SegmentFooterCacheTest, TestGetSegmentFooter) {
+    for (auto segment_ptr : _segments) {
+        std::shared_ptr<segment_v2::SegmentFooterPB> footer;
+        Status st = segment_ptr->_get_segment_footer(footer, nullptr);
+        ASSERT_TRUE(st.ok());
+    }
+
+    for (auto segment_ptr : _segments) {
+        std::shared_ptr<segment_v2::SegmentFooterPB> footer;
+        Status st = segment_ptr->_get_segment_footer(footer, nullptr);
+        ASSERT_TRUE(st.ok());
+    }
+}
+
+TEST_F(SegmentFooterCacheTest, TestGetSegmentFooterCacheKey) {
+    for (auto segment_ptr : _segments) {
+        StoragePageCache::CacheKey cache_key = 
segment_ptr->get_segment_footer_cache_key();
+        std::string path_native = segment_ptr->_file_reader->path().native();
+        size_t fsize = segment_ptr->_file_reader->size();
+        size_t offset = fsize - 12;
+        std::cout << "cache_key: " << cache_key.encode() << std::endl;
+        ASSERT_EQ(path_native, cache_key.fname);
+        ASSERT_EQ(fsize, cache_key.fsize);
+        ASSERT_EQ(offset, cache_key.offset);
+    }
+}
+
+TEST_F(SegmentFooterCacheTest, TestSemgnetFooterPBPage) {
+    StoragePageCache cache(16 * 2048, 0, 0, 16);
+    for (auto segment_ptr : _segments) {
+        std::shared_ptr<segment_v2::SegmentFooterPB> footer;
+        Status st = segment_ptr->_get_segment_footer(footer, nullptr);
+        ASSERT_TRUE(st.ok());
+        PageCacheHandle cache_handle;
+        cache.insert(segment_ptr->get_segment_footer_cache_key(), footer, 
footer->ByteSizeLong(),
+                     &cache_handle, segment_v2::PageTypePB::DATA_PAGE);
+
+        
EXPECT_EQ(cache_handle.get<std::shared_ptr<segment_v2::SegmentFooterPB>>(), 
footer);
+        auto found = cache.lookup(segment_ptr->get_segment_footer_cache_key(), 
&cache_handle,
+                                  segment_v2::PageTypePB::DATA_PAGE);
+        ASSERT_TRUE(found);
+    }
+}
+
+} // namespace doris
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to