Coverage Report

Created: 2026-09-08 18:26

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