Coverage Report

Created: 2026-09-20 10:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/cloud/src/recycler/recycler.h
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
#pragma once
19
20
#include <gen_cpp/cloud.pb.h>
21
#include <glog/logging.h>
22
23
#include <atomic>
24
#include <chrono>
25
#include <condition_variable>
26
#include <cstddef>
27
#include <cstdint>
28
#include <deque>
29
#include <functional>
30
#include <map>
31
#include <memory>
32
#include <string>
33
#include <string_view>
34
#include <thread>
35
#include <unordered_map>
36
#include <unordered_set>
37
#include <utility>
38
#include <vector>
39
40
#include "common/bvars.h"
41
#include "meta-service/delete_bitmap_lock_white_list.h"
42
#include "meta-service/txn_lazy_committer.h"
43
#include "meta-store/versionstamp.h"
44
#include "recycler/snapshot_chain_compactor.h"
45
#include "recycler/snapshot_data_migrator.h"
46
#include "recycler/storage_vault_accessor.h"
47
#include "snapshot/snapshot_manager.h"
48
49
namespace brpc {
50
class Server;
51
} // namespace brpc
52
53
namespace doris::cloud {
54
class TxnKv;
55
class InstanceRecycler;
56
class StorageVaultAccessor;
57
class Checker;
58
class SimpleThreadPool;
59
class RecyclerMetricsContext;
60
class TabletRecyclerMetricsContext;
61
class SegmentRecyclerMetricsContext;
62
63
int64_t calculate_tmp_rowset_expired_time(
64
        const std::string& instance_id_, const doris::RowsetMetaCloudPB& tmp_rowset_meta_pb,
65
        int64_t* earlest_ts /* tmp_rowset earliest expiration ts */);
66
struct RecyclerThreadPoolGroup {
67
22
    RecyclerThreadPoolGroup() = default;
68
    RecyclerThreadPoolGroup(std::shared_ptr<SimpleThreadPool> s3_producer_pool,
69
                            std::shared_ptr<SimpleThreadPool> recycle_tablet_pool,
70
                            std::shared_ptr<SimpleThreadPool> group_recycle_function_pool)
71
13
            : s3_producer_pool(std::move(s3_producer_pool)),
72
13
              recycle_tablet_pool(std::move(recycle_tablet_pool)),
73
13
              group_recycle_function_pool(std::move(group_recycle_function_pool)) {}
74
390
    ~RecyclerThreadPoolGroup() = default;
75
172
    RecyclerThreadPoolGroup(const RecyclerThreadPoolGroup&) = default;
76
    RecyclerThreadPoolGroup& operator=(RecyclerThreadPoolGroup& other) = default;
77
14
    RecyclerThreadPoolGroup& operator=(RecyclerThreadPoolGroup&& other) = default;
78
183
    RecyclerThreadPoolGroup(RecyclerThreadPoolGroup&&) = default;
79
    // used for accessor.delete_files, accessor.delete_directory
80
    std::shared_ptr<SimpleThreadPool> s3_producer_pool;
81
    // used for InstanceRecycler::recycle_tablet
82
    std::shared_ptr<SimpleThreadPool> recycle_tablet_pool;
83
    std::shared_ptr<SimpleThreadPool> group_recycle_function_pool;
84
};
85
86
class Recycler {
87
public:
88
    explicit Recycler(std::shared_ptr<TxnKv> txn_kv);
89
    ~Recycler();
90
91
    // returns 0 for success otherwise error
92
    int start(brpc::Server* server);
93
94
    void stop();
95
96
2.37k
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
97
98
0
    RecyclerThreadPoolGroup& thread_pool_group() { return _thread_pool_group; }
99
100
0
    const std::shared_ptr<SnapshotManager>& snapshot_manager() const { return snapshot_manager_; }
101
102
private:
103
    void recycle_callback();
104
105
    void instance_scanner_callback();
106
107
    void lease_recycle_jobs();
108
109
    void check_recycle_tasks();
110
111
private:
112
    friend class RecyclerServiceImpl;
113
114
    std::shared_ptr<TxnKv> txn_kv_;
115
    std::atomic_bool stopped_ {false};
116
117
    std::vector<std::thread> workers_;
118
119
    std::mutex mtx_;
120
    // notify recycle workers
121
    std::condition_variable pending_instance_cond_;
122
    std::deque<InstanceInfoPB> pending_instance_queue_;
123
    std::unordered_set<std::string> pending_instance_set_;
124
    std::unordered_map<std::string, std::shared_ptr<InstanceRecycler>> recycling_instance_map_;
125
    // notify instance scanner and lease thread
126
    std::condition_variable notifier_;
127
128
    std::string ip_port_;
129
130
    std::unique_ptr<Checker> checker_;
131
132
    RecyclerThreadPoolGroup _thread_pool_group;
133
134
    std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
135
    std::shared_ptr<SnapshotManager> snapshot_manager_;
136
    std::shared_ptr<SnapshotDataMigrator> snapshot_data_migrator_;
137
    std::shared_ptr<SnapshotChainCompactor> snapshot_chain_compactor_;
138
};
139
140
enum class RowsetRecyclingState {
141
    FORMAL_ROWSET,
142
    TMP_ROWSET,
143
};
144
145
// Represents a single rowset deletion task for batch delete
146
struct RowsetDeleteTask {
147
    RowsetMetaCloudPB rowset_meta;
148
    std::string recycle_rowset_key;       // Primary key marking "pending recycle"
149
    std::string non_versioned_rowset_key; // Legacy non-versioned rowset meta key
150
    std::string versioned_rowset_key;     // Versioned meta rowset key
151
    Versionstamp versionstamp;
152
    std::string rowset_ref_count_key;
153
};
154
155
class RecyclerMetricsContext {
156
public:
157
    enum class MetricType {
158
        SCANNED_NUM,
159
        EXPIRED_NUM,
160
        RECYCLED_NUM,
161
        RECYCLED_BYTES,
162
    };
163
164
    class MetricValue {
165
    public:
166
        // Concurrent workers only update atomics. Batch boundaries publish their snapshots.
167
165k
        MetricValue& operator+=(uint64_t delta) {
168
165k
            value_.fetch_add(delta, std::memory_order_relaxed);
169
165k
            return *this;
170
165k
        }
171
172
0
        MetricValue& operator++() {
173
0
            *this += 1;
174
0
            return *this;
175
0
        }
176
177
20.2k
        uint64_t operator++(int) { return value_.fetch_add(1, std::memory_order_relaxed); }
178
179
5.37k
        void reset() { value_.store(0, std::memory_order_relaxed); }
180
181
1.11k
        void set(uint64_t v) { value_.store(v, std::memory_order_relaxed); }
182
183
6.04k
        uint64_t value() const { return value_.load(std::memory_order_relaxed); }
184
185
    private:
186
        std::atomic_ullong value_ = 0;
187
    };
188
189
    RecyclerMetricsContext() = delete;
190
191
    explicit RecyclerMetricsContext(std::string instance_id, std::string operation_type)
192
672
            : operation_type(std::move(operation_type)),
193
672
              instance_id(std::move(instance_id)),
194
672
              start_time_(std::chrono::steady_clock::now()) {
195
672
        reset();
196
672
    }
197
198
    // Each context has one publisher; workers may update its MetricValues concurrently.
199
839
    void update_metrics() {
200
839
        auto cost = duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
201
839
                                                             start_time_)
202
839
                            .count();
203
839
        g_bvar_recycler_instance_current_round_task_elapsed_ms.put({instance_id, operation_type},
204
839
                                                                   cost);
205
839
        put(MetricType::SCANNED_NUM, kv_scanned_num.value());
206
839
        put(MetricType::EXPIRED_NUM, kv_expired_num.value());
207
839
        put(MetricType::RECYCLED_NUM, kv_recycled_num.value());
208
839
        put(MetricType::RECYCLED_BYTES, kv_recycled_bytes.value());
209
839
    }
210
211
672
    ~RecyclerMetricsContext() { finish(); }
212
213
    MetricValue kv_scanned_num;
214
    MetricValue kv_expired_num;
215
    MetricValue kv_recycled_num;
216
    MetricValue kv_recycled_bytes;
217
218
    std::string operation_type;
219
    std::string instance_id;
220
221
private:
222
    std::chrono::steady_clock::time_point start_time_;
223
224
1.34k
    void reset() {
225
1.34k
        start_time_ = std::chrono::steady_clock::now();
226
1.34k
        kv_scanned_num.reset();
227
1.34k
        kv_expired_num.reset();
228
1.34k
        kv_recycled_num.reset();
229
1.34k
        kv_recycled_bytes.reset();
230
1.34k
        put(MetricType::SCANNED_NUM, 0);
231
1.34k
        put(MetricType::EXPIRED_NUM, 0);
232
1.34k
        put(MetricType::RECYCLED_NUM, 0);
233
1.34k
        put(MetricType::RECYCLED_BYTES, 0);
234
1.34k
        g_bvar_recycler_instance_current_round_task_elapsed_ms.put({instance_id, operation_type},
235
1.34k
                                                                   0);
236
1.34k
    }
237
238
672
    void finish() {
239
672
        update_metrics();
240
672
        if (auto num = kv_recycled_num.value(); num > 0) {
241
169
            g_bvar_recycler_instance_recycle_total_num_since_started.put(
242
169
                    {instance_id, operation_type}, static_cast<int64_t>(num));
243
169
        }
244
672
        if (auto bytes = kv_recycled_bytes.value(); bytes > 0) {
245
45
            g_bvar_recycler_instance_recycle_total_bytes_since_started.put(
246
45
                    {instance_id, operation_type}, static_cast<int64_t>(bytes));
247
45
        }
248
672
        g_bvar_recycler_instance_last_round_recycled_num.put(
249
672
                {instance_id, operation_type}, static_cast<int64_t>(kv_recycled_num.value()));
250
672
        g_bvar_recycler_instance_last_round_recycled_bytes.put(
251
672
                {instance_id, operation_type}, static_cast<int64_t>(kv_recycled_bytes.value()));
252
672
        reset();
253
672
    }
254
255
8.73k
    void put(MetricType type, uint64_t value) {
256
8.73k
        switch (type) {
257
2.18k
        case MetricType::SCANNED_NUM:
258
2.18k
            g_bvar_recycler_instance_recycle_current_round_kv_scanned.put(
259
2.18k
                    {instance_id, operation_type}, value);
260
2.18k
            break;
261
2.18k
        case MetricType::EXPIRED_NUM:
262
2.18k
            g_bvar_recycler_instance_recycle_current_round_kv_expired.put(
263
2.18k
                    {instance_id, operation_type}, value);
264
2.18k
            break;
265
2.18k
        case MetricType::RECYCLED_NUM:
266
2.18k
            g_bvar_recycler_instance_recycle_current_round_kv_recycled.put(
267
2.18k
                    {instance_id, operation_type}, value);
268
2.18k
            break;
269
2.18k
        case MetricType::RECYCLED_BYTES:
270
2.18k
            g_bvar_recycler_instance_current_round_recycled_object_bytes.put(
271
2.18k
                    {instance_id, operation_type}, value);
272
2.18k
            break;
273
8.73k
        }
274
8.73k
    }
275
};
276
277
class TabletRecyclerMetricsContext : public RecyclerMetricsContext {
278
public:
279
    explicit TabletRecyclerMetricsContext(std::string instance_id)
280
183
            : RecyclerMetricsContext(std::move(instance_id), "recycle_tablet") {}
281
};
282
283
class SegmentRecyclerMetricsContext : public RecyclerMetricsContext {
284
public:
285
    explicit SegmentRecyclerMetricsContext(std::string instance_id)
286
183
            : RecyclerMetricsContext(std::move(instance_id), "recycle_segment") {}
287
};
288
289
struct OplogRecycleStats;
290
291
struct RelatedTxnOrJobAbortTask {
292
    enum class Type : uint8_t {
293
        TXN,
294
        JOB,
295
    };
296
297
    Type type = Type::TXN;
298
    std::string key;
299
    int64_t txn_id = 0;
300
    int64_t tablet_id = 0;
301
    int64_t start_version = 0;
302
    int64_t end_version = 0;
303
    std::string rowset_id;
304
    std::string job_id;
305
};
306
class InstanceRecycler {
307
public:
308
    struct PackedFileRecycleStats {
309
        int64_t num_scanned = 0;          // packed-file kv scanned
310
        int64_t num_corrected = 0;        // packed-file kv corrected
311
        int64_t num_deleted = 0;          // packed-file kv deleted
312
        int64_t num_failed = 0;           // packed-file kv failed
313
        int64_t bytes_deleted = 0;        // packed-file kv bytes deleted from txn-kv
314
        int64_t num_object_deleted = 0;   // packed-file objects deleted from storage (vault/HDFS)
315
        int64_t bytes_object_deleted = 0; // bytes deleted from storage objects
316
        int64_t rowset_scan_count = 0;    // rowset metas scanned during correction
317
    };
318
319
    explicit InstanceRecycler(std::shared_ptr<TxnKv> txn_kv, const InstanceInfoPB& instance,
320
                              RecyclerThreadPoolGroup thread_pool_group,
321
                              std::shared_ptr<TxnLazyCommitter> txn_lazy_committer);
322
    ~InstanceRecycler();
323
324
0
    std::string_view instance_id() const { return instance_id_; }
325
9
    const InstanceInfoPB& instance_info() const { return instance_info_; }
326
327
    // returns 0 for success otherwise error
328
    int init();
329
330
0
    void stop() { stopped_.store(true, std::memory_order_release); }
331
67
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
332
333
    // returns 0 for success otherwise error
334
    int do_recycle();
335
336
    // remove all kv and data in this instance, ONLY be called when instance has been deleted
337
    // returns 0 for success otherwise error
338
    int recycle_deleted_instance();
339
340
    int recycle_deleted_instance_data();
341
342
    int recycle_deleted_instance_metadata();
343
344
    int update_instance_recycle_state(InstanceRecycleState expected_state,
345
                                      InstanceRecycleState target_state);
346
347
    int update_instance_recycle_state(InstanceRecycleState expected_state,
348
                                      InstanceRecycleState target_state, Transaction* txn);
349
350
    // scan and recycle expired indexes:
351
    // 1. dropped table, dropped mv
352
    // 2. half-successtable/index when create
353
    // returns 0 for success otherwise error
354
    int recycle_indexes();
355
356
    // scan and recycle expired partitions:
357
    // 1. dropped parttion
358
    // 2. half-success partition when create
359
    // returns 0 for success otherwise error
360
    int recycle_partitions();
361
362
    // scan and recycle expired rowsets:
363
    // 1. prepare_rowset will produce recycle_rowset before uploading data to remote storage (memo)
364
    // 2. compaction will change the input rowsets to recycle_rowset
365
    // returns 0 for success otherwise error
366
    int recycle_rowsets();
367
368
    // like `recycle_rowsets`, but for versioned rowsets.
369
    int recycle_versioned_rowsets();
370
371
    // scan and recycle expired tmp rowsets:
372
    // 1. commit_rowset will produce tmp_rowset when finish upload data (load or compaction) to remote storage
373
    // returns 0 for success otherwise error
374
    int recycle_tmp_rowsets();
375
376
    /**
377
     * recycle all tablets belonging to the index specified by `index_id`
378
     *
379
     * @param partition_id if positive, only recycle tablets in this partition belonging to the specified index
380
     * @return 0 for success otherwise error
381
     */
382
    int recycle_tablets(int64_t table_id, int64_t index_id, RecyclerMetricsContext& ctx,
383
                        int64_t partition_id = -1);
384
385
    /**
386
     * recycle all rowsets belonging to the tablet specified by `tablet_id`
387
     *
388
     * @return 0 for success otherwise error
389
     */
390
    int recycle_tablet(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
391
392
    /**
393
     * like `recycle_tablet`, but for versioned tablet
394
     */
395
    int recycle_versioned_tablet(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
396
397
    // scan and recycle useless partition version kv
398
    int recycle_versions();
399
400
    // scan and recycle the orphan partitions
401
    int recycle_orphan_partitions();
402
403
    // scan and abort timeout txn label
404
    // returns 0 for success otherwise error
405
    int abort_timeout_txn();
406
407
    //scan and recycle expire txn label
408
    // returns 0 for success otherwise error
409
    int recycle_expired_txn_label();
410
411
    // scan and recycle finished or timeout copy jobs
412
    // returns 0 for success otherwise error
413
    int recycle_copy_jobs();
414
415
    // scan and recycle dropped internal stage
416
    // returns 0 for success otherwise error
417
    int recycle_stage();
418
419
    // scan and recycle expired stage objects
420
    // returns 0 for success otherwise error
421
    int recycle_expired_stage_objects();
422
423
    // scan and recycle operation logs
424
    // returns 0 for success otherwise error
425
    int recycle_operation_logs();
426
427
    // scan and recycle expired restore jobs
428
    // returns 0 for success otherwise error
429
    int recycle_restore_jobs();
430
431
    /**
432
     * Scan packed-file metadata, correct reference counters, and recycle unused packed files.
433
     *
434
     * @return 0 on success, non-zero error code otherwise
435
     */
436
    int recycle_packed_files();
437
438
    // scan and recycle snapshots
439
    // returns 0 for success otherwise error
440
    int recycle_cluster_snapshots();
441
442
    // scan and recycle ref rowsets for deleted instance
443
    // returns 0 for success otherwise error
444
    int recycle_ref_rowsets(bool* has_unrecycled_rowsets);
445
446
    bool check_recycle_tasks();
447
448
    /**
449
     * Decode the key of a packed-file metadata record into the persisted object path.
450
     *
451
     * @param key raw key persisted in txn-kv
452
     * @param packed_path output object storage path referenced by the key
453
     * @return true if decoding succeeds, false otherwise
454
     */
455
    static bool decode_packed_file_key(std::string_view key, std::string* packed_path);
456
457
30
    void TEST_add_accessor(std::string_view id, std::shared_ptr<StorageVaultAccessor> accessor) {
458
30
        accessor_map_.insert({std::string(id), std::move(accessor)});
459
30
    }
460
461
    // Recycle snapshot meta and data, return 0 for success otherwise error.
462
    int recycle_snapshot_meta_and_data(const std::string& instance_id,
463
                                       const std::string& resource_id,
464
                                       Versionstamp snapshot_version,
465
                                       const SnapshotPB& snapshot_pb);
466
467
private:
468
    // returns 0 for success otherwise error
469
    int remove_instance_key();
470
471
    // returns 0 for success otherwise error
472
    int init_obj_store_accessors();
473
474
    // returns 0 for success otherwise error
475
    int init_storage_vault_accessors();
476
477
    int recycle_stream(int64_t stream_id, const RecycleIndexPB& recycle_index,
478
                       std::string_view recycle_key);
479
480
    int recycle_table_stream_offset_prefix(std::string prefix,
481
                                           RecyclerMetricsContext* metrics_context);
482
483
    int finalize_recycle_stream(int64_t stream_id, const RecycleIndexPB& recycle_index,
484
                                std::string_view recycle_key);
485
486
    int recycle_partition_table_stream_offsets(
487
            int64_t db_id, int64_t table_id, int64_t partition_id,
488
            const google::protobuf::RepeatedPtrField<TableStreamIdentityPB>& table_streams);
489
490
    /**
491
     * Scan key-value pairs between [`begin`, `end`) with multiple rounds of range get(`RangeGetIterator`),
492
     * and perform `recycle_func` on each key-value pair.
493
     *
494
     * @param recycle_func defines how to recycle resources corresponding to a key-value pair.
495
     *                     The scan will stop if recycle_func() returns non-zero.
496
     *                     recycle_func() returns 0 if the recycling is successful or the scan can continue with ignorable errors.
497
     * @param loop_done is called after a round (`RangeGetIterator`) in the scan has no next kv. Usually used to perform a batch recycling.
498
     *                  The scan will stop if loop_done() returns non-zero.
499
     *                  loop_done() returns 0 if the recycling is successful or the scan can continue with ignorable errors.
500
     * @return 0 if all corresponding resources are recycled successfully, otherwise non-zero
501
     */
502
    int scan_and_recycle(std::string begin, std::string_view end,
503
                         std::function<int(std::string_view k, std::string_view v)> recycle_func,
504
                         std::function<int()> loop_done = nullptr,
505
                         std::function<bool(std::string*)> next_begin_getter = nullptr);
506
507
    static int next_recycle_rowset_tablet_key(const std::string& instance_id, int64_t tablet_id,
508
                                              std::string* next_key);
509
510
    int scan_recycle_rowsets_by_tablet(
511
            std::string begin, std::string_view end,
512
            std::function<int(std::string_view k, std::string_view v)> recycle_func,
513
            std::function<int()> loop_done = nullptr);
514
515
    // return 0 for success otherwise error
516
    int delete_rowset_data(const doris::RowsetMetaCloudPB& rs_meta_pb);
517
518
    // return 0 for success otherwise error
519
    // NOTE: this function ONLY be called when the file paths cannot be calculated
520
    int delete_rowset_data(const std::string& resource_id, int64_t tablet_id,
521
                           const std::string& rowset_id);
522
523
    bool is_tablet_recycled(int64_t tablet_id);
524
525
    // Return 1 if the versioned delete bitmap should be deleted,
526
    // Return 0 if it can be skipped,
527
    // negative on error.
528
    int should_delete_versioned_delete_bitmap_kvs(int64_t partition_id, int64_t tablet_id);
529
530
    int delete_versioned_delete_bitmap_kvs(int64_t partition_id, int64_t tablet_id,
531
                                           const std::string& rowset_id);
532
533
    int delete_delete_bitmap_kvs(int64_t tablet_id, const std::string& rowset_id);
534
535
    // return 0 for success otherwise error
536
    int delete_rowset_data(
537
            const std::map<std::string, doris::RowsetMetaCloudPB>& rowsets,
538
            RowsetRecyclingState type, RecyclerMetricsContext& metrics_context,
539
            std::vector<std::vector<std::string>>* delete_bitmap_key_groups = nullptr);
540
541
    // Decrement packed file ref counts for rowset segments.
542
    // Returns 0 for success, -1 for error.
543
    int decrement_packed_file_ref_counts(const doris::RowsetMetaCloudPB& rs_meta_pb);
544
545
    enum class DeleteBitmapStorageType {
546
        NOT_FOUND,
547
        IN_FDB,
548
        STANDALONE_FILE,
549
        PACKED_FILE,
550
    };
551
552
    // Process delete bitmap storage and decrement packed file ref count when needed.
553
    // Returns 0 for success, -1 for error.
554
    // out_storage_type: if not null, will be set to the delete bitmap storage type.
555
    // keys: if not null, will collect all versioned delete bitmap keys for batch deletion.
556
    int decrement_delete_bitmap_packed_file_ref_counts(int64_t tablet_id,
557
                                                       const std::string& rowset_id,
558
                                                       DeleteBitmapStorageType* out_storage_type,
559
                                                       std::vector<std::string>* keys = nullptr);
560
561
    int delete_packed_file_and_kv(const std::string& packed_file_path,
562
                                  const std::string& packed_key,
563
                                  const cloud::PackedFileInfoPB& packed_info);
564
565
    /**
566
     * Get stage storage info from instance and init StorageVaultAccessor
567
     * @return 0 if accessor is successfully inited, 1 if stage not found, negative for error
568
     */
569
    int init_copy_job_accessor(const std::string& stage_id, const StagePB::StageType& stage_type,
570
                               std::shared_ptr<StorageVaultAccessor>* accessor);
571
572
    void register_recycle_task(const std::string& task_name, int64_t start_time);
573
574
    void unregister_recycle_task(const std::string& task_name);
575
576
    // for scan all tablets and statistics metrics
577
    int scan_tablets_and_statistics(int64_t tablet_id, int64_t index_id,
578
                                    RecyclerMetricsContext& metrics_context,
579
                                    int64_t partition_id = -1, bool is_empty_tablet = false);
580
581
    // for scan all rs of tablet and statistics metrics
582
    int scan_tablet_and_statistics(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
583
584
    // Recycle operation log and the log keys. The log keys are specified by `raw_keys`.
585
    //
586
    // Both `operation_log` and `raw_keys` will be removed in the same transaction, to ensure atomicity.
587
    int recycle_operation_log(Versionstamp log_version, const std::vector<std::string>& raw_keys,
588
                              OperationLogPB operation_log,
589
                              OplogRecycleStats* oplog_stats = nullptr);
590
591
    // Recycle rowset meta and data, return 0 for success otherwise error
592
    //
593
    // This function will decrease the rowset ref count and remove the rowset meta and data if the ref count is 1.
594
    int recycle_rowset_meta_and_data(const RowsetDeleteTask& task,
595
                                     RecyclerMetricsContext& metrics_context);
596
597
    // Classify rowset task by ref_count, return 0 to add to batch delete, 1 if handled (ref>1), -1 on error
598
    int classify_rowset_task_by_ref_count(RowsetDeleteTask& task,
599
                                          std::vector<RowsetDeleteTask>& batch_delete_tasks);
600
601
    // Cleanup metadata for deleted rowsets, return 0 for success otherwise error
602
    int cleanup_rowset_metadata(const std::vector<RowsetDeleteTask>& tasks);
603
604
    // Whether the instance has any snapshots, return 0 for success otherwise error.
605
    int has_cluster_snapshots(bool* any);
606
607
    // Whether need to recycle versioned keys
608
    bool should_recycle_versioned_keys() const;
609
610
    /**
611
     * Parse the path of a packed-file fragment and output the owning tablet and rowset identifiers.
612
     *
613
     * @param path packed-file fragment path to decode
614
     * @param tablet_id output tablet identifier extracted from the path
615
     * @param rowset_id output rowset identifier extracted from the path
616
     * @return true if both identifiers are successfully parsed, false otherwise
617
     */
618
    static bool parse_packed_slice_path(std::string_view path, int64_t* tablet_id,
619
                                        std::string* rowset_id);
620
    // Check whether a rowset referenced by a packed file still exists in metadata.
621
    // @param stats optional recycle statistics collector.
622
    int check_rowset_exists(int64_t tablet_id, const std::string& rowset_id, bool* exists,
623
                            PackedFileRecycleStats* stats = nullptr);
624
    int check_recycle_and_tmp_rowset_exists(int64_t tablet_id, const std::string& rowset_id,
625
                                            int64_t txn_id, bool* recycle_exists, bool* tmp_exists);
626
    /**
627
     * Resolve which storage accessor should be used for a packed file.
628
     *
629
     * @param hint preferred storage resource identifier persisted with the file
630
     * @return pair of the resolved resource identifier and accessor; the accessor can be null if unavailable
631
     */
632
    std::pair<std::string, std::shared_ptr<StorageVaultAccessor>> resolve_packed_file_accessor(
633
            const std::string& hint);
634
    // Recompute packed-file counters and lifecycle state after validating contained fragments.
635
    // @param stats optional recycle statistics collector.
636
    int correct_packed_file_info(cloud::PackedFileInfoPB* packed_info, bool* changed,
637
                                 const std::string& packed_file_path,
638
                                 PackedFileRecycleStats* stats = nullptr);
639
    // Correct and recycle a single packed-file record, updating metadata and accounting statistics.
640
    // @param stats optional recycle statistics collector.
641
    int process_single_packed_file(const std::string& packed_key,
642
                                   const std::string& packed_file_path,
643
                                   PackedFileRecycleStats* stats);
644
    // Process a packed-file KV while scanning and aggregate recycling statistics.
645
    int handle_packed_file_kv(std::string_view key, std::string_view value,
646
                              PackedFileRecycleStats* stats, int* ret);
647
648
    // Abort the transaction/job associated with a rowset that is about to be recycled.
649
    // This function is called during rowset recycling to prevent data loss by ensuring that
650
    // the transaction/job cannot be committed after its rowset data has been deleted.
651
    //
652
    // Scenario:
653
    // When recycler detects an expired prepared rowset (e.g., from a failed load transaction/job),
654
    // it needs to recycle the rowset data. However, if the transaction/job is still active and gets
655
    // committed after the data is deleted, it would lead to data loss - the transaction/job would
656
    // reference non-existent data.
657
    //
658
    // Solution:
659
    // Before recycling rowset data, try to abort the associated transaction/job. A zero return only
660
    // permits the caller to recheck the recycle key. Object data can be deleted only when the key
661
    // still exists, still describes the same PREPARE rowset, and still belongs to the same owner.
662
    //
663
    // Parameters:
664
    //   txn_id: The transaction/job ID associated with the rowset to be recycled
665
    //
666
    // Returns:
667
    //   0 if the recycle key may be rechecked before deletion. The caller must never delete
668
    //     object data directly from the scan snapshot.
669
    //   Non-zero if object data and the recycle key must be retained for a later retry.
670
    int abort_txn_for_related_rowset(int64_t txn_id);
671
    int abort_job_for_related_rowset(int64_t tablet_id, const std::string& rowset_id,
672
                                     const std::string& job_id);
673
674
    template <typename T>
675
    int batch_abort_txn_or_job_for_recycle(
676
            const std::vector<std::string>& keys,
677
            std::vector<std::pair<std::string, RelatedTxnOrJobAbortTask>>& keys_to_recheck);
678
679
    template <typename T>
680
    void submit_batch_mark_rowsets_as_recycled_job(SimpleThreadPool& worker_pool,
681
                                                   std::vector<std::string> rowset_keys_to_mark);
682
683
    void submit_recycle_prepare_rowsets_job(SimpleThreadPool& worker_pool,
684
                                            std::vector<std::string> rowset_keys_to_abort,
685
                                            std::atomic_long* num_recycled,
686
                                            RecyclerMetricsContext* metrics_context);
687
688
    void submit_recycle_tmp_rowsets_job(SimpleThreadPool& worker_pool,
689
                                        std::vector<std::string> rowset_keys_to_abort,
690
                                        std::atomic_long* num_recycled,
691
                                        RecyclerMetricsContext* metrics_context);
692
693
private:
694
    std::atomic_bool stopped_ {false};
695
    std::shared_ptr<TxnKv> txn_kv_;
696
    std::string instance_id_;
697
    InstanceInfoPB instance_info_;
698
699
    // TODO(plat1ko): Add new accessor to map in runtime for new created storage vaults
700
    std::unordered_map<std::string, std::shared_ptr<StorageVaultAccessor>> accessor_map_;
701
    using InvertedIndexInfo =
702
            std::pair<InvertedIndexStorageFormatPB, std::vector<std::pair<int64_t, std::string>>>;
703
704
    class InvertedIndexIdCache;
705
    std::unique_ptr<InvertedIndexIdCache> inverted_index_id_cache_;
706
707
    std::mutex recycled_tablets_mtx_;
708
    // Store recycled tablets, we can skip deleting rowset data of these tablets because these data has already been deleted.
709
    std::unordered_set<int64_t> recycled_tablets_;
710
711
    std::mutex recycle_tasks_mutex;
712
    // <task_name, start_time>>
713
    std::map<std::string, int64_t> running_recycle_tasks;
714
715
    RecyclerThreadPoolGroup _thread_pool_group;
716
717
    std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
718
    std::shared_ptr<SnapshotManager> snapshot_manager_;
719
    std::shared_ptr<DeleteBitmapLockWhiteList> delete_bitmap_lock_white_list_;
720
    std::shared_ptr<ResourceManager> resource_mgr_;
721
722
    TabletRecyclerMetricsContext tablet_metrics_context_;
723
    SegmentRecyclerMetricsContext segment_metrics_context_;
724
725
    // Data tablets in the same partition have the same MoW setting. Cache both true and false so
726
    // subsequent rowsets can avoid reading the tablet index and tablet meta. Row-binlog tablets
727
    // must not populate this cache because their MoW flag is deliberately false.
728
    std::mutex partition_mow_cache_mutex;
729
    std::map<int64_t, bool> partition_mow_cache;
730
};
731
732
struct OperationLogReferenceInfo {
733
    bool referenced_by_instance = false;
734
    bool referenced_by_snapshot = false;
735
    Versionstamp referenced_snapshot_timestamp;
736
};
737
738
struct OplogRecycleStats {
739
    // Total oplog count scanned per round
740
    std::atomic<int64_t> total_num {0};
741
    // Oplogs not recycled this round (per round, written to mBvarStatus)
742
    std::atomic<int64_t> not_recycled_num {0};
743
    // Recycle failures (per round, accumulated to mBvarIntAdder at end)
744
    std::atomic<int64_t> failed_num {0};
745
    // Per-oplog-type recycled counts (incremented after successful commit)
746
    std::atomic<int64_t> recycled_commit_partition {0};
747
    std::atomic<int64_t> recycled_drop_partition {0};
748
    std::atomic<int64_t> recycled_commit_index {0};
749
    std::atomic<int64_t> recycled_drop_index {0};
750
    std::atomic<int64_t> recycled_update_tablet {0};
751
    std::atomic<int64_t> recycled_compaction {0};
752
    std::atomic<int64_t> recycled_schema_change {0};
753
    std::atomic<int64_t> recycled_commit_txn {0};
754
};
755
756
// Helper class to check if operation logs can be recycled based on snapshots and versionstamps
757
class OperationLogRecycleChecker {
758
public:
759
    OperationLogRecycleChecker(std::string_view instance_id, TxnKv* txn_kv,
760
                               const InstanceInfoPB& instance_info)
761
37
            : instance_id_(instance_id), txn_kv_(txn_kv), instance_info_(instance_info) {}
762
763
    // Initialize the checker by loading snapshots and setting max version stamp
764
    int init();
765
766
    // Check if an operation log can be recycled
767
    bool can_recycle(const Versionstamp& log_versionstamp, int64_t log_min_timestamp,
768
                     OperationLogReferenceInfo* reference_info) const;
769
770
0
    Versionstamp max_versionstamp() const { return max_versionstamp_; }
771
772
32
    const std::vector<std::pair<SnapshotPB, Versionstamp>>& get_snapshots() const {
773
32
        return snapshots_;
774
32
    }
775
776
private:
777
    std::string_view instance_id_;
778
    TxnKv* txn_kv_;
779
    const InstanceInfoPB& instance_info_;
780
    Versionstamp max_versionstamp_;
781
    Versionstamp source_snapshot_versionstamp_;
782
    std::map<Versionstamp, size_t> snapshot_indexes_;
783
    std::vector<std::pair<SnapshotPB, Versionstamp>> snapshots_;
784
};
785
786
class SnapshotDataSizeCalculator {
787
public:
788
    SnapshotDataSizeCalculator(std::string_view instance_id, std::shared_ptr<TxnKv> txn_kv)
789
33
            : instance_id_(instance_id), txn_kv_(std::move(txn_kv)) {}
790
791
    void init(const std::vector<std::pair<SnapshotPB, Versionstamp>>& snapshots);
792
793
    int calculate_operation_log_data_size(const std::string_view& log_key,
794
                                          OperationLogPB& operation_log,
795
                                          OperationLogReferenceInfo& reference_info);
796
797
    int save_snapshot_data_size_with_retry();
798
799
private:
800
    int get_all_index_partitions(int64_t db_id, int64_t table_id, int64_t index_id,
801
                                 std::vector<int64_t>* partition_ids);
802
    int get_index_partition_data_size(int64_t db_id, int64_t table_id, int64_t index_id,
803
                                      int64_t partition_id, int64_t* data_size);
804
    int save_operation_log(const std::string_view& log_key, OperationLogPB& operation_log);
805
    int save_snapshot_data_size();
806
807
    std::string_view instance_id_;
808
    std::shared_ptr<TxnKv> txn_kv_;
809
810
    int64_t instance_retained_data_size_ = 0;
811
    std::map<Versionstamp, int64_t> retained_data_size_;
812
    std::set<std::string> calculated_partitions_;
813
};
814
815
} // namespace doris::cloud