Coverage Report

Created: 2026-10-29 13:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/cloud/cloud_tablet.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_tablet.h"
19
20
#include <bvar/bvar.h>
21
#include <bvar/latency_recorder.h>
22
#include <gen_cpp/Types_types.h>
23
#include <gen_cpp/olap_file.pb.h>
24
#include <rapidjson/document.h>
25
#include <rapidjson/encodings.h>
26
#include <rapidjson/prettywriter.h>
27
#include <rapidjson/rapidjson.h>
28
#include <rapidjson/stringbuffer.h>
29
30
#include <algorithm>
31
#include <atomic>
32
#include <chrono>
33
#include <cstdint>
34
#include <memory>
35
#include <ranges>
36
#include <ratio>
37
#include <shared_mutex>
38
#include <unordered_map>
39
#include <unordered_set>
40
#include <vector>
41
42
#include "cloud/cloud_meta_mgr.h"
43
#include "cloud/cloud_storage_engine.h"
44
#include "cloud/cloud_tablet_mgr.h"
45
#include "cloud/cloud_warm_up_manager.h"
46
#include "cloud/config.h"
47
#include "common/cast_set.h"
48
#include "common/config.h"
49
#include "common/logging.h"
50
#include "cpp/sync_point.h"
51
#include "io/cache/block_file_cache_downloader.h"
52
#include "io/cache/block_file_cache_factory.h"
53
#include "storage/compaction/compaction.h"
54
#include "storage/compaction/cumulative_compaction_time_series_policy.h"
55
#include "storage/index/inverted/common/inverted_index_desc.h"
56
#include "storage/olap_define.h"
57
#include "storage/rowset/beta_rowset.h"
58
#include "storage/rowset/rowset.h"
59
#include "storage/rowset/rowset_factory.h"
60
#include "storage/rowset/rowset_fwd.h"
61
#include "storage/rowset/rowset_writer.h"
62
#include "storage/storage_policy.h"
63
#include "storage/tablet/base_tablet.h"
64
#include "storage/tablet/tablet_schema.h"
65
#include "storage/txn/txn_manager.h"
66
#include "util/debug_points.h"
67
#include "util/stack_util.h"
68
69
namespace doris {
70
#include "common/compile_check_begin.h"
71
using namespace ErrorCode;
72
73
bvar::LatencyRecorder g_cu_compaction_get_delete_bitmap_lock_time_ms(
74
        "cu_compaction_get_delete_bitmap_lock_time_ms");
75
bvar::LatencyRecorder g_base_compaction_get_delete_bitmap_lock_time_ms(
76
        "base_compaction_get_delete_bitmap_lock_time_ms");
77
78
bvar::Adder<int64_t> g_unused_rowsets_count("unused_rowsets_count");
79
bvar::Adder<int64_t> g_unused_rowsets_bytes("unused_rowsets_bytes");
80
81
bvar::Adder<int64_t> g_capture_prefer_cache_count("capture_prefer_cache_count");
82
bvar::Adder<int64_t> g_capture_with_freshness_tolerance_count(
83
        "capture_with_freshness_tolerance_count");
84
bvar::Adder<int64_t> g_capture_with_freshness_tolerance_fallback_count(
85
        "capture_with_freshness_tolerance_fallback_count");
86
bvar::Adder<int64_t> g_rowset_warmup_state_missing_count("rowset_warmup_state_missing_count");
87
bvar::Window<bvar::Adder<int64_t>> g_capture_prefer_cache_count_window(
88
        "capture_prefer_cache_count_window", &g_capture_prefer_cache_count, 30);
89
bvar::Window<bvar::Adder<int64_t>> g_capture_with_freshness_tolerance_count_window(
90
        "capture_with_freshness_tolerance_count_window", &g_capture_with_freshness_tolerance_count,
91
        30);
92
bvar::Window<bvar::Adder<int64_t>> g_capture_with_freshness_tolerance_fallback_count_window(
93
        "capture_with_freshness_tolerance_fallback_count_window",
94
        &g_capture_with_freshness_tolerance_fallback_count, 30);
95
96
static constexpr int LOAD_INITIATOR_ID = -1;
97
98
namespace {
99
100
bool is_schema_change_output_rowset(const RowsetSharedPtr& rowset,
101
3
                                    const std::vector<RowsetSharedPtr>& output_rowsets) {
102
5
    return std::ranges::any_of(output_rowsets, [&rowset](const RowsetSharedPtr& output_rowset) {
103
5
        return output_rowset->rowset_id() == rowset->rowset_id();
104
5
    });
105
3
}
106
107
} // namespace
108
109
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_submitted_segment_size(
110
        "file_cache_cloud_tablet_submitted_segment_size");
111
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_submitted_segment_num(
112
        "file_cache_cloud_tablet_submitted_segment_num");
113
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_submitted_index_size(
114
        "file_cache_cloud_tablet_submitted_index_size");
115
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_submitted_index_num(
116
        "file_cache_cloud_tablet_submitted_index_num");
117
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_finished_segment_size(
118
        "file_cache_cloud_tablet_finished_segment_size");
119
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_finished_segment_num(
120
        "file_cache_cloud_tablet_finished_segment_num");
121
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_finished_index_size(
122
        "file_cache_cloud_tablet_finished_index_size");
123
bvar::Adder<uint64_t> g_file_cache_cloud_tablet_finished_index_num(
124
        "file_cache_cloud_tablet_finished_index_num");
125
126
bvar::Adder<uint64_t> g_file_cache_recycle_cached_data_segment_num(
127
        "file_cache_recycle_cached_data_segment_num");
128
bvar::Adder<uint64_t> g_file_cache_recycle_cached_data_segment_size(
129
        "file_cache_recycle_cached_data_segment_size");
130
bvar::Adder<uint64_t> g_file_cache_recycle_cached_data_index_num(
131
        "file_cache_recycle_cached_data_index_num");
132
133
bvar::Adder<uint64_t> g_file_cache_warm_up_segment_complete_num(
134
        "file_cache_warm_up_segment_complete_num");
135
bvar::Adder<uint64_t> g_file_cache_warm_up_segment_failed_num(
136
        "file_cache_warm_up_segment_failed_num");
137
bvar::Adder<uint64_t> g_file_cache_warm_up_inverted_idx_complete_num(
138
        "file_cache_warm_up_inverted_idx_complete_num");
139
bvar::Adder<uint64_t> g_file_cache_warm_up_inverted_idx_failed_num(
140
        "file_cache_warm_up_inverted_idx_failed_num");
141
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_complete_num(
142
        "file_cache_warm_up_rowset_complete_num");
143
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_triggered_by_job_num(
144
        "file_cache_warm_up_rowset_triggered_by_job_num");
145
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_triggered_by_sync_rowset_num(
146
        "file_cache_warm_up_rowset_triggered_by_sync_rowset_num");
147
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_triggered_by_event_driven_num(
148
        "file_cache_warm_up_rowset_triggered_by_event_driven_num");
149
bvar::LatencyRecorder g_file_cache_warm_up_rowset_all_segments_latency(
150
        "file_cache_warm_up_rowset_all_segments_latency");
151
152
CloudTablet::CloudTablet(CloudStorageEngine& engine, TabletMetaSharedPtr tablet_meta)
153
226
        : BaseTablet(std::move(tablet_meta)), _engine(engine) {}
154
155
226
CloudTablet::~CloudTablet() = default;
156
157
0
bool CloudTablet::exceed_version_limit(int32_t limit) {
158
0
    return _approximate_num_rowsets.load(std::memory_order_relaxed) > limit;
159
0
}
160
161
19
std::string CloudTablet::tablet_path() const {
162
19
    return "";
163
19
}
164
165
Status CloudTablet::capture_rs_readers(const Version& spec_version,
166
                                       std::vector<RowSetSplits>* rs_splits,
167
1
                                       const CaptureRowsetOps& opts) {
168
1
    DBUG_EXECUTE_IF("CloudTablet.capture_rs_readers.return.e-230", {
169
1
        LOG_WARNING("CloudTablet.capture_rs_readers.return e-230").tag("tablet_id", tablet_id());
170
1
        return Status::Error<false>(-230, "injected error");
171
1
    });
172
1
    std::shared_lock rlock(_meta_lock);
173
1
    *rs_splits = DORIS_TRY(capture_rs_readers_unlocked(
174
1
            spec_version, CaptureRowsetOps {.skip_missing_versions = opts.skip_missing_versions}));
175
1
    return Status::OK();
176
1
}
177
178
[[nodiscard]] Result<std::vector<Version>> CloudTablet::capture_consistent_versions_unlocked(
179
50
        const Version& version_range, const CaptureRowsetOps& options) const {
180
50
    if (options.query_freshness_tolerance_ms > 0) {
181
24
        return capture_versions_with_freshness_tolerance(version_range, options);
182
26
    } else if (options.enable_prefer_cached_rowset && !enable_unique_key_merge_on_write()) {
183
13
        return capture_versions_prefer_cache(version_range);
184
13
    }
185
13
    return BaseTablet::capture_consistent_versions_unlocked(version_range, options);
186
50
}
187
188
Result<std::vector<Version>> CloudTablet::capture_versions_prefer_cache(
189
13
        const Version& spec_version) const {
190
13
    g_capture_prefer_cache_count << 1;
191
13
    Versions version_path;
192
    // Caller (capture_consistent_versions_unlocked) already holds shared
193
    // `_meta_lock`; do NOT re-acquire it here. The lock is writer-preferring,
194
    // so a recursive shared acquisition self-deadlocks if a writer queues in
195
    // between the outer and inner lock.
196
13
    auto st = _timestamped_version_tracker.capture_consistent_versions_prefer_cache(
197
13
            spec_version, version_path,
198
92
            [&](int64_t start, int64_t end) { return rowset_is_warmed_up_unlocked(start, end); });
199
13
    if (!st.ok()) {
200
0
        return ResultError(st);
201
0
    }
202
13
    int64_t path_max_version = version_path.back().second;
203
13
    VLOG_DEBUG << fmt::format(
204
0
            "[verbose] CloudTablet::capture_versions_prefer_cache, capture path: {}, "
205
0
            "tablet_id={}, spec_version={}, path_max_version={}",
206
0
            fmt::join(version_path | std::views::transform([](const auto& version) {
207
0
                          return fmt::format("{}", version.to_string());
208
0
                      }),
209
0
                      ", "),
210
0
            tablet_id(), spec_version.to_string(), path_max_version);
211
13
    return version_path;
212
13
}
213
214
230
bool CloudTablet::rowset_is_warmed_up_unlocked(int64_t start_version, int64_t end_version) const {
215
230
    if (start_version > end_version) {
216
0
        return false;
217
0
    }
218
230
    Version version {start_version, end_version};
219
230
    auto it = _rs_version_map.find(version);
220
230
    if (it == _rs_version_map.end()) {
221
78
        it = _stale_rs_version_map.find(version);
222
78
        if (it == _stale_rs_version_map.end()) {
223
0
            LOG_WARNING(
224
0
                    "fail to find Rowset in rs_version or stale_rs_version for version. "
225
0
                    "tablet={}, version={}",
226
0
                    tablet_id(), version.to_string());
227
0
            return false;
228
0
        }
229
78
    }
230
230
    const auto& rs = it->second;
231
230
    if (rs->visible_timestamp() < _engine.startup_timepoint()) {
232
        // We only care about rowsets that are created after startup time point. For other rowsets,
233
        // we assume they are warmed up.
234
13
        return true;
235
13
    }
236
217
    return is_rowset_warmed_up(rs->rowset_id());
237
230
};
238
239
Result<std::vector<Version>> CloudTablet::capture_versions_with_freshness_tolerance(
240
24
        const Version& spec_version, const CaptureRowsetOps& options) const {
241
24
    g_capture_with_freshness_tolerance_count << 1;
242
24
    using namespace std::chrono;
243
24
    auto query_freshness_tolerance_ms = options.query_freshness_tolerance_ms;
244
24
    auto freshness_limit_tp = system_clock::now() - milliseconds(query_freshness_tolerance_ms);
245
    // find a version path where every edge(rowset) has been warmuped
246
24
    Versions version_path;
247
    // Caller (capture_consistent_versions_unlocked) already holds shared
248
    // `_meta_lock`; do NOT re-acquire it here. The lock is writer-preferring,
249
    // so a recursive shared acquisition self-deadlocks if a writer queues in
250
    // between the outer and inner lock.
251
24
    if (enable_unique_key_merge_on_write()) {
252
        // For merge-on-write table, newly generated delete bitmap marks will be on the rowsets which are in newest layout.
253
        // So we can ony capture rowsets which are in newest data layout. Otherwise there may be data correctness issue.
254
12
        RETURN_IF_ERROR_RESULT(
255
12
                _timestamped_version_tracker.capture_consistent_versions_with_validator_mow(
256
12
                        spec_version, version_path, [&](int64_t start, int64_t end) {
257
12
                            return rowset_is_warmed_up_unlocked(start, end);
258
12
                        }));
259
12
    } else {
260
12
        RETURN_IF_ERROR_RESULT(
261
12
                _timestamped_version_tracker.capture_consistent_versions_with_validator(
262
12
                        spec_version, version_path, [&](int64_t start, int64_t end) {
263
12
                            return rowset_is_warmed_up_unlocked(start, end);
264
12
                        }));
265
12
    }
266
24
    int64_t path_max_version = version_path.back().second;
267
    // use std::views::concat after C++26
268
417
    auto check_fn = [this, path_max_version, freshness_limit_tp](const auto& rs_meta) {
269
417
        return _check_rowset_should_be_visible_but_not_warmed_up(rs_meta, path_max_version,
270
417
                                                                 freshness_limit_tp);
271
417
    };
272
24
    bool should_fallback =
273
24
            std::ranges::any_of(std::views::values(_tablet_meta->all_rs_metas()), check_fn) ||
274
24
            std::ranges::any_of(std::views::values(_tablet_meta->all_stale_rs_metas()), check_fn);
275
24
    if (should_fallback) {
276
        // The outer caller still holds the shared `_meta_lock`; the base
277
        // unlocked fallback below runs under that lock.
278
5
        g_capture_with_freshness_tolerance_fallback_count << 1;
279
        // if there exists a rowset which satisfies freshness tolerance and its start version is larger than the path max version
280
        // but has not been warmuped up yet, fallback to capture rowsets as usual
281
5
        return BaseTablet::capture_consistent_versions_unlocked(spec_version, options);
282
5
    }
283
19
    VLOG_DEBUG << fmt::format(
284
0
            "[verbose] CloudTablet::capture_versions_with_freshness_tolerance, capture path: {}, "
285
0
            "tablet_id={}, spec_version={}, path_max_version={}",
286
0
            fmt::join(version_path | std::views::transform([](const auto& version) {
287
0
                          return fmt::format("{}", version.to_string());
288
0
                      }),
289
0
                      ", "),
290
0
            tablet_id(), spec_version.to_string(), path_max_version);
291
19
    return version_path;
292
24
}
293
294
// There are only two tablet_states RUNNING and NOT_READY in cloud mode
295
// This function will erase the tablet from `CloudTabletMgr` when it can't find this tablet in MS.
296
39
Status CloudTablet::sync_rowsets(const SyncOptions& options, SyncRowsetStats* stats) {
297
39
    RETURN_IF_ERROR(sync_if_not_running(stats));
298
299
39
    if (options.query_version > 0) {
300
4
        DBUG_EXECUTE_IF("CloudTablet::sync_rowsets.stale_local_max_for_query_version", {
301
4
            auto target_tablet_id = dp->param<int64_t>("tablet_id", -1);
302
4
            auto stale_version = dp->param<int64_t>("version", -1);
303
4
            if (target_tablet_id == tablet_id() && stale_version >= 0) {
304
4
                std::unique_lock wlock(_meta_lock);
305
4
                LOG(INFO) << "override cloud tablet local max_version for query_version sync"
306
4
                          << ", tablet_id=" << tablet_id() << ", old_max_version=" << _max_version
307
4
                          << ", stale_version=" << stale_version
308
4
                          << ", query_version=" << options.query_version;
309
4
                _max_version = stale_version;
310
4
            }
311
4
        });
312
4
        auto lock_start = std::chrono::steady_clock::now();
313
4
        std::shared_lock rlock(_meta_lock);
314
4
        if (stats) {
315
0
            stats->meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
316
0
                                                std::chrono::steady_clock::now() - lock_start)
317
0
                                                .count();
318
0
        }
319
4
        if (_max_version >= options.query_version) {
320
3
            return Status::OK();
321
3
        }
322
4
    }
323
324
    // serially execute sync to reduce unnecessary network overhead
325
36
    auto sync_lock_start = std::chrono::steady_clock::now();
326
36
    std::unique_lock lock(_sync_meta_lock);
327
36
    if (stats) {
328
0
        stats->sync_meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
329
0
                                                 std::chrono::steady_clock::now() - sync_lock_start)
330
0
                                                 .count();
331
0
    }
332
36
    if (options.query_version > 0) {
333
1
        auto lock_start = std::chrono::steady_clock::now();
334
1
        std::shared_lock rlock(_meta_lock);
335
1
        if (stats) {
336
0
            stats->meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
337
0
                                                std::chrono::steady_clock::now() - lock_start)
338
0
                                                .count();
339
0
        }
340
1
        if (_max_version >= options.query_version) {
341
0
            return Status::OK();
342
0
        }
343
1
    }
344
345
36
    auto st = _engine.meta_mgr().sync_tablet_rowsets_unlocked(this, lock, options, stats);
346
36
    if (st.is<ErrorCode::NOT_FOUND>()) {
347
0
        clear_cache();
348
0
    }
349
350
36
    return st;
351
36
}
352
353
// Sync tablet meta and all rowset meta if not running.
354
// This could happen when BE didn't finish schema change job and another BE committed this schema change job.
355
// It should be a quite rare situation.
356
39
Status CloudTablet::sync_if_not_running(SyncRowsetStats* stats) {
357
39
    if (tablet_state() == TABLET_RUNNING) {
358
34
        return Status::OK();
359
34
    }
360
361
    // Serially execute sync to reduce unnecessary network overhead
362
5
    auto sync_lock_start = std::chrono::steady_clock::now();
363
5
    std::unique_lock lock(_sync_meta_lock);
364
5
    if (stats) {
365
0
        stats->sync_meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
366
0
                                                 std::chrono::steady_clock::now() - sync_lock_start)
367
0
                                                 .count();
368
0
    }
369
370
5
    {
371
5
        auto lock_start = std::chrono::steady_clock::now();
372
5
        std::shared_lock rlock(_meta_lock);
373
5
        if (stats) {
374
0
            stats->meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
375
0
                                                std::chrono::steady_clock::now() - lock_start)
376
0
                                                .count();
377
0
        }
378
5
        if (tablet_state() == TABLET_RUNNING) {
379
0
            return Status::OK();
380
0
        }
381
5
    }
382
383
5
    TabletMetaSharedPtr tablet_meta;
384
5
    auto st = _engine.meta_mgr().get_tablet_meta(tablet_id(), &tablet_meta);
385
5
    if (!st.ok()) {
386
0
        if (st.is<ErrorCode::NOT_FOUND>()) {
387
0
            clear_cache();
388
0
        }
389
0
        return st;
390
0
    }
391
392
5
    if (tablet_meta->tablet_state() != TABLET_RUNNING) [[unlikely]] {
393
        // MoW may go to here when load while schema change
394
5
        return Status::OK();
395
5
    }
396
397
0
    TimestampedVersionTracker empty_tracker;
398
0
    {
399
0
        auto lock_start = std::chrono::steady_clock::now();
400
0
        std::lock_guard wlock(_meta_lock);
401
0
        if (stats) {
402
0
            stats->meta_lock_wait_ns += std::chrono::duration_cast<std::chrono::nanoseconds>(
403
0
                                                std::chrono::steady_clock::now() - lock_start)
404
0
                                                .count();
405
0
        }
406
0
        RETURN_IF_ERROR(set_tablet_state(TABLET_RUNNING));
407
0
        _rs_version_map.clear();
408
0
        _stale_rs_version_map.clear();
409
0
        std::swap(_timestamped_version_tracker, empty_tracker);
410
0
        _tablet_meta->clear_rowsets();
411
0
        _tablet_meta->clear_stale_rowset();
412
0
        _max_version = -1;
413
0
    }
414
415
0
    st = _engine.meta_mgr().sync_tablet_rowsets_unlocked(this, lock, {}, stats);
416
0
    if (st.is<ErrorCode::NOT_FOUND>()) {
417
0
        clear_cache();
418
0
    }
419
0
    return st;
420
0
}
421
422
void CloudTablet::add_rowsets(std::vector<RowsetSharedPtr> to_add, bool version_overlap,
423
                              std::unique_lock<BthreadSharedMutex>& meta_lock,
424
347
                              bool warmup_delta_data) {
425
347
    if (to_add.empty()) {
426
1
        return;
427
1
    }
428
429
346
    VLOG_DEBUG << "add_rowsets tablet_id=" << tablet_id() << " stack: " << get_stack_trace();
430
431
346
    if (!version_overlap) {
432
340
        _add_rowsets_directly(to_add, warmup_delta_data);
433
340
        return;
434
340
    }
435
436
    // Filter out existed rowsets
437
6
    auto remove_it =
438
9
            std::remove_if(to_add.begin(), to_add.end(), [this](const RowsetSharedPtr& rs) {
439
9
                if (auto find_it = _rs_version_map.find(rs->version());
440
9
                    find_it == _rs_version_map.end()) {
441
7
                    return false;
442
7
                } else if (find_it->second->rowset_id() == rs->rowset_id()) {
443
2
                    return true; // Same rowset
444
2
                }
445
446
                // If version of rowset in `to_add` is equal to rowset in tablet but rowset_id is not equal,
447
                // replace existed rowset with `to_add` rowset. This may occur when:
448
                //  1. schema change converts rowsets which have been double written to new tablet
449
                //  2. cumu compaction picks single overlapping input rowset to perform compaction
450
451
                // add existed rowset to unused_rowsets to remove delete bitmap and recycle cached data
452
453
0
                std::vector<RowsetSharedPtr> unused_rowsets;
454
0
                if (auto find_it = _rs_version_map.find(rs->version());
455
0
                    find_it != _rs_version_map.end()) {
456
0
                    if (find_it->second->rowset_id() == rs->rowset_id()) {
457
0
                        LOG(WARNING) << "tablet_id=" << tablet_id()
458
0
                                     << ", rowset_id=" << rs->rowset_id().to_string()
459
0
                                     << ", existed rowset_id="
460
0
                                     << find_it->second->rowset_id().to_string();
461
0
                        DCHECK(find_it->second->rowset_id() != rs->rowset_id())
462
0
                                << "tablet_id=" << tablet_id()
463
0
                                << ", rowset_id=" << rs->rowset_id().to_string()
464
0
                                << ", existed rowset_id="
465
0
                                << find_it->second->rowset_id().to_string();
466
0
                    }
467
0
                    unused_rowsets.push_back(find_it->second);
468
0
                }
469
0
                add_unused_rowsets(unused_rowsets);
470
471
0
                _tablet_meta->delete_rs_meta_by_version(rs->version(), nullptr);
472
0
                _rs_version_map[rs->version()] = rs;
473
0
                _tablet_meta->add_rowsets_unchecked({rs});
474
0
                update_base_size(*rs);
475
0
                return true;
476
9
            });
477
478
6
    to_add.erase(remove_it, to_add.end());
479
480
    // delete rowsets with overlapped version
481
6
    std::vector<RowsetSharedPtr> to_add_directly;
482
7
    for (auto& to_add_rs : to_add) {
483
        // delete rowsets with overlapped version
484
7
        std::vector<RowsetSharedPtr> to_delete;
485
7
        Version to_add_v = to_add_rs->version();
486
        // if start_version  > max_version, we can skip checking overlap here.
487
7
        if (to_add_v.first > _max_version) {
488
            // if start_version  > max_version, we can skip checking overlap here.
489
4
            to_add_directly.push_back(to_add_rs);
490
4
        } else {
491
3
            to_add_directly.push_back(to_add_rs);
492
8
            for (auto& [v, rs] : _rs_version_map) {
493
8
                if (to_add_v.contains(v)) {
494
4
                    to_delete.push_back(rs);
495
4
                }
496
8
            }
497
3
            delete_rowsets(to_delete, meta_lock);
498
3
        }
499
7
    }
500
501
6
    _add_rowsets_directly(to_add_directly, warmup_delta_data);
502
6
}
503
504
void CloudTablet::delete_rowsets(const std::vector<RowsetSharedPtr>& to_delete,
505
110
                                 std::unique_lock<BthreadSharedMutex>&) {
506
110
    if (to_delete.empty()) {
507
1
        return;
508
1
    }
509
109
    std::vector<RowsetMetaSharedPtr> rs_metas;
510
109
    rs_metas.reserve(to_delete.size());
511
109
    int64_t now = ::time(nullptr);
512
613
    for (auto&& rs : to_delete) {
513
613
        rs->rowset_meta()->set_stale_at(now);
514
613
        rs_metas.push_back(rs->rowset_meta());
515
613
        _stale_rs_version_map[rs->version()] = rs;
516
613
    }
517
109
    _timestamped_version_tracker.add_stale_path_version(rs_metas);
518
613
    for (auto&& rs : to_delete) {
519
613
        _rs_version_map.erase(rs->version());
520
613
    }
521
522
109
    _tablet_meta->modify_rs_metas({}, rs_metas, false);
523
109
}
524
525
void CloudTablet::delete_rowsets_for_schema_change(const std::vector<RowsetSharedPtr>& to_delete,
526
                                                   std::unique_lock<BthreadSharedMutex>&,
527
5
                                                   bool recycle_deleted_rowsets) {
528
5
    if (to_delete.empty()) {
529
1
        return;
530
1
    }
531
4
    std::vector<RowsetMetaSharedPtr> rs_metas;
532
4
    rs_metas.reserve(to_delete.size());
533
5
    for (auto&& rs : to_delete) {
534
5
        rs_metas.push_back(rs->rowset_meta());
535
5
        _rs_version_map.erase(rs->version());
536
        // Remove edge from version graph so that the greedy capture algorithm
537
        // won't prefer the wider stale compaction rowset over individual SC
538
        // output rowsets (e.g. [818-822] vs [818],[819],...,[822]).
539
5
        _timestamped_version_tracker.delete_version(rs->version());
540
5
    }
541
542
    // Use same_version=true to skip adding to _stale_rs_metas. Do NOT use the
543
    // stale tracking mechanism (_stale_rs_version_map / _stale_version_path_map)
544
    // because SC output will create new rowsets with identical version ranges;
545
    // a later compaction could put those into stale as well, causing two stale
546
    // paths to reference the same version key -- when one path is cleaned first,
547
    // the other hits a DCHECK(false) in delete_expired_stale_rowsets().
548
4
    _tablet_meta->modify_rs_metas({}, rs_metas, true);
549
550
4
    if (recycle_deleted_rowsets) {
551
        // Schedule for direct cache cleanup. MS has already recycled these rowsets.
552
3
        add_unused_rowsets(to_delete);
553
3
    }
554
4
}
555
556
void CloudTablet::replace_rowsets_with_schema_change_output(
557
        const std::vector<RowsetSharedPtr>& output_rowsets, int64_t alter_version,
558
        std::unique_lock<BthreadSharedMutex>& meta_lock, const char* stage,
559
2
        bool recycle_deleted_rowsets) {
560
2
    std::vector<RowsetSharedPtr> to_delete;
561
7
    for (auto& [v, rs] : _rs_version_map) {
562
7
        if (v.first >= 2 && v.second <= alter_version &&
563
7
            !is_schema_change_output_rowset(rs, output_rowsets)) {
564
1
            to_delete.push_back(rs);
565
1
        }
566
7
    }
567
2
    if (!to_delete.empty()) {
568
1
        LOG_INFO(
569
1
                "schema change: delete {} local rowsets in [2, {}] before adding SC output, "
570
1
                "tablet_id={}, stage={}, versions=[{}]",
571
1
                to_delete.size(), alter_version, tablet_id(), stage,
572
1
                fmt::join(to_delete | std::views::transform([](const auto& rs) {
573
1
                              return rs->version().to_string();
574
1
                          }),
575
1
                          ", "));
576
1
        delete_rowsets_for_schema_change(to_delete, meta_lock, recycle_deleted_rowsets);
577
1
    }
578
2
    add_rowsets(output_rowsets, true, meta_lock, false);
579
2
}
580
581
1
uint64_t CloudTablet::delete_expired_stale_rowsets() {
582
1
    if (config::enable_mow_verbose_log) {
583
0
        LOG_INFO("begin delete_expired_stale_rowset for tablet={}", tablet_id());
584
0
    }
585
1
    std::vector<RowsetSharedPtr> expired_rowsets;
586
    // ATTN: trick, Use stale_rowsets to temporarily increase the reference count of the rowset shared pointer in _stale_rs_version_map so that in the recycle_cached_data function, it checks if the reference count is 2.
587
1
    std::vector<std::pair<Version, std::vector<RowsetSharedPtr>>> deleted_stale_rowsets;
588
1
    int64_t expired_stale_sweep_endtime =
589
1
            ::time(nullptr) - config::tablet_rowset_stale_sweep_time_sec;
590
1
    {
591
1
        std::unique_lock wlock(_meta_lock);
592
593
1
        std::vector<int64_t> path_ids;
594
        // capture the path version to delete
595
1
        _timestamped_version_tracker.capture_expired_paths(expired_stale_sweep_endtime, &path_ids);
596
597
1
        if (path_ids.empty()) {
598
0
            return 0;
599
0
        }
600
601
1
        for (int64_t path_id : path_ids) {
602
1
            int64_t start_version = -1;
603
1
            int64_t end_version = -1;
604
1
            std::vector<RowsetSharedPtr> stale_rowsets;
605
            // delete stale versions in version graph
606
1
            auto version_path = _timestamped_version_tracker.fetch_and_delete_path_by_id(path_id);
607
5
            for (auto& v_ts : version_path->timestamped_versions()) {
608
5
                auto rs_it = _stale_rs_version_map.find(v_ts->version());
609
5
                if (rs_it != _stale_rs_version_map.end()) {
610
5
                    expired_rowsets.push_back(rs_it->second);
611
5
                    stale_rowsets.push_back(rs_it->second);
612
5
                    VLOG_DEBUG << "erase stale rowset, tablet_id=" << tablet_id()
613
0
                               << " rowset_id=" << rs_it->second->rowset_id().to_string()
614
0
                               << " version=" << rs_it->first.to_string();
615
5
                    _stale_rs_version_map.erase(rs_it);
616
5
                } else {
617
0
                    LOG(WARNING) << "cannot find stale rowset " << v_ts->version() << " in tablet "
618
0
                                 << tablet_id();
619
                    // clang-format off
620
0
                    DCHECK(false) << [this, &wlock]() { wlock.unlock(); std::string json; get_compaction_status(&json); return json; }();
621
                    // clang-format on
622
0
                }
623
5
                if (start_version < 0) {
624
1
                    start_version = v_ts->version().first;
625
1
                }
626
5
                end_version = v_ts->version().second;
627
5
                _tablet_meta->delete_stale_rs_meta_by_version(v_ts->version());
628
5
            }
629
1
            Version version(start_version, end_version);
630
1
            if (!stale_rowsets.empty()) {
631
1
                deleted_stale_rowsets.emplace_back(version, std::move(stale_rowsets));
632
1
            }
633
1
        }
634
1
        _reconstruct_version_tracker_if_necessary();
635
1
    }
636
637
    // if the rowset is not used by any query, we can recycle its cached data early.
638
0
    auto recycled_rowsets = recycle_cached_data(expired_rowsets);
639
1
    if (!recycled_rowsets.empty()) {
640
0
        auto& manager = ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager();
641
0
        manager.recycle_cache(tablet_id(), recycled_rowsets);
642
0
    }
643
1
    if (config::enable_mow_verbose_log) {
644
0
        LOG_INFO("finish delete_expired_stale_rowset for tablet={}", tablet_id());
645
0
    }
646
647
1
    add_unused_rowsets(expired_rowsets);
648
1
    if (config::enable_agg_and_remove_pre_rowsets_delete_bitmap && keys_type() == UNIQUE_KEYS &&
649
1
        enable_unique_key_merge_on_write() && !deleted_stale_rowsets.empty()) {
650
        // agg delete bitmap for pre rowsets; record unused delete bitmap key ranges
651
0
        OlapStopWatch watch;
652
0
        for (const auto& [version, unused_rowsets] : deleted_stale_rowsets) {
653
            // agg delete bitmap for pre rowset
654
0
            DeleteBitmapKeyRanges remove_delete_bitmap_key_ranges;
655
0
            agg_delete_bitmap_for_stale_rowsets(version, remove_delete_bitmap_key_ranges);
656
            // add remove delete bitmap
657
0
            if (!remove_delete_bitmap_key_ranges.empty()) {
658
0
                std::vector<RowsetId> rowset_ids;
659
0
                for (const auto& rs : unused_rowsets) {
660
0
                    rowset_ids.push_back(rs->rowset_id());
661
0
                }
662
0
                std::lock_guard<std::mutex> lock(_gc_mutex);
663
0
                _unused_delete_bitmap.push_back(
664
0
                        std::make_pair(rowset_ids, remove_delete_bitmap_key_ranges));
665
0
            }
666
0
        }
667
0
        LOG(INFO) << "agg pre rowsets delete bitmap. tablet_id=" << tablet_id()
668
0
                  << ", size=" << deleted_stale_rowsets.size()
669
0
                  << ", cost(us)=" << watch.get_elapse_time_us();
670
0
    }
671
1
    return expired_rowsets.size();
672
1
}
673
674
4
bool CloudTablet::need_remove_unused_rowsets() {
675
4
    std::lock_guard<std::mutex> lock(_gc_mutex);
676
4
    return !_unused_rowsets.empty() || !_unused_delete_bitmap.empty();
677
4
}
678
679
4
void CloudTablet::add_unused_rowsets(const std::vector<RowsetSharedPtr>& rowsets) {
680
4
    std::lock_guard<std::mutex> lock(_gc_mutex);
681
9
    for (const auto& rowset : rowsets) {
682
9
        _unused_rowsets[rowset->rowset_id()] = rowset;
683
9
        g_unused_rowsets_bytes << rowset->total_disk_size();
684
9
    }
685
4
    g_unused_rowsets_count << rowsets.size();
686
4
}
687
688
0
void CloudTablet::remove_unused_rowsets() {
689
0
    std::vector<std::shared_ptr<Rowset>> removed_rowsets;
690
0
    int64_t removed_delete_bitmap_num = 0;
691
0
    OlapStopWatch watch;
692
0
    {
693
        // Warmup callbacks use _meta_lock; take it before _gc_mutex, as add_rowsets does.
694
0
        std::unique_lock meta_lock(_meta_lock);
695
0
        std::lock_guard<std::mutex> lock(_gc_mutex);
696
        // 1. select unused rowsets and remove their warmup states and delete bitmap
697
0
        for (auto it = _unused_rowsets.begin(); it != _unused_rowsets.end();) {
698
0
            auto& rs = it->second;
699
0
            if (rs.use_count() > 1) {
700
0
                LOG(WARNING) << "tablet_id:" << tablet_id() << " rowset: " << rs->rowset_id()
701
0
                             << " has " << rs.use_count() << " references, it cannot be removed";
702
0
                ++it;
703
0
                continue;
704
0
            }
705
0
            tablet_meta()->remove_rowset_delete_bitmap(rs->rowset_id(), rs->version());
706
0
            _rowset_warm_up_states.erase(rs->rowset_id());
707
0
            g_unused_rowsets_count << -1;
708
0
            g_unused_rowsets_bytes << -rs->total_disk_size();
709
0
            removed_rowsets.push_back(std::move(rs));
710
0
            it = _unused_rowsets.erase(it);
711
0
        }
712
0
    }
713
714
0
    {
715
0
        std::vector<RecycledRowsets> recycled_rowsets;
716
717
        // Keep selected rowsets alive here, but release tablet locks before clearing
718
        // caches: a compaction batch can synchronously remove many cached blocks.
719
0
        for (auto& rs : removed_rowsets) {
720
0
            rs->clear_cache();
721
0
            auto index_names = rs->get_index_file_names();
722
0
            recycled_rowsets.emplace_back(rs->rowset_id(), rs->num_segments(), index_names);
723
0
            int64_t segment_size_sum = 0;
724
0
            for (int32_t i = 0; i < rs->num_segments(); i++) {
725
0
                segment_size_sum += rs->rowset_meta()->segment_file_size(i);
726
0
            }
727
0
            g_file_cache_recycle_cached_data_segment_num << rs->num_segments();
728
0
            g_file_cache_recycle_cached_data_segment_size << segment_size_sum;
729
0
            g_file_cache_recycle_cached_data_index_num << index_names.size();
730
0
        }
731
732
0
        if (recycled_rowsets.size() > 0) {
733
0
            auto& manager =
734
0
                    ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager();
735
0
            manager.recycle_cache(tablet_id(), recycled_rowsets);
736
0
        }
737
0
    }
738
739
0
    {
740
0
        std::lock_guard<std::mutex> lock(_gc_mutex);
741
        // 2. remove delete bitmap of pre rowsets
742
0
        for (auto it = _unused_delete_bitmap.begin(); it != _unused_delete_bitmap.end();) {
743
0
            auto& rowset_ids = std::get<0>(*it);
744
0
            bool find_unused_rowset = false;
745
0
            for (const auto& rowset_id : rowset_ids) {
746
0
                if (_unused_rowsets.find(rowset_id) != _unused_rowsets.end()) {
747
0
                    LOG(INFO) << "can not remove pre rowset delete bitmap because rowset is in use"
748
0
                              << ", tablet_id=" << tablet_id() << ", rowset_id=" << rowset_id;
749
0
                    find_unused_rowset = true;
750
0
                    break;
751
0
                }
752
0
            }
753
0
            if (find_unused_rowset) {
754
0
                ++it;
755
0
                continue;
756
0
            }
757
0
            auto& key_ranges = std::get<1>(*it);
758
0
            tablet_meta()->delete_bitmap().remove(key_ranges);
759
0
            it = _unused_delete_bitmap.erase(it);
760
0
            removed_delete_bitmap_num++;
761
            // TODO(kaijie): recycle cache for unused delete bitmap
762
0
        }
763
0
    }
764
765
0
    LOG(INFO) << "tablet_id=" << tablet_id() << ", unused_rowset size=" << _unused_rowsets.size()
766
0
              << ", unused_delete_bitmap size=" << _unused_delete_bitmap.size()
767
0
              << ", removed_rowsets_num=" << removed_rowsets.size()
768
0
              << ", removed_delete_bitmap_num=" << removed_delete_bitmap_num
769
0
              << ", cost(us)=" << watch.get_elapse_time_us();
770
0
}
771
772
1.00k
void CloudTablet::update_base_size(const Rowset& rs) {
773
    // Define base rowset as the rowset of version [2-x]
774
1.00k
    if (rs.start_version() == 2) {
775
150
        _base_size = rs.total_disk_size();
776
150
    }
777
1.00k
}
778
779
0
void CloudTablet::clear_cache() {
780
0
    auto recycled_rowsets = CloudTablet::recycle_cached_data(get_snapshot_rowset(true));
781
0
    if (!recycled_rowsets.empty()) {
782
0
        auto& manager = ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager();
783
0
        manager.recycle_cache(tablet_id(), recycled_rowsets);
784
0
    }
785
0
    _engine.tablet_mgr().erase_tablet(tablet_id());
786
0
}
787
788
std::vector<RecycledRowsets> CloudTablet::recycle_cached_data(
789
1
        const std::vector<RowsetSharedPtr>& rowsets) {
790
1
    std::vector<RecycledRowsets> recycled_rowsets;
791
5
    for (const auto& rs : rowsets) {
792
        // rowsets and tablet._rs_version_map each hold a rowset shared_ptr, so at this point, the reference count of the shared_ptr is at least 2.
793
5
        if (rs.use_count() > 2) {
794
5
            LOG(WARNING) << "Rowset " << rs->rowset_id().to_string() << " has " << rs.use_count()
795
5
                         << " references. File Cache won't be recycled when query is using it.";
796
5
            continue;
797
5
        }
798
0
        rs->clear_cache();
799
0
        auto index_names = rs->get_index_file_names();
800
0
        recycled_rowsets.emplace_back(rs->rowset_id(), rs->num_segments(), index_names);
801
802
0
        int64_t segment_size_sum = 0;
803
0
        for (int32_t i = 0; i < rs->num_segments(); i++) {
804
0
            segment_size_sum += rs->rowset_meta()->segment_file_size(i);
805
0
        }
806
0
        g_file_cache_recycle_cached_data_segment_num << rs->num_segments();
807
0
        g_file_cache_recycle_cached_data_segment_size << segment_size_sum;
808
0
        g_file_cache_recycle_cached_data_index_num << index_names.size();
809
0
    }
810
1
    return recycled_rowsets;
811
1
}
812
813
void CloudTablet::reset_approximate_stats(int64_t num_rowsets, int64_t num_segments,
814
6
                                          int64_t num_rows, int64_t data_size) {
815
6
    _approximate_num_segments.store(num_segments, std::memory_order_relaxed);
816
6
    _approximate_num_rows.store(num_rows, std::memory_order_relaxed);
817
6
    _approximate_data_size.store(data_size, std::memory_order_relaxed);
818
6
    int64_t cumu_num_deltas = 0;
819
6
    int64_t cumu_num_rowsets = 0;
820
6
    auto cp = _cumulative_point.load(std::memory_order_relaxed);
821
10
    for (auto& [v, r] : _rs_version_map) {
822
10
        if (v.second < cp) {
823
4
            continue;
824
4
        }
825
6
        cumu_num_deltas += r->is_segments_overlapping() ? r->num_segments() : 1;
826
6
        ++cumu_num_rowsets;
827
6
    }
828
    // num_rowsets may be less than the size of _rs_version_map when there are some hole rowsets
829
    // in the version map, so we use the max value to ensure that the approximate number
830
    // of rowsets is at least the size of _rs_version_map.
831
    // Note that this is not the exact number of rowsets, but an approximate number.
832
6
    int64_t approximate_num_rowsets =
833
6
            std::max(num_rowsets, static_cast<int64_t>(_rs_version_map.size()));
834
6
    _approximate_num_rowsets.store(approximate_num_rowsets, std::memory_order_relaxed);
835
6
    _approximate_cumu_num_rowsets.store(cumu_num_rowsets, std::memory_order_relaxed);
836
6
    _approximate_cumu_num_deltas.store(cumu_num_deltas, std::memory_order_relaxed);
837
6
}
838
839
Result<std::unique_ptr<RowsetWriter>> CloudTablet::create_rowset_writer(
840
1
        RowsetWriterContext& context, bool vertical) {
841
1
    context.rowset_id = _engine.next_rowset_id();
842
    // FIXME(plat1ko): Seems `tablet_id` and `index_id` has been set repeatedly
843
1
    context.tablet_id = tablet_id();
844
1
    context.index_id = index_id();
845
1
    context.partition_id = partition_id();
846
1
    context.enable_unique_key_merge_on_write = enable_unique_key_merge_on_write();
847
1
    context.encrypt_algorithm = tablet_meta()->encryption_algorithm();
848
1
    context.inverted_index_storage_format = tablet_meta()->inverted_index_storage_format();
849
1
    context.persist_inverted_index_storage_format =
850
1
            tablet_meta()->has_inverted_index_storage_format();
851
1
    return RowsetFactory::create_rowset_writer(_engine, context, vertical);
852
1
}
853
854
// create a rowset writer with rowset_id and seg_id
855
// after writer, merge this transient rowset with original rowset
856
Result<std::unique_ptr<RowsetWriter>> CloudTablet::create_transient_rowset_writer(
857
        const Rowset& rowset, std::shared_ptr<PartialUpdateInfo> partial_update_info,
858
0
        int64_t txn_expiration) {
859
0
    if (rowset.rowset_meta_state() != RowsetStatePB::BEGIN_PARTIAL_UPDATE &&
860
0
        rowset.rowset_meta_state() != RowsetStatePB::COMMITTED) [[unlikely]] {
861
0
        auto msg = fmt::format(
862
0
                "wrong rowset state when create_transient_rowset_writer, rowset state should be "
863
0
                "BEGIN_PARTIAL_UPDATE or COMMITTED, but found {}, rowset_id={}, tablet_id={}",
864
0
                RowsetStatePB_Name(rowset.rowset_meta_state()), rowset.rowset_id().to_string(),
865
0
                tablet_id());
866
        // see `CloudRowsetWriter::build` for detail.
867
        // if this is in a retry task, the rowset state may have been changed to RowsetStatePB::COMMITTED
868
        // in `RowsetMeta::merge_rowset_meta()` in previous trials.
869
0
        LOG(WARNING) << msg;
870
0
        DCHECK(false) << msg;
871
0
    }
872
0
    RowsetWriterContext context;
873
0
    context.rowset_state = PREPARED;
874
0
    context.segments_overlap = OVERLAPPING;
875
    // During a partial update, the extracted columns of a variant should not be included in the tablet schema.
876
    // This is because the partial update for a variant needs to ignore the extracted columns.
877
    // Otherwise, the schema types in different rowsets might be inconsistent. When performing a partial update,
878
    // the complete variant is constructed by reading all the sub-columns of the variant.
879
0
    context.tablet_schema = rowset.tablet_schema()->copy_without_variant_extracted_columns();
880
0
    context.newest_write_timestamp = UnixSeconds();
881
0
    context.tablet_id = table_id();
882
0
    context.enable_segcompaction = false;
883
0
    context.write_type = DataWriteType::TYPE_DIRECT;
884
0
    context.partial_update_info = std::move(partial_update_info);
885
0
    context.is_transient_rowset_writer = true;
886
0
    context.rowset_id = rowset.rowset_id();
887
0
    context.tablet_id = tablet_id();
888
0
    context.index_id = index_id();
889
0
    context.partition_id = partition_id();
890
0
    context.enable_unique_key_merge_on_write = enable_unique_key_merge_on_write();
891
0
    context.txn_expiration = txn_expiration;
892
0
    context.encrypt_algorithm = tablet_meta()->encryption_algorithm();
893
0
    context.inverted_index_storage_format = tablet_meta()->inverted_index_storage_format();
894
0
    context.persist_inverted_index_storage_format =
895
0
            tablet_meta()->has_inverted_index_storage_format();
896
    // TODO(liaoxin) enable packed file for transient rowset
897
0
    context.allow_packed_file = false;
898
899
0
    auto storage_resource = rowset.rowset_meta()->remote_storage_resource();
900
0
    if (!storage_resource) {
901
0
        return ResultError(std::move(storage_resource.error()));
902
0
    }
903
904
0
    context.storage_resource = *storage_resource.value();
905
906
0
    return RowsetFactory::create_rowset_writer(_engine, context, false)
907
0
            .transform([&](auto&& writer) {
908
0
                writer->set_segment_start_id(cast_set<int32_t>(rowset.num_segments()));
909
0
                return writer;
910
0
            });
911
0
}
912
913
3
int64_t CloudTablet::get_cloud_base_compaction_score() const {
914
3
    if (_tablet_meta->compaction_policy() == CUMULATIVE_TIME_SERIES_POLICY) {
915
0
        bool has_delete = false;
916
0
        int64_t point = cumulative_layer_point();
917
0
        std::shared_lock rlock(_meta_lock);
918
0
        for (const auto& [_, rs_meta] : _tablet_meta->all_rs_metas()) {
919
0
            if (rs_meta->start_version() >= point) {
920
0
                continue;
921
0
            }
922
0
            if (rs_meta->has_delete_predicate()) {
923
0
                has_delete = true;
924
0
                break;
925
0
            }
926
0
        }
927
0
        if (!has_delete) {
928
0
            return 0;
929
0
        }
930
0
    }
931
932
3
    return _approximate_num_rowsets.load(std::memory_order_relaxed) -
933
3
           _approximate_cumu_num_rowsets.load(std::memory_order_relaxed);
934
3
}
935
936
7
int64_t CloudTablet::get_cloud_cumu_compaction_score() const {
937
    // TODO(plat1ko): Propose an algorithm that considers tablet's key type, number of delete rowsets,
938
    //  number of tablet versions simultaneously.
939
7
    return _approximate_cumu_num_deltas.load(std::memory_order_relaxed);
940
7
}
941
942
// return a json string to show the compaction status of this tablet
943
33
void CloudTablet::get_compaction_status(std::string* json_result) {
944
33
    rapidjson::Document root;
945
33
    root.SetObject();
946
947
33
    rapidjson::Document path_arr;
948
33
    path_arr.SetArray();
949
950
33
    std::vector<RowsetSharedPtr> rowsets;
951
33
    std::vector<RowsetSharedPtr> stale_rowsets;
952
33
    {
953
33
        std::shared_lock rdlock(_meta_lock);
954
33
        rowsets.reserve(_rs_version_map.size());
955
148
        for (auto& it : _rs_version_map) {
956
148
            rowsets.push_back(it.second);
957
148
        }
958
33
        stale_rowsets.reserve(_stale_rs_version_map.size());
959
540
        for (auto& it : _stale_rs_version_map) {
960
540
            stale_rowsets.push_back(it.second);
961
540
        }
962
33
    }
963
33
    std::sort(rowsets.begin(), rowsets.end(), Rowset::comparator);
964
33
    std::sort(stale_rowsets.begin(), stale_rowsets.end(), Rowset::comparator);
965
966
    // get snapshot version path json_doc
967
33
    _timestamped_version_tracker.get_stale_version_path_json_doc(path_arr);
968
33
    rapidjson::Value compaction_policy_value;
969
33
    auto compaction_policy = _tablet_meta->compaction_policy();
970
33
    compaction_policy_value.SetString(compaction_policy.c_str(),
971
33
                                      cast_set<uint32_t>(compaction_policy.length()),
972
33
                                      root.GetAllocator());
973
33
    root.AddMember("compaction policy", compaction_policy_value, root.GetAllocator());
974
33
    root.AddMember("cumulative point", _cumulative_point.load(), root.GetAllocator());
975
33
    rapidjson::Value cumu_value;
976
33
    std::string format_str = ToStringFromUnixMillis(_last_cumu_compaction_failure_millis.load());
977
33
    cumu_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
978
33
                         root.GetAllocator());
979
33
    root.AddMember("last cumulative failure time", cumu_value, root.GetAllocator());
980
33
    rapidjson::Value base_value;
981
33
    format_str = ToStringFromUnixMillis(_last_base_compaction_failure_millis.load());
982
33
    base_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
983
33
                         root.GetAllocator());
984
33
    root.AddMember("last base failure time", base_value, root.GetAllocator());
985
33
    rapidjson::Value full_value;
986
33
    format_str = ToStringFromUnixMillis(_last_full_compaction_failure_millis.load());
987
33
    full_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
988
33
                         root.GetAllocator());
989
33
    root.AddMember("last full failure time", full_value, root.GetAllocator());
990
33
    rapidjson::Value cumu_success_value;
991
33
    format_str = ToStringFromUnixMillis(_last_cumu_compaction_success_millis.load());
992
33
    cumu_success_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
993
33
                                 root.GetAllocator());
994
33
    root.AddMember("last cumulative success time", cumu_success_value, root.GetAllocator());
995
33
    rapidjson::Value base_success_value;
996
33
    format_str = ToStringFromUnixMillis(_last_base_compaction_success_millis.load());
997
33
    base_success_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
998
33
                                 root.GetAllocator());
999
33
    root.AddMember("last base success time", base_success_value, root.GetAllocator());
1000
33
    rapidjson::Value full_success_value;
1001
33
    format_str = ToStringFromUnixMillis(_last_full_compaction_success_millis.load());
1002
33
    full_success_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
1003
33
                                 root.GetAllocator());
1004
33
    root.AddMember("last full success time", full_success_value, root.GetAllocator());
1005
33
    rapidjson::Value cumu_schedule_value;
1006
33
    format_str = ToStringFromUnixMillis(_last_cumu_compaction_schedule_millis.load());
1007
33
    cumu_schedule_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
1008
33
                                  root.GetAllocator());
1009
33
    root.AddMember("last cumulative schedule time", cumu_schedule_value, root.GetAllocator());
1010
33
    rapidjson::Value base_schedule_value;
1011
33
    format_str = ToStringFromUnixMillis(_last_base_compaction_schedule_millis.load());
1012
33
    base_schedule_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
1013
33
                                  root.GetAllocator());
1014
33
    root.AddMember("last base schedule time", base_schedule_value, root.GetAllocator());
1015
33
    rapidjson::Value full_schedule_value;
1016
33
    format_str = ToStringFromUnixMillis(_last_full_compaction_schedule_millis.load());
1017
33
    full_schedule_value.SetString(format_str.c_str(), cast_set<uint>(format_str.length()),
1018
33
                                  root.GetAllocator());
1019
33
    root.AddMember("last full schedule time", full_schedule_value, root.GetAllocator());
1020
33
    rapidjson::Value cumu_compaction_status_value;
1021
33
    cumu_compaction_status_value.SetString(_last_cumu_compaction_status.c_str(),
1022
33
                                           cast_set<uint>(_last_cumu_compaction_status.length()),
1023
33
                                           root.GetAllocator());
1024
33
    root.AddMember("last cumulative status", cumu_compaction_status_value, root.GetAllocator());
1025
33
    rapidjson::Value base_compaction_status_value;
1026
33
    base_compaction_status_value.SetString(_last_base_compaction_status.c_str(),
1027
33
                                           cast_set<uint>(_last_base_compaction_status.length()),
1028
33
                                           root.GetAllocator());
1029
33
    root.AddMember("last base status", base_compaction_status_value, root.GetAllocator());
1030
33
    rapidjson::Value full_compaction_status_value;
1031
33
    full_compaction_status_value.SetString(_last_full_compaction_status.c_str(),
1032
33
                                           cast_set<uint>(_last_full_compaction_status.length()),
1033
33
                                           root.GetAllocator());
1034
33
    root.AddMember("last full status", full_compaction_status_value, root.GetAllocator());
1035
33
    rapidjson::Value exec_compaction_time;
1036
33
    std::string num_str {std::to_string(exec_compaction_time_us.load())};
1037
33
    exec_compaction_time.SetString(num_str.c_str(), cast_set<uint>(num_str.length()),
1038
33
                                   root.GetAllocator());
1039
33
    root.AddMember("exec compaction time us", exec_compaction_time, root.GetAllocator());
1040
33
    rapidjson::Value local_read_time;
1041
33
    num_str = std::to_string(local_read_time_us.load());
1042
33
    local_read_time.SetString(num_str.c_str(), cast_set<uint>(num_str.length()),
1043
33
                              root.GetAllocator());
1044
33
    root.AddMember("compaction local read time us", local_read_time, root.GetAllocator());
1045
33
    rapidjson::Value remote_read_time;
1046
33
    num_str = std::to_string(remote_read_time_us.load());
1047
33
    remote_read_time.SetString(num_str.c_str(), cast_set<uint>(num_str.length()),
1048
33
                               root.GetAllocator());
1049
33
    root.AddMember("compaction remote read time us", remote_read_time, root.GetAllocator());
1050
1051
    // print all rowsets' version as an array
1052
33
    rapidjson::Document versions_arr;
1053
33
    rapidjson::Document missing_versions_arr;
1054
33
    versions_arr.SetArray();
1055
33
    missing_versions_arr.SetArray();
1056
33
    int64_t last_version = -1;
1057
148
    for (auto& rowset : rowsets) {
1058
148
        const Version& ver = rowset->version();
1059
148
        if (ver.first != last_version + 1) {
1060
0
            rapidjson::Value miss_value;
1061
0
            miss_value.SetString(fmt::format("[{}-{}]", last_version + 1, ver.first - 1).c_str(),
1062
0
                                 missing_versions_arr.GetAllocator());
1063
0
            missing_versions_arr.PushBack(miss_value, missing_versions_arr.GetAllocator());
1064
0
        }
1065
148
        rapidjson::Value value;
1066
148
        std::string version_str = rowset->get_rowset_info_str();
1067
148
        value.SetString(version_str.c_str(), cast_set<uint32_t>(version_str.length()),
1068
148
                        versions_arr.GetAllocator());
1069
148
        versions_arr.PushBack(value, versions_arr.GetAllocator());
1070
148
        last_version = ver.second;
1071
148
    }
1072
33
    root.AddMember("rowsets", versions_arr, root.GetAllocator());
1073
33
    root.AddMember("missing_rowsets", missing_versions_arr, root.GetAllocator());
1074
1075
    // print all stale rowsets' version as an array
1076
33
    rapidjson::Document stale_versions_arr;
1077
33
    stale_versions_arr.SetArray();
1078
540
    for (auto& rowset : stale_rowsets) {
1079
540
        rapidjson::Value value;
1080
540
        std::string version_str = rowset->get_rowset_info_str();
1081
540
        value.SetString(version_str.c_str(), cast_set<uint32_t>(version_str.length()),
1082
540
                        stale_versions_arr.GetAllocator());
1083
540
        stale_versions_arr.PushBack(value, stale_versions_arr.GetAllocator());
1084
540
    }
1085
33
    root.AddMember("stale_rowsets", stale_versions_arr, root.GetAllocator());
1086
1087
    // add stale version rowsets
1088
33
    root.AddMember("stale version path", path_arr, root.GetAllocator());
1089
1090
    // to json string
1091
33
    rapidjson::StringBuffer strbuf;
1092
33
    rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(strbuf);
1093
33
    root.Accept(writer);
1094
33
    *json_result = std::string(strbuf.GetString());
1095
33
}
1096
1097
14
void CloudTablet::set_cumulative_layer_point(int64_t new_point) {
1098
14
    if (new_point == Tablet::K_INVALID_CUMULATIVE_POINT || new_point >= _cumulative_point) {
1099
14
        _cumulative_point = new_point;
1100
14
        return;
1101
14
    }
1102
    // cumulative point should only be reset to -1, or be increased
1103
    // FIXME: could happen in currently unresolved race conditions
1104
14
    LOG(WARNING) << "Unexpected cumulative point: " << new_point
1105
0
                 << ", origin: " << _cumulative_point.load();
1106
0
}
1107
1108
Status CloudTablet::check_rowset_schema_for_build_index(std::vector<TColumn>& columns,
1109
10
                                                        int schema_version) {
1110
10
    std::map<std::string, TabletColumn> fe_col_map;
1111
14
    for (int i = 0; i < columns.size(); i++) {
1112
4
        fe_col_map[columns[i].column_name] = TabletColumn(columns[i]);
1113
4
    }
1114
1115
10
    std::shared_lock rlock(_meta_lock);
1116
10
    for (const auto& [version, rs] : _rs_version_map) {
1117
4
        if (version.first == 0) {
1118
0
            continue;
1119
0
        }
1120
1121
4
        if (rs->tablet_schema()->schema_version() >= schema_version) {
1122
0
            continue;
1123
0
        }
1124
1125
4
        for (auto rs_col : rs->tablet_schema()->columns()) {
1126
4
            auto find_ret = fe_col_map.find(rs_col->name());
1127
4
            if (find_ret == fe_col_map.end()) {
1128
1
                return Status::InternalError(
1129
1
                        "check rowset meta failed:rowset's col is dropped in FE.");
1130
1
            }
1131
1132
3
            if (rs_col->unique_id() != find_ret->second.unique_id()) {
1133
1
                return Status::InternalError("check rowset meta failed:col id not match.");
1134
1
            }
1135
1136
2
            if (rs_col->type() != find_ret->second.type()) {
1137
1
                return Status::InternalError("check rowset meta failed:col type not match.");
1138
1
            }
1139
2
        }
1140
4
    }
1141
1142
7
    return Status::OK();
1143
10
}
1144
1145
Result<RowsetSharedPtr> CloudTablet::pick_a_rowset_for_index_change(int schema_version,
1146
15
                                                                    bool& is_base_rowset) {
1147
15
    TEST_SYNC_POINT_RETURN_WITH_VALUE("CloudTablet::pick_a_rowset_for_index_change",
1148
8
                                      Result<RowsetSharedPtr>(nullptr));
1149
8
    RowsetSharedPtr ret_rowset = nullptr;
1150
8
    std::shared_lock rlock(_meta_lock);
1151
8
    for (const auto& [version, rs] : _rs_version_map) {
1152
8
        if (version.first == 0) {
1153
0
            continue;
1154
0
        }
1155
8
        if (rs->num_rows() == 0) {
1156
1
            VLOG_DEBUG << "[index_change]find empty rs, index change may "
1157
0
                          "failed, id="
1158
0
                       << rs->rowset_id().to_string();
1159
1
        }
1160
1161
8
        if (rs->tablet_schema()->schema_version() >= schema_version) {
1162
2
            VLOG_DEBUG << "[index_change] skip rowset " << rs->tablet_schema()->schema_version()
1163
0
                       << "," << schema_version;
1164
2
            continue;
1165
2
        }
1166
1167
6
        if (ret_rowset == nullptr) {
1168
6
            ret_rowset = rs;
1169
6
            continue;
1170
6
        }
1171
1172
0
        if (rs->start_version() > ret_rowset->start_version()) {
1173
0
            ret_rowset = rs;
1174
0
        }
1175
0
    }
1176
1177
8
    if (ret_rowset != nullptr) {
1178
6
        is_base_rowset = ret_rowset->version().first < _cumulative_point;
1179
6
    }
1180
1181
8
    return ret_rowset;
1182
15
}
1183
1184
0
std::vector<RowsetSharedPtr> CloudTablet::pick_candidate_rowsets_to_base_compaction_unlocked() {
1185
0
    std::vector<RowsetSharedPtr> candidate_rowsets;
1186
0
    for (const auto& [version, rs] : _rs_version_map) {
1187
0
        if (version.first != 0 && version.first < _cumulative_point &&
1188
0
            (_alter_version == -1 || version.second <= _alter_version)) {
1189
0
            candidate_rowsets.push_back(rs);
1190
0
        }
1191
0
    }
1192
0
    std::sort(candidate_rowsets.begin(), candidate_rowsets.end(), Rowset::comparator);
1193
0
    return candidate_rowsets;
1194
0
}
1195
1196
0
std::vector<RowsetSharedPtr> CloudTablet::pick_candidate_rowsets_to_full_compaction_unlocked() {
1197
0
    std::vector<RowsetSharedPtr> candidate_rowsets;
1198
0
    for (auto& [v, rs] : _rs_version_map) {
1199
        // MUST NOT compact rowset [0-1] for some historical reasons (see cloud_schema_change)
1200
0
        if (v.first != 0) {
1201
0
            candidate_rowsets.push_back(rs);
1202
0
        }
1203
0
    }
1204
0
    std::sort(candidate_rowsets.begin(), candidate_rowsets.end(), Rowset::comparator);
1205
0
    return candidate_rowsets;
1206
0
}
1207
1208
0
CalcDeleteBitmapExecutor* CloudTablet::calc_delete_bitmap_executor() {
1209
0
    return _engine.calc_delete_bitmap_executor();
1210
0
}
1211
1212
Status CloudTablet::save_delete_bitmap(const TabletTxnInfo* txn_info, int64_t txn_id,
1213
                                       DeleteBitmapPtr delete_bitmap, RowsetWriter* rowset_writer,
1214
                                       const RowsetIdUnorderedSet& cur_rowset_ids, int64_t lock_id,
1215
0
                                       int64_t next_visible_version) {
1216
0
    RowsetSharedPtr rowset = txn_info->rowset;
1217
0
    int64_t cur_version = rowset->start_version();
1218
    // update delete bitmap info, in order to avoid recalculation when trying again
1219
0
    RETURN_IF_ERROR(_engine.txn_delete_bitmap_cache().update_tablet_txn_info(
1220
0
            txn_id, tablet_id(), delete_bitmap, cur_rowset_ids, PublishStatus::PREPARE));
1221
1222
0
    if (txn_info->partial_update_info && txn_info->partial_update_info->is_partial_update() &&
1223
0
        rowset_writer->num_rows() > 0) {
1224
0
        DBUG_EXECUTE_IF("CloudTablet::save_delete_bitmap.update_tmp_rowset.error", {
1225
0
            return Status::InternalError<false>("injected update_tmp_rowset error.");
1226
0
        });
1227
0
        const auto& rowset_meta = rowset->rowset_meta();
1228
0
        RETURN_IF_ERROR(_engine.meta_mgr().update_tmp_rowset(*rowset_meta, table_id()));
1229
0
    }
1230
1231
0
    RETURN_IF_ERROR(save_delete_bitmap_to_ms(cur_version, txn_id, delete_bitmap, lock_id,
1232
0
                                             next_visible_version, rowset));
1233
1234
    // store the delete bitmap with sentinel marks in txn_delete_bitmap_cache because if the txn is retried for some reason,
1235
    // it will use the delete bitmap from txn_delete_bitmap_cache when re-calculating the delete bitmap, during which it will do
1236
    // delete bitmap correctness check. If we store the new_delete_bitmap, the delete bitmap correctness check will fail
1237
0
    RETURN_IF_ERROR(_engine.txn_delete_bitmap_cache().update_tablet_txn_info(
1238
0
            txn_id, tablet_id(), delete_bitmap, cur_rowset_ids, PublishStatus::SUCCEED,
1239
0
            txn_info->publish_info));
1240
1241
0
    DBUG_EXECUTE_IF("CloudTablet::save_delete_bitmap.enable_sleep", {
1242
0
        auto sleep_sec = dp->param<int>("sleep", 5);
1243
0
        std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));
1244
0
    });
1245
1246
0
    DBUG_EXECUTE_IF("CloudTablet::save_delete_bitmap.injected_error", {
1247
0
        auto retry = dp->param<bool>("retry", false);
1248
0
        auto sleep_sec = dp->param<int>("sleep", 0);
1249
0
        std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));
1250
0
        if (retry) { // return DELETE_BITMAP_LOCK_ERROR to let it retry
1251
0
            return Status::Error<ErrorCode::DELETE_BITMAP_LOCK_ERROR>(
1252
0
                    "injected DELETE_BITMAP_LOCK_ERROR");
1253
0
        } else {
1254
0
            return Status::InternalError<false>("injected non-retryable error");
1255
0
        }
1256
0
    });
1257
1258
0
    return Status::OK();
1259
0
}
1260
1261
Status CloudTablet::save_delete_bitmap_to_ms(int64_t cur_version, int64_t txn_id,
1262
                                             DeleteBitmapPtr delete_bitmap, int64_t lock_id,
1263
0
                                             int64_t next_visible_version, RowsetSharedPtr rowset) {
1264
0
    DeleteBitmapPtr new_delete_bitmap = std::make_shared<DeleteBitmap>(tablet_id());
1265
0
    for (auto iter = delete_bitmap->delete_bitmap.begin();
1266
0
         iter != delete_bitmap->delete_bitmap.end(); ++iter) {
1267
        // skip sentinel mark, which is used for delete bitmap correctness check
1268
0
        if (std::get<1>(iter->first) != DeleteBitmap::INVALID_SEGMENT_ID) {
1269
0
            new_delete_bitmap->merge(
1270
0
                    {std::get<0>(iter->first), std::get<1>(iter->first), cur_version},
1271
0
                    iter->second);
1272
0
        }
1273
0
    }
1274
    // lock_id != -1 means this is in an explict txn
1275
0
    bool is_explicit_txn = (lock_id != -1);
1276
0
    auto ms_lock_id = !is_explicit_txn ? txn_id : lock_id;
1277
0
    std::optional<StorageResource> storage_resource;
1278
0
    auto storage_resource_result = rowset->rowset_meta()->remote_storage_resource();
1279
0
    if (storage_resource_result) {
1280
0
        storage_resource = *storage_resource_result.value();
1281
0
    }
1282
0
    RETURN_IF_ERROR(_engine.meta_mgr().update_delete_bitmap(
1283
0
            *this, ms_lock_id, LOAD_INITIATOR_ID, new_delete_bitmap.get(), new_delete_bitmap.get(),
1284
0
            rowset->rowset_id().to_string(), storage_resource,
1285
0
            config::delete_bitmap_store_write_version, table_id(), txn_id, is_explicit_txn,
1286
0
            next_visible_version));
1287
0
    return Status::OK();
1288
0
}
1289
1290
1
Versions CloudTablet::calc_missed_versions(int64_t spec_version, Versions existing_versions) const {
1291
1
    DCHECK(spec_version > 0) << "invalid spec_version: " << spec_version;
1292
1293
    // sort the existing versions in ascending order
1294
1
    std::sort(existing_versions.begin(), existing_versions.end(),
1295
3
              [](const Version& a, const Version& b) {
1296
                  // simple because 2 versions are certainly not overlapping
1297
3
                  return a.first < b.first;
1298
3
              });
1299
1300
    // From the first version(=0), find the missing version until spec_version
1301
1
    int64_t last_version = -1;
1302
1
    Versions missed_versions;
1303
4
    for (const Version& version : existing_versions) {
1304
4
        if (version.first > last_version + 1) {
1305
            // there is a hole between versions
1306
2
            missed_versions.emplace_back(last_version + 1, std::min(version.first, spec_version));
1307
2
        }
1308
4
        last_version = version.second;
1309
4
        if (last_version >= spec_version) {
1310
1
            break;
1311
1
        }
1312
4
    }
1313
1
    if (last_version < spec_version) {
1314
        // there is a hole between the last version and the specificed version.
1315
0
        missed_versions.emplace_back(last_version + 1, spec_version);
1316
0
    }
1317
1
    return missed_versions;
1318
1
}
1319
1320
Status CloudTablet::calc_delete_bitmap_for_compaction(
1321
        const std::vector<RowsetSharedPtr>& input_rowsets, const RowsetSharedPtr& output_rowset,
1322
        const RowIdConversion& rowid_conversion, ReaderType compaction_type, int64_t merged_rows,
1323
        int64_t filtered_rows, int64_t initiator, DeleteBitmapPtr& output_rowset_delete_bitmap,
1324
0
        bool allow_delete_in_cumu_compaction, int64_t& get_delete_bitmap_lock_start_time) {
1325
0
    output_rowset_delete_bitmap = std::make_shared<DeleteBitmap>(tablet_id());
1326
0
    std::unique_ptr<RowLocationSet> missed_rows;
1327
0
    if ((config::enable_missing_rows_correctness_check ||
1328
0
         config::enable_mow_compaction_correctness_check_core ||
1329
0
         config::enable_mow_compaction_correctness_check_fail) &&
1330
0
        !allow_delete_in_cumu_compaction &&
1331
0
        (compaction_type == ReaderType::READER_CUMULATIVE_COMPACTION ||
1332
0
         !config::enable_prune_delete_sign_when_base_compaction)) {
1333
        // also check duplicate key for base compaction when config::enable_prune_delete_sign_when_base_compaction==false
1334
0
        missed_rows = std::make_unique<RowLocationSet>();
1335
0
        LOG(INFO) << "RowLocation Set inited succ for tablet:" << tablet_id();
1336
0
    }
1337
1338
0
    std::unique_ptr<std::map<RowsetSharedPtr, RowLocationPairList>> location_map;
1339
0
    if (config::enable_rowid_conversion_correctness_check &&
1340
0
        tablet_schema()->cluster_key_uids().empty()) {
1341
0
        location_map = std::make_unique<std::map<RowsetSharedPtr, RowLocationPairList>>();
1342
0
        LOG(INFO) << "Location Map inited succ for tablet:" << tablet_id();
1343
0
    }
1344
1345
    // 1. calc delete bitmap for historical data
1346
0
    RETURN_IF_ERROR(_engine.meta_mgr().sync_tablet_rowsets(this));
1347
0
    Version version = max_version();
1348
0
    std::size_t missed_rows_size = 0;
1349
0
    calc_compaction_output_rowset_delete_bitmap(
1350
0
            input_rowsets, rowid_conversion, 0, version.second + 1, missed_rows.get(),
1351
0
            location_map.get(), tablet_meta()->delete_bitmap(), output_rowset_delete_bitmap.get());
1352
    // In cluster-key MOW compaction, rows are sorted by cluster key, so duplicate unique keys
1353
    // may be non-adjacent in merge order. Scan the output primary key index to delete older
1354
    // duplicate rows inside the output rowset.
1355
0
    if (!tablet_schema()->cluster_key_uids().empty()) {
1356
0
        RETURN_IF_ERROR(calc_compaction_output_rowset_internal_delete_bitmap(
1357
0
                input_rowsets, output_rowset, rowid_conversion, output_rowset_delete_bitmap.get()));
1358
0
    }
1359
0
    if (missed_rows) {
1360
0
        missed_rows_size = missed_rows->size();
1361
0
        if (!allow_delete_in_cumu_compaction) {
1362
0
            if ((compaction_type == ReaderType::READER_CUMULATIVE_COMPACTION ||
1363
0
                 !config::enable_prune_delete_sign_when_base_compaction) &&
1364
0
                tablet_state() == TABLET_RUNNING) {
1365
0
                if (merged_rows + filtered_rows >= 0 &&
1366
0
                    merged_rows + filtered_rows != missed_rows_size) {
1367
0
                    std::string err_msg = fmt::format(
1368
0
                            "cumulative compaction: the merged rows({}), the filtered rows({}) is "
1369
0
                            "not equal to missed rows({}) in rowid conversion, tablet_id: {}, "
1370
0
                            "table_id:{}",
1371
0
                            merged_rows, filtered_rows, missed_rows_size, tablet_id(), table_id());
1372
0
                    LOG(WARNING) << err_msg;
1373
0
                    if (config::enable_mow_compaction_correctness_check_core) {
1374
0
                        CHECK(false) << err_msg;
1375
0
                    } else if (config::enable_mow_compaction_correctness_check_fail) {
1376
0
                        return Status::InternalError<false>(err_msg);
1377
0
                    } else {
1378
0
                        DCHECK(false) << err_msg;
1379
0
                    }
1380
0
                }
1381
0
            }
1382
0
        }
1383
0
    }
1384
0
    if (location_map) {
1385
0
        RETURN_IF_ERROR(check_rowid_conversion(output_rowset, *location_map));
1386
0
        location_map->clear();
1387
0
    }
1388
1389
    // 2. calc delete bitmap for incremental data
1390
0
    int64_t t1 = MonotonicMicros();
1391
0
    RETURN_IF_ERROR(_engine.meta_mgr().get_delete_bitmap_update_lock(
1392
0
            *this, COMPACTION_DELETE_BITMAP_LOCK_ID, initiator));
1393
0
    int64_t t2 = MonotonicMicros();
1394
0
    if (compaction_type == ReaderType::READER_CUMULATIVE_COMPACTION) {
1395
0
        g_cu_compaction_get_delete_bitmap_lock_time_ms << (t2 - t1) / 1000;
1396
0
    } else if (compaction_type == ReaderType::READER_BASE_COMPACTION) {
1397
0
        g_base_compaction_get_delete_bitmap_lock_time_ms << (t2 - t1) / 1000;
1398
0
    }
1399
0
    get_delete_bitmap_lock_start_time = t2;
1400
0
    RETURN_IF_ERROR(_engine.meta_mgr().sync_tablet_rowsets(this));
1401
0
    int64_t t3 = MonotonicMicros();
1402
1403
0
    calc_compaction_output_rowset_delete_bitmap(
1404
0
            input_rowsets, rowid_conversion, version.second, UINT64_MAX, missed_rows.get(),
1405
0
            location_map.get(), tablet_meta()->delete_bitmap(), output_rowset_delete_bitmap.get());
1406
0
    int64_t t4 = MonotonicMicros();
1407
0
    if (location_map) {
1408
0
        RETURN_IF_ERROR(check_rowid_conversion(output_rowset, *location_map));
1409
0
    }
1410
0
    int64_t t5 = MonotonicMicros();
1411
1412
    // 3. store delete bitmap
1413
0
    DeleteBitmapPtr delete_bitmap_v2 = nullptr;
1414
0
    auto delete_bitmap_size = output_rowset_delete_bitmap->delete_bitmap.size();
1415
0
    auto store_version = config::delete_bitmap_store_write_version;
1416
0
    if (store_version == 2 || store_version == 3) {
1417
0
        delete_bitmap_v2 = std::make_shared<DeleteBitmap>(*output_rowset_delete_bitmap);
1418
0
        std::vector<std::pair<RowsetId, int64_t>> retained_rowsets_to_seg_num;
1419
0
        {
1420
0
            std::shared_lock rlock(get_header_lock());
1421
0
            for (const auto& [rowset_version, rowset_ptr] : rowset_map()) {
1422
0
                if (rowset_version.second < output_rowset->start_version()) {
1423
0
                    retained_rowsets_to_seg_num.emplace_back(
1424
0
                            std::make_pair(rowset_ptr->rowset_id(), rowset_ptr->num_segments()));
1425
0
                }
1426
0
            }
1427
0
        }
1428
0
        if (config::enable_agg_delta_delete_bitmap_for_store_v2) {
1429
0
            tablet_meta()->delete_bitmap().subset_and_agg(
1430
0
                    retained_rowsets_to_seg_num, output_rowset->start_version(),
1431
0
                    output_rowset->end_version(), delete_bitmap_v2.get());
1432
0
        } else {
1433
0
            tablet_meta()->delete_bitmap().subset(
1434
0
                    retained_rowsets_to_seg_num, output_rowset->start_version(),
1435
0
                    output_rowset->end_version(), delete_bitmap_v2.get());
1436
0
        }
1437
0
    }
1438
0
    std::optional<StorageResource> storage_resource;
1439
0
    auto storage_resource_result = output_rowset->rowset_meta()->remote_storage_resource();
1440
0
    if (storage_resource_result) {
1441
0
        storage_resource = *storage_resource_result.value();
1442
0
    }
1443
0
    auto st = _engine.meta_mgr().update_delete_bitmap(
1444
0
            *this, -1, initiator, output_rowset_delete_bitmap.get(), delete_bitmap_v2.get(),
1445
0
            output_rowset->rowset_id().to_string(), storage_resource, store_version, table_id());
1446
0
    int64_t t6 = MonotonicMicros();
1447
0
    LOG(INFO) << "calc_delete_bitmap_for_compaction, tablet_id=" << tablet_id()
1448
0
              << ", get lock cost " << (t2 - t1) << " us, sync rowsets cost " << (t3 - t2)
1449
0
              << " us, calc delete bitmap cost " << (t4 - t3) << " us, check rowid conversion cost "
1450
0
              << (t5 - t4) << " us, store delete bitmap cost " << (t6 - t5)
1451
0
              << " us, st=" << st.to_string() << ". store_version=" << store_version
1452
0
              << ", calculated delete bitmap size=" << delete_bitmap_size
1453
0
              << ", update delete bitmap size="
1454
0
              << output_rowset_delete_bitmap->delete_bitmap.size();
1455
0
    return st;
1456
0
}
1457
1458
void CloudTablet::agg_delete_bitmap_for_compaction(
1459
        int64_t start_version, int64_t end_version, const std::vector<RowsetSharedPtr>& pre_rowsets,
1460
        DeleteBitmapPtr& new_delete_bitmap, std::map<std::string, int64_t>& pre_rowset_to_versions,
1461
2
        PreRowsetDeleteBitmapStats* pre_rowset_delete_bitmap_stats) {
1462
2
    auto& delete_bitmap = tablet_meta()->delete_bitmap();
1463
6
    for (const auto& rowset : pre_rowsets) {
1464
6
        if (pre_rowset_delete_bitmap_stats != nullptr) {
1465
3
            auto& rowset_delete_bitmap_stats =
1466
3
                    (*pre_rowset_delete_bitmap_stats)[rowset->rowset_id().to_string()];
1467
3
            std::shared_lock lock(delete_bitmap.lock);
1468
3
            const auto bitmap_start_version = static_cast<DeleteBitmap::Version>(start_version);
1469
3
            const auto bitmap_end_version = static_cast<DeleteBitmap::Version>(end_version);
1470
7
            for (uint32_t seg_id = 0; seg_id < rowset->num_segments(); ++seg_id) {
1471
4
                DeleteBitmap::BitmapKey segment_start {rowset->rowset_id(), seg_id,
1472
4
                                                       bitmap_start_version};
1473
4
                for (auto it = delete_bitmap.delete_bitmap.lower_bound(segment_start);
1474
8
                     it != delete_bitmap.delete_bitmap.end(); ++it) {
1475
6
                    const auto& [key, bitmap] = *it;
1476
6
                    if (std::get<0>(key) != rowset->rowset_id() || std::get<1>(key) != seg_id ||
1477
6
                        std::get<2>(key) >= bitmap_end_version) {
1478
2
                        break;
1479
2
                    }
1480
4
                    rowset_delete_bitmap_stats.emplace_back(seg_id, std::get<2>(key),
1481
4
                                                            bitmap.getSizeInBytes());
1482
4
                }
1483
4
            }
1484
3
        }
1485
14
        for (uint32_t seg_id = 0; seg_id < rowset->num_segments(); ++seg_id) {
1486
8
            auto d = delete_bitmap.get_agg_without_cache({rowset->rowset_id(), seg_id, end_version},
1487
8
                                                         start_version);
1488
8
            if (d->isEmpty()) {
1489
2
                continue;
1490
2
            }
1491
6
            VLOG_DEBUG << "agg delete bitmap for tablet_id=" << tablet_id()
1492
0
                       << ", rowset_id=" << rowset->rowset_id() << ", seg_id=" << seg_id
1493
0
                       << ", rowset_version=" << rowset->version().to_string()
1494
0
                       << ". compaction start_version=" << start_version
1495
0
                       << ", end_version=" << end_version
1496
0
                       << ". delete_bitmap cardinality=" << d->cardinality();
1497
6
            DeleteBitmap::BitmapKey end_key {rowset->rowset_id(), seg_id, end_version};
1498
6
            new_delete_bitmap->set(end_key, *d);
1499
6
            pre_rowset_to_versions[rowset->rowset_id().to_string()] = rowset->version().second;
1500
6
        }
1501
6
    }
1502
2
}
1503
1504
10
Status CloudTablet::sync_meta() {
1505
    // Keep tablet metadata refresh serialized with rowset sync and apply local metadata updates
1506
    // under _meta_lock. The lock order must stay _sync_meta_lock -> _meta_lock.
1507
10
    std::unique_lock lock(_sync_meta_lock);
1508
1509
10
    TabletMetaSharedPtr tablet_meta;
1510
10
    auto st = _engine.meta_mgr().get_tablet_meta(tablet_id(), &tablet_meta);
1511
10
    if (!st.ok()) {
1512
1
        if (st.is<ErrorCode::NOT_FOUND>()) {
1513
0
            clear_cache();
1514
0
        }
1515
1
        return st;
1516
1
    }
1517
1518
9
    auto new_compaction_policy = tablet_meta->compaction_policy();
1519
9
    auto new_ttl_seconds = tablet_meta->ttl_seconds();
1520
9
    auto new_time_series_compaction_goal_size_mbytes =
1521
9
            tablet_meta->time_series_compaction_goal_size_mbytes();
1522
9
    auto new_time_series_compaction_file_count_threshold =
1523
9
            tablet_meta->time_series_compaction_file_count_threshold();
1524
9
    auto new_time_series_compaction_time_threshold_seconds =
1525
9
            tablet_meta->time_series_compaction_time_threshold_seconds();
1526
9
    auto new_time_series_compaction_empty_rowsets_threshold =
1527
9
            tablet_meta->time_series_compaction_empty_rowsets_threshold();
1528
9
    auto new_time_series_compaction_level_threshold =
1529
9
            tablet_meta->time_series_compaction_level_threshold();
1530
9
    auto new_disable_auto_compaction = tablet_meta->tablet_schema()->disable_auto_compaction();
1531
9
    auto new_vertical_compaction_num_columns_per_group =
1532
9
            tablet_meta->vertical_compaction_num_columns_per_group();
1533
1534
9
    {
1535
9
        std::unique_lock wlock(_meta_lock);
1536
9
        if (_tablet_meta->compaction_policy() != new_compaction_policy) {
1537
4
            _tablet_meta->set_compaction_policy(new_compaction_policy);
1538
4
        }
1539
9
        if (_tablet_meta->ttl_seconds() != new_ttl_seconds) {
1540
1
            _tablet_meta->set_ttl_seconds(new_ttl_seconds);
1541
1
        }
1542
9
        if (_tablet_meta->time_series_compaction_goal_size_mbytes() !=
1543
9
            new_time_series_compaction_goal_size_mbytes) {
1544
1
            _tablet_meta->set_time_series_compaction_goal_size_mbytes(
1545
1
                    new_time_series_compaction_goal_size_mbytes);
1546
1
        }
1547
9
        if (_tablet_meta->time_series_compaction_file_count_threshold() !=
1548
9
            new_time_series_compaction_file_count_threshold) {
1549
1
            _tablet_meta->set_time_series_compaction_file_count_threshold(
1550
1
                    new_time_series_compaction_file_count_threshold);
1551
1
        }
1552
9
        if (_tablet_meta->time_series_compaction_time_threshold_seconds() !=
1553
9
            new_time_series_compaction_time_threshold_seconds) {
1554
1
            _tablet_meta->set_time_series_compaction_time_threshold_seconds(
1555
1
                    new_time_series_compaction_time_threshold_seconds);
1556
1
        }
1557
9
        if (_tablet_meta->time_series_compaction_empty_rowsets_threshold() !=
1558
9
            new_time_series_compaction_empty_rowsets_threshold) {
1559
1
            _tablet_meta->set_time_series_compaction_empty_rowsets_threshold(
1560
1
                    new_time_series_compaction_empty_rowsets_threshold);
1561
1
        }
1562
9
        if (_tablet_meta->time_series_compaction_level_threshold() !=
1563
9
            new_time_series_compaction_level_threshold) {
1564
1
            _tablet_meta->set_time_series_compaction_level_threshold(
1565
1
                    new_time_series_compaction_level_threshold);
1566
1
        }
1567
9
        if (_tablet_meta->tablet_schema()->disable_auto_compaction() !=
1568
9
            new_disable_auto_compaction) {
1569
5
            _tablet_meta->mutable_tablet_schema()->set_disable_auto_compaction(
1570
5
                    new_disable_auto_compaction);
1571
5
        }
1572
9
        if (_tablet_meta->vertical_compaction_num_columns_per_group() !=
1573
9
            new_vertical_compaction_num_columns_per_group) {
1574
1
            _tablet_meta->set_vertical_compaction_num_columns_per_group(
1575
1
                    new_vertical_compaction_num_columns_per_group);
1576
1
        }
1577
9
    }
1578
1579
9
    last_sync_tablet_meta_time_s = ::time(nullptr);
1580
9
    return Status::OK();
1581
10
}
1582
1583
0
void CloudTablet::build_tablet_report_info(TTabletInfo* tablet_info) {
1584
0
    std::shared_lock rdlock(_meta_lock);
1585
0
    tablet_info->__set_total_version_count(_tablet_meta->version_count());
1586
0
    tablet_info->__set_tablet_id(_tablet_meta->tablet_id());
1587
    // Currently, this information will not be used by the cloud report,
1588
    // but it may be used in the future.
1589
0
}
1590
1591
Status CloudTablet::check_delete_bitmap_cache(int64_t txn_id,
1592
0
                                              DeleteBitmap* expected_delete_bitmap) {
1593
0
    DeleteBitmapPtr cached_delete_bitmap;
1594
0
    CloudStorageEngine& engine = ExecEnv::GetInstance()->storage_engine().to_cloud();
1595
0
    Status st = engine.txn_delete_bitmap_cache().get_delete_bitmap(
1596
0
            txn_id, tablet_id(), &cached_delete_bitmap, nullptr, nullptr);
1597
0
    if (st.ok()) {
1598
0
        bool res = (expected_delete_bitmap->cardinality() == cached_delete_bitmap->cardinality());
1599
0
        auto msg = fmt::format(
1600
0
                "delete bitmap cache check failed, cur_cardinality={}, cached_cardinality={}"
1601
0
                "txn_id={}, tablet_id={}",
1602
0
                expected_delete_bitmap->cardinality(), cached_delete_bitmap->cardinality(), txn_id,
1603
0
                tablet_id());
1604
0
        if (!res) {
1605
0
            DCHECK(res) << msg;
1606
0
            return Status::InternalError<false>(msg);
1607
0
        }
1608
0
    }
1609
0
    return Status::OK();
1610
0
}
1611
1612
437
WarmUpState CloudTablet::get_rowset_warmup_state(RowsetId rowset_id) {
1613
437
    std::shared_lock rlock(_meta_lock);
1614
437
    auto it = _rowset_warm_up_states.find(rowset_id);
1615
437
    if (it == _rowset_warm_up_states.end()) {
1616
30
        return {.trigger_source = WarmUpTriggerSource::NONE, .progress = WarmUpProgress::NONE};
1617
30
    }
1618
407
    return it->second.state;
1619
437
}
1620
1621
std::optional<uint64_t> CloudTablet::add_rowset_warmup_state(
1622
        const RowsetMeta& rowset, WarmUpTriggerSource source,
1623
376
        std::chrono::steady_clock::time_point start_tp) {
1624
376
    std::lock_guard wlock(_meta_lock);
1625
376
    return add_rowset_warmup_state_unlocked(rowset, source, start_tp);
1626
376
}
1627
1628
bool CloudTablet::update_rowset_warmup_state_inverted_idx_num(WarmUpTriggerSource source,
1629
                                                              RowsetId rowset_id, int64_t delta,
1630
7
                                                              uint64_t generation) {
1631
7
    std::lock_guard wlock(_meta_lock);
1632
7
    return update_rowset_warmup_state_inverted_idx_num_unlocked(source, rowset_id, delta,
1633
7
                                                                generation);
1634
7
}
1635
1636
bool CloudTablet::update_rowset_warmup_state_inverted_idx_num_unlocked(WarmUpTriggerSource source,
1637
                                                                       RowsetId rowset_id,
1638
                                                                       int64_t delta,
1639
7
                                                                       uint64_t generation) {
1640
7
    auto it = _rowset_warm_up_states.find(rowset_id);
1641
7
    if (it == _rowset_warm_up_states.end()) {
1642
0
        return false;
1643
0
    }
1644
    // A JOB may replace the attempt while its previous producer is still registering indexes.
1645
7
    if (it->second.generation != generation || it->second.state.trigger_source != source) {
1646
3
        return false;
1647
3
    }
1648
4
    it->second.num_inverted_idx += delta;
1649
4
    return true;
1650
7
}
1651
1652
std::optional<uint64_t> CloudTablet::add_rowset_warmup_state_unlocked(
1653
        const RowsetMeta& rowset, WarmUpTriggerSource source,
1654
376
        std::chrono::steady_clock::time_point start_tp) {
1655
376
    auto rowset_id = rowset.rowset_id();
1656
1657
    // Check if rowset already has warmup state
1658
376
    if (_rowset_warm_up_states.contains(rowset_id)) {
1659
161
        auto existing_state = _rowset_warm_up_states[rowset_id].state;
1660
1661
        // For job-triggered warmup (one-time and periodic warmup), allow it to proceed
1662
        // except when there's already another job-triggered warmup in progress
1663
161
        if (source == WarmUpTriggerSource::JOB) {
1664
156
            if (existing_state.trigger_source == WarmUpTriggerSource::JOB &&
1665
156
                existing_state.progress == WarmUpProgress::DOING) {
1666
                // Same job type already in progress, skip to avoid duplicate warmup
1667
1
                return std::nullopt;
1668
1
            }
1669
156
        } else {
1670
            // For non-job warmup (EVENT_DRIVEN, SYNC_ROWSET), skip if any warmup exists
1671
5
            return std::nullopt;
1672
5
        }
1673
161
    }
1674
1675
370
    if (source == WarmUpTriggerSource::JOB) {
1676
210
        g_file_cache_warm_up_rowset_triggered_by_job_num << 1;
1677
210
    } else if (source == WarmUpTriggerSource::SYNC_ROWSET) {
1678
6
        g_file_cache_warm_up_rowset_triggered_by_sync_rowset_num << 1;
1679
154
    } else if (source == WarmUpTriggerSource::EVENT_DRIVEN) {
1680
154
        g_file_cache_warm_up_rowset_triggered_by_event_driven_num << 1;
1681
154
    }
1682
370
    _rowset_warm_up_states[rowset_id] = {
1683
370
            .state = {.trigger_source = source,
1684
370
                      .progress = (rowset.num_segments() == 0 ? WarmUpProgress::DONE
1685
370
                                                              : WarmUpProgress::DOING)},
1686
370
            .generation = ++_next_warmup_generation,
1687
370
            .num_segments = rowset.num_segments(),
1688
370
            .start_tp = start_tp};
1689
370
    return _next_warmup_generation;
1690
376
}
1691
1692
size_t CloudTablet::gc_rowset_warmup_states(std::chrono::seconds grace_period,
1693
409
                                            std::chrono::steady_clock::time_point now) {
1694
409
    std::unique_lock lock(_meta_lock);
1695
    // Rowset GC can leave an empty map with a large bucket array.
1696
409
    if (_rowset_warm_up_states.empty()) {
1697
97
        decltype(_rowset_warm_up_states)().swap(_rowset_warm_up_states);
1698
97
        return 0;
1699
97
    }
1700
1701
312
    std::unordered_set<RowsetId> live_rowset_ids;
1702
312
    live_rowset_ids.reserve(_rs_version_map.size() + _stale_rs_version_map.size());
1703
312
    traverse_rowsets_unlocked(
1704
312
            [&](const RowsetSharedPtr& rs) { live_rowset_ids.insert(rs->rowset_id()); }, true);
1705
1706
312
    size_t removed = 0;
1707
759
    for (auto it = _rowset_warm_up_states.begin(); it != _rowset_warm_up_states.end();) {
1708
447
        const auto& info = it->second;
1709
        // R1 may be warmed up, compacted into R2, and never synchronized locally. Keep
1710
        // recent attempts for deduplication and DOING attempts for cache-aware queries.
1711
        // Stale rowsets can still be selected by those queries and must also be retained.
1712
447
        if (info.state.progress == WarmUpProgress::DONE && info.has_finished() &&
1713
447
            now - info.start_tp >= grace_period && !live_rowset_ids.contains(it->first)) {
1714
185
            it = _rowset_warm_up_states.erase(it);
1715
185
            ++removed;
1716
262
        } else {
1717
262
            ++it;
1718
262
        }
1719
447
    }
1720
1721
    // Erasing nodes does not release the bucket array. Shrink only at a low watermark
1722
    // so normal insertion/GC cycles do not repeatedly rebuild the table.
1723
312
    if (_rowset_warm_up_states.empty()) {
1724
54
        decltype(_rowset_warm_up_states)().swap(_rowset_warm_up_states);
1725
258
    } else if (_rowset_warm_up_states.size() < _rowset_warm_up_states.bucket_count() / 4) {
1726
42
        _rowset_warm_up_states.rehash(0);
1727
42
    }
1728
312
    return removed;
1729
409
}
1730
1731
423
void CloudTablet::RowsetWarmUpInfo::update_state() {
1732
423
    if (has_finished()) {
1733
212
        g_file_cache_warm_up_rowset_complete_num << 1;
1734
212
        auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(
1735
212
                            std::chrono::steady_clock::now() - start_tp)
1736
212
                            .count();
1737
212
        g_file_cache_warm_up_rowset_all_segments_latency << cost;
1738
212
        state.progress = WarmUpProgress::DONE;
1739
212
    }
1740
423
}
1741
1742
WarmUpState CloudTablet::complete_rowset_segment_warmup(WarmUpTriggerSource trigger_source,
1743
                                                        RowsetId rowset_id, Status status,
1744
                                                        int64_t segment_num,
1745
                                                        int64_t inverted_idx_num,
1746
427
                                                        uint64_t generation) {
1747
427
    std::lock_guard wlock(_meta_lock);
1748
427
    auto it = _rowset_warm_up_states.find(rowset_id);
1749
    // GC permits a new attempt for the same RowsetId. Late callbacks from the old
1750
    // attempt must not advance the new attempt, even when their sources match.
1751
427
    if (it == _rowset_warm_up_states.end() || it->second.generation != generation) {
1752
2
        return {.trigger_source = WarmUpTriggerSource::NONE, .progress = WarmUpProgress::NONE};
1753
2
    }
1754
425
    auto& warmup_info = it->second;
1755
425
    if (warmup_info.state.trigger_source != trigger_source) {
1756
        // Only the same trigger source can update the state
1757
2
        return warmup_info.state;
1758
2
    }
1759
423
    VLOG_DEBUG << "complete rowset segment warmup for rowset " << rowset_id << ", " << status;
1760
423
    if (segment_num > 0) {
1761
421
        g_file_cache_warm_up_segment_complete_num << segment_num;
1762
421
        if (!status.ok()) {
1763
1
            g_file_cache_warm_up_segment_failed_num << segment_num;
1764
1
        }
1765
421
    }
1766
423
    if (inverted_idx_num > 0) {
1767
2
        g_file_cache_warm_up_inverted_idx_complete_num << inverted_idx_num;
1768
2
        if (!status.ok()) {
1769
0
            g_file_cache_warm_up_inverted_idx_failed_num << inverted_idx_num;
1770
0
        }
1771
2
    }
1772
423
    warmup_info.done(segment_num, inverted_idx_num);
1773
423
    return warmup_info.state;
1774
425
}
1775
1776
224
bool CloudTablet::is_rowset_warmed_up(const RowsetId& rowset_id) const {
1777
224
    auto it = _rowset_warm_up_states.find(rowset_id);
1778
224
    if (it == _rowset_warm_up_states.end()) {
1779
        // The rowset is not in warmup state, which means the rowset has never been warmed up.
1780
        // This may happen when the upstream BE tried to warm up rowsets on this BE but this BE
1781
        // was restarting so the warmup failed, and _rowset_warm_up_states has no entry for it.
1782
        //
1783
        // Normally the startup_timepoint check in rowset_is_warmed_up_unlocked() would filter out
1784
        // such rowsets (visible_timestamp < startup_timepoint → assumed warmed up). However,
1785
        // compaction-produced rowsets have their visible_timestamp set at rowset builder
1786
        // initialization time rather than the final transaction commit time on meta-service,
1787
        // so their visible_timestamp can be earlier than startup_timepoint, causing the
1788
        // startup_timepoint check to NOT filter them out and reaching here with no warmup entry.
1789
        //
1790
        // If such a rowset is before the cumulative compaction point and base compaction never
1791
        // happens, returning false here would cause the version path algorithm to exclude it,
1792
        // leading to a persistently low path_max_version. With continuous upstream ingestion,
1793
        // the freshness tolerance fallback check would keep triggering, making every query on
1794
        // this tablet fall back to reading all data from remote storage.
1795
        //
1796
        // Returning true (optimistically treating it as warmed up) allows the version path to
1797
        // include it. On cache miss the data is transparently read from remote storage per-segment
1798
        // and cached locally in 1MB blocks, so the problem self-heals through subsequent queries.
1799
8
        g_rowset_warmup_state_missing_count << 1;
1800
8
        LOG_EVERY_N(WARNING, 100) << fmt::format(
1801
1
                "rowset warmup state missing, considering it as warmed up. tablet_id={}, "
1802
1
                "rowset_id={}",
1803
1
                tablet_id(), rowset_id.to_string());
1804
8
        return true;
1805
8
    }
1806
216
    return it->second.state.progress == WarmUpProgress::DONE;
1807
224
}
1808
1809
675
void CloudTablet::add_warmed_up_rowset(const RowsetId& rowset_id) {
1810
675
    _rowset_warm_up_states[rowset_id] = {
1811
675
            .state = {.trigger_source = WarmUpTriggerSource::SYNC_ROWSET,
1812
675
                      .progress = WarmUpProgress::DONE},
1813
675
            .num_segments = 1,
1814
675
            .start_tp = std::chrono::steady_clock::now()};
1815
675
}
1816
1817
93
void CloudTablet::add_not_warmed_up_rowset(const RowsetId& rowset_id) {
1818
93
    _rowset_warm_up_states[rowset_id] = {
1819
93
            .state = {.trigger_source = WarmUpTriggerSource::SYNC_ROWSET,
1820
93
                      .progress = WarmUpProgress::DOING},
1821
93
            .num_segments = 1,
1822
93
            .start_tp = std::chrono::steady_clock::now()};
1823
93
}
1824
1825
bool CloudTablet::_check_rowset_should_be_visible_but_not_warmed_up(
1826
        const RowsetMetaSharedPtr& rs_meta, int64_t path_max_version,
1827
417
        std::chrono::system_clock::time_point freshness_limit_tp) const {
1828
417
    if (rs_meta->version() == Version {0, 1}) {
1829
        // skip rowset[0-1]
1830
22
        return false;
1831
22
    }
1832
395
    bool ret = rs_meta->start_version() > path_max_version &&
1833
395
               rs_meta->visible_timestamp() < freshness_limit_tp;
1834
395
    if (ret && config::read_cluster_cache_opt_verbose_log) {
1835
5
        using namespace std::chrono;
1836
5
        std::time_t t1 = system_clock::to_time_t(rs_meta->visible_timestamp());
1837
5
        std::tm tm1 = *std::localtime(&t1);
1838
5
        std::ostringstream oss1;
1839
5
        oss1 << std::put_time(&tm1, "%Y-%m-%d %H:%M:%S");
1840
1841
5
        std::time_t t2 = system_clock::to_time_t(freshness_limit_tp);
1842
5
        std::tm tm2 = *std::localtime(&t2);
1843
5
        std::ostringstream oss2;
1844
5
        oss2 << std::put_time(&tm2, "%Y-%m-%d %H:%M:%S");
1845
5
        LOG_INFO(
1846
5
                "[verbose] CloudTablet::capture_rs_readers_with_freshness_tolerance, "
1847
5
                "find a rowset which should be visible but not warmed up, tablet_id={}, "
1848
5
                "path_max_version={}, rowset_id={}, version={}, visible_time={}, "
1849
5
                "freshness_limit={}, version_graph={}, rowset_warmup_digest={}",
1850
5
                tablet_id(), path_max_version, rs_meta->rowset_id().to_string(),
1851
5
                rs_meta->version().to_string(), oss1.str(), oss2.str(),
1852
5
                _timestamped_version_tracker.debug_string(), rowset_warmup_digest());
1853
5
    }
1854
395
    return ret;
1855
417
}
1856
1857
void CloudTablet::_submit_segment_download_task(const RowsetSharedPtr& rs,
1858
                                                const StorageResource* storage_resource, int seg_id,
1859
0
                                                int64_t expiration_time, uint64_t generation) {
1860
    // clang-format off
1861
0
    const auto& rowset_meta = rs->rowset_meta();
1862
0
    auto self = std::dynamic_pointer_cast<CloudTablet>(shared_from_this());
1863
    // Use rowset_meta->fs() instead of storage_resource->fs to support packed file.
1864
    // RowsetMeta::fs() wraps the underlying FileSystem with PackedFileSystem when
1865
    // packed_slice_locations is not empty, which correctly maps segment file paths
1866
    // to their actual locations within packed files.
1867
0
    auto file_system = rowset_meta->fs();
1868
0
    if (!file_system) {
1869
0
        LOG(WARNING) << "failed to get file system for tablet_id=" << _tablet_meta->tablet_id()
1870
0
                     << ", rowset_id=" << rowset_meta->rowset_id();
1871
0
        return;
1872
0
    }
1873
0
    _engine.file_cache_block_downloader().submit_download_task(io::DownloadFileMeta {
1874
0
            .path = storage_resource->remote_segment_path(*rowset_meta, seg_id),
1875
0
            .file_size = rs->rowset_meta()->segment_file_size(seg_id),
1876
0
            .file_system = file_system,
1877
0
            .ctx = {
1878
0
                    .expiration_time = expiration_time,
1879
0
                    .is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
1880
0
                    .is_warmup = true
1881
0
            },
1882
0
            .download_done {[=](Status st) {
1883
0
                DBUG_EXECUTE_IF("CloudTablet::add_rowsets.download_data.callback.block_compaction_rowset", {
1884
0
                            if (rs->version().second > rs->version().first) {
1885
0
                                auto sleep_time = dp->param<int>("sleep", 3);
1886
0
                                LOG_INFO(
1887
0
                                        "[verbose] block download for rowset={}, "
1888
0
                                        "version={}, sleep={}",
1889
0
                                        rs->rowset_id().to_string(),
1890
0
                                        rs->version().to_string(), sleep_time);
1891
0
                                std::this_thread::sleep_for(
1892
0
                                        std::chrono::seconds(sleep_time));
1893
0
                            }
1894
0
                });
1895
0
                self->complete_rowset_segment_warmup(WarmUpTriggerSource::SYNC_ROWSET, rowset_meta->rowset_id(), st, 1, 0, generation);
1896
0
                if (!st) {
1897
0
                    LOG_WARNING("add rowset warm up error ").error(st);
1898
0
                }
1899
0
            }},
1900
0
            .tablet_id = _tablet_meta->tablet_id(),
1901
0
    });
1902
    // clang-format on
1903
0
}
1904
1905
void CloudTablet::_submit_inverted_index_download_task(const RowsetSharedPtr& rs,
1906
                                                       const StorageResource* storage_resource,
1907
                                                       const io::Path& idx_path, int64_t idx_size,
1908
                                                       int64_t expiration_time,
1909
0
                                                       uint64_t generation) {
1910
    // clang-format off
1911
0
    const auto& rowset_meta = rs->rowset_meta();
1912
0
    auto self = std::dynamic_pointer_cast<CloudTablet>(shared_from_this());
1913
    // Use rowset_meta->fs() instead of storage_resource->fs to support packed file for idx files.
1914
0
    auto file_system = rowset_meta->fs();
1915
0
    if (!file_system) {
1916
0
        LOG(WARNING) << "failed to get file system for tablet_id=" << _tablet_meta->tablet_id()
1917
0
                     << ", rowset_id=" << rowset_meta->rowset_id();
1918
0
        return;
1919
0
    }
1920
0
    io::DownloadFileMeta meta {
1921
0
            .path = idx_path,
1922
0
            .file_size = idx_size,
1923
0
            .file_system = file_system,
1924
0
            .ctx = {
1925
0
                    .expiration_time = expiration_time,
1926
0
                    .is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
1927
0
                    .is_warmup = true
1928
0
            },
1929
0
            .download_done {[=](Status st) {
1930
0
                DBUG_EXECUTE_IF("CloudTablet::add_rowsets.download_idx.callback.block", {
1931
0
                    auto sleep_time = dp->param<int>("sleep", 3);
1932
0
                    LOG_INFO(
1933
0
                            "[verbose] block download for "
1934
0
                            "rowset={}, inverted_idx_file={}, "
1935
0
                            "sleep={}",
1936
0
                            rs->rowset_id().to_string(), idx_path.string(), sleep_time);
1937
0
                    std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
1938
0
                });
1939
0
                self->complete_rowset_segment_warmup(WarmUpTriggerSource::SYNC_ROWSET, rowset_meta->rowset_id(), st, 0, 1, generation);
1940
0
                if (!st) {
1941
0
                    LOG_WARNING("add rowset warm up error ").error(st);
1942
0
                }
1943
0
            }},
1944
0
            .tablet_id = _tablet_meta->tablet_id(),
1945
0
    };
1946
0
    self->update_rowset_warmup_state_inverted_idx_num_unlocked(WarmUpTriggerSource::SYNC_ROWSET, rowset_meta->rowset_id(), 1, generation);
1947
0
    _engine.file_cache_block_downloader().submit_download_task(std::move(meta));
1948
0
    g_file_cache_cloud_tablet_submitted_index_num << 1;
1949
0
    g_file_cache_cloud_tablet_submitted_index_size << idx_size;
1950
    // clang-format on
1951
0
}
1952
1953
void CloudTablet::_add_rowsets_directly(std::vector<RowsetSharedPtr>& rowsets,
1954
346
                                        bool warmup_delta_data) {
1955
346
#ifdef BE_TEST
1956
346
    warmup_delta_data = false;
1957
346
#endif
1958
1.00k
    for (auto& rs : rowsets) {
1959
1.00k
        if (warmup_delta_data) {
1960
            // Pre-set encryption algorithm to avoid re-entrant get_tablet() call
1961
            // inside RowsetMeta::fs() which causes SingleFlight deadlock when the
1962
            // tablet is not yet cached (during initial load_tablet).
1963
0
            rs->rowset_meta()->set_encryption_algorithm(_tablet_meta->encryption_algorithm());
1964
0
            std::optional<uint64_t> generation;
1965
            // Warmup rowset data in background
1966
0
            for (int seg_id = 0; seg_id < rs->num_segments(); ++seg_id) {
1967
0
                const auto& rowset_meta = rs->rowset_meta();
1968
0
                constexpr int64_t interval = 600; // 10 mins
1969
                // When BE restart and receive the `load_sync` rpc, it will sync all historical rowsets first time.
1970
                // So we need to filter out the old rowsets avoid to download the whole table.
1971
0
                if (::time(nullptr) - rowset_meta->newest_write_timestamp() >= interval) {
1972
0
                    continue;
1973
0
                }
1974
1975
0
                auto storage_resource = rowset_meta->remote_storage_resource();
1976
0
                if (!storage_resource) {
1977
0
                    LOG(WARNING) << storage_resource.error();
1978
0
                    continue;
1979
0
                }
1980
1981
0
                int64_t expiration_time = _tablet_meta->file_cache_ttl_expiration_time();
1982
0
                g_file_cache_cloud_tablet_submitted_segment_num << 1;
1983
0
                if (rs->rowset_meta()->segment_file_size(seg_id) > 0) {
1984
0
                    g_file_cache_cloud_tablet_submitted_segment_size
1985
0
                            << rs->rowset_meta()->segment_file_size(seg_id);
1986
0
                }
1987
0
                if (!generation) {
1988
0
                    VLOG_DEBUG << "warm up rowset " << rs->version() << "(" << rs->rowset_id()
1989
0
                               << ") triggerd by sync rowset";
1990
0
                    generation = add_rowset_warmup_state_unlocked(*(rs->rowset_meta()),
1991
0
                                                                  WarmUpTriggerSource::SYNC_ROWSET);
1992
0
                    if (!generation) {
1993
0
                        LOG(INFO) << "found duplicate warmup task for rowset " << rs->rowset_id()
1994
0
                                  << ", skip it";
1995
0
                        break;
1996
0
                    }
1997
0
                }
1998
1999
0
                if (!config::file_cache_enable_only_warm_up_idx) {
2000
0
                    _submit_segment_download_task(rs, storage_resource.value(), seg_id,
2001
0
                                                  expiration_time, *generation);
2002
0
                }
2003
2004
0
                auto schema_ptr = rowset_meta->tablet_schema();
2005
0
                auto idx_version = schema_ptr->get_inverted_index_storage_format();
2006
0
                if (idx_version == InvertedIndexStorageFormatPB::V1) {
2007
0
                    std::unordered_map<int64_t, int64_t> index_size_map;
2008
0
                    auto&& inverted_index_info = rowset_meta->inverted_index_file_info(seg_id);
2009
0
                    for (const auto& info : inverted_index_info.index_info()) {
2010
0
                        if (info.index_file_size() != -1) {
2011
0
                            index_size_map[info.index_id()] = info.index_file_size();
2012
0
                        } else {
2013
0
                            VLOG_DEBUG << "Invalid index_file_size for segment_id " << seg_id
2014
0
                                       << ", index_id " << info.index_id();
2015
0
                        }
2016
0
                    }
2017
0
                    for (const auto& index : schema_ptr->inverted_indexes()) {
2018
0
                        auto idx_path = storage_resource.value()->remote_idx_v1_path(
2019
0
                                *rowset_meta, seg_id, index->index_id(), index->get_index_suffix());
2020
0
                        _submit_inverted_index_download_task(rs, storage_resource.value(), idx_path,
2021
0
                                                             index_size_map[index->index_id()],
2022
0
                                                             expiration_time, *generation);
2023
0
                    }
2024
0
                } else {
2025
0
                    if (schema_ptr->has_inverted_or_ann_index()) {
2026
0
                        auto&& inverted_index_info = rowset_meta->inverted_index_file_info(seg_id);
2027
0
                        int64_t idx_size = 0;
2028
0
                        if (inverted_index_info.has_index_size()) {
2029
0
                            idx_size = inverted_index_info.index_size();
2030
0
                        } else {
2031
0
                            VLOG_DEBUG << "index_size is not set for segment " << seg_id;
2032
0
                        }
2033
0
                        auto idx_path =
2034
0
                                storage_resource.value()->remote_idx_v2_path(*rowset_meta, seg_id);
2035
0
                        _submit_inverted_index_download_task(rs, storage_resource.value(), idx_path,
2036
0
                                                             idx_size, expiration_time,
2037
0
                                                             *generation);
2038
0
                    }
2039
0
                }
2040
0
            }
2041
0
        }
2042
1.00k
        _rs_version_map.emplace(rs->version(), rs);
2043
1.00k
        _timestamped_version_tracker.add_version(rs->version());
2044
1.00k
        _max_version = std::max(rs->end_version(), _max_version);
2045
1.00k
        update_base_size(*rs);
2046
1.00k
    }
2047
346
    _tablet_meta->add_rowsets_unchecked(rowsets);
2048
346
}
2049
2050
16
void CloudTablet::clear_unused_visible_pending_rowsets() {
2051
16
    int64_t cur_max_version = max_version().second;
2052
16
    int32_t max_version_count = max_version_config();
2053
16
    int64_t current_time = std::chrono::duration_cast<std::chrono::seconds>(
2054
16
                                   std::chrono::system_clock::now().time_since_epoch())
2055
16
                                   .count();
2056
2057
16
    std::unique_lock<std::mutex> wlock(_visible_pending_rs_lock);
2058
39
    for (auto it = _visible_pending_rs_map.begin(); it != _visible_pending_rs_map.end();) {
2059
23
        if (int64_t version = it->first, expiration_time = it->second.expiration_time;
2060
23
            version <= cur_max_version || expiration_time < current_time) {
2061
19
            it = _visible_pending_rs_map.erase(it);
2062
19
        } else {
2063
4
            ++it;
2064
4
        }
2065
23
    }
2066
2067
16
    while (!_visible_pending_rs_map.empty() && _visible_pending_rs_map.size() > max_version_count) {
2068
0
        _visible_pending_rs_map.erase(--_visible_pending_rs_map.end());
2069
0
    }
2070
16
}
2071
2072
void CloudTablet::try_make_committed_rs_visible(int64_t txn_id, int64_t visible_version,
2073
0
                                                int64_t version_update_time_ms) {
2074
0
    if (enable_unique_key_merge_on_write()) {
2075
        // for mow tablet, we get committed rowset from `CloudTxnDeleteBitmapCache` rather than `CommittedRowsetManager`
2076
0
        try_make_committed_rs_visible_for_mow(txn_id, visible_version, version_update_time_ms);
2077
0
        return;
2078
0
    }
2079
2080
0
    auto& committed_rs_mgr = _engine.committed_rs_mgr();
2081
0
    auto res = committed_rs_mgr.get_committed_rowset(txn_id, tablet_id());
2082
0
    if (!res.has_value()) {
2083
0
        return;
2084
0
    }
2085
0
    auto [rowset_meta, expiration_time] = res.value();
2086
0
    bool is_empty_rowset = (rowset_meta == nullptr);
2087
0
    if (!is_empty_rowset) {
2088
0
        rowset_meta->set_cloud_fields_after_visible(visible_version, version_update_time_ms);
2089
0
    }
2090
0
    {
2091
0
        std::lock_guard<std::mutex> lock(_visible_pending_rs_lock);
2092
0
        _visible_pending_rs_map.emplace(
2093
0
                visible_version,
2094
0
                VisiblePendingRowset {rowset_meta, expiration_time, is_empty_rowset});
2095
0
    }
2096
0
    apply_visible_pending_rowsets();
2097
0
    committed_rs_mgr.remove_committed_rowset(txn_id, tablet_id());
2098
0
}
2099
2100
void CloudTablet::try_make_committed_rs_visible_for_mow(int64_t txn_id, int64_t visible_version,
2101
0
                                                        int64_t version_update_time_ms) {
2102
0
    Defer defer {[&] {
2103
0
        _engine.txn_delete_bitmap_cache().remove_unused_tablet_txn_info(txn_id, tablet_id());
2104
0
    }};
2105
0
    auto res = _engine.txn_delete_bitmap_cache().get_rowset_and_delete_bitmap(txn_id, tablet_id());
2106
0
    if (!res.has_value()) {
2107
0
        return;
2108
0
    }
2109
0
    auto [rowset, delete_bitmap] = res.value();
2110
0
    bool is_empty_rowset = (rowset == nullptr);
2111
0
    {
2112
0
        std::unique_lock lock {_sync_meta_lock};
2113
0
        std::unique_lock meta_wlock {_meta_lock};
2114
0
        if (_max_version + 1 != visible_version) {
2115
0
            return;
2116
0
        }
2117
0
        if (is_empty_rowset) {
2118
0
            Versions existing_versions;
2119
0
            for (const auto& [_, rs] : tablet_meta()->all_rs_metas()) {
2120
0
                existing_versions.emplace_back(rs->version());
2121
0
            }
2122
0
            if (existing_versions.empty()) {
2123
0
                return;
2124
0
            }
2125
0
            auto max_version = std::ranges::max(existing_versions, {}, &Version::first);
2126
0
            auto prev_rowset = get_rowset_by_version(max_version);
2127
0
            auto st = _engine.meta_mgr().create_empty_rowset_for_hole(
2128
0
                    this, visible_version, prev_rowset->rowset_meta(), &rowset);
2129
0
            if (!st.ok()) {
2130
0
                return;
2131
0
            }
2132
0
        } else {
2133
0
            for (const auto& [delete_bitmap_key, bitmap_value] : delete_bitmap->delete_bitmap) {
2134
                // skip sentinel mark, which is used for delete bitmap correctness check
2135
0
                if (std::get<1>(delete_bitmap_key) != DeleteBitmap::INVALID_SEGMENT_ID) {
2136
0
                    tablet_meta()->delete_bitmap().merge(
2137
0
                            {std::get<0>(delete_bitmap_key), std::get<1>(delete_bitmap_key),
2138
0
                             visible_version},
2139
0
                            bitmap_value);
2140
0
                }
2141
0
            }
2142
0
        }
2143
0
        rowset->rowset_meta()->set_cloud_fields_after_visible(visible_version,
2144
0
                                                              version_update_time_ms);
2145
0
        add_rowsets({rowset}, false, meta_wlock, true);
2146
0
    }
2147
0
    LOG(INFO) << "mow added visible pending rowset, txn_id=" << txn_id
2148
0
              << ", tablet_id=" << tablet_id() << ", version=" << visible_version
2149
0
              << ", rowset_id=" << rowset->rowset_id().to_string();
2150
0
}
2151
2152
15
void CloudTablet::apply_visible_pending_rowsets() {
2153
15
    Defer defer {[&] { clear_unused_visible_pending_rowsets(); }};
2154
2155
15
    std::unique_lock lock(_sync_meta_lock);
2156
15
    std::unique_lock meta_wlock(_meta_lock);
2157
15
    int64_t next_version = _max_version + 1;
2158
15
    std::vector<RowsetSharedPtr> to_add;
2159
15
    std::lock_guard<std::mutex> pending_lock(_visible_pending_rs_lock);
2160
15
    for (auto it = _visible_pending_rs_map.upper_bound(_max_version);
2161
31
         it != _visible_pending_rs_map.end(); ++it) {
2162
21
        int64_t version = it->first;
2163
21
        if (version != next_version) break;
2164
2165
17
        auto& pending_rs = it->second;
2166
17
        if (pending_rs.is_empty_rowset) {
2167
3
            RowsetSharedPtr prev_rowset {nullptr};
2168
3
            if (!to_add.empty()) {
2169
1
                prev_rowset = to_add.back();
2170
2
            } else {
2171
2
                Versions existing_versions;
2172
2
                for (const auto& [_, rs] : tablet_meta()->all_rs_metas()) {
2173
1
                    existing_versions.emplace_back(rs->version());
2174
1
                }
2175
2
                if (existing_versions.empty()) {
2176
1
                    break;
2177
1
                }
2178
1
                auto max_version = std::ranges::max(existing_versions, {}, &Version::first);
2179
1
                prev_rowset = get_rowset_by_version(max_version);
2180
1
            }
2181
2
            RowsetSharedPtr rowset;
2182
2
            auto st = _engine.meta_mgr().create_empty_rowset_for_hole(
2183
2
                    this, version, prev_rowset->rowset_meta(), &rowset);
2184
2
            if (!st.ok()) {
2185
0
                return;
2186
0
            }
2187
2
            to_add.push_back(std::move(rowset));
2188
14
        } else {
2189
14
            RowsetSharedPtr rowset;
2190
14
            auto st = RowsetFactory::create_rowset(nullptr, "", pending_rs.rowset_meta, &rowset);
2191
14
            if (!st.ok()) {
2192
0
                LOG(WARNING) << "failed to create rowset from pending rowset meta, tablet_id="
2193
0
                             << tablet_id() << ", version=" << version
2194
0
                             << ", rowset_id=" << pending_rs.rowset_meta->rowset_id().to_string()
2195
0
                             << ", error=" << st;
2196
0
                break;
2197
0
            }
2198
14
            to_add.push_back(std::move(rowset));
2199
14
        }
2200
16
        next_version++;
2201
16
    }
2202
15
    if (!to_add.empty()) {
2203
10
        add_rowsets(to_add, false, meta_wlock, true);
2204
10
        LOG_INFO(
2205
10
                "applied_visible_pending_rowsets, tablet_id={}, new_max_version={}, "
2206
10
                "count={}, new_rowsets={}",
2207
10
                tablet_id(), _max_version, to_add.size(),
2208
16
                fmt::join(to_add | std::views::transform([](const RowsetSharedPtr& rs) {
2209
16
                              return fmt::format("{}{}", rs->rowset_id().to_string(),
2210
16
                                                 rs->version().to_string());
2211
16
                          }),
2212
10
                          ","));
2213
10
    }
2214
15
}
2215
2216
#include "common/compile_check_end.h"
2217
2218
} // namespace doris