/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 |