Coverage Report

Created: 2026-03-20 14:52

/root/doris/cloud/src/recycler/recycler.h
Line
Count
Source (jump to first uncovered line)
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 <condition_variable>
25
#include <cstdint>
26
#include <deque>
27
#include <functional>
28
#include <memory>
29
#include <string>
30
#include <string_view>
31
#include <thread>
32
#include <unordered_map>
33
#include <unordered_set>
34
#include <utility>
35
36
#include "common/bvars.h"
37
#include "meta-service/delete_bitmap_lock_white_list.h"
38
#include "meta-service/txn_lazy_committer.h"
39
#include "meta-store/versionstamp.h"
40
#include "recycler/snapshot_chain_compactor.h"
41
#include "recycler/snapshot_data_migrator.h"
42
#include "recycler/storage_vault_accessor.h"
43
#include "recycler/white_black_list.h"
44
#include "snapshot/snapshot_manager.h"
45
46
namespace brpc {
47
class Server;
48
} // namespace brpc
49
50
namespace doris::cloud {
51
class TxnKv;
52
class InstanceRecycler;
53
class StorageVaultAccessor;
54
class Checker;
55
class SimpleThreadPool;
56
class RecyclerMetricsContext;
57
class TabletRecyclerMetricsContext;
58
class SegmentRecyclerMetricsContext;
59
struct RecyclerThreadPoolGroup {
60
8
    RecyclerThreadPoolGroup() = default;
61
    RecyclerThreadPoolGroup(std::shared_ptr<SimpleThreadPool> s3_producer_pool,
62
                            std::shared_ptr<SimpleThreadPool> recycle_tablet_pool,
63
                            std::shared_ptr<SimpleThreadPool> group_recycle_function_pool)
64
            : s3_producer_pool(std::move(s3_producer_pool)),
65
              recycle_tablet_pool(std::move(recycle_tablet_pool)),
66
11
              group_recycle_function_pool(std::move(group_recycle_function_pool)) {}
67
281
    ~RecyclerThreadPoolGroup() = default;
68
131
    RecyclerThreadPoolGroup(const RecyclerThreadPoolGroup&) = default;
69
    RecyclerThreadPoolGroup& operator=(RecyclerThreadPoolGroup& other) = default;
70
11
    RecyclerThreadPoolGroup& operator=(RecyclerThreadPoolGroup&& other) = default;
71
131
    RecyclerThreadPoolGroup(RecyclerThreadPoolGroup&&) = default;
72
    // used for accessor.delete_files, accessor.delete_directory
73
    std::shared_ptr<SimpleThreadPool> s3_producer_pool;
74
    // used for InstanceRecycler::recycle_tablet
75
    std::shared_ptr<SimpleThreadPool> recycle_tablet_pool;
76
    std::shared_ptr<SimpleThreadPool> group_recycle_function_pool;
77
};
78
79
class Recycler {
80
public:
81
    explicit Recycler(std::shared_ptr<TxnKv> txn_kv);
82
    ~Recycler();
83
84
    // returns 0 for success otherwise error
85
    int start(brpc::Server* server);
86
87
    void stop();
88
89
291
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
90
91
0
    RecyclerThreadPoolGroup& thread_pool_group() { return _thread_pool_group; }
92
93
private:
94
    void recycle_callback();
95
96
    void instance_scanner_callback();
97
98
    void lease_recycle_jobs();
99
100
    void check_recycle_tasks();
101
102
private:
103
    friend class RecyclerServiceImpl;
104
105
    std::shared_ptr<TxnKv> txn_kv_;
106
    std::atomic_bool stopped_ {false};
107
108
    std::vector<std::thread> workers_;
109
110
    std::mutex mtx_;
111
    // notify recycle workers
112
    std::condition_variable pending_instance_cond_;
113
    std::deque<InstanceInfoPB> pending_instance_queue_;
114
    std::unordered_set<std::string> pending_instance_set_;
115
    std::unordered_map<std::string, std::shared_ptr<InstanceRecycler>> recycling_instance_map_;
116
    // notify instance scanner and lease thread
117
    std::condition_variable notifier_;
118
119
    std::string ip_port_;
120
121
    WhiteBlackList instance_filter_;
122
    std::unique_ptr<Checker> checker_;
123
124
    RecyclerThreadPoolGroup _thread_pool_group;
125
126
    std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
127
    std::shared_ptr<SnapshotManager> snapshot_manager_;
128
    std::shared_ptr<SnapshotDataMigrator> snapshot_data_migrator_;
129
    std::shared_ptr<SnapshotChainCompactor> snapshot_chain_compactor_;
130
};
131
132
enum class RowsetRecyclingState {
133
    FORMAL_ROWSET,
134
    TMP_ROWSET,
135
};
136
137
// Represents a single rowset deletion task for batch delete
138
struct RowsetDeleteTask {
139
    RowsetMetaCloudPB rowset_meta;
140
    std::string recycle_rowset_key;       // Primary key marking "pending recycle"
141
    std::string non_versioned_rowset_key; // Legacy non-versioned rowset meta key
142
    std::string versioned_rowset_key;     // Versioned meta rowset key
143
    Versionstamp versionstamp;
144
    std::string rowset_ref_count_key;
145
};
146
147
class RecyclerMetricsContext {
148
public:
149
9
    RecyclerMetricsContext() = default;
150
151
    RecyclerMetricsContext(std::string instance_id, std::string operation_type)
152
508
            : operation_type(std::move(operation_type)), instance_id(std::move(instance_id)) {
153
508
        start();
154
508
    }
155
156
516
    ~RecyclerMetricsContext() = default;
157
158
    std::atomic_ullong total_need_recycle_data_size = 0;
159
    std::atomic_ullong total_need_recycle_num = 0;
160
161
    std::atomic_ullong total_recycled_data_size = 0;
162
    std::atomic_ullong total_recycled_num = 0;
163
164
    std::string operation_type;
165
    std::string instance_id;
166
167
    double start_time = 0;
168
169
508
    void start() {
170
508
        start_time = duration_cast<std::chrono::milliseconds>(
171
508
                             std::chrono::system_clock::now().time_since_epoch())
172
508
                             .count();
173
508
    }
174
175
251
    double duration() const {
176
251
        return duration_cast<std::chrono::milliseconds>(
177
251
                       std::chrono::system_clock::now().time_since_epoch())
178
251
                       .count() -
179
251
               start_time;
180
251
    }
181
182
20
    void reset() {
183
20
        total_need_recycle_data_size = 0;
184
20
        total_need_recycle_num = 0;
185
20
        total_recycled_data_size = 0;
186
20
        total_recycled_num = 0;
187
20
        start_time = duration_cast<std::chrono::milliseconds>(
188
20
                             std::chrono::system_clock::now().time_since_epoch())
189
20
                             .count();
190
20
    }
191
192
251
    void finish_report() {
193
251
        if (!operation_type.empty()) {
194
251
            double cost = duration();
195
251
            g_bvar_recycler_instance_last_round_recycle_elpased_ts.put(
196
251
                    {instance_id, operation_type}, cost);
197
251
            g_bvar_recycler_instance_recycle_round.put({instance_id, operation_type}, 1);
198
251
            g_bvar_recycler_instance_recycle_total_bytes_since_started.put(
199
251
                    {instance_id, operation_type}, total_recycled_data_size.load());
200
251
            g_bvar_recycler_instance_recycle_total_num_since_started.put(
201
251
                    {instance_id, operation_type}, total_recycled_num.load());
202
251
            LOG(INFO) << "recycle instance: " << instance_id
203
251
                      << ", operation type: " << operation_type << ", cost: " << cost
204
251
                      << " ms, total recycled num: " << total_recycled_num.load()
205
251
                      << ", total recycled data size: " << total_recycled_data_size.load()
206
251
                      << " bytes";
207
251
            if (cost != 0) {
208
234
                if (total_recycled_num.load() != 0) {
209
61
                    g_bvar_recycler_instance_recycle_time_per_resource.put(
210
61
                            {instance_id, operation_type}, cost / total_recycled_num.load());
211
61
                }
212
234
                g_bvar_recycler_instance_recycle_bytes_per_ms.put(
213
234
                        {instance_id, operation_type}, total_recycled_data_size.load() / cost);
214
234
            }
215
251
        }
216
251
    }
217
218
    // `is_begin` is used to initialize total num of items need to be recycled
219
1.05k
    void report(bool is_begin = false) {
220
1.05k
        if (!operation_type.empty()) {
221
            // is init
222
1.03k
            if (is_begin) {
223
3
                auto value = total_need_recycle_num.load();
224
225
3
                g_bvar_recycler_instance_last_round_to_recycle_bytes.put(
226
3
                        {instance_id, operation_type}, total_need_recycle_data_size.load());
227
3
                g_bvar_recycler_instance_last_round_to_recycle_num.put(
228
3
                        {instance_id, operation_type}, value);
229
1.02k
            } else {
230
1.02k
                g_bvar_recycler_instance_last_round_recycled_bytes.put(
231
1.02k
                        {instance_id, operation_type}, total_recycled_data_size.load());
232
1.02k
                g_bvar_recycler_instance_last_round_recycled_num.put({instance_id, operation_type},
233
1.02k
                                                                     total_recycled_num.load());
234
1.02k
            }
235
1.03k
        }
236
1.05k
    }
237
};
238
239
class TabletRecyclerMetricsContext : public RecyclerMetricsContext {
240
public:
241
131
    TabletRecyclerMetricsContext() : RecyclerMetricsContext("global_recycler", "recycle_tablet") {}
242
};
243
244
class SegmentRecyclerMetricsContext : public RecyclerMetricsContext {
245
public:
246
    SegmentRecyclerMetricsContext()
247
131
            : RecyclerMetricsContext("global_recycler", "recycle_segment") {}
248
};
249
250
struct OplogRecycleStats;
251
252
class InstanceRecycler {
253
public:
254
    struct PackedFileRecycleStats {
255
        int64_t num_scanned = 0;          // packed-file kv scanned
256
        int64_t num_corrected = 0;        // packed-file kv corrected
257
        int64_t num_deleted = 0;          // packed-file kv deleted
258
        int64_t num_failed = 0;           // packed-file kv failed
259
        int64_t bytes_deleted = 0;        // packed-file kv bytes deleted from txn-kv
260
        int64_t num_object_deleted = 0;   // packed-file objects deleted from storage (vault/HDFS)
261
        int64_t bytes_object_deleted = 0; // bytes deleted from storage objects
262
        int64_t rowset_scan_count = 0;    // rowset metas scanned during correction
263
    };
264
265
    explicit InstanceRecycler(std::shared_ptr<TxnKv> txn_kv, const InstanceInfoPB& instance,
266
                              RecyclerThreadPoolGroup thread_pool_group,
267
                              std::shared_ptr<TxnLazyCommitter> txn_lazy_committer);
268
    ~InstanceRecycler();
269
270
0
    std::string_view instance_id() const { return instance_id_; }
271
5
    const InstanceInfoPB& instance_info() const { return instance_info_; }
272
273
    // returns 0 for success otherwise error
274
    int init();
275
276
0
    void stop() { stopped_.store(true, std::memory_order_release); }
277
80
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
278
279
    // returns 0 for success otherwise error
280
    int do_recycle();
281
282
    // remove all kv and data in this instance, ONLY be called when instance has been deleted
283
    // returns 0 for success otherwise error
284
    int recycle_deleted_instance();
285
286
    // scan and recycle expired indexes:
287
    // 1. dropped table, dropped mv
288
    // 2. half-successtable/index when create
289
    // returns 0 for success otherwise error
290
    int recycle_indexes();
291
292
    // scan and recycle expired partitions:
293
    // 1. dropped parttion
294
    // 2. half-success partition when create
295
    // returns 0 for success otherwise error
296
    int recycle_partitions();
297
298
    // scan and recycle expired rowsets:
299
    // 1. prepare_rowset will produce recycle_rowset before uploading data to remote storage (memo)
300
    // 2. compaction will change the input rowsets to recycle_rowset
301
    // returns 0 for success otherwise error
302
    int recycle_rowsets();
303
304
    // like `recycle_rowsets`, but for versioned rowsets.
305
    int recycle_versioned_rowsets();
306
307
    // scan and recycle expired tmp rowsets:
308
    // 1. commit_rowset will produce tmp_rowset when finish upload data (load or compaction) to remote storage
309
    // returns 0 for success otherwise error
310
    int recycle_tmp_rowsets();
311
312
    /**
313
     * recycle all tablets belonging to the index specified by `index_id`
314
     *
315
     * @param partition_id if positive, only recycle tablets in this partition belonging to the specified index
316
     * @return 0 for success otherwise error
317
     */
318
    int recycle_tablets(int64_t table_id, int64_t index_id, RecyclerMetricsContext& ctx,
319
                        int64_t partition_id = -1);
320
321
    /**
322
     * recycle all rowsets belonging to the tablet specified by `tablet_id`
323
     *
324
     * @return 0 for success otherwise error
325
     */
326
    int recycle_tablet(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
327
328
    /**
329
     * like `recycle_tablet`, but for versioned tablet
330
     */
331
    int recycle_versioned_tablet(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
332
333
    // scan and recycle useless partition version kv
334
    int recycle_versions();
335
336
    // scan and recycle the orphan partitions
337
    int recycle_orphan_partitions();
338
339
    // scan and abort timeout txn label
340
    // returns 0 for success otherwise error
341
    int abort_timeout_txn();
342
343
    //scan and recycle expire txn label
344
    // returns 0 for success otherwise error
345
    int recycle_expired_txn_label();
346
347
    // scan and recycle finished or timeout copy jobs
348
    // returns 0 for success otherwise error
349
    int recycle_copy_jobs();
350
351
    // scan and recycle dropped internal stage
352
    // returns 0 for success otherwise error
353
    int recycle_stage();
354
355
    // scan and recycle expired stage objects
356
    // returns 0 for success otherwise error
357
    int recycle_expired_stage_objects();
358
359
    // scan and recycle operation logs
360
    // returns 0 for success otherwise error
361
    int recycle_operation_logs();
362
363
    // scan and recycle expired restore jobs
364
    // returns 0 for success otherwise error
365
    int recycle_restore_jobs();
366
367
    /**
368
     * Scan packed-file metadata, correct reference counters, and recycle unused packed files.
369
     *
370
     * @return 0 on success, non-zero error code otherwise
371
     */
372
    int recycle_packed_files();
373
374
    // scan and recycle snapshots
375
    // returns 0 for success otherwise error
376
    int recycle_cluster_snapshots();
377
378
    // scan and recycle ref rowsets for deleted instance
379
    // returns 0 for success otherwise error
380
    int recycle_ref_rowsets(bool* has_unrecycled_rowsets);
381
382
    bool check_recycle_tasks();
383
384
    int scan_and_statistics_indexes();
385
386
    int scan_and_statistics_partitions();
387
388
    int scan_and_statistics_rowsets();
389
390
    int scan_and_statistics_tmp_rowsets();
391
392
    int scan_and_statistics_abort_timeout_txn();
393
394
    int scan_and_statistics_expired_txn_label();
395
396
    int scan_and_statistics_copy_jobs();
397
398
    int scan_and_statistics_stage();
399
400
    int scan_and_statistics_expired_stage_objects();
401
402
    int scan_and_statistics_versions();
403
404
    int scan_and_statistics_restore_jobs();
405
406
    void scan_and_statistics_operation_logs();
407
408
    /**
409
     * Decode the key of a packed-file metadata record into the persisted object path.
410
     *
411
     * @param key raw key persisted in txn-kv
412
     * @param packed_path output object storage path referenced by the key
413
     * @return true if decoding succeeds, false otherwise
414
     */
415
    static bool decode_packed_file_key(std::string_view key, std::string* packed_path);
416
417
29
    void TEST_add_accessor(std::string_view id, std::shared_ptr<StorageVaultAccessor> accessor) {
418
29
        accessor_map_.insert({std::string(id), std::move(accessor)});
419
29
    }
420
421
    // Recycle snapshot meta and data, return 0 for success otherwise error.
422
    int recycle_snapshot_meta_and_data(const std::string& resource_id,
423
                                       Versionstamp snapshot_version,
424
                                       const SnapshotPB& snapshot_pb);
425
426
private:
427
    // returns 0 for success otherwise error
428
    int init_obj_store_accessors();
429
430
    // returns 0 for success otherwise error
431
    int init_storage_vault_accessors();
432
433
    /**
434
     * Scan key-value pairs between [`begin`, `end`), and perform `recycle_func` on each key-value pair.
435
     *
436
     * @param recycle_func defines how to recycle resources corresponding to a key-value pair. Returns 0 if the recycling is successful.
437
     * @param loop_done is called after `RangeGetIterator` has no next kv. Usually used to perform a batch recycling. Returns 0 if success. 
438
     * @return 0 if all corresponding resources are recycled successfully, otherwise non-zero
439
     */
440
    int scan_and_recycle(std::string begin, std::string_view end,
441
                         std::function<int(std::string_view k, std::string_view v)> recycle_func,
442
                         std::function<int()> loop_done = nullptr);
443
444
    // return 0 for success otherwise error
445
    int delete_rowset_data(const doris::RowsetMetaCloudPB& rs_meta_pb);
446
447
    // return 0 for success otherwise error
448
    // NOTE: this function ONLY be called when the file paths cannot be calculated
449
    int delete_rowset_data(const std::string& resource_id, int64_t tablet_id,
450
                           const std::string& rowset_id);
451
452
    // return 0 for success otherwise error
453
    int delete_rowset_data(const std::map<std::string, doris::RowsetMetaCloudPB>& rowsets,
454
                           RowsetRecyclingState type, RecyclerMetricsContext& metrics_context);
455
456
    // Decrement packed file ref counts for rowset segments.
457
    // Returns 0 for success, -1 for error.
458
    int decrement_packed_file_ref_counts(const doris::RowsetMetaCloudPB& rs_meta_pb);
459
460
    // Decrement packed file ref count for delete bitmap if it's stored in packed file.
461
    // Returns 0 for success, -1 for error.
462
    // If delete bitmap is not stored in packed file, this function does nothing and returns 0.
463
    // out_is_packed: if not null, will be set to true if delete bitmap is stored in packed file.
464
    int decrement_delete_bitmap_packed_file_ref_counts(int64_t tablet_id,
465
                                                       const std::string& rowset_id,
466
                                                       bool* out_is_packed);
467
468
    int delete_packed_file_and_kv(const std::string& packed_file_path,
469
                                  const std::string& packed_key,
470
                                  const cloud::PackedFileInfoPB& packed_info);
471
472
    /**
473
     * Get stage storage info from instance and init StorageVaultAccessor
474
     * @return 0 if accessor is successfully inited, 1 if stage not found, negative for error
475
     */
476
    int init_copy_job_accessor(const std::string& stage_id, const StagePB::StageType& stage_type,
477
                               std::shared_ptr<StorageVaultAccessor>* accessor);
478
479
    void register_recycle_task(const std::string& task_name, int64_t start_time);
480
481
    void unregister_recycle_task(const std::string& task_name);
482
483
    // for scan all tablets and statistics metrics
484
    int scan_tablets_and_statistics(int64_t tablet_id, int64_t index_id,
485
                                    RecyclerMetricsContext& metrics_context,
486
                                    int64_t partition_id = -1, bool is_empty_tablet = false);
487
488
    // for scan all rs of tablet and statistics metrics
489
    int scan_tablet_and_statistics(int64_t tablet_id, RecyclerMetricsContext& metrics_context);
490
491
    // Recycle operation log and the log keys. The log keys are specified by `raw_keys`.
492
    //
493
    // Both `operation_log` and `raw_keys` will be removed in the same transaction, to ensure atomicity.
494
    int recycle_operation_log(Versionstamp log_version, const std::vector<std::string>& raw_keys,
495
                              OperationLogPB operation_log,
496
                              OplogRecycleStats* oplog_stats = nullptr);
497
498
    // Recycle rowset meta and data, return 0 for success otherwise error
499
    //
500
    // This function will decrease the rowset ref count and remove the rowset meta and data if the ref count is 1.
501
    int recycle_rowset_meta_and_data(const RowsetDeleteTask& task);
502
503
    // Classify rowset task by ref_count, return 0 to add to batch delete, 1 if handled (ref>1), -1 on error
504
    int classify_rowset_task_by_ref_count(RowsetDeleteTask& task,
505
                                          std::vector<RowsetDeleteTask>& batch_delete_tasks);
506
507
    // Cleanup metadata for deleted rowsets, return 0 for success otherwise error
508
    int cleanup_rowset_metadata(const std::vector<RowsetDeleteTask>& tasks);
509
510
    // Whether the instance has any snapshots, return 0 for success otherwise error.
511
    int has_cluster_snapshots(bool* any);
512
513
    // Whether need to recycle versioned keys
514
    bool should_recycle_versioned_keys() const;
515
516
    /**
517
     * Parse the path of a packed-file fragment and output the owning tablet and rowset identifiers.
518
     *
519
     * @param path packed-file fragment path to decode
520
     * @param tablet_id output tablet identifier extracted from the path
521
     * @param rowset_id output rowset identifier extracted from the path
522
     * @return true if both identifiers are successfully parsed, false otherwise
523
     */
524
    static bool parse_packed_slice_path(std::string_view path, int64_t* tablet_id,
525
                                        std::string* rowset_id);
526
    // Check whether a rowset referenced by a packed file still exists in metadata.
527
    // @param stats optional recycle statistics collector.
528
    int check_rowset_exists(int64_t tablet_id, const std::string& rowset_id, bool* exists,
529
                            PackedFileRecycleStats* stats = nullptr);
530
    int check_recycle_and_tmp_rowset_exists(int64_t tablet_id, const std::string& rowset_id,
531
                                            int64_t txn_id, bool* recycle_exists, bool* tmp_exists);
532
    /**
533
     * Resolve which storage accessor should be used for a packed file.
534
     *
535
     * @param hint preferred storage resource identifier persisted with the file
536
     * @return pair of the resolved resource identifier and accessor; the accessor can be null if unavailable
537
     */
538
    std::pair<std::string, std::shared_ptr<StorageVaultAccessor>> resolve_packed_file_accessor(
539
            const std::string& hint);
540
    // Recompute packed-file counters and lifecycle state after validating contained fragments.
541
    // @param stats optional recycle statistics collector.
542
    int correct_packed_file_info(cloud::PackedFileInfoPB* packed_info, bool* changed,
543
                                 const std::string& packed_file_path,
544
                                 PackedFileRecycleStats* stats = nullptr);
545
    // Correct and recycle a single packed-file record, updating metadata and accounting statistics.
546
    // @param stats optional recycle statistics collector.
547
    int process_single_packed_file(const std::string& packed_key,
548
                                   const std::string& packed_file_path,
549
                                   PackedFileRecycleStats* stats);
550
    // Process a packed-file KV while scanning and aggregate recycling statistics.
551
    int handle_packed_file_kv(std::string_view key, std::string_view value,
552
                              PackedFileRecycleStats* stats, int* ret);
553
554
    // Abort the transaction/job associated with a rowset that is about to be recycled.
555
    // This function is called during rowset recycling to prevent data loss by ensuring that
556
    // the transaction/job cannot be committed after its rowset data has been deleted.
557
    //
558
    // Scenario:
559
    // When recycler detects an expired prepared rowset (e.g., from a failed load transaction/job),
560
    // it needs to recycle the rowset data. However, if the transaction/job is still active and gets
561
    // committed after the data is deleted, it would lead to data loss - the transaction/job would
562
    // reference non-existent data.
563
    //
564
    // Solution:
565
    // Before recycling the rowset data, this function aborts the associated transaction/job to ensure
566
    // it cannot be committed. This guarantees that:
567
    // 1. The transaction/job state is marked as ABORTED
568
    // 2. Any subsequent commit_rowset/commit_txn attempts will fail
569
    // 3. The rowset data can be safely deleted without risk of data loss
570
    //
571
    // Parameters:
572
    //   txn_id: The transaction/job ID associated with the rowset to be recycled
573
    //
574
    // Returns:
575
    //   0 on success, -1 on failure
576
    int abort_txn_for_related_rowset(int64_t txn_id);
577
    int abort_job_for_related_rowset(const RowsetMetaCloudPB& rowset_meta);
578
579
    template <typename T>
580
    int abort_txn_or_job_for_recycle(T& rowset_meta_pb);
581
582
private:
583
    std::atomic_bool stopped_ {false};
584
    std::shared_ptr<TxnKv> txn_kv_;
585
    std::string instance_id_;
586
    InstanceInfoPB instance_info_;
587
588
    // TODO(plat1ko): Add new accessor to map in runtime for new created storage vaults
589
    std::unordered_map<std::string, std::shared_ptr<StorageVaultAccessor>> accessor_map_;
590
    using InvertedIndexInfo =
591
            std::pair<InvertedIndexStorageFormatPB, std::vector<std::pair<int64_t, std::string>>>;
592
593
    class InvertedIndexIdCache;
594
    std::unique_ptr<InvertedIndexIdCache> inverted_index_id_cache_;
595
596
    std::mutex recycled_tablets_mtx_;
597
    // Store recycled tablets, we can skip deleting rowset data of these tablets because these data has already been deleted.
598
    std::unordered_set<int64_t> recycled_tablets_;
599
600
    std::mutex recycle_tasks_mutex;
601
    // <task_name, start_time>>
602
    std::map<std::string, int64_t> running_recycle_tasks;
603
604
    RecyclerThreadPoolGroup _thread_pool_group;
605
606
    std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
607
    std::shared_ptr<SnapshotManager> snapshot_manager_;
608
    std::shared_ptr<DeleteBitmapLockWhiteList> delete_bitmap_lock_white_list_;
609
    std::shared_ptr<ResourceManager> resource_mgr_;
610
611
    TabletRecyclerMetricsContext tablet_metrics_context_;
612
    SegmentRecyclerMetricsContext segment_metrics_context_;
613
};
614
615
struct OperationLogReferenceInfo {
616
    bool referenced_by_instance = false;
617
    bool referenced_by_snapshot = false;
618
    Versionstamp referenced_snapshot_timestamp;
619
};
620
621
struct OplogRecycleStats {
622
    // Total oplog count scanned per round
623
    std::atomic<int64_t> total_num {0};
624
    // Oplogs not recycled this round (per round, written to mBvarStatus)
625
    std::atomic<int64_t> not_recycled_num {0};
626
    // Recycle failures (per round, accumulated to mBvarIntAdder at end)
627
    std::atomic<int64_t> failed_num {0};
628
    // Per-oplog-type recycled counts (incremented after successful commit)
629
    std::atomic<int64_t> recycled_commit_partition {0};
630
    std::atomic<int64_t> recycled_drop_partition {0};
631
    std::atomic<int64_t> recycled_commit_index {0};
632
    std::atomic<int64_t> recycled_drop_index {0};
633
    std::atomic<int64_t> recycled_update_tablet {0};
634
    std::atomic<int64_t> recycled_compaction {0};
635
    std::atomic<int64_t> recycled_schema_change {0};
636
    std::atomic<int64_t> recycled_commit_txn {0};
637
};
638
639
// Helper class to check if operation logs can be recycled based on snapshots and versionstamps
640
class OperationLogRecycleChecker {
641
public:
642
    OperationLogRecycleChecker(std::string_view instance_id, TxnKv* txn_kv,
643
                               const InstanceInfoPB& instance_info)
644
35
            : instance_id_(instance_id), txn_kv_(txn_kv), instance_info_(instance_info) {}
645
646
    // Initialize the checker by loading snapshots and setting max version stamp
647
    int init();
648
649
    // Check if an operation log can be recycled
650
    bool can_recycle(const Versionstamp& log_versionstamp, int64_t log_min_timestamp,
651
                     OperationLogReferenceInfo* reference_info) const;
652
653
0
    Versionstamp max_versionstamp() const { return max_versionstamp_; }
654
655
28
    const std::vector<std::pair<SnapshotPB, Versionstamp>>& get_snapshots() const {
656
28
        return snapshots_;
657
28
    }
658
659
private:
660
    std::string_view instance_id_;
661
    TxnKv* txn_kv_;
662
    const InstanceInfoPB& instance_info_;
663
    Versionstamp max_versionstamp_;
664
    Versionstamp source_snapshot_versionstamp_;
665
    std::map<Versionstamp, size_t> snapshot_indexes_;
666
    std::vector<std::pair<SnapshotPB, Versionstamp>> snapshots_;
667
};
668
669
class SnapshotDataSizeCalculator {
670
public:
671
    SnapshotDataSizeCalculator(std::string_view instance_id, std::shared_ptr<TxnKv> txn_kv)
672
29
            : instance_id_(instance_id), txn_kv_(std::move(txn_kv)) {}
673
674
    void init(const std::vector<std::pair<SnapshotPB, Versionstamp>>& snapshots);
675
676
    int calculate_operation_log_data_size(const std::string_view& log_key,
677
                                          OperationLogPB& operation_log,
678
                                          OperationLogReferenceInfo& reference_info);
679
680
    int save_snapshot_data_size_with_retry();
681
682
private:
683
    int get_all_index_partitions(int64_t db_id, int64_t table_id, int64_t index_id,
684
                                 std::vector<int64_t>* partition_ids);
685
    int get_index_partition_data_size(int64_t db_id, int64_t table_id, int64_t index_id,
686
                                      int64_t partition_id, int64_t* data_size);
687
    int save_operation_log(const std::string_view& log_key, OperationLogPB& operation_log);
688
    int save_snapshot_data_size();
689
690
    std::string_view instance_id_;
691
    std::shared_ptr<TxnKv> txn_kv_;
692
693
    int64_t instance_retained_data_size_ = 0;
694
    std::map<Versionstamp, int64_t> retained_data_size_;
695
    std::set<std::string> calculated_partitions_;
696
};
697
698
} // namespace doris::cloud