Coverage Report

Created: 2026-08-14 14:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/cloud/cloud_txn_delete_bitmap_cache.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "cloud/cloud_txn_delete_bitmap_cache.h"
19
20
#include <fmt/core.h>
21
22
#include <chrono>
23
#include <memory>
24
#include <shared_mutex>
25
26
#include "cloud/config.h"
27
#include "common/status.h"
28
#include "cpp/sync_point.h"
29
#include "storage/olap_common.h"
30
#include "storage/rowset/rowset_fwd.h"
31
#include "storage/tablet/tablet_meta.h"
32
#include "storage/txn/txn_manager.h"
33
34
namespace doris {
35
36
CloudTxnDeleteBitmapCache::CloudTxnDeleteBitmapCache(size_t size_in_bytes)
37
0
        : LRUCachePolicy(CachePolicy::CacheType::CLOUD_TXN_DELETE_BITMAP_CACHE, size_in_bytes,
38
0
                         LRUCacheType::SIZE, /*stale_sweep_time_s*/ 86400, /*num_shards*/ 4,
39
0
                         /*element_count_capacity*/ 0, /*enable_prune*/ true,
40
0
                         /*is_lru_k*/ false),
41
0
          _stop_latch(1) {}
42
43
0
CloudTxnDeleteBitmapCache::~CloudTxnDeleteBitmapCache() {
44
0
    _stop_latch.count_down();
45
0
    _clean_thread->join();
46
0
}
47
48
0
Status CloudTxnDeleteBitmapCache::init() {
49
0
    auto st = Thread::create(
50
0
            "CloudTxnDeleteBitmapCache", "clean_txn_dbm_thread",
51
0
            [this]() { this->_clean_thread_callback(); }, &_clean_thread);
52
0
    if (!st.ok()) {
53
0
        LOG(WARNING) << "failed to create thread for CloudTxnDeleteBitmapCache, error: " << st;
54
0
    }
55
0
    return st;
56
0
}
57
58
Status CloudTxnDeleteBitmapCache::get_tablet_txn_info(
59
        TTransactionId transaction_id, int64_t tablet_id, RowsetSharedPtr* rowset,
60
        DeleteBitmapPtr* delete_bitmap, RowsetIdUnorderedSet* rowset_ids, int64_t* txn_expiration,
61
        std::shared_ptr<PartialUpdateInfo>* partial_update_info,
62
        std::shared_ptr<PublishStatus>* publish_status, TxnPublishInfo* previous_publish_info,
63
0
        RowBinlogTxnInfo* attach_row_binlog) {
64
0
    {
65
0
        std::shared_lock<std::shared_mutex> rlock(_rwlock);
66
0
        TxnKey key(transaction_id, tablet_id);
67
0
        DBUG_EXECUTE_IF("CloudTxnDeleteBitmapCache.get_tablet_txn_info.not_found", {
68
0
            return Status::Error<ErrorCode::NOT_FOUND>(
69
0
                    "not found txn info for test, tablet_id={}, transaction_id={}", tablet_id,
70
0
                    transaction_id);
71
0
        });
72
0
        auto iter = _txn_map.find(key);
73
0
        if (iter == _txn_map.end()) {
74
0
            return Status::Error<ErrorCode::NOT_FOUND, false>(
75
0
                    "not found txn info, tablet_id={}, transaction_id={}", tablet_id,
76
0
                    transaction_id);
77
0
        }
78
0
        *rowset = iter->second.rowset;
79
0
        *txn_expiration = iter->second.txn_expiration;
80
0
        *partial_update_info = iter->second.partial_update_info;
81
0
        *publish_status = iter->second.publish_status;
82
0
        *previous_publish_info = iter->second.publish_info;
83
0
        if (attach_row_binlog != nullptr) {
84
0
            *attach_row_binlog = iter->second.attach_row_binlog;
85
0
        }
86
0
    }
87
88
0
    auto st = get_delete_bitmap(transaction_id, tablet_id, delete_bitmap, rowset_ids, nullptr);
89
90
0
    if (st.is<ErrorCode::NOT_FOUND>()) {
91
        // Because of the rowset_ids become empty, all delete bitmap
92
        // will be recalculate in CalcDeleteBitmapTask
93
0
        if (delete_bitmap != nullptr) {
94
0
            *delete_bitmap = std::make_shared<DeleteBitmap>(tablet_id);
95
0
        }
96
        // to avoid to skip calculating
97
0
        **publish_status = PublishStatus::INIT;
98
99
0
        return Status::OK();
100
0
    }
101
0
    return st;
102
0
}
103
104
Result<std::pair<RowsetSharedPtr, DeleteBitmapPtr>>
105
CloudTxnDeleteBitmapCache::get_rowset_and_delete_bitmap(TTransactionId transaction_id,
106
0
                                                        int64_t tablet_id) {
107
0
    RowsetSharedPtr rowset;
108
0
    {
109
0
        std::shared_lock<std::shared_mutex> rlock(_rwlock);
110
0
        TxnKey txn_key(transaction_id, tablet_id);
111
0
        if (_empty_rowset_markers.contains(txn_key)) {
112
0
            return std::make_pair(nullptr, nullptr);
113
0
        }
114
0
        auto iter = _txn_map.find(txn_key);
115
0
        if (iter == _txn_map.end()) {
116
0
            return ResultError(Status::InternalError<false>(""));
117
0
        }
118
0
        if (!(iter->second.publish_status &&
119
0
              *(iter->second.publish_status) == PublishStatus::SUCCEED)) {
120
0
            return ResultError(Status::InternalError<false>(""));
121
0
        }
122
0
        rowset = iter->second.rowset;
123
0
    }
124
125
0
    std::string key_str = fmt::format("{}/{}", transaction_id, tablet_id);
126
0
    CacheKey key(key_str);
127
0
    Cache::Handle* handle = lookup(key);
128
129
0
    DBUG_EXECUTE_IF("CloudTxnDeleteBitmapCache::get_delete_bitmap.cache_miss", {
130
0
        handle = nullptr;
131
0
        LOG(INFO) << "CloudTxnDeleteBitmapCache::get_delete_bitmap.cache_miss, make cache missed "
132
0
                     "when get delete bitmap, txn_id:"
133
0
                  << transaction_id << ", tablet_id: " << tablet_id;
134
0
    });
135
0
    DeleteBitmapCacheValue* val =
136
0
            handle == nullptr ? nullptr : reinterpret_cast<DeleteBitmapCacheValue*>(value(handle));
137
0
    if (!val) {
138
0
        return ResultError(Status::InternalError<false>(""));
139
0
    }
140
0
    Defer defer {[this, handle] { release(handle); }};
141
0
    return std::make_pair(rowset, val->delete_bitmap);
142
0
}
143
144
Status CloudTxnDeleteBitmapCache::get_delete_bitmap(
145
        TTransactionId transaction_id, int64_t tablet_id, DeleteBitmapPtr* delete_bitmap,
146
0
        RowsetIdUnorderedSet* rowset_ids, std::shared_ptr<PublishStatus>* publish_status) {
147
0
    if (publish_status) {
148
0
        std::shared_lock<std::shared_mutex> rlock(_rwlock);
149
0
        TxnKey txn_key(transaction_id, tablet_id);
150
0
        auto iter = _txn_map.find(txn_key);
151
0
        if (iter == _txn_map.end()) {
152
0
            return Status::Error<ErrorCode::NOT_FOUND, false>(
153
0
                    "not found txn info, tablet_id={}, transaction_id={}", tablet_id,
154
0
                    transaction_id);
155
0
        }
156
0
        *publish_status = iter->second.publish_status;
157
0
    }
158
0
    std::string key_str = fmt::format("{}/{}", transaction_id, tablet_id);
159
0
    CacheKey key(key_str);
160
0
    Cache::Handle* handle = lookup(key);
161
162
0
    DBUG_EXECUTE_IF("CloudTxnDeleteBitmapCache::get_delete_bitmap.cache_miss", {
163
0
        handle = nullptr;
164
0
        LOG(INFO) << "CloudTxnDeleteBitmapCache::get_delete_bitmap.cache_miss, make cache missed "
165
0
                     "when get delete bitmap, txn_id:"
166
0
                  << transaction_id << ", tablet_id: " << tablet_id;
167
0
    });
168
169
0
    DeleteBitmapCacheValue* val =
170
0
            handle == nullptr ? nullptr : reinterpret_cast<DeleteBitmapCacheValue*>(value(handle));
171
0
    if (val) {
172
0
        *delete_bitmap = val->delete_bitmap;
173
0
        if (rowset_ids) {
174
0
            *rowset_ids = val->rowset_ids;
175
0
        }
176
        // must call release handle to reduce the reference count,
177
        // otherwise there will be memory leak
178
0
        release(handle);
179
0
    } else {
180
0
        LOG_INFO("cache missed when get delete bitmap")
181
0
                .tag("txn_id", transaction_id)
182
0
                .tag("tablet_id", tablet_id);
183
0
        return Status::Error<ErrorCode::NOT_FOUND, false>(
184
0
                "cache missed when get delete bitmap, tablet_id={}, transaction_id={}", tablet_id,
185
0
                transaction_id);
186
0
    }
187
0
    return Status::OK();
188
0
}
189
190
void CloudTxnDeleteBitmapCache::set_tablet_txn_info(
191
        TTransactionId transaction_id, int64_t tablet_id, DeleteBitmapPtr delete_bitmap,
192
        const RowsetIdUnorderedSet& rowset_ids, RowsetSharedPtr rowset, int64_t txn_expiration,
193
        std::shared_ptr<PartialUpdateInfo> partial_update_info,
194
0
        const RowBinlogTxnInfo& attach_row_binlog) {
195
0
    int64_t txn_expiration_min =
196
0
            duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch())
197
0
                    .count() +
198
0
            config::tablet_txn_info_min_expired_seconds;
199
0
    txn_expiration = std::max(txn_expiration_min, txn_expiration);
200
0
    {
201
0
        std::unique_lock<std::shared_mutex> wlock(_rwlock);
202
0
        TxnKey txn_key(transaction_id, tablet_id);
203
0
        std::shared_ptr<PublishStatus> publish_status =
204
0
                std::make_shared<PublishStatus>(PublishStatus::INIT);
205
0
        _txn_map[txn_key] = TxnVal(rowset, txn_expiration, std::move(partial_update_info),
206
0
                                   std::move(publish_status), attach_row_binlog);
207
0
        _expiration_txn.emplace(txn_expiration, txn_key);
208
0
    }
209
0
    std::string key_str = fmt::format("{}/{}", transaction_id, tablet_id);
210
0
    CacheKey key(key_str);
211
212
0
    auto val = new DeleteBitmapCacheValue(delete_bitmap, rowset_ids);
213
0
    size_t charge = sizeof(DeleteBitmapCacheValue);
214
0
    for (auto& [k, v] : val->delete_bitmap->delete_bitmap) {
215
0
        charge += v.getSizeInBytes();
216
0
    }
217
0
    auto* handle = insert(key, val, charge, charge, CachePriority::NORMAL);
218
    // must call release handle to reduce the reference count,
219
    // otherwise there will be memory leak
220
0
    release(handle);
221
0
    LOG_INFO("set txn related delete bitmap")
222
0
            .tag("txn_id", transaction_id)
223
0
            .tag("expiration", txn_expiration)
224
0
            .tag("tablet_id", tablet_id)
225
0
            .tag("delete_bitmap_size", charge)
226
0
            .tag("delete_bitmap_count", delete_bitmap->get_delete_bitmap_count())
227
0
            .tag("delete_bitmap_cardinality", delete_bitmap->cardinality());
228
0
}
229
230
Status CloudTxnDeleteBitmapCache::update_tablet_txn_info(TTransactionId transaction_id,
231
                                                         int64_t tablet_id,
232
                                                         DeleteBitmapPtr delete_bitmap,
233
                                                         const RowsetIdUnorderedSet& rowset_ids,
234
                                                         PublishStatus publish_status,
235
0
                                                         TxnPublishInfo publish_info) {
236
0
    {
237
0
        std::unique_lock<std::shared_mutex> wlock(_rwlock);
238
0
        TxnKey txn_key(transaction_id, tablet_id);
239
0
        if (!_txn_map.contains(txn_key)) {
240
0
            return Status::Error<ErrorCode::NOT_FOUND, false>(
241
0
                    "not found txn info, tablet_id={}, transaction_id={}, may be expired and be "
242
0
                    "removed",
243
0
                    tablet_id, transaction_id);
244
0
        }
245
0
        TxnVal& txn_val = _txn_map[txn_key];
246
0
        *(txn_val.publish_status) = publish_status;
247
0
        if (publish_status == PublishStatus::SUCCEED) {
248
0
            txn_val.publish_info = publish_info;
249
0
        }
250
0
    }
251
0
    std::string key_str = fmt::format("{}/{}", transaction_id, tablet_id);
252
0
    CacheKey key(key_str);
253
254
0
    auto val = new DeleteBitmapCacheValue(delete_bitmap, rowset_ids);
255
0
    size_t charge = sizeof(DeleteBitmapCacheValue);
256
0
    for (auto& [k, v] : val->delete_bitmap->delete_bitmap) {
257
0
        charge += v.getSizeInBytes();
258
0
    }
259
0
    auto* handle = insert(key, val, charge, charge, CachePriority::NORMAL);
260
    // must call release handle to reduce the reference count,
261
    // otherwise there will be memory leak
262
0
    release(handle);
263
0
    if (config::enable_mow_verbose_log) {
264
0
        LOG_INFO("update txn related delete bitmap")
265
0
                .tag("txn_id", transaction_id)
266
0
                .tag("tablt_id", tablet_id)
267
0
                .tag("delete_bitmap_size", charge)
268
0
                .tag("delete_bitmap_count", delete_bitmap->get_delete_bitmap_count())
269
0
                .tag("delete_bitmap_cardinality", delete_bitmap->cardinality())
270
0
                .tag("publish_status", static_cast<int>(publish_status));
271
0
    }
272
0
    return Status::OK();
273
0
}
274
275
0
void CloudTxnDeleteBitmapCache::remove_expired_tablet_txn_info() {
276
0
    TEST_SYNC_POINT_RETURN_WITH_VOID("CloudTxnDeleteBitmapCache::remove_expired_tablet_txn_info");
277
0
    std::unique_lock<std::shared_mutex> wlock(_rwlock);
278
0
    while (!_expiration_txn.empty()) {
279
0
        auto iter = _expiration_txn.begin();
280
0
        bool in_txn_map = _txn_map.find(iter->second) != _txn_map.end();
281
0
        bool in_markers = _empty_rowset_markers.find(iter->second) != _empty_rowset_markers.end();
282
0
        if (!in_txn_map && !in_markers) {
283
0
            _expiration_txn.erase(iter);
284
0
            continue;
285
0
        }
286
0
        int64_t current_time = duration_cast<std::chrono::seconds>(
287
0
                                       std::chrono::system_clock::now().time_since_epoch())
288
0
                                       .count();
289
0
        if (iter->first > current_time) {
290
0
            break;
291
0
        }
292
        // Clean from _txn_map if exists
293
0
        auto txn_iter = _txn_map.find(iter->second);
294
0
        if ((txn_iter != _txn_map.end()) && (iter->first == txn_iter->second.txn_expiration)) {
295
0
            LOG_INFO("clean expired delete bitmap")
296
0
                    .tag("txn_id", txn_iter->first.txn_id)
297
0
                    .tag("expiration", txn_iter->second.txn_expiration)
298
0
                    .tag("tablt_id", txn_iter->first.tablet_id);
299
0
            std::string key_str = std::to_string(txn_iter->first.txn_id) + "/" +
300
0
                                  std::to_string(txn_iter->first.tablet_id); // Cache key container
301
0
            CacheKey cache_key(key_str);
302
0
            erase(cache_key);
303
0
            _txn_map.erase(iter->second);
304
0
        }
305
        // Clean from _empty_rowset_markers if exists
306
0
        auto marker_iter = _empty_rowset_markers.find(iter->second);
307
0
        if (marker_iter != _empty_rowset_markers.end()) {
308
0
            LOG_INFO("clean expired empty rowset marker")
309
0
                    .tag("txn_id", iter->second.txn_id)
310
0
                    .tag("tablet_id", iter->second.tablet_id);
311
0
            _empty_rowset_markers.erase(marker_iter);
312
0
        }
313
0
        _expiration_txn.erase(iter);
314
0
    }
315
0
}
316
317
void CloudTxnDeleteBitmapCache::remove_unused_tablet_txn_info(TTransactionId transaction_id,
318
0
                                                              int64_t tablet_id) {
319
0
    std::unique_lock<std::shared_mutex> wlock(_rwlock);
320
0
    TxnKey txn_key(transaction_id, tablet_id);
321
0
    auto txn_iter = _txn_map.find(txn_key);
322
0
    if (txn_iter != _txn_map.end()) {
323
0
        LOG_INFO("remove unused tablet txn info")
324
0
                .tag("txn_id", txn_iter->first.txn_id)
325
0
                .tag("tablt_id", txn_iter->first.tablet_id);
326
0
        std::string key_str = std::to_string(txn_iter->first.txn_id) + "/" +
327
0
                              std::to_string(txn_iter->first.tablet_id); // Cache key container
328
0
        CacheKey cache_key(key_str);
329
0
        erase(cache_key);
330
0
        _txn_map.erase(txn_key);
331
0
    }
332
0
}
333
334
void CloudTxnDeleteBitmapCache::mark_empty_rowset(TTransactionId txn_id, int64_t tablet_id,
335
0
                                                  int64_t txn_expiration) {
336
0
    int64_t txn_expiration_min =
337
0
            duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch())
338
0
                    .count() +
339
0
            config::tablet_txn_info_min_expired_seconds;
340
0
    txn_expiration = std::max(txn_expiration_min, txn_expiration);
341
342
0
    if (config::enable_mow_verbose_log) {
343
0
        LOG_INFO("mark empty rowset")
344
0
                .tag("txn_id", txn_id)
345
0
                .tag("tablet_id", tablet_id)
346
0
                .tag("expiration", txn_expiration);
347
0
    }
348
0
    std::unique_lock<std::shared_mutex> wlock(_rwlock);
349
0
    TxnKey txn_key(txn_id, tablet_id);
350
0
    _empty_rowset_markers.emplace(txn_key);
351
0
    _expiration_txn.emplace(txn_expiration, txn_key);
352
0
}
353
354
0
bool CloudTxnDeleteBitmapCache::is_empty_rowset(TTransactionId txn_id, int64_t tablet_id) {
355
0
    std::shared_lock<std::shared_mutex> rlock(_rwlock);
356
0
    TxnKey txn_key(txn_id, tablet_id);
357
0
    return _empty_rowset_markers.contains(txn_key);
358
0
}
359
360
0
void CloudTxnDeleteBitmapCache::_clean_thread_callback() {
361
0
    do {
362
0
        remove_expired_tablet_txn_info();
363
0
    } while (!_stop_latch.wait_for(
364
0
            std::chrono::seconds(config::remove_expired_tablet_txn_info_interval_seconds)));
365
0
}
366
367
} // namespace doris