Coverage Report

Created: 2026-10-28 23:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/cloud/src/recycler/checker.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 "resource-manager/resource_manager.h"
21
#if defined(USE_LIBCPP) && _LIBCPP_ABI_VERSION <= 1
22
#define _LIBCPP_ABI_INCOMPLETE_TYPES_IN_DEQUE
23
#endif
24
#include <atomic>
25
#include <condition_variable>
26
#include <cstdint>
27
#include <deque>
28
#include <functional>
29
#include <map>
30
#include <mutex>
31
#include <optional>
32
#include <string_view>
33
#include <thread>
34
#include <tuple>
35
#include <unordered_map>
36
#include <unordered_set>
37
38
#include "meta-store/txn_kv_error.h"
39
#include "recycler/storage_vault_accessor.h"
40
#include "recycler/white_black_list.h"
41
#include "snapshot/snapshot_manager.h"
42
43
namespace doris {
44
class RowsetMetaCloudPB;
45
} // namespace doris
46
47
namespace doris::cloud {
48
class StorageVaultAccessor;
49
class InstanceChecker;
50
class TxnKv;
51
class InstanceInfoPB;
52
class ObjectStoreInfoPB;
53
class HdfsVaultInfo;
54
55
struct PendingTableStreamDrop {
56
    int64_t base_db_id;
57
    int64_t base_table_id;
58
    int64_t stream_db_id;
59
60
    bool matches(int64_t offset_base_db_id, int64_t offset_base_table_id,
61
2
                 int64_t offset_stream_db_id) const {
62
2
        return base_db_id == offset_base_db_id && base_table_id == offset_base_table_id &&
63
2
               stream_db_id == offset_stream_db_id;
64
2
    }
65
};
66
67
TxnErrorCode collect_pending_table_stream_drops(
68
        const std::shared_ptr<TxnKv>& txn_kv, std::string_view instance_id,
69
        std::unordered_map<int64_t, PendingTableStreamDrop>* pending_drops);
70
71
struct SharedResourceOwner {
72
    std::string instance_id;
73
    std::string resource_id;
74
};
75
76
// resource id -> all instance/resource pairs that use the same physical storage.
77
using SharedResourceOwners = std::unordered_map<std::string, std::vector<SharedResourceOwner>>;
78
79
// Tells which instance/resource pairs write into the same physical storage.
80
// Resource IDs are local to an instance, so the physical storage location is
81
// matched instead: descendants can write into inherited resources even after
82
// decoupling removes their source_instance_id.
83
//
84
// One index is shared by all checkers of a scheduling round. It is loaded
85
// lazily on the first query, and the result (success or failure) is cached
86
// until the round ends. A new round must create a new index to see changes.
87
class SharedStorageIndex {
88
public:
89
    // If `instances` is given, it is used as the full instance list instead of
90
    // scanning all instances from the kv store.
91
    explicit SharedStorageIndex(
92
            std::shared_ptr<TxnKv> txn_kv,
93
            std::optional<std::vector<InstanceInfoPB>> instances = std::nullopt);
94
95
    // For every resource of `instance_id`, fill in all owners sharing its physical storage.
96
    // A resource without identifiable storage location is only owned by itself.
97
    // Return 0 on success, -1 if the index failed to load or the instance is unknown.
98
    // Never fills partial owners on failure.
99
    int get_shared_resource_owners(std::string_view instance_id, SharedResourceOwners* owners);
100
101
private:
102
    // Physical storage location. All fields must match exactly, overlapping
103
    // prefixes are not merged. Credentials are deliberately not part of it.
104
    struct StorageLocation {
105
        std::string type;     // "s3" or "hdfs"
106
        std::string endpoint; // S3 endpoint, or HDFS fs_name
107
        std::string bucket;   // empty for HDFS
108
        std::string prefix;
109
110
0
        bool operator<(const StorageLocation& other) const {
111
0
            return std::tie(type, endpoint, bucket, prefix) <
112
0
                   std::tie(other.type, other.endpoint, other.bucket, other.prefix);
113
0
        }
114
    };
115
116
    // Resources without connection info have no location, so that they never
117
    // match unrelated resources.
118
    using ResourceLocations = std::map<std::string, std::optional<StorageLocation>>;
119
120
    static std::optional<StorageLocation> s3_location(const ObjectStoreInfoPB& obj_info);
121
    static std::optional<StorageLocation> hdfs_location(const HdfsVaultInfo& hdfs_info);
122
123
    void ensure_loaded();
124
    int load_all_instances();
125
    int load_instance(const InstanceInfoPB& instance);
126
    int load_storage_vaults(const std::string& instance_id);
127
    void add_resource(const std::string& instance_id, const std::string& resource_id,
128
                      std::optional<StorageLocation> location);
129
130
    std::shared_ptr<TxnKv> txn_kv_;
131
    // Consumed by the first load.
132
    std::optional<std::vector<InstanceInfoPB>> preloaded_instances_;
133
134
    std::once_flag load_once_;
135
    int load_status_ = -1;
136
    // instance_id -> (resource_id -> location)
137
    std::unordered_map<std::string, ResourceLocations> instance_resource_locations_;
138
    // location -> all instance/resource pairs located there
139
    std::map<StorageLocation, std::vector<SharedResourceOwner>> location_to_owners_;
140
};
141
142
class Checker {
143
public:
144
    explicit Checker(std::shared_ptr<TxnKv> txn_kv);
145
    ~Checker();
146
147
    int start();
148
149
    void stop();
150
320
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
151
152
private:
153
    void lease_check_jobs();
154
    void inspect_instance_check_interval();
155
    void do_inspect(const InstanceInfoPB& instance);
156
157
private:
158
    friend class RecyclerServiceImpl;
159
160
    std::shared_ptr<TxnKv> txn_kv_;
161
    std::shared_ptr<ResourceManager> resource_mgr_;
162
    std::atomic_bool stopped_ {false};
163
    std::string ip_port_;
164
    std::vector<std::thread> workers_;
165
166
    std::mutex mtx_;
167
    // notify check workers
168
    std::condition_variable pending_instance_cond_;
169
    struct PendingInstance {
170
        InstanceInfoPB instance;
171
        std::shared_ptr<SharedStorageIndex> shared_storage_index;
172
    };
173
    std::deque<PendingInstance> pending_instance_queue_;
174
    // instance_id -> enqueue_timestamp
175
    std::unordered_map<std::string, long> pending_instance_map_;
176
    std::unordered_map<std::string, std::shared_ptr<InstanceChecker>> working_instance_map_;
177
    // notify instance scanner and lease thread
178
    std::condition_variable notifier_;
179
180
    WhiteBlackList instance_filter_;
181
};
182
183
class InstanceChecker {
184
public:
185
    explicit InstanceChecker(std::shared_ptr<TxnKv> txn_kv, const std::string& instance_id,
186
                             std::shared_ptr<ResourceManager> resource_mgr = nullptr,
187
                             std::shared_ptr<SharedStorageIndex> shared_storage_index = nullptr);
188
    // Return 0 if success, otherwise error
189
    int init(const InstanceInfoPB& instance);
190
    // Return 0 if success.
191
    // Return 1 if data leak is identified.
192
    // Return negative if a temporary error occurred during the check process.
193
    int do_inverted_check();
194
195
    // Return 0 if success.
196
    // Return 1 if data loss is identified.
197
    // Return negative if a temporary error occurred during the check process.
198
    int do_check();
199
200
    // Return 0 if success.
201
    // Return 1 if delete bitmap leak is identified.
202
    // Return negative if a temporary error occurred during the check process.
203
    int do_delete_bitmap_inverted_check();
204
205
    // version = 1 : https://github.com/apache/doris/pull/40204
206
    // checks if https://github.com/apache/doris/pull/40204 works as expected
207
    // the stale delete bitmap will be cleared in MS when BE delete expired stale rowsets
208
    // NOTE: stale rowsets will be lost after BE restarts, so there may be some stale delete bitmaps
209
    // which will not be cleared.
210
    // version = 2 : https://github.com/apache/doris/pull/49822
211
    int do_delete_bitmap_storage_optimize_check(int version = 2);
212
213
    int do_mow_job_key_check();
214
215
    int do_tablet_stats_key_check();
216
217
    int do_restore_job_check();
218
219
    int do_txn_key_check();
220
221
    // check table and partition version key
222
    // table version should be greater than the versions of all its partitions
223
    // Return 0 if success, otherwise error
224
    int do_version_key_check();
225
226
    // Return 0 if success.
227
    // Return 1 if meta rowset key leak or loss is identified.
228
    // Return negative if a temporary error occurred during the check process.
229
    int do_meta_rowset_key_check();
230
231
    // Return 0 if success.
232
    // Return 1 if snapshot key and file leak or loss is identified.
233
    // Return negative if a temporary error occurred during the check process.
234
    int do_snapshots_check();
235
236
    // Return 0 if success.
237
    // Return 1 if mvcc meta key and data leak or loss is identified.
238
    // Return negative if a temporary error occurred during the check process.
239
    int do_mvcc_meta_key_check();
240
241
    // Return 0 if all Table Stream mappings and offsets are consistent.
242
    // Return 1 if an inconsistent mapping or offset is identified.
243
    // Return negative if a temporary error occurred during the check process.
244
    int do_table_stream_check();
245
246
    // Return 0 if success.
247
    // Return 1 if packed file metadata leak or loss is identified.
248
    // Return negative if a temporary error occurred during the check process.
249
    int do_packed_file_check();
250
251
    StorageVaultAccessor* get_accessor(const std::string& id);
252
253
0
    ResourceManager* resource_mgr() const { return resource_mgr_.get(); }
254
255
0
    SharedStorageIndex* shared_storage_index() const { return shared_storage_index_.get(); }
256
257
    void get_all_accessor(std::vector<StorageVaultAccessor*>* accessors);
258
259
0
    std::string_view instance_id() const { return instance_id_; }
260
261
0
    void TEST_add_accessor(std::string_view id, std::shared_ptr<StorageVaultAccessor> accessor) {
262
0
        accessor_map_.insert({std::string(id), std::move(accessor)});
263
0
    }
264
265
    // If there are multiple buckets, return the minimum lifecycle; if there are no buckets (i.e.
266
    // all accessors are HdfsAccessor), return INT64_MAX.
267
    // Return 0 if success, otherwise error
268
    int get_bucket_lifecycle(int64_t* lifecycle_days);
269
0
    void stop() { stopped_.store(true, std::memory_order_release); }
270
5.86k
    bool stopped() const { return stopped_.load(std::memory_order_acquire); }
271
272
private:
273
    struct RowsetIndexesFormatV1 {
274
        std::string rowset_id;
275
        std::unordered_set<int64_t> segment_ids;
276
        std::unordered_set<std::string> index_ids;
277
    };
278
279
    struct RowsetIndexesFormatV2 {
280
        std::string rowset_id;
281
        std::unordered_set<int64_t> segment_ids;
282
    };
283
284
private:
285
    // returns 0 for success otherwise error
286
    int init_obj_store_accessors(const InstanceInfoPB& instance);
287
288
    // returns 0 for success otherwise error
289
    int init_storage_vault_accessors(const InstanceInfoPB& instance);
290
291
    int traverse_mow_tablet(const std::function<int(int64_t, bool)>& check_func);
292
    int traverse_rowset_delete_bitmaps(
293
            int64_t tablet_id, std::string rowset_id,
294
            const std::function<int(int64_t, std::string_view, int64_t, int64_t)>& callback);
295
    int collect_tablet_rowsets(
296
            int64_t tablet_id,
297
            const std::function<void(const doris::RowsetMetaCloudPB&)>& collect_cb);
298
    int collect_unexpired_job_tmp_rowsets(
299
            std::unordered_map<int64_t, std::unordered_set<std::string>>& tmp_rowsets);
300
    int get_pending_delete_bitmap_keys(int64_t tablet_id,
301
                                       std::unordered_set<std::string>& pending_delete_bitmaps);
302
    int check_delete_bitmap_storage_optimize_v2(int64_t tablet_id, bool has_sequence_col,
303
                                                int64_t& abnormal_rowsets_num);
304
305
    int check_inverted_index_file_storage_format_v1(int64_t tablet_id, const std::string& file_path,
306
                                                    const std::string& rowset_info,
307
                                                    RowsetIndexesFormatV1& rowset_index_cache_v1);
308
309
    int check_inverted_index_file_storage_format_v2(int64_t tablet_id, const std::string& file_path,
310
                                                    const std::string& rowset_info,
311
                                                    RowsetIndexesFormatV2& rowset_index_cache_v2);
312
313
    // Return 0 if success.
314
    // Return 1 if key loss is abnormal.
315
    // Return negative if a temporary error occurred during the check process.
316
    int check_stats_tablet_key(std::string_view key, std::string_view value);
317
318
    // Return 0 if success.
319
    // Return 1 if key loss is identified.
320
    // Return negative if a temporary error occurred during the check process.
321
    int check_stats_tablet_key_exists(std::string_view key, std::string_view value);
322
323
    // Return 0 if success.
324
    // Return 1 if key leak is identified.
325
    // Return negative if a temporary error occurred during the check process.
326
    int check_stats_tablet_key_leaked(std::string_view key, std::string_view value);
327
    int check_txn_info_key(std::string_view key, std::string_view value);
328
329
    int check_txn_label_key(std::string_view key, std::string_view value);
330
331
    int check_txn_index_key(std::string_view key, std::string_view value);
332
333
    int check_txn_running_key(std::string_view key, std::string_view value);
334
335
    // Only check whether the meta rowset key is leak
336
    // in do_inverted_check() function, check whether the key is lost by comparing data file with key
337
    // Return 0 if success.
338
    // Return 1 if meta rowset key leak is identified.
339
    // Return negative if a temporary error occurred during the check process.
340
    int check_meta_rowset_key(std::string_view key, std::string_view value);
341
342
    // if TxnInfoKey's finish time > current time, it should not find tmp rowset
343
    // Return 0 if success.
344
    // Return 1 if meta tmp rowset key is abnormal.
345
    // Return negative if a temporary error occurred during the check process.
346
    int check_meta_tmp_rowset_key(std::string_view key, std::string_view value);
347
348
    /**
349
     * It is used to scan the key in the range from start_key to end_key 
350
     * and then perform handle operations on each group of kv
351
     * 
352
     * @param start_key Range begining. Note that this function will modify the `start_key`
353
     * @param end_key Range ending
354
     * @param handle_kv Operations on kv
355
     * @return code int 0 for success to scan and hanle, 1 for success to scan but handle abnormally, -1 for failed to handle 
356
     */
357
    int scan_and_handle_kv(std::string& start_key, const std::string& end_key,
358
                           std::function<int(std::string_view, std::string_view)> handle_kv);
359
360
    std::atomic_bool stopped_ {false};
361
    std::shared_ptr<TxnKv> txn_kv_;
362
    std::string instance_id_;
363
    // id -> accessor
364
    std::unordered_map<std::string, std::shared_ptr<StorageVaultAccessor>> accessor_map_;
365
    std::shared_ptr<SnapshotManager> snapshot_manager_;
366
    std::shared_ptr<ResourceManager> resource_mgr_;
367
    bool table_stream_versioned_write_ {false};
368
    std::shared_ptr<SharedStorageIndex> shared_storage_index_;
369
};
370
371
} // namespace doris::cloud