Coverage Report

Created: 2026-10-31 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/cloud/src/recycler/checker.cpp
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
#include "recycler/checker.h"
19
20
#include <aws/s3/S3Client.h>
21
#include <aws/s3/model/ListObjectsV2Request.h>
22
#include <butil/endpoint.h>
23
#include <butil/strings/string_split.h>
24
#include <fmt/core.h>
25
#include <gen_cpp/cloud.pb.h>
26
#include <gen_cpp/olap_file.pb.h>
27
#include <glog/logging.h>
28
29
#include <algorithm>
30
#include <chrono>
31
#include <climits>
32
#include <cstdint>
33
#include <functional>
34
#include <memory>
35
#include <mutex>
36
#include <numeric>
37
#include <optional>
38
#include <sstream>
39
#include <string_view>
40
#include <unordered_map>
41
#include <unordered_set>
42
#include <vector>
43
44
#include "common/bvars.h"
45
#include "common/config.h"
46
#include "common/defer.h"
47
#include "common/encryption_util.h"
48
#include "common/logging.h"
49
#include "common/rowset_segment_id.h"
50
#include "common/util.h"
51
#include "cpp/sync_point.h"
52
#include "meta-service/meta_service.h"
53
#include "meta-service/meta_service_schema.h"
54
#include "meta-service/meta_service_tablet_stats.h"
55
#include "meta-store/blob_message.h"
56
#include "meta-store/clone_chain_reader.h"
57
#include "meta-store/keys.h"
58
#include "meta-store/meta_reader.h"
59
#include "meta-store/txn_kv.h"
60
#include "meta-store/versioned_value.h"
61
#include "snapshot/snapshot_manager_factory.h"
62
#ifdef ENABLE_HDFS_STORAGE_VAULT
63
#include "recycler/hdfs_accessor.h"
64
#endif
65
#include "recycler/s3_accessor.h"
66
#include "recycler/storage_vault_accessor.h"
67
#ifdef UNIT_TEST
68
#include "../test/mock_accessor.h"
69
#endif
70
#include "recycler/recycler.h"
71
#include "recycler/util.h"
72
73
namespace doris::cloud {
74
namespace config {
75
extern int32_t brpc_listen_port;
76
extern int32_t scan_instances_interval_seconds;
77
extern int32_t recycle_job_lease_expired_ms;
78
extern int32_t recycle_concurrency;
79
extern std::vector<std::string> recycle_whitelist;
80
extern std::vector<std::string> recycle_blacklist;
81
extern bool enable_inverted_check;
82
} // namespace config
83
84
using namespace std::chrono;
85
86
TxnErrorCode collect_pending_table_stream_drops(
87
        const std::shared_ptr<TxnKv>& txn_kv, std::string_view instance_id,
88
4
        std::unordered_map<int64_t, PendingTableStreamDrop>* pending_drops) {
89
4
    pending_drops->clear();
90
4
    const std::string log_key = versioned::log_key(instance_id);
91
4
    const std::string begin_key = encode_versioned_key(log_key, Versionstamp::min());
92
4
    const std::string end_key = encode_versioned_key(log_key, Versionstamp::max());
93
4
    std::unique_ptr<BlobIterator> iter = blob_get_range(txn_kv, begin_key, end_key, true);
94
7
    for (; iter->valid(); iter->next()) {
95
5
        OperationLogPB operation_log;
96
5
        if (!iter->parse_value(&operation_log)) {
97
1
            LOG_WARNING("failed to parse OperationLogPB while checking Table Stream metadata")
98
1
                    .tag("instance_id", instance_id)
99
1
                    .tag("key", hex(iter->key()));
100
1
            return TxnErrorCode::TXN_INVALID_DATA;
101
1
        }
102
4
        if (!operation_log.has_drop_index() ||
103
4
            operation_log.drop_index().object_type() != IndexObjectTypePB::TABLE_STREAM) {
104
1
            continue;
105
1
        }
106
107
3
        const DropIndexLogPB& drop_index = operation_log.drop_index();
108
3
        if (!drop_index.has_db_id() || !drop_index.has_table_id() ||
109
3
            !drop_index.has_stream_db_id()) {
110
0
            LOG_WARNING("Table Stream DropIndexLogPB is missing its binding")
111
0
                    .tag("instance_id", instance_id)
112
0
                    .tag("operation_log", operation_log.ShortDebugString());
113
0
            return TxnErrorCode::TXN_INVALID_DATA;
114
0
        }
115
3
        PendingTableStreamDrop drop {.base_db_id = drop_index.db_id(),
116
3
                                     .base_table_id = drop_index.table_id(),
117
3
                                     .stream_db_id = drop_index.stream_db_id()};
118
3
        for (int64_t stream_id : drop_index.index_ids()) {
119
3
            auto [existing, inserted] = pending_drops->emplace(stream_id, drop);
120
3
            if (!inserted && (existing->second.base_db_id != drop.base_db_id ||
121
1
                              existing->second.base_table_id != drop.base_table_id ||
122
1
                              existing->second.stream_db_id != drop.stream_db_id)) {
123
1
                LOG_WARNING("conflicting pending Table Stream drops")
124
1
                        .tag("instance_id", instance_id)
125
1
                        .tag("stream_id", stream_id);
126
1
                return TxnErrorCode::TXN_INVALID_DATA;
127
1
            }
128
3
        }
129
3
    }
130
2
    return iter->error_code();
131
4
}
132
133
Checker::Checker(std::shared_ptr<TxnKv> txn_kv)
134
5
        : txn_kv_(std::move(txn_kv)), resource_mgr_(std::make_shared<ResourceManager>(txn_kv_)) {
135
5
    ip_port_ = std::string(butil::my_ip_cstr()) + ":" + std::to_string(config::brpc_listen_port);
136
5
}
137
138
5
Checker::~Checker() {
139
5
    if (!stopped()) {
140
1
        stop();
141
1
    }
142
5
}
143
144
4
int Checker::start() {
145
4
    DCHECK(txn_kv_);
146
4
    instance_filter_.reset(config::recycle_whitelist, config::recycle_blacklist);
147
148
    // launch instance scanner
149
4
    auto scanner_func = [this]() {
150
4
        std::this_thread::sleep_for(
151
4
                std::chrono::seconds(config::recycler_sleep_before_scheduling_seconds));
152
8
        while (!stopped()) {
153
4
            std::vector<InstanceInfoPB> instances;
154
4
            if (get_all_instances(txn_kv_.get(), instances) != 0) {
155
                // Do not publish a partial owner index after a failed scan.
156
0
                instances.clear();
157
0
            }
158
4
            LOG(INFO) << "Checker get instances: " << [&instances] {
159
4
                std::stringstream ss;
160
30
                for (auto& i : instances) ss << ' ' << i.instance_id();
161
4
                return ss.str();
162
4
            }();
163
4
            if (!instances.empty()) {
164
3
                auto shared_storage_index =
165
3
                        std::make_shared<SharedStorageIndex>(txn_kv_, instances);
166
                // enqueue instances
167
3
                std::lock_guard lock(mtx_);
168
30
                for (auto& instance : instances) {
169
30
                    if (instance_filter_.filter_out(instance.instance_id())) continue;
170
30
                    if (instance.status() == InstanceInfoPB::DELETED) continue;
171
30
                    using namespace std::chrono;
172
30
                    auto enqueue_time_s =
173
30
                            duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
174
30
                    auto [_, success] =
175
30
                            pending_instance_map_.insert({instance.instance_id(), enqueue_time_s});
176
                    // skip instance already in pending queue
177
30
                    if (success) {
178
30
                        pending_instance_queue_.push_back(
179
30
                                {std::move(instance), shared_storage_index});
180
30
                    }
181
30
                }
182
3
                pending_instance_cond_.notify_all();
183
3
            }
184
4
            {
185
4
                std::unique_lock lock(mtx_);
186
4
                notifier_.wait_for(lock,
187
4
                                   std::chrono::seconds(config::scan_instances_interval_seconds),
188
8
                                   [&]() { return stopped(); });
189
4
            }
190
4
        }
191
4
    };
192
4
    workers_.emplace_back(scanner_func);
193
    // Launch lease thread
194
4
    workers_.emplace_back([this] { lease_check_jobs(); });
195
    // Launch inspect thread
196
4
    workers_.emplace_back([this] { inspect_instance_check_interval(); });
197
198
    // launch check workers
199
8
    auto checker_func = [this]() {
200
38
        while (!stopped()) {
201
            // fetch instance to check
202
37
            InstanceInfoPB instance;
203
37
            std::shared_ptr<SharedStorageIndex> shared_storage_index;
204
37
            long enqueue_time_s = 0;
205
37
            {
206
37
                std::unique_lock lock(mtx_);
207
50
                pending_instance_cond_.wait(lock, [&]() -> bool {
208
50
                    return !pending_instance_queue_.empty() || stopped();
209
50
                });
210
37
                if (stopped()) {
211
7
                    return;
212
7
                }
213
30
                instance = std::move(pending_instance_queue_.front().instance);
214
30
                shared_storage_index =
215
30
                        std::move(pending_instance_queue_.front().shared_storage_index);
216
30
                pending_instance_queue_.pop_front();
217
30
                enqueue_time_s = pending_instance_map_[instance.instance_id()];
218
30
                pending_instance_map_.erase(instance.instance_id());
219
30
            }
220
0
            const auto& instance_id = instance.instance_id();
221
30
            {
222
30
                std::lock_guard lock(mtx_);
223
                // skip instance in recycling
224
30
                if (working_instance_map_.count(instance_id)) {
225
0
                    LOG(INFO) << "checker skip instance already working, instance_id="
226
0
                              << instance_id;
227
0
                    continue;
228
0
                }
229
30
            }
230
30
            auto checker = std::make_shared<InstanceChecker>(txn_kv_, instance.instance_id(),
231
30
                                                             resource_mgr_,
232
30
                                                             std::move(shared_storage_index));
233
30
            if (checker->init(instance) != 0) {
234
0
                LOG(WARNING) << "failed to init instance checker, instance_id="
235
0
                             << instance.instance_id();
236
0
                continue;
237
0
            }
238
30
            std::string check_job_key;
239
30
            job_check_key({instance.instance_id()}, &check_job_key);
240
30
            LOG(INFO) << "checker picked instance, instance_id=" << instance.instance_id()
241
30
                      << " enqueue_time_s=" << enqueue_time_s;
242
30
            int ret = prepare_instance_recycle_job(txn_kv_.get(), check_job_key,
243
30
                                                   instance.instance_id(), ip_port_,
244
30
                                                   config::check_object_interval_seconds * 1000);
245
30
            if (ret != 0) { // Prepare failed
246
20
                LOG(WARNING) << "checker prepare job failed, instance_id=" << instance.instance_id()
247
20
                             << " ret=" << ret;
248
20
                continue;
249
20
            } else {
250
10
                std::lock_guard lock(mtx_);
251
10
                working_instance_map_.emplace(instance_id, checker);
252
10
            }
253
10
            if (stopped()) return;
254
10
            using namespace std::chrono;
255
10
            auto ctime_ms =
256
10
                    duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
257
10
            g_bvar_checker_enqueue_cost_s.put(instance_id, ctime_ms / 1000 - enqueue_time_s);
258
259
10
            bool success {true};
260
20
            auto log_progress = [&](std::string_view stage) {
261
20
                LOG(INFO) << "checker progress, instance_id=" << instance_id << " stage=" << stage;
262
20
            };
263
264
10
            log_progress("do_check");
265
10
            if (int ret = checker->do_check(); ret != 0) {
266
0
                success = false;
267
0
            }
268
269
10
            if (config::enable_inverted_check) {
270
0
                log_progress("do_inverted_check");
271
0
                if (int ret = checker->do_inverted_check(); ret != 0) {
272
0
                    success = false;
273
0
                }
274
0
            }
275
276
10
            if (config::enable_delete_bitmap_inverted_check) {
277
0
                log_progress("do_delete_bitmap_inverted_check");
278
0
                if (int ret = checker->do_delete_bitmap_inverted_check(); ret != 0) {
279
0
                    success = false;
280
0
                }
281
0
            }
282
283
10
            if (config::enable_mow_job_key_check) {
284
0
                log_progress("do_mow_job_key_check");
285
0
                if (int ret = checker->do_mow_job_key_check(); ret != 0) {
286
0
                    success = false;
287
0
                }
288
0
            }
289
290
10
            if (config::enable_tablet_stats_key_check) {
291
0
                log_progress("do_tablet_stats_key_check");
292
0
                if (int ret = checker->do_tablet_stats_key_check(); ret != 0) {
293
0
                    success = false;
294
0
                }
295
0
            }
296
297
10
            if (config::enable_restore_job_check) {
298
0
                log_progress("do_restore_job_check");
299
0
                if (int ret = checker->do_restore_job_check(); ret != 0) {
300
0
                    success = false;
301
0
                }
302
0
            }
303
304
10
            if (config::enable_txn_key_check) {
305
0
                log_progress("do_txn_key_check");
306
0
                if (int ret = checker->do_txn_key_check(); ret != 0) {
307
0
                    success = false;
308
0
                }
309
0
            }
310
311
10
            if (config::enable_meta_rowset_key_check) {
312
0
                log_progress("do_meta_rowset_key_check");
313
0
                if (int ret = checker->do_meta_rowset_key_check(); ret != 0) {
314
0
                    success = false;
315
0
                }
316
0
            }
317
318
10
            if (config::enable_delete_bitmap_storage_optimize_v2_check) {
319
0
                log_progress("do_delete_bitmap_storage_optimize_check_v2");
320
0
                if (int ret = checker->do_delete_bitmap_storage_optimize_check(2 /*version*/);
321
0
                    ret != 0) {
322
0
                    success = false;
323
0
                }
324
0
            }
325
326
10
            if (config::enable_version_key_check) {
327
0
                log_progress("do_version_key_check");
328
0
                if (int ret = checker->do_version_key_check(); ret != 0) {
329
0
                    success = false;
330
0
                }
331
0
            }
332
333
10
            if (config::enable_snapshot_check) {
334
0
                log_progress("do_snapshots_check");
335
0
                if (int ret = checker->do_snapshots_check(); ret != 0) {
336
0
                    success = false;
337
0
                }
338
0
            }
339
340
10
            if (config::enable_mvcc_meta_key_check) {
341
0
                log_progress("do_mvcc_meta_key_check");
342
0
                if (int ret = checker->do_mvcc_meta_key_check(); ret != 0) {
343
0
                    success = false;
344
0
                }
345
0
            }
346
347
10
            log_progress("do_table_stream_check");
348
10
            if (int ret = checker->do_table_stream_check(); ret != 0) {
349
0
                success = false;
350
0
            }
351
352
10
            if (config::enable_packed_file_check) {
353
0
                log_progress("do_packed_file_check");
354
0
                if (int ret = checker->do_packed_file_check(); ret != 0) {
355
0
                    success = false;
356
0
                }
357
0
            }
358
359
            // If instance checker has been aborted, don't finish this job
360
10
            if (!checker->stopped()) {
361
10
                finish_instance_recycle_job(txn_kv_.get(), check_job_key, instance.instance_id(),
362
10
                                            ip_port_, success, ctime_ms);
363
10
            }
364
10
            LOG(INFO) << "checker finished instance, instance_id=" << instance.instance_id()
365
10
                      << " success=" << success;
366
10
            {
367
10
                std::lock_guard lock(mtx_);
368
10
                working_instance_map_.erase(instance.instance_id());
369
10
            }
370
10
        }
371
8
    };
372
4
    int num_threads = config::recycle_concurrency; // FIXME: use a new config entry?
373
12
    for (int i = 0; i < num_threads; ++i) {
374
8
        workers_.emplace_back(checker_func);
375
8
    }
376
4
    return 0;
377
4
}
378
379
5
void Checker::stop() {
380
5
    stopped_ = true;
381
5
    notifier_.notify_all();
382
5
    pending_instance_cond_.notify_all();
383
5
    {
384
5
        std::lock_guard lock(mtx_);
385
5
        for (auto& [_, checker] : working_instance_map_) {
386
0
            checker->stop();
387
0
        }
388
5
    }
389
20
    for (auto& w : workers_) {
390
20
        if (w.joinable()) w.join();
391
20
    }
392
5
}
393
394
4
void Checker::lease_check_jobs() {
395
55
    while (!stopped()) {
396
51
        std::vector<std::string> instances;
397
51
        instances.reserve(working_instance_map_.size());
398
51
        {
399
51
            std::lock_guard lock(mtx_);
400
51
            for (auto& [id, _] : working_instance_map_) {
401
30
                instances.push_back(id);
402
30
            }
403
51
        }
404
51
        for (auto& i : instances) {
405
30
            std::string check_job_key;
406
30
            job_check_key({i}, &check_job_key);
407
30
            int ret = lease_instance_recycle_job(txn_kv_.get(), check_job_key, i, ip_port_);
408
30
            if (ret == 1) {
409
0
                std::lock_guard lock(mtx_);
410
0
                if (auto it = working_instance_map_.find(i); it != working_instance_map_.end()) {
411
0
                    it->second->stop();
412
0
                }
413
0
            }
414
30
        }
415
51
        {
416
51
            std::unique_lock lock(mtx_);
417
51
            notifier_.wait_for(lock,
418
51
                               std::chrono::milliseconds(config::recycle_job_lease_expired_ms / 3),
419
102
                               [&]() { return stopped(); });
420
51
        }
421
51
    }
422
4
}
423
0
#define LOG_CHECK_INTERVAL_ALARM LOG(WARNING) << "Err for check interval: "
424
34
void Checker::do_inspect(const InstanceInfoPB& instance) {
425
34
    std::string check_job_key = job_check_key({instance.instance_id()});
426
34
    std::unique_ptr<Transaction> txn;
427
34
    std::string val;
428
34
    TxnErrorCode err = txn_kv_->create_txn(&txn);
429
34
    if (err != TxnErrorCode::TXN_OK) {
430
0
        LOG_CHECK_INTERVAL_ALARM << "failed to create txn";
431
0
        return;
432
0
    }
433
34
    err = txn->get(check_job_key, &val);
434
34
    if (err != TxnErrorCode::TXN_OK && err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
435
0
        LOG_CHECK_INTERVAL_ALARM << "failed to get kv, err=" << err
436
0
                                 << " key=" << hex(check_job_key);
437
0
        return;
438
0
    }
439
34
    auto checker = InstanceChecker(txn_kv_, instance.instance_id(), resource_mgr_);
440
34
    if (checker.init(instance) != 0) {
441
0
        LOG_CHECK_INTERVAL_ALARM << "failed to init instance checker, instance_id="
442
0
                                 << instance.instance_id();
443
0
        return;
444
0
    }
445
446
34
    int64_t bucket_lifecycle_days = 0;
447
34
    if (checker.get_bucket_lifecycle(&bucket_lifecycle_days) != 0) {
448
0
        LOG_CHECK_INTERVAL_ALARM << "failed to get bucket lifecycle, instance_id="
449
0
                                 << instance.instance_id();
450
0
        return;
451
0
    }
452
34
    DCHECK(bucket_lifecycle_days > 0);
453
454
34
    if (bucket_lifecycle_days == INT64_MAX) {
455
        // No s3 bucket (may all accessors are HdfsAccessor), skip inspect
456
34
        return;
457
34
    }
458
459
0
    int64_t last_ctime_ms = -1;
460
0
    auto job_status = JobRecyclePB::IDLE;
461
0
    auto has_last_ctime = [&]() {
462
0
        JobRecyclePB job_info;
463
0
        if (!job_info.ParseFromString(val)) {
464
0
            LOG_CHECK_INTERVAL_ALARM << "failed to parse JobRecyclePB, key=" << hex(check_job_key);
465
0
        }
466
0
        DCHECK(job_info.instance_id() == instance.instance_id());
467
0
        if (!job_info.has_last_ctime_ms()) return false;
468
0
        last_ctime_ms = job_info.last_ctime_ms();
469
0
        job_status = job_info.status();
470
0
        g_bvar_checker_last_success_time_ms.put(instance.instance_id(),
471
0
                                                job_info.last_success_time_ms());
472
0
        return true;
473
0
    };
474
0
    using namespace std::chrono;
475
0
    auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
476
0
    if (err == TxnErrorCode::TXN_KEY_NOT_FOUND || !has_last_ctime()) {
477
        // Use instance's ctime for instances that do not have job's last ctime
478
0
        last_ctime_ms = instance.ctime();
479
0
    }
480
0
    DCHECK(now - last_ctime_ms >= 0);
481
0
    int64_t expiration_ms =
482
0
            bucket_lifecycle_days > config::reserved_buffer_days
483
0
                    ? (bucket_lifecycle_days - config::reserved_buffer_days) * 86400000
484
0
                    : bucket_lifecycle_days * 86400000;
485
0
    TEST_SYNC_POINT_CALLBACK("Checker:do_inspect", &last_ctime_ms);
486
0
    if (now - last_ctime_ms >= expiration_ms) {
487
0
        LOG_CHECK_INTERVAL_ALARM << "check risks, instance_id: " << instance.instance_id()
488
0
                                 << " last_ctime_ms: " << last_ctime_ms
489
0
                                 << " job_status: " << job_status
490
0
                                 << " bucket_lifecycle_days: " << bucket_lifecycle_days
491
0
                                 << " reserved_buffer_days: " << config::reserved_buffer_days
492
0
                                 << " expiration_ms: " << expiration_ms;
493
0
    }
494
0
}
495
#undef LOG_CHECK_INTERVAL_ALARM
496
4
void Checker::inspect_instance_check_interval() {
497
8
    while (!stopped()) {
498
4
        LOG(INFO) << "start to inspect instance check interval";
499
4
        std::vector<InstanceInfoPB> instances;
500
4
        get_all_instances(txn_kv_.get(), instances);
501
30
        for (const auto& instance : instances) {
502
30
            if (instance_filter_.filter_out(instance.instance_id())) continue;
503
30
            if (stopped()) return;
504
30
            if (instance.status() == InstanceInfoPB::DELETED) continue;
505
30
            do_inspect(instance);
506
30
        }
507
4
        {
508
4
            std::unique_lock lock(mtx_);
509
4
            notifier_.wait_for(lock, std::chrono::seconds(config::scan_instances_interval_seconds),
510
7
                               [&]() { return stopped(); });
511
4
        }
512
4
    }
513
4
}
514
515
// return 0 for success get a key, 1 for key not found, negative for error
516
26
int key_exist(TxnKv* txn_kv, std::string_view key) {
517
26
    std::unique_ptr<Transaction> txn;
518
26
    TxnErrorCode err = txn_kv->create_txn(&txn);
519
26
    if (err != TxnErrorCode::TXN_OK) {
520
0
        LOG(WARNING) << "failed to init txn, err=" << err;
521
0
        return -1;
522
0
    }
523
26
    std::string val;
524
26
    switch (txn->get(key, &val)) {
525
23
    case TxnErrorCode::TXN_OK:
526
23
        return 0;
527
3
    case TxnErrorCode::TXN_KEY_NOT_FOUND:
528
3
        return 1;
529
0
    default:
530
0
        return -1;
531
26
    }
532
26
}
533
534
SharedStorageIndex::SharedStorageIndex(std::shared_ptr<TxnKv> txn_kv,
535
                                       std::optional<std::vector<InstanceInfoPB>> instances)
536
98
        : txn_kv_(std::move(txn_kv)), preloaded_instances_(std::move(instances)) {}
537
538
std::optional<SharedStorageIndex::StorageLocation> SharedStorageIndex::s3_location(
539
0
        const ObjectStoreInfoPB& obj_info) {
540
    // Missing connection info must not make unrelated resources match.
541
0
    if (obj_info.endpoint().empty() || obj_info.bucket().empty()) {
542
0
        return std::nullopt;
543
0
    }
544
0
    return StorageLocation {"s3", obj_info.endpoint(), obj_info.bucket(), obj_info.prefix()};
545
0
}
546
547
std::optional<SharedStorageIndex::StorageLocation> SharedStorageIndex::hdfs_location(
548
0
        const HdfsVaultInfo& hdfs_info) {
549
0
    if (hdfs_info.build_conf().fs_name().empty()) {
550
0
        return std::nullopt;
551
0
    }
552
0
    return StorageLocation {"hdfs", hdfs_info.build_conf().fs_name(), "", hdfs_info.prefix()};
553
0
}
554
555
0
void SharedStorageIndex::ensure_loaded() {
556
0
    std::call_once(load_once_, [this] {
557
0
        load_status_ = load_all_instances();
558
0
        if (load_status_ != 0) {
559
            // Never expose a partially built index.
560
0
            instance_resource_locations_.clear();
561
0
            location_to_owners_.clear();
562
0
        }
563
0
    });
564
0
}
565
566
0
int SharedStorageIndex::load_all_instances() {
567
0
    std::vector<InstanceInfoPB> instances;
568
0
    if (preloaded_instances_.has_value()) {
569
0
        instances = std::move(*preloaded_instances_);
570
0
        preloaded_instances_.reset();
571
0
    } else if (get_all_instances(txn_kv_.get(), instances) != 0) {
572
0
        return -1;
573
0
    }
574
    // Load all instances, even those not scheduled for checking: decoupled
575
    // instances can still share storage with the checked one.
576
0
    for (const auto& instance : instances) {
577
0
        if (load_instance(instance) != 0) {
578
0
            return -1;
579
0
        }
580
0
    }
581
0
    return 0;
582
0
}
583
584
0
int SharedStorageIndex::load_instance(const InstanceInfoPB& instance) {
585
0
    const std::string& instance_id = instance.instance_id();
586
    // Register the instance even if it has no resources, so that it can be told
587
    // apart from an unknown instance.
588
0
    instance_resource_locations_[instance_id];
589
    // Locations of the resources in `resource_ids` are filled by the vault scan below.
590
0
    for (const auto& resource_id : instance.resource_ids()) {
591
0
        add_resource(instance_id, resource_id, std::nullopt);
592
0
    }
593
0
    for (const auto& obj_info : instance.obj_info()) {
594
0
        add_resource(instance_id, obj_info.id(), s3_location(obj_info));
595
0
    }
596
0
    if (instance.resource_ids().empty()) {
597
0
        return 0;
598
0
    }
599
0
    return load_storage_vaults(instance_id);
600
0
}
601
602
0
int SharedStorageIndex::load_storage_vaults(const std::string& instance_id) {
603
0
    std::unique_ptr<Transaction> txn;
604
0
    if (txn_kv_->create_txn(&txn) != TxnErrorCode::TXN_OK) {
605
0
        return -1;
606
0
    }
607
0
    auto iter = txn->full_range_get(storage_vault_key({instance_id, ""}),
608
0
                                    storage_vault_key({instance_id, "\xff"}));
609
0
    for (auto entry = iter->next(); entry.has_value(); entry = iter->next()) {
610
0
        StorageVaultPB vault;
611
0
        if (!vault.ParseFromArray(entry->second.data(), entry->second.size())) {
612
0
            LOG_WARNING("malformed storage vault").tag("instance_id", instance_id);
613
0
            return -1;
614
0
        }
615
0
        std::optional<StorageLocation> location;
616
0
        if (vault.has_obj_info()) {
617
0
            location = s3_location(vault.obj_info());
618
0
        } else if (vault.has_hdfs_info()) {
619
0
            location = hdfs_location(vault.hdfs_info());
620
0
        }
621
0
        add_resource(instance_id, vault.id(), std::move(location));
622
0
    }
623
0
    return iter->is_valid() ? 0 : -1;
624
0
}
625
626
void SharedStorageIndex::add_resource(const std::string& instance_id,
627
                                      const std::string& resource_id,
628
0
                                      std::optional<StorageLocation> location) {
629
0
    auto& resource_location = instance_resource_locations_[instance_id][resource_id];
630
0
    if (!location.has_value()) {
631
0
        return;
632
0
    }
633
0
    if (!resource_location.has_value()) {
634
0
        resource_location = location;
635
0
    }
636
0
    location_to_owners_[std::move(*location)].push_back({instance_id, resource_id});
637
0
}
638
639
int SharedStorageIndex::get_shared_resource_owners(std::string_view instance_id,
640
0
                                                   SharedResourceOwners* owners) {
641
0
    ensure_loaded();
642
0
    owners->clear();
643
0
    if (load_status_ != 0) {
644
0
        return -1;
645
0
    }
646
0
    auto instance = instance_resource_locations_.find(std::string(instance_id));
647
0
    if (instance == instance_resource_locations_.end()) {
648
0
        LOG_WARNING("instance not found while checking shared resources")
649
0
                .tag("instance_id", instance_id);
650
0
        return -1;
651
0
    }
652
0
    for (const auto& [resource_id, location] : instance->second) {
653
0
        if (location.has_value()) {
654
0
            (*owners)[resource_id] = location_to_owners_.at(*location);
655
0
        } else {
656
            // Without a known location, the resource is only owned by its own instance.
657
0
            (*owners)[resource_id] = {{instance->first, resource_id}};
658
0
        }
659
0
    }
660
0
    return 0;
661
0
}
662
663
InstanceChecker::InstanceChecker(std::shared_ptr<TxnKv> txn_kv, const std::string& instance_id,
664
                                 std::shared_ptr<ResourceManager> resource_mgr,
665
                                 std::shared_ptr<SharedStorageIndex> shared_storage_index)
666
125
        : txn_kv_(txn_kv),
667
125
          instance_id_(instance_id),
668
125
          resource_mgr_(std::move(resource_mgr)),
669
125
          shared_storage_index_(std::move(shared_storage_index)) {
670
125
    snapshot_manager_ = create_snapshot_manager(txn_kv);
671
125
    if (!shared_storage_index_) {
672
95
        shared_storage_index_ = std::make_shared<SharedStorageIndex>(txn_kv);
673
95
    }
674
125
    if (!resource_mgr_) {
675
61
        resource_mgr_ = std::make_shared<ResourceManager>(std::move(txn_kv));
676
61
        resource_mgr_->init();
677
61
    }
678
125
}
679
680
125
int InstanceChecker::init(const InstanceInfoPB& instance) {
681
125
    table_stream_versioned_write_ = instance.multi_version_status() == MULTI_VERSION_WRITE_ONLY ||
682
125
                                    instance.multi_version_status() == MULTI_VERSION_READ_WRITE;
683
125
    resource_mgr_->refresh_instance(instance_id_, instance);
684
685
125
    int ret = init_obj_store_accessors(instance);
686
125
    if (ret != 0) {
687
0
        return ret;
688
0
    }
689
690
125
    return init_storage_vault_accessors(instance);
691
125
}
692
693
125
int InstanceChecker::init_obj_store_accessors(const InstanceInfoPB& instance) {
694
125
    for (const auto& obj_info : instance.obj_info()) {
695
100
#ifdef UNIT_TEST
696
100
        auto accessor = std::make_shared<MockAccessor>();
697
#else
698
        auto s3_conf = S3Conf::from_obj_store_info(obj_info);
699
        if (!s3_conf) {
700
            LOG(WARNING) << "failed to init object accessor, instance_id=" << instance_id_;
701
            return -1;
702
        }
703
704
        std::shared_ptr<S3Accessor> accessor;
705
        int ret = S3Accessor::create(std::move(*s3_conf), &accessor);
706
        if (ret != 0) {
707
            LOG(WARNING) << "failed to init object accessor. instance_id=" << instance_id_
708
                         << " resource_id=" << obj_info.id();
709
            return ret;
710
        }
711
#endif
712
713
100
        accessor_map_.emplace(obj_info.id(), std::move(accessor));
714
100
    }
715
716
125
    return 0;
717
125
}
718
719
125
int InstanceChecker::init_storage_vault_accessors(const InstanceInfoPB& instance) {
720
125
    if (instance.resource_ids().empty()) {
721
125
        return 0;
722
125
    }
723
724
0
    FullRangeGetOptions opts(txn_kv_);
725
0
    opts.prefetch = true;
726
0
    auto it = txn_kv_->full_range_get(storage_vault_key({instance_id_, ""}),
727
0
                                      storage_vault_key({instance_id_, "\xff"}), std::move(opts));
728
729
0
    for (auto kv = it->next(); kv.has_value(); kv = it->next()) {
730
0
        auto [k, v] = *kv;
731
0
        StorageVaultPB vault;
732
0
        if (!vault.ParseFromArray(v.data(), v.size())) {
733
0
            LOG(WARNING) << "malformed storage vault, unable to deserialize key=" << hex(k);
734
0
            return -1;
735
0
        }
736
0
        TEST_SYNC_POINT_CALLBACK("InstanceRecycler::init_storage_vault_accessors.mock_vault",
737
0
                                 &accessor_map_, &vault);
738
0
        if (vault.has_hdfs_info()) {
739
0
#ifdef ENABLE_HDFS_STORAGE_VAULT
740
0
            auto accessor = std::make_shared<HdfsAccessor>(vault.hdfs_info());
741
0
            int ret = accessor->init();
742
0
            if (ret != 0) {
743
0
                LOG(WARNING) << "failed to init hdfs accessor. instance_id=" << instance_id_
744
0
                             << " resource_id=" << vault.id() << " name=" << vault.name();
745
0
                return ret;
746
0
            }
747
748
0
            accessor_map_.emplace(vault.id(), std::move(accessor));
749
#else
750
            LOG(ERROR) << "HDFS is disabled (via the ENABLE_HDFS_STORAGE_VAULT build option), "
751
                       << "but HDFS storage vaults were detected";
752
#endif
753
0
        } else if (vault.has_obj_info()) {
754
0
#ifdef UNIT_TEST
755
0
            auto accessor = std::make_shared<MockAccessor>();
756
#else
757
            auto s3_conf = S3Conf::from_obj_store_info(vault.obj_info());
758
            if (!s3_conf) {
759
                LOG(WARNING) << "failed to init object accessor, instance_id=" << instance_id_;
760
                return -1;
761
            }
762
763
            std::shared_ptr<S3Accessor> accessor;
764
            int ret = S3Accessor::create(std::move(*s3_conf), &accessor);
765
            if (ret != 0) {
766
                LOG(WARNING) << "failed to init s3 accessor. instance_id=" << instance_id_
767
                             << " resource_id=" << vault.id() << " name=" << vault.name();
768
                return ret;
769
            }
770
#endif
771
772
0
            accessor_map_.emplace(vault.id(), std::move(accessor));
773
0
        }
774
0
    }
775
776
0
    if (!it->is_valid()) {
777
0
        LOG_WARNING("failed to get storage vault kv");
778
0
        return -1;
779
0
    }
780
0
    return 0;
781
0
}
782
783
16
int InstanceChecker::do_check() {
784
16
    TEST_SYNC_POINT("InstanceChecker.do_check");
785
16
    LOG(INFO) << "begin to check instance objects instance_id=" << instance_id_;
786
16
    int check_ret = 0;
787
16
    long num_scanned = 0;
788
16
    long num_scanned_with_segment = 0;
789
16
    long num_rowset_loss = 0;
790
16
    long instance_volume = 0;
791
16
    using namespace std::chrono;
792
16
    auto start_time = steady_clock::now();
793
16
    DORIS_CLOUD_DEFER {
794
16
        auto cost = duration<float>(steady_clock::now() - start_time).count();
795
16
        LOG(INFO) << "check instance objects finished, cost=" << cost
796
16
                  << "s. instance_id=" << instance_id_ << " num_scanned=" << num_scanned
797
16
                  << " num_scanned_with_segment=" << num_scanned_with_segment
798
16
                  << " num_rowset_loss=" << num_rowset_loss
799
16
                  << " instance_volume=" << instance_volume;
800
16
        g_bvar_checker_num_scanned.put(instance_id_, num_scanned);
801
16
        g_bvar_checker_num_scanned_with_segment.put(instance_id_, num_scanned_with_segment);
802
16
        g_bvar_checker_num_check_failed.put(instance_id_, num_rowset_loss);
803
16
        g_bvar_checker_check_cost_s.put(instance_id_, static_cast<long>(cost));
804
        // FIXME(plat1ko): What if some list operation failed?
805
16
        g_bvar_checker_instance_volume.put(instance_id_, instance_volume);
806
16
    };
807
808
16
    struct TabletFiles {
809
16
        int64_t tablet_id {0};
810
16
        std::string resource_id;
811
16
        std::unordered_set<std::string> files;
812
16
    };
813
16
    TabletFiles tablet_files_cache;
814
815
4.05k
    auto check_rowset_objects = [&, this](doris::RowsetMetaCloudPB& rs_meta, std::string_view key) {
816
4.05k
        if (rs_meta.num_segments() == 0) {
817
0
            return;
818
0
        }
819
820
4.05k
        bool data_loss = false;
821
4.05k
        bool segment_file_loss = false;
822
4.05k
        bool index_file_loss = false;
823
824
4.05k
        DORIS_CLOUD_DEFER {
825
4.05k
            if (data_loss) {
826
30
                LOG(INFO) << "segment file is" << (segment_file_loss ? "" : " not") << " loss, "
827
30
                          << "index file is" << (index_file_loss ? "" : " not") << " loss, "
828
30
                          << "rowset.tablet_id = " << rs_meta.tablet_id();
829
30
                num_rowset_loss++;
830
30
            }
831
4.05k
        };
832
833
4.05k
        ++num_scanned_with_segment;
834
4.05k
        if (tablet_files_cache.tablet_id != rs_meta.tablet_id() ||
835
4.05k
            tablet_files_cache.resource_id != rs_meta.resource_id()) {
836
454
            long tablet_volume = 0;
837
            // Clear cache
838
454
            tablet_files_cache.tablet_id = 0;
839
454
            tablet_files_cache.files.clear();
840
            // Get all file paths under this tablet directory
841
454
            auto find_it = accessor_map_.find(rs_meta.resource_id());
842
454
            if (find_it == accessor_map_.end()) {
843
0
                LOG_WARNING("resource id not found in accessor map")
844
0
                        .tag("resource_id", rs_meta.resource_id())
845
0
                        .tag("tablet_id", rs_meta.tablet_id())
846
0
                        .tag("rowset_id", rs_meta.rowset_id_v2());
847
0
                check_ret = -1;
848
0
                return;
849
0
            }
850
851
454
            std::unique_ptr<ListIterator> list_iter;
852
454
            int ret = find_it->second->list_directory(tablet_path_prefix(rs_meta.tablet_id()),
853
454
                                                      &list_iter);
854
454
            if (ret != 0) { // No need to log, because S3Accessor has logged this error
855
0
                check_ret = -1;
856
0
                return;
857
0
            }
858
859
18.5k
            for (auto file = list_iter->next(); file.has_value(); file = list_iter->next()) {
860
18.0k
                tablet_files_cache.files.insert(std::move(file->path));
861
18.0k
                tablet_volume += file->size;
862
18.0k
            }
863
454
            if (!list_iter->is_valid()) {
864
0
                check_ret = -1;
865
0
                return;
866
0
            }
867
454
            tablet_files_cache.tablet_id = rs_meta.tablet_id();
868
454
            tablet_files_cache.resource_id = rs_meta.resource_id();
869
454
            instance_volume += tablet_volume;
870
454
        }
871
872
16.1k
        for (int i = 0; i < rs_meta.num_segments(); ++i) {
873
12.0k
            auto segment_id = rowset_segment_id(rs_meta, i);
874
12.0k
            auto path = segment_path(rs_meta.tablet_id(), rs_meta.rowset_id_v2(), segment_id);
875
876
            // Skip check if segment is already packed into a larger file
877
12.0k
            const auto& index_map = rs_meta.packed_slice_locations();
878
12.0k
            if (index_map.find(path) != index_map.end()) {
879
0
                continue;
880
0
            }
881
882
12.0k
            if (tablet_files_cache.files.contains(path)) {
883
12.0k
                continue;
884
12.0k
            }
885
886
14
            if (1 == key_exist(txn_kv_.get(), key)) {
887
                // Rowset has been deleted instead of data loss
888
0
                break;
889
0
            }
890
14
            data_loss = true;
891
14
            segment_file_loss = true;
892
14
            TEST_SYNC_POINT_CALLBACK("InstanceChecker.do_check1", &path);
893
14
            LOG(WARNING) << "object not exist, path=" << path
894
14
                         << ", rs_meta=" << rs_meta.ShortDebugString() << " key=" << hex(key);
895
14
        }
896
897
4.05k
        std::unique_ptr<Transaction> txn;
898
4.05k
        TxnErrorCode err = txn_kv_->create_txn(&txn);
899
4.05k
        if (err != TxnErrorCode::TXN_OK) {
900
0
            LOG(WARNING) << "failed to init txn, err=" << err;
901
0
            check_ret = -1;
902
0
            return;
903
0
        }
904
905
4.05k
        TabletIndexPB tablet_index;
906
4.05k
        CloneChainReader reader(instance_id_, resource_mgr_.get());
907
4.05k
        int index_ret =
908
4.05k
                get_tablet_idx(txn_kv_.get(), instance_id_, rs_meta.tablet_id(), tablet_index);
909
4.05k
        if (index_ret == 1) {
910
0
            err = reader.get_tablet_index(txn.get(), rs_meta.tablet_id(), &tablet_index);
911
0
        }
912
4.05k
        if (index_ret < 0 || err != TxnErrorCode::TXN_OK) {
913
0
            LOG(WARNING) << "failed to get tablet index, tablet_id= " << rs_meta.tablet_id();
914
0
            check_ret = -1;
915
0
            return;
916
0
        }
917
918
4.05k
        auto tablet_schema_key =
919
4.05k
                meta_schema_key({instance_id_, tablet_index.index_id(), rs_meta.schema_version()});
920
4.05k
        ValueBuf tablet_schema_val;
921
4.05k
        err = cloud::blob_get(txn.get(), tablet_schema_key, &tablet_schema_val);
922
4.05k
        auto* schema = rs_meta.mutable_tablet_schema();
923
4.05k
        if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
924
            // Clone/rollback rowsets may reference a schema that only exists in
925
            // versioned metadata, possibly in an ancestor of the current instance.
926
2.00k
            err = reader.get_tablet_schema(txn.get(), tablet_index.index_id(),
927
2.00k
                                           rs_meta.schema_version(), schema);
928
2.05k
        } else if (err == TxnErrorCode::TXN_OK && !parse_schema_value(tablet_schema_val, schema)) {
929
0
            check_ret = -1;
930
0
            LOG(WARNING) << "malformed schema value, key=" << hex(tablet_schema_key);
931
0
            return;
932
0
        }
933
4.05k
        if (err != TxnErrorCode::TXN_OK) {
934
2.00k
            check_ret = -1;
935
2.00k
            LOG(WARNING) << "failed to get schema, err=" << err;
936
2.00k
            return;
937
2.00k
        }
938
939
2.05k
        std::vector<std::pair<int64_t, std::string>> index_ids;
940
2.05k
        for (const auto& i : rs_meta.tablet_schema().index()) {
941
2.05k
            if (i.has_index_type() && i.index_type() == IndexType::INVERTED) {
942
2.05k
                index_ids.emplace_back(i.index_id(), i.index_suffix_name());
943
2.05k
            }
944
2.05k
        }
945
2.05k
        if (!index_ids.empty()) {
946
2.05k
            const auto& index_map = rs_meta.packed_slice_locations();
947
2.05k
            const auto index_format =
948
2.05k
                    rs_meta.has_inverted_index_storage_format()
949
2.05k
                            ? rs_meta.inverted_index_storage_format()
950
2.05k
                            : rs_meta.tablet_schema().inverted_index_storage_format();
951
8.10k
            for (int i = 0; i < rs_meta.num_segments(); ++i) {
952
6.05k
                auto segment_id = rowset_segment_id(rs_meta, i);
953
6.05k
                std::vector<std::string> index_path_v;
954
6.05k
                if (index_format == InvertedIndexStorageFormatPB::V1) {
955
6.01k
                    for (const auto& index_id : index_ids) {
956
6.01k
                        LOG(INFO) << "check inverted index, tablet_id=" << rs_meta.tablet_id()
957
6.01k
                                  << " rowset_id=" << rs_meta.rowset_id_v2()
958
6.01k
                                  << " segment_id=" << segment_id << " index_id=" << index_id.first
959
6.01k
                                  << " index_suffix_name=" << index_id.second;
960
6.01k
                        index_path_v.emplace_back(inverted_index_path_v1(
961
6.01k
                                rs_meta.tablet_id(), rs_meta.rowset_id_v2(), segment_id,
962
6.01k
                                index_id.first, index_id.second));
963
6.01k
                    }
964
6.01k
                } else {
965
40
                    index_path_v.emplace_back(inverted_index_path_v2(
966
40
                            rs_meta.tablet_id(), rs_meta.rowset_id_v2(), segment_id));
967
40
                }
968
969
6.05k
                if (std::ranges::all_of(index_path_v, [&](const auto& idx_file_path) {
970
                        // Skip check if inverted index file is already packed into a larger file
971
6.05k
                        if (index_map.find(idx_file_path) != index_map.end()) {
972
0
                            return true;
973
0
                        }
974
6.05k
                        if (!tablet_files_cache.files.contains(idx_file_path)) {
975
23
                            LOG(INFO) << "loss index file: " << idx_file_path;
976
23
                            return false;
977
23
                        }
978
6.03k
                        return true;
979
6.05k
                    })) {
980
6.03k
                    continue;
981
6.03k
                }
982
23
                index_file_loss = true;
983
23
                data_loss = true;
984
23
            }
985
2.05k
        }
986
2.05k
    };
987
988
    // scan visible rowsets
989
16
    auto start_key = meta_rowset_key({instance_id_, 0, 0});
990
16
    auto end_key = meta_rowset_key({instance_id_, INT64_MAX, 0});
991
992
16
    std::unique_ptr<RangeGetIterator> it;
993
32
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
994
16
        std::unique_ptr<Transaction> txn;
995
16
        TxnErrorCode err = txn_kv_->create_txn(&txn);
996
16
        if (err != TxnErrorCode::TXN_OK) {
997
0
            LOG(WARNING) << "failed to init txn, err=" << err;
998
0
            return -1;
999
0
        }
1000
1001
16
        err = txn->get(start_key, end_key, &it);
1002
16
        if (err != TxnErrorCode::TXN_OK) {
1003
0
            LOG(WARNING) << "internal error, failed to get rowset meta, err=" << err;
1004
0
            return -1;
1005
0
        }
1006
16
        num_scanned += it->size();
1007
1008
4.07k
        while (it->has_next() && !stopped()) {
1009
4.05k
            auto [k, v] = it->next();
1010
4.05k
            if (!it->has_next()) {
1011
6
                start_key = k;
1012
6
            }
1013
1014
4.05k
            doris::RowsetMetaCloudPB rs_meta;
1015
4.05k
            if (!rs_meta.ParseFromArray(v.data(), v.size())) {
1016
0
                ++num_rowset_loss;
1017
0
                LOG(WARNING) << "malformed rowset meta. key=" << hex(k) << " val=" << hex(v);
1018
0
                continue;
1019
0
            }
1020
4.05k
            check_rowset_objects(rs_meta, k);
1021
4.05k
        }
1022
16
        start_key.push_back('\x00'); // Update to next smallest key for iteration
1023
16
    }
1024
1025
16
    return num_rowset_loss > 0 ? 1 : check_ret;
1026
16
}
1027
1028
34
int InstanceChecker::get_bucket_lifecycle(int64_t* lifecycle_days) {
1029
    // If there are multiple buckets, return the minimum lifecycle.
1030
34
    int64_t min_lifecycle_days = INT64_MAX;
1031
34
    int64_t tmp_lifecycle_days = 0;
1032
34
    for (const auto& [id, accessor] : accessor_map_) {
1033
34
        if (accessor->type() != AccessorType::S3) {
1034
34
            continue;
1035
34
        }
1036
1037
0
        auto* s3_accessor = static_cast<S3Accessor*>(accessor.get());
1038
1039
0
        if (s3_accessor->check_versioning() != 0) {
1040
0
            return -1;
1041
0
        }
1042
1043
0
        if (s3_accessor->get_lifecycle(&tmp_lifecycle_days) != 0) {
1044
0
            return -1;
1045
0
        }
1046
1047
0
        if (tmp_lifecycle_days < min_lifecycle_days) {
1048
0
            min_lifecycle_days = tmp_lifecycle_days;
1049
0
        }
1050
0
    }
1051
34
    *lifecycle_days = min_lifecycle_days;
1052
34
    return 0;
1053
34
}
1054
1055
5
int InstanceChecker::do_inverted_check() {
1056
5
    if (accessor_map_.size() > 1) {
1057
0
        LOG(INFO) << "currently not support inverted check for multi accessor. instance_id="
1058
0
                  << instance_id_;
1059
0
        return 0;
1060
0
    }
1061
1062
5
    LOG(INFO) << "begin to inverted check objects instance_id=" << instance_id_;
1063
5
    int check_ret = 0;
1064
5
    long num_scanned = 0;
1065
5
    long num_file_leak = 0;
1066
5
    using namespace std::chrono;
1067
5
    auto start_time = steady_clock::now();
1068
5
    DORIS_CLOUD_DEFER {
1069
5
        g_bvar_inverted_checker_num_scanned.put(instance_id_, num_scanned);
1070
5
        g_bvar_inverted_checker_num_check_failed.put(instance_id_, num_file_leak);
1071
5
        auto cost = duration<float>(steady_clock::now() - start_time).count();
1072
5
        LOG(INFO) << "inverted check instance objects finished, cost=" << cost
1073
5
                  << "s. instance_id=" << instance_id_ << " num_scanned=" << num_scanned
1074
5
                  << " num_file_leak=" << num_file_leak;
1075
5
    };
1076
1077
5
    struct TabletRowsets {
1078
5
        int64_t tablet_id {0};
1079
5
        std::unordered_set<std::string> rowset_ids;
1080
5
    };
1081
5
    TabletRowsets tablet_rowsets_cache;
1082
1083
5
    RowsetIndexesFormatV1 rowset_index_cache_v1;
1084
5
    RowsetIndexesFormatV2 rowset_index_cache_v2;
1085
1086
    // Return 0 if check success, return 1 if file is garbage data, negative if error occurred
1087
108
    auto check_segment_file = [&](const std::string& obj_key) {
1088
108
        std::vector<std::string> str;
1089
108
        butil::SplitString(obj_key, '/', &str);
1090
        // data/{tablet_id}/{rowset_id}_{seg_num}.dat
1091
108
        if (str.size() < 3) {
1092
            // clang-format off
1093
0
            LOG(WARNING) << "split obj_key error, str.size() should be less than 3,"
1094
0
                         << " value = " << str.size();
1095
            // clang-format on
1096
0
            return -1;
1097
0
        }
1098
1099
108
        int64_t tablet_id = atol(str[1].c_str());
1100
108
        if (tablet_id <= 0) {
1101
0
            LOG(WARNING) << "failed to parse tablet_id, key=" << obj_key;
1102
0
            return -1;
1103
0
        }
1104
1105
108
        if (!str[2].ends_with(".dat")) {
1106
            // skip check not segment file
1107
54
            return 0;
1108
54
        }
1109
1110
54
        std::string rowset_id;
1111
54
        if (auto pos = str.back().find('_'); pos != std::string::npos) {
1112
54
            rowset_id = str.back().substr(0, pos);
1113
54
        } else {
1114
0
            LOG(WARNING) << "failed to parse rowset_id, key=" << obj_key;
1115
0
            return -1;
1116
0
        }
1117
1118
54
        if (tablet_rowsets_cache.tablet_id == tablet_id) {
1119
7
            if (tablet_rowsets_cache.rowset_ids.contains(rowset_id)) {
1120
2
                return 0;
1121
5
            } else {
1122
5
                LOG(WARNING) << "rowset not exists, key=" << obj_key;
1123
5
                return -1;
1124
5
            }
1125
7
        }
1126
        // Get all rowset id of this tablet
1127
47
        tablet_rowsets_cache.tablet_id = tablet_id;
1128
47
        tablet_rowsets_cache.rowset_ids.clear();
1129
47
        std::unique_ptr<Transaction> txn;
1130
47
        TxnErrorCode err = txn_kv_->create_txn(&txn);
1131
47
        if (err != TxnErrorCode::TXN_OK) {
1132
0
            LOG(WARNING) << "failed to create txn";
1133
0
            return -1;
1134
0
        }
1135
47
        std::unique_ptr<RangeGetIterator> it;
1136
47
        auto begin = meta_rowset_key({instance_id_, tablet_id, 0});
1137
47
        auto end = meta_rowset_key({instance_id_, tablet_id, INT64_MAX});
1138
84
        while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1139
47
            TxnErrorCode err = txn->get(begin, end, &it);
1140
47
            if (err != TxnErrorCode::TXN_OK) {
1141
0
                LOG(WARNING) << "failed to get rowset kv, err=" << err;
1142
0
                return -1;
1143
0
            }
1144
47
            if (!it->has_next()) {
1145
10
                break;
1146
10
            }
1147
37
            while (it->has_next()) {
1148
                // recycle corresponding resources
1149
37
                auto [k, v] = it->next();
1150
37
                doris::RowsetMetaCloudPB rowset;
1151
37
                if (!rowset.ParseFromArray(v.data(), v.size())) {
1152
0
                    LOG(WARNING) << "malformed rowset meta value, key=" << hex(k);
1153
0
                    return -1;
1154
0
                }
1155
37
                tablet_rowsets_cache.rowset_ids.insert(rowset.rowset_id_v2());
1156
37
                if (!it->has_next()) {
1157
37
                    begin = k;
1158
37
                    begin.push_back('\x00'); // Update to next smallest key for iteration
1159
37
                    break;
1160
37
                }
1161
37
            }
1162
37
        }
1163
1164
47
        if (!tablet_rowsets_cache.rowset_ids.contains(rowset_id)) {
1165
            // Garbage data leak
1166
12
            LOG(WARNING) << "rowset should be recycled, key=" << obj_key;
1167
12
            return 1;
1168
12
        }
1169
1170
35
        return 0;
1171
47
    };
1172
1173
108
    auto check_inverted_index_file = [&](const std::string& obj_key) {
1174
108
        std::vector<std::string> str;
1175
108
        butil::SplitString(obj_key, '/', &str);
1176
        // format v1: data/{tablet_id}/{rowset_id}_{seg_num}_{idx_id}{idx_suffix}.idx
1177
        // format v2: data/{tablet_id}/{rowset_id}_{seg_num}.idx
1178
108
        if (str.size() < 3) {
1179
            // clang-format off
1180
0
            LOG(WARNING) << "split obj_key error, str.size() should be less than 3,"
1181
0
                         << " value = " << str.size();
1182
            // clang-format on
1183
0
            return -1;
1184
0
        }
1185
1186
108
        int64_t tablet_id = atol(str[1].c_str());
1187
108
        if (tablet_id <= 0) {
1188
0
            LOG(WARNING) << "failed to parse tablet_id, key=" << obj_key;
1189
0
            return -1;
1190
0
        }
1191
1192
        // v1: {rowset_id}_{seg_num}_{idx_id}{idx_suffix}.idx
1193
        // v2: {rowset_id}_{seg_num}.idx
1194
108
        std::string rowset_info = str.back();
1195
1196
108
        if (!rowset_info.ends_with(".idx")) {
1197
54
            return 0; // Not an index file
1198
54
        }
1199
1200
54
        InvertedIndexStorageFormatPB inverted_index_storage_format =
1201
54
                std::count(rowset_info.begin(), rowset_info.end(), '_') > 1
1202
54
                        ? InvertedIndexStorageFormatPB::V1
1203
54
                        : InvertedIndexStorageFormatPB::V2;
1204
1205
54
        size_t pos = rowset_info.find_last_of('_');
1206
54
        if (pos == std::string::npos || pos + 1 >= str.back().size() - 4) {
1207
0
            LOG(WARNING) << "Invalid index_id format, key=" << obj_key;
1208
0
            return -1;
1209
0
        }
1210
54
        if (inverted_index_storage_format == InvertedIndexStorageFormatPB::V1) {
1211
14
            return check_inverted_index_file_storage_format_v1(tablet_id, obj_key, rowset_info,
1212
14
                                                               rowset_index_cache_v1);
1213
40
        } else {
1214
40
            return check_inverted_index_file_storage_format_v2(tablet_id, obj_key, rowset_info,
1215
40
                                                               rowset_index_cache_v2);
1216
40
        }
1217
54
    };
1218
    // so we choose to skip here.
1219
5
    TEST_SYNC_POINT_RETURN_WITH_VALUE("InstanceChecker::do_inverted_check", (int)0);
1220
1221
3
    for (auto& [_, accessor] : accessor_map_) {
1222
3
        std::unique_ptr<ListIterator> list_iter;
1223
3
        int ret = accessor->list_directory("data", &list_iter);
1224
3
        if (ret != 0) {
1225
0
            return -1;
1226
0
        }
1227
1228
111
        for (auto file = list_iter->next(); file.has_value(); file = list_iter->next()) {
1229
108
            const auto& path = file->path;
1230
108
            if (path == "data/packed_file" || path.starts_with("data/packed_file/")) {
1231
0
                continue; // packed_file has dedicated check logic
1232
0
            }
1233
108
            ++num_scanned;
1234
108
            int ret = check_segment_file(path);
1235
108
            if (ret != 0) {
1236
17
                LOG(WARNING) << "failed to check segment file, uri=" << accessor->uri()
1237
17
                             << " path=" << path;
1238
17
                if (ret == 1) {
1239
12
                    ++num_file_leak;
1240
12
                } else {
1241
5
                    check_ret = -1;
1242
5
                }
1243
17
            }
1244
108
            ret = check_inverted_index_file(path);
1245
108
            if (ret != 0) {
1246
13
                LOG(WARNING) << "failed to check index file, uri=" << accessor->uri()
1247
13
                             << " path=" << path;
1248
13
                if (ret == 1) {
1249
13
                    ++num_file_leak;
1250
13
                } else {
1251
0
                    check_ret = -1;
1252
0
                }
1253
13
            }
1254
108
        }
1255
1256
3
        if (!list_iter->is_valid()) {
1257
0
            LOG(WARNING) << "failed to list data directory. uri=" << accessor->uri();
1258
0
            return -1;
1259
0
        }
1260
3
    }
1261
3
    return num_file_leak > 0 ? 1 : check_ret;
1262
3
}
1263
1264
3
int InstanceChecker::traverse_mow_tablet(const std::function<int(int64_t, bool)>& check_func) {
1265
3
    std::unique_ptr<RangeGetIterator> it;
1266
3
    auto begin = meta_rowset_key({instance_id_, 0, 0});
1267
3
    auto end = meta_rowset_key({instance_id_, std::numeric_limits<int64_t>::max(), 0});
1268
43
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1269
43
        std::unique_ptr<Transaction> txn;
1270
43
        TxnErrorCode err = txn_kv_->create_txn(&txn);
1271
43
        if (err != TxnErrorCode::TXN_OK) {
1272
0
            LOG(WARNING) << "failed to create txn";
1273
0
            return -1;
1274
0
        }
1275
43
        err = txn->get(begin, end, &it, false, 1);
1276
43
        if (err != TxnErrorCode::TXN_OK) {
1277
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1278
0
            return -1;
1279
0
        }
1280
43
        if (!it->has_next()) {
1281
3
            break;
1282
3
        }
1283
80
        while (it->has_next() && !stopped()) {
1284
40
            auto [k, v] = it->next();
1285
40
            std::string_view k1 = k;
1286
40
            k1.remove_prefix(1);
1287
40
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
1288
40
            decode_key(&k1, &out);
1289
            // 0x01 "meta" ${instance_id} "rowset" ${tablet_id} ${version} -> RowsetMetaCloudPB
1290
40
            auto tablet_id = std::get<int64_t>(std::get<0>(out[3]));
1291
1292
40
            if (!it->has_next()) {
1293
                // Update to next smallest key for iteration
1294
                // scan for next tablet in this instance
1295
40
                begin = meta_rowset_key({instance_id_, tablet_id + 1, 0});
1296
40
            }
1297
1298
40
            TabletMetaCloudPB tablet_meta;
1299
40
            int ret = get_tablet_meta(txn_kv_.get(), instance_id_, tablet_id, tablet_meta);
1300
40
            if (ret < 0) {
1301
0
                LOG(WARNING) << fmt::format(
1302
0
                        "failed to get_tablet_meta in do_delete_bitmap_integrity_check(), "
1303
0
                        "instance_id={}, tablet_id={}",
1304
0
                        instance_id_, tablet_id);
1305
0
                return ret;
1306
0
            }
1307
1308
40
            if (tablet_meta.enable_unique_key_merge_on_write()) {
1309
                // only check merge-on-write table
1310
30
                bool has_sequence_col = tablet_meta.schema().has_sequence_col_idx() &&
1311
30
                                        tablet_meta.schema().sequence_col_idx() != -1;
1312
30
                int ret = check_func(tablet_id, has_sequence_col);
1313
30
                if (ret < 0) {
1314
                    // return immediately when encounter unexpected error,
1315
                    // otherwise, we continue to check the next tablet
1316
0
                    return ret;
1317
0
                }
1318
30
            }
1319
40
        }
1320
40
    }
1321
3
    return 0;
1322
3
}
1323
1324
int InstanceChecker::traverse_rowset_delete_bitmaps(
1325
        int64_t tablet_id, std::string rowset_id,
1326
0
        const std::function<int(int64_t, std::string_view, int64_t, int64_t)>& callback) {
1327
0
    std::unique_ptr<RangeGetIterator> it;
1328
0
    auto begin = meta_delete_bitmap_key({instance_id_, tablet_id, rowset_id, 0, 0});
1329
0
    auto end = meta_delete_bitmap_key({instance_id_, tablet_id, rowset_id,
1330
0
                                       std::numeric_limits<int64_t>::max(),
1331
0
                                       std::numeric_limits<int64_t>::max()});
1332
0
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1333
0
        std::unique_ptr<Transaction> txn;
1334
0
        TxnErrorCode err = txn_kv_->create_txn(&txn);
1335
0
        if (err != TxnErrorCode::TXN_OK) {
1336
0
            LOG(WARNING) << "failed to create txn";
1337
0
            return -1;
1338
0
        }
1339
0
        err = txn->get(begin, end, &it);
1340
0
        if (err != TxnErrorCode::TXN_OK) {
1341
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1342
0
            return -1;
1343
0
        }
1344
0
        if (!it->has_next()) {
1345
0
            break;
1346
0
        }
1347
0
        while (it->has_next() && !stopped()) {
1348
0
            auto [k, v] = it->next();
1349
0
            std::string_view k1 = k;
1350
0
            k1.remove_prefix(1);
1351
0
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
1352
0
            decode_key(&k1, &out);
1353
            // 0x01 "meta" ${instance_id} "delete_bitmap" ${tablet_id} ${rowset_id} ${version} ${segment_id} -> roaringbitmap
1354
0
            auto version = std::get<std::int64_t>(std::get<0>(out[5]));
1355
0
            auto segment_id = std::get<std::int64_t>(std::get<0>(out[6]));
1356
1357
0
            int ret = callback(tablet_id, rowset_id, version, segment_id);
1358
0
            if (ret != 0) {
1359
0
                return ret;
1360
0
            }
1361
1362
0
            if (!it->has_next()) {
1363
0
                begin = k;
1364
0
                begin.push_back('\x00'); // Update to next smallest key for iteration
1365
0
                break;
1366
0
            }
1367
0
        }
1368
0
    }
1369
1370
0
    return 0;
1371
0
}
1372
1373
int InstanceChecker::collect_tablet_rowsets(
1374
53
        int64_t tablet_id, const std::function<void(const doris::RowsetMetaCloudPB&)>& collect_cb) {
1375
53
    std::unique_ptr<Transaction> txn;
1376
53
    TxnErrorCode err = txn_kv_->create_txn(&txn);
1377
53
    if (err != TxnErrorCode::TXN_OK) {
1378
0
        LOG(WARNING) << "failed to create txn";
1379
0
        return -1;
1380
0
    }
1381
53
    std::unique_ptr<RangeGetIterator> it;
1382
53
    auto begin = meta_rowset_key({instance_id_, tablet_id, 0});
1383
53
    auto end = meta_rowset_key({instance_id_, tablet_id + 1, 0});
1384
1385
53
    int64_t rowsets_num {0};
1386
103
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1387
53
        TxnErrorCode err = txn->get(begin, end, &it);
1388
53
        if (err != TxnErrorCode::TXN_OK) {
1389
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1390
0
            return -1;
1391
0
        }
1392
53
        if (!it->has_next()) {
1393
3
            break;
1394
3
        }
1395
394
        while (it->has_next() && !stopped()) {
1396
394
            auto [k, v] = it->next();
1397
394
            doris::RowsetMetaCloudPB rowset;
1398
394
            if (!rowset.ParseFromArray(v.data(), v.size())) {
1399
0
                LOG(WARNING) << "malformed rowset meta value, key=" << hex(k);
1400
0
                return -1;
1401
0
            }
1402
1403
394
            ++rowsets_num;
1404
394
            collect_cb(rowset);
1405
1406
394
            if (!it->has_next()) {
1407
50
                begin = k;
1408
50
                begin.push_back('\x00'); // Update to next smallest key for iteration
1409
50
                break;
1410
50
            }
1411
394
        }
1412
50
    }
1413
1414
53
    LOG(INFO) << fmt::format(
1415
53
            "[delete bitmap checker] successfully collect rowsets for instance_id={}, "
1416
53
            "tablet_id={}, rowsets_num={}",
1417
53
            instance_id_, tablet_id, rowsets_num);
1418
53
    return 0;
1419
53
}
1420
1421
5
int InstanceChecker::do_delete_bitmap_inverted_check() {
1422
5
    LOG(INFO) << fmt::format(
1423
5
            "[delete bitmap checker] begin to do_delete_bitmap_inverted_check for instance_id={}",
1424
5
            instance_id_);
1425
1426
    // number of delete bitmap keys being scanned
1427
5
    int64_t total_delete_bitmap_keys {0};
1428
    // number of delete bitmaps which belongs to non mow tablet
1429
5
    int64_t abnormal_delete_bitmaps {0};
1430
    // number of delete bitmaps which doesn't have corresponding rowset in MS
1431
5
    int64_t leaked_delete_bitmaps {0};
1432
1433
5
    auto start_time = std::chrono::steady_clock::now();
1434
5
    DORIS_CLOUD_DEFER {
1435
5
        g_bvar_inverted_checker_leaked_delete_bitmaps.put(instance_id_, leaked_delete_bitmaps);
1436
5
        g_bvar_inverted_checker_abnormal_delete_bitmaps.put(instance_id_, abnormal_delete_bitmaps);
1437
5
        g_bvar_inverted_checker_delete_bitmaps_scanned.put(instance_id_, total_delete_bitmap_keys);
1438
1439
5
        auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(
1440
5
                            std::chrono::steady_clock::now() - start_time)
1441
5
                            .count();
1442
5
        if (leaked_delete_bitmaps > 0 || abnormal_delete_bitmaps > 0) {
1443
3
            LOG(WARNING) << fmt::format(
1444
3
                    "[delete bitmap check fails] delete bitmap inverted check for instance_id={}, "
1445
3
                    "cost={} ms, total_delete_bitmap_keys={}, leaked_delete_bitmaps={}, "
1446
3
                    "abnormal_delete_bitmaps={}",
1447
3
                    instance_id_, cost, total_delete_bitmap_keys, leaked_delete_bitmaps,
1448
3
                    abnormal_delete_bitmaps);
1449
3
        } else {
1450
2
            LOG(INFO) << fmt::format(
1451
2
                    "[delete bitmap checker] delete bitmap inverted check for instance_id={}, "
1452
2
                    "passed. cost={} ms, total_delete_bitmap_keys={}",
1453
2
                    instance_id_, cost, total_delete_bitmap_keys);
1454
2
        }
1455
5
    };
1456
1457
5
    struct TabletsRowsetsCache {
1458
5
        int64_t tablet_id {-1};
1459
5
        bool enable_merge_on_write {false};
1460
5
        std::unordered_set<std::string> rowsets {};
1461
5
        std::unordered_set<std::string> pending_delete_bitmaps {};
1462
5
    } tablet_rowsets_cache {};
1463
1464
5
    std::unordered_map<int64_t, std::unordered_set<std::string>> unexpired_tmp_rowsets;
1465
5
    if (int ret = collect_unexpired_job_tmp_rowsets(unexpired_tmp_rowsets); ret < 0) {
1466
0
        return ret;
1467
0
    }
1468
1469
5
    std::unique_ptr<RangeGetIterator> it;
1470
5
    auto begin = meta_delete_bitmap_key({instance_id_, 0, "", 0, 0});
1471
5
    auto end =
1472
5
            meta_delete_bitmap_key({instance_id_, std::numeric_limits<int64_t>::max(), "", 0, 0});
1473
10
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1474
5
        std::unique_ptr<Transaction> txn;
1475
5
        TxnErrorCode err = txn_kv_->create_txn(&txn);
1476
5
        if (err != TxnErrorCode::TXN_OK) {
1477
0
            LOG(WARNING) << "failed to create txn";
1478
0
            return -1;
1479
0
        }
1480
5
        err = txn->get(begin, end, &it);
1481
5
        if (err != TxnErrorCode::TXN_OK) {
1482
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1483
0
            return -1;
1484
0
        }
1485
5
        if (!it->has_next()) {
1486
0
            break;
1487
0
        }
1488
388
        while (it->has_next() && !stopped()) {
1489
383
            auto [k, v] = it->next();
1490
383
            std::string_view k1 = k;
1491
383
            k1.remove_prefix(1);
1492
383
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
1493
383
            decode_key(&k1, &out);
1494
            // 0x01 "meta" ${instance_id} "delete_bitmap" ${tablet_id} ${rowset_id} ${version} ${segment_id} -> roaringbitmap
1495
383
            auto tablet_id = std::get<int64_t>(std::get<0>(out[3]));
1496
383
            auto rowset_id = std::get<std::string>(std::get<0>(out[4]));
1497
383
            auto version = std::get<std::int64_t>(std::get<0>(out[5]));
1498
383
            auto segment_id = std::get<std::int64_t>(std::get<0>(out[6]));
1499
1500
383
            ++total_delete_bitmap_keys;
1501
1502
383
            if (!it->has_next()) {
1503
5
                begin = k;
1504
5
                begin.push_back('\x00'); // Update to next smallest key for iteration
1505
5
            }
1506
1507
383
            if (tablet_rowsets_cache.tablet_id == -1 ||
1508
383
                tablet_rowsets_cache.tablet_id != tablet_id) {
1509
33
                if (tablet_rowsets_cache.tablet_id != -1) {
1510
28
                    unexpired_tmp_rowsets.erase(tablet_rowsets_cache.tablet_id);
1511
28
                }
1512
33
                TabletMetaCloudPB tablet_meta;
1513
33
                int ret = get_tablet_meta(txn_kv_.get(), instance_id_, tablet_id, tablet_meta);
1514
33
                if (ret < 0) {
1515
0
                    LOG(WARNING) << fmt::format(
1516
0
                            "[delete bitmap checker] failed to get_tablet_meta in "
1517
0
                            "do_delete_bitmap_inverted_check(), instance_id={}, tablet_id={}",
1518
0
                            instance_id_, tablet_id);
1519
0
                    return ret;
1520
0
                }
1521
1522
33
                tablet_rowsets_cache.tablet_id = tablet_id;
1523
33
                tablet_rowsets_cache.enable_merge_on_write =
1524
33
                        tablet_meta.enable_unique_key_merge_on_write();
1525
33
                tablet_rowsets_cache.rowsets.clear();
1526
33
                tablet_rowsets_cache.pending_delete_bitmaps.clear();
1527
1528
33
                if (tablet_rowsets_cache.enable_merge_on_write) {
1529
                    // only collect rowsets for merge-on-write tablet
1530
23
                    auto collect_cb =
1531
199
                            [&tablet_rowsets_cache](const doris::RowsetMetaCloudPB& rowset) {
1532
199
                                tablet_rowsets_cache.rowsets.insert(rowset.rowset_id_v2());
1533
199
                            };
1534
23
                    ret = collect_tablet_rowsets(tablet_id, collect_cb);
1535
23
                    if (ret < 0) {
1536
0
                        return ret;
1537
0
                    }
1538
                    // get pending delete bitmaps
1539
23
                    ret = get_pending_delete_bitmap_keys(
1540
23
                            tablet_id, tablet_rowsets_cache.pending_delete_bitmaps);
1541
23
                    if (ret < 0) {
1542
0
                        return ret;
1543
0
                    }
1544
23
                }
1545
33
            }
1546
383
            DCHECK_EQ(tablet_id, tablet_rowsets_cache.tablet_id);
1547
1548
383
            if (!tablet_rowsets_cache.enable_merge_on_write) {
1549
                // clang-format off
1550
40
                TEST_SYNC_POINT_CALLBACK(
1551
40
                        "InstanceChecker::do_delete_bitmap_inverted_check.get_abnormal_delete_bitmap",
1552
40
                        &tablet_id, &rowset_id, &version, &segment_id);
1553
                // clang-format on
1554
40
                ++abnormal_delete_bitmaps;
1555
                // log an error and continue to check the next delete bitmap
1556
40
                LOG(WARNING) << fmt::format(
1557
40
                        "[delete bitmap check fails] find a delete bitmap belongs to tablet "
1558
40
                        "which is not a merge-on-write table! instance_id={}, tablet_id={}, "
1559
40
                        "version={}, segment_id={}",
1560
40
                        instance_id_, tablet_id, version, segment_id);
1561
40
                continue;
1562
40
            }
1563
1564
343
            bool belongs_to_unexpired_tmp_rowset = false;
1565
343
            auto tmp_rowsets_it = unexpired_tmp_rowsets.find(tablet_id);
1566
343
            if (tmp_rowsets_it != unexpired_tmp_rowsets.end()) {
1567
1
                belongs_to_unexpired_tmp_rowset = tmp_rowsets_it->second.contains(rowset_id);
1568
1
            }
1569
1570
343
            if (!tablet_rowsets_cache.rowsets.contains(rowset_id) &&
1571
343
                !tablet_rowsets_cache.pending_delete_bitmaps.contains(std::string(k)) &&
1572
343
                !belongs_to_unexpired_tmp_rowset) {
1573
112
                TEST_SYNC_POINT_CALLBACK(
1574
112
                        "InstanceChecker::do_delete_bitmap_inverted_check.get_leaked_delete_bitmap",
1575
112
                        &tablet_id, &rowset_id, &version, &segment_id);
1576
112
                ++leaked_delete_bitmaps;
1577
                // log an error and continue to check the next delete bitmap
1578
112
                LOG(WARNING) << fmt::format(
1579
112
                        "[delete bitmap check fails] can't find corresponding rowset for delete "
1580
112
                        "bitmap instance_id={}, tablet_id={}, rowset_id={}, version={}, "
1581
112
                        "segment_id={}",
1582
112
                        instance_id_, tablet_id, rowset_id, version, segment_id);
1583
112
            }
1584
343
        }
1585
5
    }
1586
1587
5
    return (leaked_delete_bitmaps > 0 || abnormal_delete_bitmaps > 0) ? 1 : 0;
1588
5
}
1589
1590
int InstanceChecker::collect_unexpired_job_tmp_rowsets(
1591
5
        std::unordered_map<int64_t, std::unordered_set<std::string>>& tmp_rowsets) {
1592
5
    static constexpr int64_t max_unexpired_tmp_rowsets = 1000;
1593
5
    auto begin = meta_rowset_tmp_key({instance_id_, 0, 0});
1594
5
    auto end = meta_rowset_tmp_key({instance_id_, INT64_MAX, 0});
1595
5
    std::unique_ptr<RangeGetIterator> it;
1596
5
    int64_t num_scanned = 0;
1597
5
    int64_t num_non_job = 0;
1598
5
    int64_t num_skipped_non_job_txns = 0;
1599
5
    int64_t num_unexpired = 0;
1600
5
    int64_t num_expired = 0;
1601
5
    int64_t last_txn_id = -1;
1602
5
    int64_t current_time = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
1603
1604
8
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1605
6
        std::unique_ptr<Transaction> txn;
1606
6
        TxnErrorCode err = txn_kv_->create_txn(&txn);
1607
6
        if (err != TxnErrorCode::TXN_OK) {
1608
0
            LOG(WARNING) << "failed to create txn";
1609
0
            return -1;
1610
0
        }
1611
6
        err = txn->get(begin, end, &it);
1612
6
        if (err != TxnErrorCode::TXN_OK) {
1613
0
            LOG(WARNING) << "failed to get tmp rowset kv, err=" << err;
1614
0
            return -1;
1615
0
        }
1616
6
        if (!it->has_next()) {
1617
3
            break;
1618
3
        }
1619
5
        while (it->has_next() && !stopped()) {
1620
3
            auto [k, v] = it->next();
1621
3
            ++num_scanned;
1622
1623
3
            std::string_view k1 = k;
1624
3
            k1.remove_prefix(1);
1625
3
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
1626
3
            if (decode_key(&k1, &out) != 0 || out.size() < 5) {
1627
0
                LOG(WARNING) << "malformed tmp rowset key, key=" << hex(k);
1628
0
                return -1;
1629
0
            }
1630
            // 0x01 "meta" ${instance_id} "rowset_tmp" ${txn_id} ${tablet_id} -> RowsetMetaCloudPB
1631
3
            auto txn_id = std::get<int64_t>(std::get<0>(out[3]));
1632
3
            bool is_first_rowset_of_txn = last_txn_id != txn_id;
1633
3
            last_txn_id = txn_id;
1634
1635
3
            doris::RowsetMetaCloudPB rowset;
1636
3
            if (!rowset.ParseFromArray(v.data(), v.size())) {
1637
0
                LOG(WARNING) << "malformed tmp rowset meta, key=" << hex(k);
1638
0
                return -1;
1639
0
            }
1640
3
            if (!rowset.has_job_id() || rowset.job_id().empty()) {
1641
1
                ++num_non_job;
1642
1
                if (is_first_rowset_of_txn) {
1643
1
                    ++num_skipped_non_job_txns;
1644
1
                    if (txn_id == INT64_MAX) {
1645
0
                        begin = end;
1646
1
                    } else {
1647
1
                        begin = meta_rowset_tmp_key({instance_id_, txn_id + 1, 0});
1648
1
                    }
1649
1
                    it.reset();
1650
1
                    break;
1651
1
                }
1652
0
                if (!it->has_next()) {
1653
0
                    begin = k;
1654
0
                    begin.push_back('\x00');
1655
0
                }
1656
0
                continue;
1657
1
            }
1658
1659
            // Must use the same threshold as the recycler so that a delete bitmap is never
1660
            // reported as leaked while its tmp rowset is still alive from the recycler's view.
1661
            // `earlest_ts` is a local sentinel initialized to 0 on purpose: it keeps the value
1662
            // below any real expiration so the helper never updates the recycler's
1663
            // earliest-ts bvar (the checker must not touch the recycler's metrics).
1664
2
            int64_t earlest_ts = 0;
1665
2
            int64_t expiration =
1666
2
                    calculate_tmp_rowset_expired_time(instance_id_, rowset, &earlest_ts);
1667
2
            if (current_time < expiration) {
1668
1
                tmp_rowsets[rowset.tablet_id()].insert(rowset.rowset_id_v2());
1669
1
                ++num_unexpired;
1670
1
                if (num_unexpired >= max_unexpired_tmp_rowsets) {
1671
0
                    LOG(WARNING)
1672
0
                            << "collect unexpired tmp rowsets for delete bitmap checker reached "
1673
0
                            << "limit, remaining tmp rowsets will not be considered and may cause "
1674
0
                            << "false positives, instance_id=" << instance_id_
1675
0
                            << ", num_scanned=" << num_scanned << ", num_non_job=" << num_non_job
1676
0
                            << ", num_skipped_non_job_txns=" << num_skipped_non_job_txns
1677
0
                            << ", num_unexpired=" << num_unexpired
1678
0
                            << ", num_expired=" << num_expired
1679
0
                            << ", limit=" << max_unexpired_tmp_rowsets;
1680
0
                    return 0;
1681
0
                }
1682
1
            } else {
1683
1
                ++num_expired;
1684
1
            }
1685
1686
2
            if (!it->has_next()) {
1687
2
                begin = k;
1688
2
                begin.push_back('\x00');
1689
2
            }
1690
2
        }
1691
3
    }
1692
1693
5
    LOG(INFO) << "collect unexpired tmp rowsets for delete bitmap checker finished, instance_id="
1694
5
              << instance_id_ << ", num_scanned=" << num_scanned << ", num_non_job=" << num_non_job
1695
5
              << ", num_skipped_non_job_txns=" << num_skipped_non_job_txns
1696
5
              << ", num_unexpired=" << num_unexpired << ", num_expired=" << num_expired;
1697
5
    return 0;
1698
5
}
1699
1700
int InstanceChecker::get_pending_delete_bitmap_keys(
1701
53
        int64_t tablet_id, std::unordered_set<std::string>& pending_delete_bitmaps) {
1702
53
    std::unique_ptr<Transaction> txn;
1703
53
    TxnErrorCode err = txn_kv_->create_txn(&txn);
1704
53
    if (err != TxnErrorCode::TXN_OK) {
1705
0
        LOG(WARNING) << "failed to create txn";
1706
0
        return -1;
1707
0
    }
1708
53
    std::string pending_key = meta_pending_delete_bitmap_key({instance_id_, tablet_id});
1709
53
    std::string pending_val;
1710
53
    err = txn->get(pending_key, &pending_val);
1711
53
    if (err != TxnErrorCode::TXN_OK && err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
1712
0
        LOG(WARNING) << "failed to get pending delete bitmap kv, err=" << err;
1713
0
        return -1;
1714
0
    }
1715
53
    if (err == TxnErrorCode::TXN_OK) {
1716
2
        PendingDeleteBitmapPB pending_info;
1717
2
        if (!pending_info.ParseFromString(pending_val)) [[unlikely]] {
1718
0
            LOG(WARNING) << "failed to parse PendingDeleteBitmapPB, tablet=" << tablet_id;
1719
0
            return -1;
1720
0
        }
1721
12
        for (auto& delete_bitmap_key : pending_info.delete_bitmap_keys()) {
1722
12
            pending_delete_bitmaps.emplace(std::string(delete_bitmap_key));
1723
12
        }
1724
2
    }
1725
53
    return 0;
1726
53
}
1727
1728
int InstanceChecker::check_inverted_index_file_storage_format_v1(
1729
        int64_t tablet_id, const std::string& file_path, const std::string& rowset_info,
1730
14
        RowsetIndexesFormatV1& rowset_index_cache_v1) {
1731
    // format v1: data/{tablet_id}/{rowset_id}_{seg_num}_{idx_id}{idx_suffix}.idx
1732
14
    std::string rowset_id;
1733
14
    int64_t segment_id;
1734
14
    std::string index_id_with_suffix_name;
1735
    // {rowset_id}_{seg_num}_{idx_id}{idx_suffix}.idx
1736
14
    std::vector<std::string> str;
1737
14
    butil::SplitString(rowset_info.substr(0, rowset_info.size() - 4), '_', &str);
1738
14
    if (str.size() < 3) {
1739
0
        LOG(WARNING) << "Split rowset info with '_' error, str size < 3, rowset_info = "
1740
0
                     << rowset_info;
1741
0
        return -1;
1742
0
    }
1743
14
    rowset_id = str[0];
1744
14
    segment_id = std::atoll(str[1].c_str());
1745
14
    index_id_with_suffix_name = str[2];
1746
1747
14
    if (rowset_index_cache_v1.rowset_id == rowset_id) {
1748
0
        if (rowset_index_cache_v1.segment_ids.contains(segment_id)) {
1749
0
            if (auto it = rowset_index_cache_v1.index_ids.find(index_id_with_suffix_name);
1750
0
                it == rowset_index_cache_v1.index_ids.end()) {
1751
                // clang-format off
1752
0
                LOG(WARNING) << fmt::format("index_id with suffix name not found, rowset_info = {}, obj_key = {}", rowset_info, file_path);
1753
                // clang-format on
1754
0
                return -1;
1755
0
            }
1756
0
        } else {
1757
            // clang-format off
1758
0
            LOG(WARNING) << fmt::format("segment id not found, rowset_info = {}, obj_key = {}", rowset_info, file_path);
1759
            // clang-format on
1760
0
            return -1;
1761
0
        }
1762
0
    }
1763
1764
14
    rowset_index_cache_v1.rowset_id = rowset_id;
1765
14
    rowset_index_cache_v1.segment_ids.clear();
1766
14
    rowset_index_cache_v1.index_ids.clear();
1767
1768
14
    std::unique_ptr<Transaction> txn;
1769
14
    TxnErrorCode err = txn_kv_->create_txn(&txn);
1770
14
    if (err != TxnErrorCode::TXN_OK) {
1771
0
        LOG(WARNING) << "failed to create txn";
1772
0
        return -1;
1773
0
    }
1774
14
    std::unique_ptr<RangeGetIterator> it;
1775
14
    auto begin = meta_rowset_key({instance_id_, tablet_id, 0});
1776
14
    auto end = meta_rowset_key({instance_id_, tablet_id, INT64_MAX});
1777
20
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1778
14
        TxnErrorCode err = txn->get(begin, end, &it);
1779
14
        if (err != TxnErrorCode::TXN_OK) {
1780
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1781
0
            return -1;
1782
0
        }
1783
14
        if (!it->has_next()) {
1784
8
            break;
1785
8
        }
1786
6
        while (it->has_next()) {
1787
            // recycle corresponding resources
1788
6
            auto [k, v] = it->next();
1789
6
            doris::RowsetMetaCloudPB rs_meta;
1790
6
            if (!rs_meta.ParseFromArray(v.data(), v.size())) {
1791
0
                LOG(WARNING) << "malformed rowset meta value, key=" << hex(k);
1792
0
                return -1;
1793
0
            }
1794
1795
6
            TabletIndexPB tablet_index;
1796
6
            if (get_tablet_idx(txn_kv_.get(), instance_id_, rs_meta.tablet_id(), tablet_index) ==
1797
6
                -1) {
1798
0
                LOG(WARNING) << "failedt to get tablet index, tablet_id= " << rs_meta.tablet_id();
1799
0
                return -1;
1800
0
            }
1801
1802
6
            auto tablet_schema_key = meta_schema_key(
1803
6
                    {instance_id_, tablet_index.index_id(), rs_meta.schema_version()});
1804
6
            ValueBuf tablet_schema_val;
1805
6
            err = cloud::blob_get(txn.get(), tablet_schema_key, &tablet_schema_val);
1806
1807
6
            if (err != TxnErrorCode::TXN_OK) {
1808
0
                LOG(WARNING) << "failed to get schema, err=" << err;
1809
0
                return -1;
1810
0
            }
1811
1812
6
            auto* schema = rs_meta.mutable_tablet_schema();
1813
6
            if (!parse_schema_value(tablet_schema_val, schema)) {
1814
0
                LOG(WARNING) << "malformed schema value, key=" << hex(tablet_schema_key);
1815
0
                return -1;
1816
0
            }
1817
1818
12
            for (int64_t i = 0; i < rs_meta.num_segments(); i++) {
1819
6
                rowset_index_cache_v1.segment_ids.insert(rowset_segment_id(rs_meta, i));
1820
6
            }
1821
1822
6
            for (const auto& i : rs_meta.tablet_schema().index()) {
1823
6
                if (i.has_index_type() && i.index_type() == IndexType::INVERTED) {
1824
6
                    LOG(INFO) << fmt::format(
1825
6
                            "record index info, index_id: {}, index_suffix_name: {}", i.index_id(),
1826
6
                            i.index_suffix_name());
1827
6
                    rowset_index_cache_v1.index_ids.insert(
1828
6
                            fmt::format("{}{}", i.index_id(), i.index_suffix_name()));
1829
6
                }
1830
6
            }
1831
1832
6
            if (!it->has_next()) {
1833
6
                begin = k;
1834
6
                begin.push_back('\x00'); // Update to next smallest key for iteration
1835
6
                break;
1836
6
            }
1837
6
        }
1838
6
    }
1839
1840
14
    if (!rowset_index_cache_v1.segment_ids.contains(segment_id)) {
1841
        // Garbage data leak
1842
        // clang-format off
1843
8
        LOG(WARNING) << "rowset_index_cache_v1.segment_ids don't contains segment_id, rowset should be recycled,"
1844
8
                     << " key = " << file_path
1845
8
                     << " segment_id = " << segment_id;
1846
        // clang-format on
1847
8
        return 1;
1848
8
    }
1849
1850
6
    if (!rowset_index_cache_v1.index_ids.contains(index_id_with_suffix_name)) {
1851
        // Garbage data leak
1852
        // clang-format off
1853
0
        LOG(WARNING) << "rowset_index_cache_v1.index_ids don't contains index_id_with_suffix_name,"
1854
0
                     << " rowset with inde meta should be recycled, key=" << file_path
1855
0
                     << " index_id_with_suffix_name=" << index_id_with_suffix_name;
1856
        // clang-format on
1857
0
        return 1;
1858
0
    }
1859
1860
6
    return 0;
1861
6
}
1862
1863
int InstanceChecker::check_inverted_index_file_storage_format_v2(
1864
        int64_t tablet_id, const std::string& file_path, const std::string& rowset_info,
1865
40
        RowsetIndexesFormatV2& rowset_index_cache_v2) {
1866
40
    std::string rowset_id;
1867
40
    int64_t segment_id;
1868
    // {rowset_id}_{seg_num}.idx
1869
40
    std::vector<std::string> str;
1870
40
    butil::SplitString(rowset_info.substr(0, rowset_info.size() - 4), '_', &str);
1871
40
    if (str.size() < 2) {
1872
        // clang-format off
1873
0
        LOG(WARNING) << "Split rowset info with '_' error, str size < 2, rowset_info = " << rowset_info;
1874
        // clang-format on
1875
0
        return -1;
1876
0
    }
1877
40
    rowset_id = str[0];
1878
40
    segment_id = std::atoll(str[1].c_str());
1879
1880
40
    if (rowset_index_cache_v2.rowset_id == rowset_id) {
1881
0
        if (!rowset_index_cache_v2.segment_ids.contains(segment_id)) {
1882
            // clang-format off
1883
0
            LOG(WARNING) << fmt::format("index file not found, rowset_info = {}, obj_key = {}", rowset_info, file_path);
1884
            // clang-format on
1885
0
            return -1;
1886
0
        }
1887
0
    }
1888
1889
40
    rowset_index_cache_v2.rowset_id = rowset_id;
1890
40
    rowset_index_cache_v2.segment_ids.clear();
1891
1892
40
    std::unique_ptr<Transaction> txn;
1893
40
    TxnErrorCode err = txn_kv_->create_txn(&txn);
1894
40
    if (err != TxnErrorCode::TXN_OK) {
1895
0
        LOG(WARNING) << "failed to create txn";
1896
0
        return -1;
1897
0
    }
1898
40
    std::unique_ptr<RangeGetIterator> it;
1899
40
    auto begin = meta_rowset_key({instance_id_, tablet_id, 0});
1900
40
    auto end = meta_rowset_key({instance_id_, tablet_id, INT64_MAX});
1901
75
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
1902
40
        TxnErrorCode err = txn->get(begin, end, &it);
1903
40
        if (err != TxnErrorCode::TXN_OK) {
1904
0
            LOG(WARNING) << "failed to get rowset kv, err=" << err;
1905
0
            return -1;
1906
0
        }
1907
40
        if (!it->has_next()) {
1908
5
            break;
1909
5
        }
1910
35
        while (it->has_next()) {
1911
            // recycle corresponding resources
1912
35
            auto [k, v] = it->next();
1913
35
            doris::RowsetMetaCloudPB rs_meta;
1914
35
            if (!rs_meta.ParseFromArray(v.data(), v.size())) {
1915
0
                LOG(WARNING) << "malformed rowset meta value, key=" << hex(k);
1916
0
                return -1;
1917
0
            }
1918
1919
70
            for (int64_t i = 0; i < rs_meta.num_segments(); i++) {
1920
35
                rowset_index_cache_v2.segment_ids.insert(rowset_segment_id(rs_meta, i));
1921
35
            }
1922
1923
35
            if (!it->has_next()) {
1924
35
                begin = k;
1925
35
                begin.push_back('\x00'); // Update to next smallest key for iteration
1926
35
                break;
1927
35
            }
1928
35
        }
1929
35
    }
1930
1931
40
    if (!rowset_index_cache_v2.segment_ids.contains(segment_id)) {
1932
        // Garbage data leak
1933
5
        LOG(WARNING) << "rowset with index meta should be recycled, key=" << file_path;
1934
5
        return 1;
1935
5
    }
1936
1937
35
    return 0;
1938
40
}
1939
1940
int InstanceChecker::check_delete_bitmap_storage_optimize_v2(
1941
        int64_t tablet_id, bool has_sequence_col,
1942
30
        int64_t& rowsets_with_useless_delete_bitmap_version) {
1943
    // end_version: create_time
1944
30
    std::map<int64_t, int64_t> tablet_rowsets_map {};
1945
    // rowset_id: {start_version, end_version}
1946
30
    std::map<std::string, std::pair<int64_t, int64_t>> rowset_version_map;
1947
    // Get all visible rowsets of this tablet
1948
195
    auto collect_cb = [&](const doris::RowsetMetaCloudPB& rowset) {
1949
195
        if (rowset.start_version() == 0 && rowset.end_version() == 1) {
1950
            // ignore dummy rowset [0-1]
1951
0
            return;
1952
0
        }
1953
195
        tablet_rowsets_map[rowset.end_version()] = rowset.creation_time();
1954
195
        rowset_version_map[rowset.rowset_id_v2()] =
1955
195
                std::make_pair(rowset.start_version(), rowset.end_version());
1956
195
    };
1957
30
    if (int ret = collect_tablet_rowsets(tablet_id, collect_cb); ret != 0) {
1958
0
        return ret;
1959
0
    }
1960
1961
30
    std::unordered_set<std::string> pending_delete_bitmaps;
1962
30
    if (auto ret = get_pending_delete_bitmap_keys(tablet_id, pending_delete_bitmaps); ret < 0) {
1963
0
        return ret;
1964
0
    }
1965
1966
30
    std::unique_ptr<RangeGetIterator> it;
1967
30
    auto begin = meta_delete_bitmap_key({instance_id_, tablet_id, "", 0, 0});
1968
30
    auto end = meta_delete_bitmap_key({instance_id_, tablet_id + 1, "", 0, 0});
1969
30
    std::string last_rowset_id = "";
1970
30
    int64_t last_version = 0;
1971
30
    int64_t last_failed_version = 0;
1972
30
    std::vector<int64_t> failed_versions;
1973
30
    auto print_failed_versions = [&]() {
1974
4
        TEST_SYNC_POINT_CALLBACK(
1975
4
                "InstanceChecker::check_delete_bitmap_storage_optimize_v2.get_abnormal_"
1976
4
                "rowset",
1977
4
                &tablet_id, &last_rowset_id);
1978
4
        rowsets_with_useless_delete_bitmap_version++;
1979
        // some versions are continuous, such as [8, 9, 10, 11, 13, 17, 18]
1980
        // print as [8-11, 13, 17-18]
1981
4
        int64_t last_start_version = -1;
1982
4
        int64_t last_end_version = -1;
1983
4
        std::stringstream ss;
1984
4
        ss << "[";
1985
9
        for (int64_t version : failed_versions) {
1986
9
            if (last_start_version == -1) {
1987
4
                last_start_version = version;
1988
4
                last_end_version = version;
1989
4
                continue;
1990
4
            }
1991
5
            if (last_end_version + 1 == version) {
1992
2
                last_end_version = version;
1993
3
            } else {
1994
3
                if (last_start_version == last_end_version) {
1995
3
                    ss << last_start_version << ", ";
1996
3
                } else {
1997
0
                    ss << last_start_version << "-" << last_end_version << ", ";
1998
0
                }
1999
3
                last_start_version = version;
2000
3
                last_end_version = version;
2001
3
            }
2002
5
        }
2003
4
        if (last_start_version == last_end_version) {
2004
3
            ss << last_start_version;
2005
3
        } else {
2006
1
            ss << last_start_version << "-" << last_end_version;
2007
1
        }
2008
4
        ss << "]";
2009
4
        std::stringstream version_str;
2010
4
        auto it = rowset_version_map.find(last_rowset_id);
2011
4
        if (it != rowset_version_map.end()) {
2012
4
            version_str << "[" << it->second.first << "-" << it->second.second << "]";
2013
4
        }
2014
4
        LOG(WARNING) << fmt::format(
2015
4
                "[delete bitmap check fails] delete bitmap storage optimize v2 check fail "
2016
4
                "for instance_id={}, tablet_id={}, rowset_id={}, version={} found delete "
2017
4
                "bitmap with versions={}, size={}",
2018
4
                instance_id_, tablet_id, last_rowset_id, version_str.str(), ss.str(),
2019
4
                failed_versions.size());
2020
4
    };
2021
30
    using namespace std::chrono;
2022
30
    int64_t now = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
2023
60
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
2024
30
        std::unique_ptr<Transaction> txn;
2025
30
        TxnErrorCode err = txn_kv_->create_txn(&txn);
2026
30
        if (err != TxnErrorCode::TXN_OK) {
2027
0
            LOG(WARNING) << "failed to create txn";
2028
0
            return -1;
2029
0
        }
2030
30
        err = txn->get(begin, end, &it);
2031
30
        if (err != TxnErrorCode::TXN_OK) {
2032
0
            LOG(WARNING) << "failed to get delete bitmap kv, err=" << err;
2033
0
            return -1;
2034
0
        }
2035
30
        if (!it->has_next()) {
2036
0
            break;
2037
0
        }
2038
691
        while (it->has_next() && !stopped()) {
2039
661
            auto [k, v] = it->next();
2040
661
            std::string_view k1 = k;
2041
661
            k1.remove_prefix(1);
2042
661
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2043
661
            decode_key(&k1, &out);
2044
            // 0x01 "meta" ${instance_id} "delete_bitmap" ${tablet_id} ${rowset_id} ${version} ${segment_id} -> roaringbitmap
2045
661
            auto rowset_id = std::get<std::string>(std::get<0>(out[4]));
2046
661
            auto version = std::get<std::int64_t>(std::get<0>(out[5]));
2047
661
            if (!it->has_next()) {
2048
30
                begin = k;
2049
30
                begin.push_back('\x00'); // Update to next smallest key for iteration
2050
30
            }
2051
661
            if (rowset_id == last_rowset_id && version == last_version) {
2052
                // skip the same rowset and version
2053
87
                continue;
2054
87
            }
2055
574
            if (rowset_id != last_rowset_id && !failed_versions.empty()) {
2056
3
                print_failed_versions();
2057
3
                last_failed_version = 0;
2058
3
                failed_versions.clear();
2059
3
            }
2060
574
            last_rowset_id = rowset_id;
2061
574
            last_version = version;
2062
574
            if (tablet_rowsets_map.find(version) != tablet_rowsets_map.end()) {
2063
548
                continue;
2064
548
            }
2065
26
            auto version_it = rowset_version_map.find(rowset_id);
2066
26
            if (version_it == rowset_version_map.end()) {
2067
                // checked in do_delete_bitmap_inverted_check
2068
1
                continue;
2069
1
            }
2070
25
            if (pending_delete_bitmaps.contains(std::string(k))) {
2071
3
                continue;
2072
3
            }
2073
22
            if (has_sequence_col && version >= version_it->second.first &&
2074
22
                version <= version_it->second.second) {
2075
5
                continue;
2076
5
            }
2077
            // there may be an interval in this situation:
2078
            // 1. finish compaction job; 2. checker; 3. finish agg and remove delete bitmap to ms
2079
17
            auto rowset_it = tablet_rowsets_map.upper_bound(version);
2080
17
            if (rowset_it == tablet_rowsets_map.end()) {
2081
1
                if (version != last_failed_version) {
2082
1
                    failed_versions.push_back(version);
2083
1
                }
2084
1
                last_failed_version = version;
2085
1
                continue;
2086
1
            }
2087
16
            if (rowset_it->second + config::delete_bitmap_storage_optimize_v2_check_skip_seconds >=
2088
16
                now) {
2089
8
                continue;
2090
8
            }
2091
8
            if (version != last_failed_version) {
2092
8
                failed_versions.push_back(version);
2093
8
            }
2094
8
            last_failed_version = version;
2095
8
        }
2096
30
    }
2097
30
    if (!failed_versions.empty()) {
2098
1
        print_failed_versions();
2099
1
    }
2100
30
    LOG(INFO) << fmt::format(
2101
30
            "[delete bitmap checker] finish check delete bitmap storage optimize v2 for "
2102
30
            "instance_id={}, tablet_id={}, rowsets_num={}, "
2103
30
            "rowsets_with_useless_delete_bitmap_version={}",
2104
30
            instance_id_, tablet_id, tablet_rowsets_map.size(),
2105
30
            rowsets_with_useless_delete_bitmap_version);
2106
30
    return (rowsets_with_useless_delete_bitmap_version > 1 ? 1 : 0);
2107
30
}
2108
2109
3
int InstanceChecker::do_delete_bitmap_storage_optimize_check(int version) {
2110
3
    if (version != 2) {
2111
0
        return -1;
2112
0
    }
2113
3
    int64_t total_tablets_num {0};
2114
3
    int64_t failed_tablets_num {0};
2115
2116
    // for v2 check
2117
3
    int64_t max_rowsets_with_useless_delete_bitmap_version = 0;
2118
3
    int64_t tablet_id_with_max_rowsets_with_useless_delete_bitmap_version = 0;
2119
2120
    // check that for every visible rowset, there exists at least delete one bitmap in MS
2121
30
    int ret = traverse_mow_tablet([&](int64_t tablet_id, bool has_sequence_col) {
2122
30
        ++total_tablets_num;
2123
30
        int64_t rowsets_with_useless_delete_bitmap_version = 0;
2124
30
        int res = check_delete_bitmap_storage_optimize_v2(
2125
30
                tablet_id, has_sequence_col, rowsets_with_useless_delete_bitmap_version);
2126
30
        if (rowsets_with_useless_delete_bitmap_version >
2127
30
            max_rowsets_with_useless_delete_bitmap_version) {
2128
1
            max_rowsets_with_useless_delete_bitmap_version =
2129
1
                    rowsets_with_useless_delete_bitmap_version;
2130
1
            tablet_id_with_max_rowsets_with_useless_delete_bitmap_version = tablet_id;
2131
1
        }
2132
30
        failed_tablets_num += (res != 0);
2133
30
        return res;
2134
30
    });
2135
2136
3
    if (ret < 0) {
2137
0
        return ret;
2138
0
    }
2139
2140
3
    g_bvar_max_rowsets_with_useless_delete_bitmap_version.put(
2141
3
            instance_id_, max_rowsets_with_useless_delete_bitmap_version);
2142
2143
3
    std::stringstream ss;
2144
3
    ss << "[delete bitmap checker] check delete bitmap storage optimize v" << version
2145
3
       << " for instance_id=" << instance_id_ << ", total_tablets_num=" << total_tablets_num
2146
3
       << ", failed_tablets_num=" << failed_tablets_num
2147
3
       << ". max_rowsets_with_useless_delete_bitmap_version="
2148
3
       << max_rowsets_with_useless_delete_bitmap_version
2149
3
       << ", tablet_id=" << tablet_id_with_max_rowsets_with_useless_delete_bitmap_version;
2150
3
    LOG(INFO) << ss.str();
2151
2152
3
    return (failed_tablets_num > 0) ? 1 : 0;
2153
3
}
2154
2155
3
int InstanceChecker::do_mow_job_key_check() {
2156
3
    std::unique_ptr<RangeGetIterator> it;
2157
3
    std::string begin = mow_tablet_job_key({instance_id_, 0, 0});
2158
3
    std::string end = mow_tablet_job_key({instance_id_, INT64_MAX, 0});
2159
3
    MowTabletJobPB mow_tablet_job;
2160
4
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
2161
3
        std::unique_ptr<Transaction> txn;
2162
3
        TxnErrorCode err = txn_kv_->create_txn(&txn);
2163
3
        if (err != TxnErrorCode::TXN_OK) {
2164
0
            LOG(WARNING) << "failed to create txn";
2165
0
            return -1;
2166
0
        }
2167
3
        err = txn->get(begin, end, &it);
2168
3
        if (err != TxnErrorCode::TXN_OK) {
2169
0
            LOG(WARNING) << "failed to get mow tablet job key, err=" << err;
2170
0
            return -1;
2171
0
        }
2172
3
        int64_t now = duration_cast<std::chrono::seconds>(
2173
3
                              std::chrono::system_clock::now().time_since_epoch())
2174
3
                              .count();
2175
3
        while (it->has_next() && !stopped()) {
2176
2
            auto [k, v] = it->next();
2177
2
            std::string_view k1 = k;
2178
2
            k1.remove_prefix(1);
2179
2
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2180
2
            decode_key(&k1, &out);
2181
            // 0x01 "meta" ${instance_id} "mow_tablet_job" ${table_id} ${initiator}
2182
2
            auto table_id = std::get<int64_t>(std::get<0>(out[3]));
2183
2
            auto initiator = std::get<int64_t>(std::get<0>(out[4]));
2184
2
            if (!mow_tablet_job.ParseFromArray(v.data(), v.size())) [[unlikely]] {
2185
0
                LOG(WARNING) << "failed to parse MowTabletJobPB";
2186
0
                return -1;
2187
0
            }
2188
2
            int64_t expiration = mow_tablet_job.expiration();
2189
            // check job key failed should meet both following two condition:
2190
            // 1. job key is expired
2191
            // 2. table lock key is not found or key is not expired
2192
2
            if (expiration < now - config::mow_job_key_check_expiration_diff_seconds) {
2193
2
                std::string lock_key =
2194
2
                        meta_delete_bitmap_update_lock_key({instance_id_, table_id, -1});
2195
2
                std::string lock_val;
2196
2
                err = txn->get(lock_key, &lock_val);
2197
2
                std::string reason = "";
2198
2
                if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
2199
0
                    reason = "table lock key not found";
2200
2201
2
                } else {
2202
2
                    DeleteBitmapUpdateLockPB lock_info;
2203
2
                    if (!lock_info.ParseFromString(lock_val)) [[unlikely]] {
2204
0
                        LOG(WARNING) << "failed to parse DeleteBitmapUpdateLockPB";
2205
0
                        return -1;
2206
0
                    }
2207
2
                    if (lock_info.expiration() > now || lock_info.lock_id() != -1) {
2208
2
                        reason = "table lock is not expired,lock_id=" +
2209
2
                                 std::to_string(lock_info.lock_id());
2210
2
                    }
2211
2
                }
2212
2
                if (reason != "") {
2213
2
                    LOG(WARNING) << fmt::format(
2214
2
                            "[compaction key check fails] mow job key check fail for "
2215
2
                            "instance_id={}, table_id={}, initiator={}, expiration={}, now={}, "
2216
2
                            "reason={}",
2217
2
                            instance_id_, table_id, initiator, expiration, now, reason);
2218
2
                    return -1;
2219
2
                }
2220
2
            }
2221
2
        }
2222
1
        begin = it->next_begin_key(); // Update to next smallest key for iteration
2223
1
    }
2224
1
    return 0;
2225
3
}
2226
4
int InstanceChecker::do_tablet_stats_key_check() {
2227
4
    int ret = 0;
2228
2229
4
    int64_t nums_leak = 0;
2230
4
    int64_t nums_loss = 0;
2231
4
    int64_t nums_scanned = 0;
2232
4
    int64_t nums_abnormal = 0;
2233
2234
4
    std::string begin = meta_tablet_key({instance_id_, 0, 0, 0, 0});
2235
4
    std::string end = meta_tablet_key({instance_id_, INT64_MAX, 0, 0, 0});
2236
    // inverted check tablet exists
2237
4
    LOG(INFO) << "begin inverted check stats_tablet_key";
2238
4
    ret = scan_and_handle_kv(begin, end, [&](std::string_view key, std::string_view value) {
2239
4
        int ret = check_stats_tablet_key_exists(key, value);
2240
4
        nums_scanned++;
2241
4
        if (ret == 1) {
2242
1
            nums_loss++;
2243
1
        }
2244
4
        return ret;
2245
4
    });
2246
4
    if (ret == -1) {
2247
0
        LOG(WARNING) << "failed to inverted check if stats tablet key exists";
2248
0
        return -1;
2249
4
    } else if (ret == 1) {
2250
1
        LOG(WARNING) << "stats_tablet_key loss, nums_scanned=" << nums_scanned
2251
1
                     << ", nums_loss=" << nums_loss;
2252
1
        return 1;
2253
1
    }
2254
4
    LOG(INFO) << "finish inverted check stats_tablet_key, nums_scanned=" << nums_scanned
2255
3
              << ", nums_loss=" << nums_loss;
2256
2257
3
    begin = stats_tablet_key({instance_id_, 0, 0, 0, 0});
2258
3
    end = stats_tablet_key({instance_id_, INT64_MAX, 0, 0, 0});
2259
3
    nums_scanned = 0;
2260
    // check tablet exists
2261
3
    LOG(INFO) << "begin check stats_tablet_key leaked";
2262
4
    ret = scan_and_handle_kv(begin, end, [&](std::string_view key, std::string_view value) {
2263
4
        int ret = check_stats_tablet_key_leaked(key, value);
2264
4
        nums_scanned++;
2265
4
        if (ret == 1) {
2266
1
            nums_leak++;
2267
1
        }
2268
4
        return ret;
2269
4
    });
2270
3
    if (ret == -1) {
2271
0
        LOG(WARNING) << "failed to check if stats tablet key exists";
2272
0
        return -1;
2273
3
    } else if (ret == 1) {
2274
1
        LOG(WARNING) << "stats_tablet_key leaked, nums_scanned=" << nums_scanned
2275
1
                     << ", nums_leak=" << nums_leak;
2276
1
        return 1;
2277
1
    }
2278
3
    LOG(INFO) << "finish check stats_tablet_key leaked, nums_scanned=" << nums_scanned
2279
2
              << ", nums_leak=" << nums_leak;
2280
2281
2
    begin = stats_tablet_key({instance_id_, 0, 0, 0, 0});
2282
2
    end = stats_tablet_key({instance_id_, INT64_MAX, 0, 0, 0});
2283
2
    nums_scanned = 0;
2284
    // check if key is normal
2285
2
    LOG(INFO) << "begin check stats_tablet_key abnormal";
2286
2
    ret = scan_and_handle_kv(begin, end, [&](std::string_view key, std::string_view value) {
2287
2
        int ret = check_stats_tablet_key(key, value);
2288
2
        nums_scanned++;
2289
2
        if (ret == 1) {
2290
1
            nums_abnormal++;
2291
1
        }
2292
2
        return ret;
2293
2
    });
2294
2
    if (ret == -1) {
2295
0
        LOG(WARNING) << "failed to check if stats tablet key exists";
2296
0
        return -1;
2297
2
    } else if (ret == 1) {
2298
1
        LOG(WARNING) << "stats_tablet_key abnormal, nums_scanned=" << nums_scanned
2299
1
                     << ", nums_abnormal=" << nums_abnormal;
2300
1
        return 1;
2301
1
    }
2302
2
    LOG(INFO) << "finish check stats_tablet_key, nums_scanned=" << nums_scanned
2303
1
              << ", nums_abnormal=" << nums_abnormal;
2304
1
    return 0;
2305
2
}
2306
2307
4
int InstanceChecker::check_stats_tablet_key_exists(std::string_view key, std::string_view value) {
2308
4
    std::string_view k1 = key;
2309
4
    k1.remove_prefix(1);
2310
4
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2311
4
    decode_key(&k1, &out);
2312
    // 0x01 "meta" ${instance_id} "tablet" ${table_id} ${index_id} ${partition_id} ${tablet_id}
2313
4
    auto table_id = std::get<int64_t>(std::get<0>(out[3]));
2314
4
    auto index_id = std::get<int64_t>(std::get<0>(out[4]));
2315
4
    auto partition_id = std::get<int64_t>(std::get<0>(out[5]));
2316
4
    auto tablet_id = std::get<int64_t>(std::get<0>(out[6]));
2317
4
    std::string tablet_stats_key =
2318
4
            stats_tablet_key({instance_id_, table_id, index_id, partition_id, tablet_id});
2319
4
    int ret = key_exist(txn_kv_.get(), tablet_stats_key);
2320
4
    if (ret == 1) {
2321
        // clang-format off
2322
1
        LOG(WARNING) << "stats tablet key's tablet key loss,"
2323
1
                    << " stats tablet key=" << hex(tablet_stats_key)
2324
1
                    << " meta tablet key=" << hex(key);
2325
        // clang-format on
2326
1
        return 1;
2327
3
    } else if (ret == -1) {
2328
0
        LOG(WARNING) << "failed to check key exists, key=" << hex(tablet_stats_key);
2329
0
        return -1;
2330
0
    }
2331
4
    LOG(INFO) << "check stats_tablet_key_exists ok, key=" << hex(key);
2332
3
    return 0;
2333
4
}
2334
2335
4
int InstanceChecker::check_stats_tablet_key_leaked(std::string_view key, std::string_view value) {
2336
4
    std::string_view k1 = key;
2337
4
    k1.remove_prefix(1);
2338
4
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2339
4
    decode_key(&k1, &out);
2340
    // 0x01 "stats" ${instance_id} "tablet" ${table_id} ${index_id} ${partition_id} ${tablet_id}
2341
4
    auto table_id = std::get<int64_t>(std::get<0>(out[3]));
2342
4
    auto index_id = std::get<int64_t>(std::get<0>(out[4]));
2343
4
    auto partition_id = std::get<int64_t>(std::get<0>(out[5]));
2344
4
    auto tablet_id = std::get<int64_t>(std::get<0>(out[6]));
2345
4
    std::string tablet_key =
2346
4
            meta_tablet_key({instance_id_, table_id, index_id, partition_id, tablet_id});
2347
4
    int ret = key_exist(txn_kv_.get(), tablet_key);
2348
4
    if (ret == 1) {
2349
        // clang-format off
2350
1
        LOG(WARNING) << "stats tablet key's tablet key leak,"
2351
1
                    << " stats tablet key=" << hex(key)
2352
1
                    << " meta tablet key=" << hex(tablet_key);
2353
        // clang-format on
2354
1
        return 1;
2355
3
    } else if (ret == -1) {
2356
0
        LOG(WARNING) << "failed to check key exists, key=" << hex(tablet_key);
2357
0
        return -1;
2358
0
    }
2359
4
    LOG(INFO) << "check stats_tablet_key_leaked ok, key=" << hex(key);
2360
3
    return 0;
2361
4
}
2362
2363
2
int InstanceChecker::check_stats_tablet_key(std::string_view key, std::string_view value) {
2364
2
    TabletStatsPB tablet_stats_pb;
2365
2
    std::string_view k1 = key;
2366
2
    k1.remove_prefix(1);
2367
2
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2368
2
    decode_key(&k1, &out);
2369
    // 0x01 "stats" ${instance_id} "tablet" ${table_id} ${index_id} ${partition_id} ${tablet_id}
2370
2
    auto tablet_id = std::get<int64_t>(std::get<0>(out[6]));
2371
2
    std::unique_ptr<Transaction> txn;
2372
2
    TxnErrorCode err = txn_kv_->create_txn(&txn);
2373
2
    if (err != TxnErrorCode::TXN_OK) {
2374
0
        LOG_WARNING("failed to recycle tablet ")
2375
0
                .tag("tablet id", tablet_id)
2376
0
                .tag("instance_id", instance_id_)
2377
0
                .tag("reason", "failed to create txn");
2378
0
        return -1;
2379
0
    }
2380
2
    std::string tablet_idx_key = meta_tablet_idx_key({instance_id_, tablet_id});
2381
2
    std::string tablet_idx_val;
2382
2
    TabletIndexPB tablet_idx;
2383
2
    err = txn->get(tablet_idx_key, &tablet_idx_val);
2384
2
    if (err != TxnErrorCode::TXN_OK) {
2385
        // clang-format off
2386
0
        LOG(WARNING) << "failed to get tablet index key,"
2387
0
                        << " key=" << hex(tablet_idx_key)
2388
0
                        << " code=" << err;
2389
        // clang-format on
2390
0
        return -1;
2391
0
    }
2392
2
    tablet_idx.ParseFromString(tablet_idx_val);
2393
2
    MetaServiceCode code = MetaServiceCode::OK;
2394
2
    std::string msg;
2395
2
    internal_get_tablet_stats(code, msg, txn.get(), instance_id_, tablet_idx, tablet_stats_pb);
2396
2
    if (code != MetaServiceCode::OK) {
2397
        // clang-format off
2398
0
        LOG(WARNING) << "failed to get tablet stats,"
2399
0
                        << " code=" << code 
2400
0
                        << " msg=" << msg;
2401
        // clang-format on
2402
0
        return -1;
2403
0
    }
2404
2405
2
    GetRowsetResponse resp;
2406
    // get rowsets in tablet
2407
2
    internal_get_rowset(txn.get(), 0, std::numeric_limits<int64_t>::max() - 1, instance_id_,
2408
2
                        tablet_id, code, msg, &resp);
2409
2
    if (code != MetaServiceCode::OK) {
2410
0
        LOG_WARNING("failed to get rowsets of tablet when check stats tablet key")
2411
0
                .tag("tablet id", tablet_id)
2412
0
                .tag("msg", msg)
2413
0
                .tag("code", code)
2414
0
                .tag("instance id", instance_id_);
2415
0
        return -1;
2416
0
    }
2417
2
    int64_t num_rows = 0;
2418
2
    int64_t num_rowsets = 0;
2419
2
    int64_t num_segments = 0;
2420
2
    int64_t total_data_size = 0;
2421
2
    for (const auto& rs_meta : resp.rowset_meta()) {
2422
2
        num_rows += rs_meta.num_rows();
2423
2
        num_rowsets++;
2424
2
        num_segments += rs_meta.num_segments();
2425
2
        total_data_size += rs_meta.total_disk_size();
2426
2
    }
2427
2
    int ret = 0;
2428
2
    if (tablet_stats_pb.data_size() != total_data_size) {
2429
1
        ret = 1;
2430
        // clang-format off
2431
1
        LOG(WARNING) << " tablet_stats_pb's data size is not same with all rowset total data size,"
2432
1
                        << " tablet_stats_pb's data size=" << tablet_stats_pb.data_size()
2433
1
                        << " all rowset total data size=" << total_data_size
2434
1
                        << " stats tablet meta=" << tablet_stats_pb.ShortDebugString();
2435
        // clang-format on
2436
1
    } else if (tablet_stats_pb.num_rows() != num_rows) {
2437
0
        ret = 1;
2438
        // clang-format off
2439
0
        LOG(WARNING) << " tablet_stats_pb's num_rows is not same with all rowset total num_rows,"
2440
0
                        << " tablet_stats_pb's num_rows=" << tablet_stats_pb.num_rows()
2441
0
                        << " all rowset total num_rows=" << num_rows
2442
0
                        << " stats tablet meta=" << tablet_stats_pb.ShortDebugString();
2443
        // clang-format on
2444
1
    } else if (tablet_stats_pb.num_rowsets() != num_rowsets) {
2445
0
        ret = 1;
2446
        // clang-format off
2447
0
        LOG(WARNING) << " tablet_stats_pb's num_rowsets is not same with all rowset nums,"
2448
0
                        << " tablet_stats_pb's num_rowsets=" << tablet_stats_pb.num_rowsets()
2449
0
                        << " all rowset nums=" << num_rowsets
2450
0
                        << " stats tablet meta=" << tablet_stats_pb.ShortDebugString();
2451
        // clang-format on
2452
1
    } else if (tablet_stats_pb.num_segments() != num_segments) {
2453
0
        ret = 1;
2454
        // clang-format off
2455
0
        LOG(WARNING) << " tablet_stats_pb's num_segments is not same with all rowset total num_segments,"
2456
0
                        << " tablet_stats_pb's num_segments=" << tablet_stats_pb.num_segments()
2457
0
                        << " all rowset total num_segments=" << num_segments
2458
0
                        << " stats tablet meta=" << tablet_stats_pb.ShortDebugString();
2459
        // clang-format on
2460
0
    }
2461
2462
2
    return ret;
2463
2
}
2464
2465
int InstanceChecker::scan_and_handle_kv(
2466
        std::string& start_key, const std::string& end_key,
2467
81
        std::function<int(std::string_view, std::string_view)> handle_kv) {
2468
81
    std::unique_ptr<Transaction> txn;
2469
81
    int ret = 0;
2470
81
    TxnErrorCode err = txn_kv_->create_txn(&txn);
2471
81
    if (err != TxnErrorCode::TXN_OK) {
2472
0
        LOG(WARNING) << "failed to init txn";
2473
0
        return -1;
2474
0
    }
2475
81
    std::unique_ptr<RangeGetIterator> it;
2476
81
    int limit = 10000;
2477
81
    TEST_SYNC_POINT_CALLBACK("InstanceChecker:scan_and_handle_kv:limit", &limit);
2478
162
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
2479
82
        err = txn->get(start_key, end_key, &it, false, limit);
2480
82
        TEST_SYNC_POINT_CALLBACK("InstanceChecker:scan_and_handle_kv:get_err", &err);
2481
82
        if (err == TxnErrorCode::TXN_TOO_OLD) {
2482
1
            LOG(WARNING) << "failed to get range kv, err=txn too old, "
2483
1
                         << " now fallback to non snapshot scan";
2484
1
            err = txn_kv_->create_txn(&txn);
2485
1
            if (err == TxnErrorCode::TXN_OK) {
2486
1
                err = txn->get(start_key, end_key, &it);
2487
1
            }
2488
1
        }
2489
82
        if (err != TxnErrorCode::TXN_OK) {
2490
0
            LOG(WARNING) << "internal error, failed to get range kv, err=" << err;
2491
0
            return -1;
2492
0
        }
2493
2494
340
        while (it->has_next() && !stopped()) {
2495
259
            auto [k, v] = it->next();
2496
2497
259
            int handle_ret = handle_kv(k, v);
2498
259
            if (handle_ret == -1) {
2499
1
                return -1;
2500
258
            } else {
2501
258
                ret = std::max(ret, handle_ret);
2502
258
            }
2503
258
            if (!it->has_next()) {
2504
52
                start_key = k;
2505
52
            }
2506
258
        }
2507
81
        start_key = it->next_begin_key();
2508
81
    }
2509
80
    return ret;
2510
81
}
2511
2512
// The check validates Offset values and the Latest/Versioned projection invariant. FE Catalog is
2513
// the authority for Stream existence and binding, so no MS-side Stream Mapping is checked here.
2514
35
int InstanceChecker::do_table_stream_check() {
2515
35
    struct VersionedOffset {
2516
35
        Versionstamp versionstamp;
2517
35
        TableStreamOffsetPB offset;
2518
35
    };
2519
2520
35
    auto decode_components =
2521
35
            [](std::string_view key, size_t expected_size,
2522
55
               std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>>* components) {
2523
55
                if (key.empty()) {
2524
0
                    return false;
2525
0
                }
2526
55
                key.remove_prefix(1);
2527
55
                return decode_key(&key, components) == 0 && components->size() == expected_size;
2528
55
            };
2529
2530
35
    auto classify_recycle_index = [&](const RecycleIndexPB* recycle_index, int64_t base_db_id,
2531
35
                                      int64_t base_table_id, int64_t stream_db_id,
2532
43
                                      int64_t stream_id) {
2533
43
        if (recycle_index == nullptr) {
2534
27
            return 0;
2535
27
        }
2536
16
        if (recycle_index->object_type() != TABLE_STREAM || !recycle_index->has_db_id() ||
2537
16
            recycle_index->db_id() != base_db_id || !recycle_index->has_table_id() ||
2538
16
            recycle_index->table_id() != base_table_id || !recycle_index->has_stream_db_id() ||
2539
16
            recycle_index->stream_db_id() != stream_db_id || !recycle_index->has_state()) {
2540
6
            LOG_WARNING("Recycle Index does not match Table Stream Offset")
2541
6
                    .tag("instance_id", instance_id_)
2542
6
                    .tag("stream_id", stream_id)
2543
6
                    .tag("recycle_index", recycle_index->ShortDebugString());
2544
6
            return 1;
2545
6
        }
2546
10
        switch (recycle_index->state()) {
2547
2
        case RecycleIndexPB::PREPARED:
2548
4
        case RecycleIndexPB::DROPPED:
2549
4
            return 0;
2550
4
        case RecycleIndexPB::RECYCLING:
2551
4
            return 2;
2552
2
        default:
2553
2
            LOG_WARNING("Recycle Index has invalid state for Table Stream Offset")
2554
2
                    .tag("instance_id", instance_id_)
2555
2
                    .tag("stream_id", stream_id)
2556
2
                    .tag("state", recycle_index->state());
2557
2
            return 1;
2558
10
        }
2559
10
    };
2560
2561
35
    std::unordered_map<int64_t, std::optional<RecycleIndexPB>> recycle_indexes;
2562
35
    auto classify_cached_recycle_index = [&](int64_t base_db_id, int64_t base_table_id,
2563
41
                                             int64_t stream_db_id, int64_t stream_id) {
2564
41
        auto cached = recycle_indexes.find(stream_id);
2565
41
        if (cached == recycle_indexes.end()) {
2566
25
            std::unique_ptr<Transaction> txn;
2567
25
            TxnErrorCode err = txn_kv_->create_txn(&txn);
2568
25
            if (err != TxnErrorCode::TXN_OK) {
2569
0
                LOG_WARNING("failed to create transaction for Recycle Index check")
2570
0
                        .tag("instance_id", instance_id_)
2571
0
                        .tag("stream_id", stream_id)
2572
0
                        .tag("error", err);
2573
0
                return -1;
2574
0
            }
2575
25
            std::string value;
2576
25
            err = txn->get(recycle_index_key({instance_id_, stream_id}), &value, true);
2577
25
            if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
2578
17
                cached = recycle_indexes.emplace(stream_id, std::nullopt).first;
2579
17
            } else if (err != TxnErrorCode::TXN_OK) {
2580
0
                LOG_WARNING("failed to read Recycle Index during Table Stream Offset check")
2581
0
                        .tag("instance_id", instance_id_)
2582
0
                        .tag("stream_id", stream_id)
2583
0
                        .tag("error", err);
2584
0
                return -1;
2585
8
            } else {
2586
8
                RecycleIndexPB recycle_index;
2587
8
                if (!recycle_index.ParseFromString(value)) {
2588
1
                    LOG_WARNING("failed to parse Recycle Index during Table Stream Offset check")
2589
1
                            .tag("instance_id", instance_id_)
2590
1
                            .tag("stream_id", stream_id);
2591
1
                    return -1;
2592
1
                }
2593
7
                cached = recycle_indexes.emplace(stream_id, std::move(recycle_index)).first;
2594
7
            }
2595
25
        }
2596
40
        const RecycleIndexPB* recycle_index =
2597
40
                cached->second.has_value() ? &cached->second.value() : nullptr;
2598
40
        return classify_recycle_index(recycle_index, base_db_id, base_table_id, stream_db_id,
2599
40
                                      stream_id);
2600
41
    };
2601
2602
35
    int check_ret = 0;
2603
35
    std::unordered_map<std::string, TableStreamOffsetPB> latest_offsets;
2604
35
    auto validate_offset = [&](int64_t base_db_id, int64_t base_table_id, int64_t stream_db_id,
2605
35
                               int64_t stream_id, int64_t partition_id,
2606
43
                               const TableStreamOffsetPB& offset) {
2607
43
        if (base_db_id <= 0 || base_table_id <= 0 || stream_db_id <= 0 || stream_id <= 0 ||
2608
43
            partition_id <= 0 || !offset.has_partition_id() || !offset.has_state() ||
2609
43
            !offset.has_offset_tso() || offset.partition_id() != partition_id ||
2610
43
            (offset.state() != TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING &&
2611
41
             offset.state() != TABLE_STREAM_OFFSET_CONSUMED)) {
2612
2
            LOG_WARNING("Table Stream Offset value does not match its key")
2613
2
                    .tag("instance_id", instance_id_)
2614
2
                    .tag("stream_id", stream_id)
2615
2
                    .tag("key_partition_id", partition_id)
2616
2
                    .tag("value_partition_id", offset.partition_id())
2617
2
                    .tag("state", offset.state());
2618
2
            return 1;
2619
2
        }
2620
2621
41
        return classify_cached_recycle_index(base_db_id, base_table_id, stream_db_id, stream_id);
2622
43
    };
2623
2624
35
    std::string begin = table_stream_offset_key({instance_id_, 0, 0, 0, 0, 0});
2625
35
    const std::string latest_end = table_stream_offset_key({instance_id_, INT64_MAX, 0, 0, 0, 0});
2626
35
    int ret = scan_and_handle_kv(
2627
35
            begin, latest_end, [&](std::string_view key, std::string_view value) {
2628
23
                std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> components;
2629
23
                if (!decode_components(key, 8, &components)) {
2630
0
                    LOG_WARNING("failed to decode Latest Stream Offset key").tag("key", hex(key));
2631
0
                    return -1;
2632
0
                }
2633
23
                TableStreamOffsetPB offset;
2634
23
                if (!offset.ParseFromArray(value.data(), value.size())) {
2635
0
                    LOG_WARNING("failed to parse Latest Stream Offset").tag("key", hex(key));
2636
0
                    return -1;
2637
0
                }
2638
23
                int64_t base_db_id = std::get<int64_t>(std::get<0>(components[3]));
2639
23
                int64_t base_table_id = std::get<int64_t>(std::get<0>(components[4]));
2640
23
                int64_t stream_db_id = std::get<int64_t>(std::get<0>(components[5]));
2641
23
                int64_t stream_id = std::get<int64_t>(std::get<0>(components[6]));
2642
23
                int64_t partition_id = std::get<int64_t>(std::get<0>(components[7]));
2643
23
                int validation = validate_offset(base_db_id, base_table_id, stream_db_id, stream_id,
2644
23
                                                 partition_id, offset);
2645
23
                if (validation == 2) {
2646
2
                    return 0;
2647
2
                }
2648
21
                if (validation != 0) {
2649
6
                    return validation;
2650
6
                }
2651
15
                latest_offsets.emplace(std::string(key), std::move(offset));
2652
15
                return 0;
2653
21
            });
2654
35
    if (ret < 0) {
2655
1
        return ret;
2656
1
    }
2657
34
    check_ret = std::max(check_ret, ret);
2658
34
    TEST_SYNC_POINT("InstanceChecker::do_table_stream_check::after_latest_scan");
2659
2660
34
    std::unordered_map<std::string, VersionedOffset> versioned_offsets;
2661
34
    begin = versioned::table_stream_offset_key({instance_id_, 0, 0, 0, 0, 0});
2662
34
    const std::string versioned_end =
2663
34
            versioned::table_stream_offset_key({instance_id_, INT64_MAX, 0, 0, 0, 0});
2664
34
    ret = scan_and_handle_kv(
2665
34
            begin, versioned_end, [&](std::string_view key, std::string_view value) {
2666
20
                std::string_view logical_key = key;
2667
20
                Versionstamp versionstamp;
2668
20
                if (!decode_versioned_key(&logical_key, &versionstamp)) {
2669
0
                    LOG_WARNING("failed to decode Versioned Stream Offset versionstamp")
2670
0
                            .tag("key", hex(key));
2671
0
                    return -1;
2672
0
                }
2673
20
                std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> components;
2674
20
                if (!decode_components(logical_key, 8, &components)) {
2675
0
                    LOG_WARNING("failed to decode Versioned Stream Offset key")
2676
0
                            .tag("key", hex(key));
2677
0
                    return -1;
2678
0
                }
2679
20
                TableStreamOffsetPB offset;
2680
20
                if (!offset.ParseFromArray(value.data(), value.size())) {
2681
0
                    LOG_WARNING("failed to parse Versioned Stream Offset").tag("key", hex(key));
2682
0
                    return -1;
2683
0
                }
2684
20
                int64_t base_db_id = std::get<int64_t>(std::get<0>(components[3]));
2685
20
                int64_t base_table_id = std::get<int64_t>(std::get<0>(components[4]));
2686
20
                int64_t stream_db_id = std::get<int64_t>(std::get<0>(components[5]));
2687
20
                int64_t stream_id = std::get<int64_t>(std::get<0>(components[6]));
2688
20
                int64_t partition_id = std::get<int64_t>(std::get<0>(components[7]));
2689
20
                int validation = validate_offset(base_db_id, base_table_id, stream_db_id, stream_id,
2690
20
                                                 partition_id, offset);
2691
20
                if (validation == 2) {
2692
1
                    return 0;
2693
1
                }
2694
19
                if (validation != 0) {
2695
5
                    return validation;
2696
5
                }
2697
2698
14
                std::string latest_key =
2699
14
                        table_stream_offset_key({instance_id_, base_db_id, base_table_id,
2700
14
                                                 stream_db_id, stream_id, partition_id});
2701
14
                auto it = versioned_offsets.find(latest_key);
2702
14
                if (it == versioned_offsets.end() || it->second.versionstamp < versionstamp) {
2703
14
                    versioned_offsets[std::move(latest_key)] =
2704
14
                            VersionedOffset {versionstamp, std::move(offset)};
2705
14
                }
2706
14
                return 0;
2707
19
            });
2708
34
    if (ret < 0) {
2709
0
        return ret;
2710
0
    }
2711
34
    check_ret = std::max(check_ret, ret);
2712
2713
34
    if (table_stream_versioned_write_) {
2714
20
        std::unordered_set<std::string> apparent_inconsistencies;
2715
20
        for (const auto& [key, latest] : latest_offsets) {
2716
12
            auto it = versioned_offsets.find(key);
2717
12
            if (it == versioned_offsets.end() ||
2718
12
                latest.SerializeAsString() != it->second.offset.SerializeAsString()) {
2719
9
                apparent_inconsistencies.insert(key);
2720
9
            }
2721
12
        }
2722
20
        for (const auto& entry : versioned_offsets) {
2723
10
            const std::string& key = entry.first;
2724
10
            if (!latest_offsets.contains(key)) {
2725
3
                apparent_inconsistencies.insert(key);
2726
3
            }
2727
10
        }
2728
2729
20
        auto recheck_projection = [&](const std::string& latest_key) -> int {
2730
12
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> components;
2731
12
            if (!decode_components(latest_key, 8, &components)) {
2732
0
                LOG_WARNING("failed to decode Latest Stream Offset key during recheck")
2733
0
                        .tag("instance_id", instance_id_)
2734
0
                        .tag("latest_key", hex(latest_key));
2735
0
                return -1;
2736
0
            }
2737
12
            int64_t base_db_id = std::get<int64_t>(std::get<0>(components[3]));
2738
12
            int64_t base_table_id = std::get<int64_t>(std::get<0>(components[4]));
2739
12
            int64_t stream_db_id = std::get<int64_t>(std::get<0>(components[5]));
2740
12
            int64_t stream_id = std::get<int64_t>(std::get<0>(components[6]));
2741
12
            int64_t partition_id = std::get<int64_t>(std::get<0>(components[7]));
2742
2743
12
            std::unique_ptr<Transaction> txn;
2744
12
            TxnErrorCode err = txn_kv_->create_txn(&txn);
2745
12
            if (err != TxnErrorCode::TXN_OK) {
2746
0
                LOG_WARNING("failed to create transaction for Table Stream Offset recheck")
2747
0
                        .tag("instance_id", instance_id_)
2748
0
                        .tag("latest_key", hex(latest_key))
2749
0
                        .tag("error", err);
2750
0
                return -1;
2751
0
            }
2752
2753
12
            std::string recycle_value;
2754
12
            err = txn->get(recycle_index_key({instance_id_, stream_id}), &recycle_value, true);
2755
12
            if (err == TxnErrorCode::TXN_OK) {
2756
3
                RecycleIndexPB recycle_index;
2757
3
                if (!recycle_index.ParseFromString(recycle_value)) {
2758
0
                    LOG_WARNING("failed to parse Recycle Index during Table Stream Offset recheck")
2759
0
                            .tag("instance_id", instance_id_)
2760
0
                            .tag("stream_id", stream_id);
2761
0
                    return -1;
2762
0
                }
2763
3
                int action = classify_recycle_index(&recycle_index, base_db_id, base_table_id,
2764
3
                                                    stream_db_id, stream_id);
2765
3
                if (action != 0) {
2766
1
                    return action == 2 ? 0 : action;
2767
1
                }
2768
3
            }
2769
11
            if (err != TxnErrorCode::TXN_OK && err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
2770
0
                LOG_WARNING("failed to read Recycle Index during Table Stream Offset recheck")
2771
0
                        .tag("instance_id", instance_id_)
2772
0
                        .tag("stream_id", stream_id)
2773
0
                        .tag("error", err);
2774
0
                return -1;
2775
0
            }
2776
2777
11
            std::string latest_value;
2778
11
            const TxnErrorCode latest_err = txn->get(latest_key, &latest_value, true);
2779
11
            if (latest_err != TxnErrorCode::TXN_OK &&
2780
11
                latest_err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
2781
0
                LOG_WARNING("failed to read Latest Stream Offset during recheck")
2782
0
                        .tag("instance_id", instance_id_)
2783
0
                        .tag("latest_key", hex(latest_key))
2784
0
                        .tag("error", latest_err);
2785
0
                return -1;
2786
0
            }
2787
2788
11
            const std::string versioned_key =
2789
11
                    versioned::table_stream_offset_key({instance_id_, base_db_id, base_table_id,
2790
11
                                                        stream_db_id, stream_id, partition_id});
2791
11
            std::string versioned_value;
2792
11
            const TxnErrorCode versioned_err =
2793
11
                    versioned_get(txn.get(), versioned_key, nullptr, &versioned_value, true);
2794
11
            if (versioned_err != TxnErrorCode::TXN_OK &&
2795
11
                versioned_err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
2796
0
                LOG_WARNING("failed to read Versioned Stream Offset during recheck")
2797
0
                        .tag("instance_id", instance_id_)
2798
0
                        .tag("versioned_key", hex(versioned_key))
2799
0
                        .tag("error", versioned_err);
2800
0
                return -1;
2801
0
            }
2802
11
            if (latest_err == TxnErrorCode::TXN_KEY_NOT_FOUND &&
2803
11
                versioned_err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
2804
0
                return 0;
2805
0
            }
2806
11
            if (latest_err == TxnErrorCode::TXN_KEY_NOT_FOUND ||
2807
11
                versioned_err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
2808
6
                return 1;
2809
6
            }
2810
2811
5
            TableStreamOffsetPB latest_offset;
2812
5
            TableStreamOffsetPB versioned_offset;
2813
5
            if (!latest_offset.ParseFromString(latest_value) ||
2814
5
                !versioned_offset.ParseFromString(versioned_value)) {
2815
0
                return 1;
2816
0
            }
2817
5
            return latest_offset.SerializeAsString() == versioned_offset.SerializeAsString() ? 0
2818
5
                                                                                             : 1;
2819
5
        };
2820
2821
20
        for (const std::string& key : apparent_inconsistencies) {
2822
12
            int recheck = recheck_projection(key);
2823
12
            if (recheck < 0) {
2824
0
                return recheck;
2825
0
            }
2826
12
            if (recheck > 0) {
2827
9
                LOG_WARNING("Latest and Versioned Stream Offset heads are inconsistent")
2828
9
                        .tag("instance_id", instance_id_)
2829
9
                        .tag("latest_key", hex(key));
2830
9
                check_ret = 1;
2831
9
            }
2832
12
        }
2833
20
    }
2834
2835
34
    return check_ret;
2836
34
}
2837
2838
2
int InstanceChecker::do_version_key_check() {
2839
2
    std::unique_ptr<RangeGetIterator> table_it;
2840
2
    std::string begin = table_version_key({instance_id_, 0, 0});
2841
2
    std::string end = table_version_key({instance_id_, INT64_MAX, 0});
2842
2
    bool check_res = true;
2843
4
    while (table_it == nullptr /* may be not init */ || (table_it->more() && !stopped())) {
2844
2
        std::unique_ptr<Transaction> txn;
2845
2
        TxnErrorCode err = txn_kv_->create_txn(&txn);
2846
2
        if (err != TxnErrorCode::TXN_OK) {
2847
0
            LOG(WARNING) << "failed to create txn";
2848
0
            return -1;
2849
0
        }
2850
2
        err = txn->get(begin, end, &table_it);
2851
2
        if (err != TxnErrorCode::TXN_OK) {
2852
0
            LOG(WARNING) << "failed to get mow tablet job key, err=" << err;
2853
0
            return -1;
2854
0
        }
2855
4
        while (table_it->has_next() && !stopped()) {
2856
2
            auto [k, v] = table_it->next();
2857
2
            std::string_view k1 = k;
2858
2
            k1.remove_prefix(1);
2859
2
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2860
2
            decode_key(&k1, &out);
2861
2
            int64_t table_version = -1;
2862
            // 0x01 "version" ${instance_id} "table" ${db_id} ${tbl_id}
2863
2
            if (!txn->decode_atomic_int(v, &table_version)) {
2864
0
                LOG(WARNING) << "malformed table version value";
2865
0
                return -1;
2866
0
            }
2867
2
            auto table_id = std::get<int64_t>(std::get<0>(out[4]));
2868
2
            auto db_id = std::get<int64_t>(std::get<0>(out[3]));
2869
2
            std::string partition_version_key_begin =
2870
2
                    partition_version_key({instance_id_, db_id, table_id, 0});
2871
2
            std::string partition_version_key_end =
2872
2
                    partition_version_key({instance_id_, db_id, table_id, INT64_MAX});
2873
2
            VersionPB partition_version_pb;
2874
2875
2
            std::unique_ptr<RangeGetIterator> part_it;
2876
4
            while (part_it == nullptr /* may be not init */ || (part_it->more() && !stopped())) {
2877
2
                std::unique_ptr<Transaction> txn;
2878
2
                TxnErrorCode err = txn_kv_->create_txn(&txn);
2879
2
                if (err != TxnErrorCode::TXN_OK) {
2880
0
                    LOG(WARNING) << "failed to create txn";
2881
0
                    return -1;
2882
0
                }
2883
2
                err = txn->get(partition_version_key_begin, partition_version_key_end, &part_it);
2884
2
                if (err != TxnErrorCode::TXN_OK) {
2885
0
                    LOG(WARNING) << "failed to get mow tablet job key, err=" << err;
2886
0
                    return -1;
2887
0
                }
2888
13
                while (part_it->has_next() && !stopped()) {
2889
11
                    auto [k, v] = part_it->next();
2890
                    // 0x01 "version" ${instance_id} "partition" ${db_id} ${tbl_id} ${partition_id}
2891
11
                    std::string_view k1 = k;
2892
11
                    k1.remove_prefix(1);
2893
11
                    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
2894
11
                    decode_key(&k1, &out);
2895
11
                    if (!partition_version_pb.ParseFromArray(v.data(), v.size())) [[unlikely]] {
2896
0
                        LOG(WARNING) << "failed to parse partition VersionPB";
2897
0
                        return -1;
2898
0
                    }
2899
11
                    auto partition_id = std::get<int64_t>(std::get<0>(out[5]));
2900
11
                    int64_t partition_version = partition_version_pb.version();
2901
11
                    if (table_version < partition_version) {
2902
3
                        check_res = false;
2903
3
                        LOG(WARNING)
2904
3
                                << "table version is less than partition version,"
2905
3
                                << " table_id: " << table_id << "tablet_version: " << table_version
2906
3
                                << " partition_id: " << partition_id
2907
3
                                << " partition_version: " << partition_version;
2908
3
                    }
2909
11
                }
2910
2
                partition_version_key_begin = part_it->next_begin_key();
2911
2
            }
2912
2
        }
2913
2
        begin = table_it->next_begin_key(); // Update to next smallest key for iteration
2914
2
    }
2915
2
    return check_res ? 0 : -1;
2916
2
}
2917
2918
1
int InstanceChecker::do_restore_job_check() {
2919
1
    int64_t num_prepared = 0;
2920
1
    int64_t num_committed = 0;
2921
1
    int64_t num_dropped = 0;
2922
1
    int64_t num_completed = 0;
2923
1
    int64_t num_recycling = 0;
2924
1
    int64_t num_cost_many_time = 0;
2925
1
    const int64_t COST_MANY_THRESHOLD = 3600;
2926
2927
1
    using namespace std::chrono;
2928
1
    auto start_time = steady_clock::now();
2929
1
    DORIS_CLOUD_DEFER {
2930
1
        g_bvar_checker_restore_job_prepared_state.put(instance_id_, num_prepared);
2931
1
        g_bvar_checker_restore_job_committed_state.put(instance_id_, num_committed);
2932
1
        g_bvar_checker_restore_job_dropped_state.put(instance_id_, num_dropped);
2933
1
        g_bvar_checker_restore_job_completed_state.put(instance_id_, num_completed);
2934
1
        g_bvar_checker_restore_job_recycling_state.put(instance_id_, num_recycling);
2935
1
        g_bvar_checker_restore_job_cost_many_time.put(instance_id_, num_cost_many_time);
2936
1
        auto cost_ms =
2937
1
                duration_cast<std::chrono::milliseconds>(steady_clock::now() - start_time).count();
2938
1
        LOG(INFO) << "check instance restore jobs finished, cost=" << cost_ms
2939
1
                  << "ms. instance_id=" << instance_id_ << " num_prepared=" << num_prepared
2940
1
                  << " num_committed=" << num_committed << " num_dropped=" << num_dropped
2941
1
                  << " num_completed=" << num_completed << " num_recycling=" << num_recycling
2942
1
                  << " num_cost_many_time=" << num_cost_many_time;
2943
1
    };
2944
2945
1
    LOG_INFO("begin to check restore jobs").tag("instance_id", instance_id_);
2946
2947
1
    JobRestoreTabletKeyInfo restore_job_key_info0 {instance_id_, 0};
2948
1
    JobRestoreTabletKeyInfo restore_job_key_info1 {instance_id_, INT64_MAX};
2949
1
    std::string begin;
2950
1
    std::string end;
2951
1
    job_restore_tablet_key(restore_job_key_info0, &begin);
2952
1
    job_restore_tablet_key(restore_job_key_info1, &end);
2953
1
    std::unique_ptr<RangeGetIterator> it;
2954
2
    while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
2955
1
        std::unique_ptr<Transaction> txn;
2956
1
        TxnErrorCode err = txn_kv_->create_txn(&txn);
2957
1
        if (err != TxnErrorCode::TXN_OK) {
2958
0
            LOG(WARNING) << "failed to create txn";
2959
0
            return -1;
2960
0
        }
2961
1
        err = txn->get(begin, end, &it);
2962
1
        if (err != TxnErrorCode::TXN_OK) {
2963
0
            LOG(WARNING) << "failed to get mow tablet job key, err=" << err;
2964
0
            return -1;
2965
0
        }
2966
2967
1
        if (!it->has_next()) {
2968
0
            break;
2969
0
        }
2970
3
        while (it->has_next()) {
2971
3
            auto [k, v] = it->next();
2972
3
            RestoreJobCloudPB restore_job_pb;
2973
3
            if (!restore_job_pb.ParseFromArray(v.data(), v.size())) {
2974
0
                LOG_WARNING("malformed restore job value").tag("key", hex(k));
2975
0
                return -1;
2976
0
            }
2977
2978
3
            switch (restore_job_pb.state()) {
2979
1
            case RestoreJobCloudPB::PREPARED:
2980
1
                ++num_prepared;
2981
1
                break;
2982
1
            case RestoreJobCloudPB::COMMITTED:
2983
1
                ++num_committed;
2984
1
                break;
2985
0
            case RestoreJobCloudPB::DROPPED:
2986
0
                ++num_dropped;
2987
0
                break;
2988
1
            case RestoreJobCloudPB::COMPLETED:
2989
1
                ++num_completed;
2990
1
                break;
2991
0
            case RestoreJobCloudPB::RECYCLING:
2992
0
                ++num_recycling;
2993
0
                break;
2994
0
            default:
2995
0
                break;
2996
3
            }
2997
2998
3
            int64_t current_time = ::time(nullptr);
2999
3
            if ((restore_job_pb.state() == RestoreJobCloudPB::PREPARED ||
3000
3
                 restore_job_pb.state() == RestoreJobCloudPB::COMMITTED) &&
3001
3
                current_time > restore_job_pb.ctime_s() + COST_MANY_THRESHOLD) {
3002
                // restore job run more than 1 hour
3003
1
                ++num_cost_many_time;
3004
1
                LOG_WARNING("restore job cost too many time")
3005
1
                        .tag("key", hex(k))
3006
1
                        .tag("tablet_id", restore_job_pb.tablet_id())
3007
1
                        .tag("state", restore_job_pb.state())
3008
1
                        .tag("ctime_s", restore_job_pb.ctime_s())
3009
1
                        .tag("mtime_s", restore_job_pb.mtime_s());
3010
1
            }
3011
3012
3
            if (!it->has_next()) {
3013
1
                begin = k;
3014
1
                begin.push_back('\x00'); // Update to next smallest key for iteration
3015
1
                break;
3016
1
            }
3017
3
        }
3018
1
    }
3019
1
    return 0;
3020
1
}
3021
3022
3
int InstanceChecker::check_txn_info_key(std::string_view key, std::string_view value) {
3023
3
    std::unordered_map<int64_t, std::string> txn_info_;
3024
3
    TxnLabelPB txn_label_pb;
3025
3026
6
    auto handle_check_txn_label_key = [&](std::string_view key, std::string_view value) -> int {
3027
6
        TxnInfoPB txn_info_pb;
3028
6
        std::string_view k1 = key;
3029
6
        k1.remove_prefix(1);
3030
6
        std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
3031
6
        decode_key(&k1, &out);
3032
        // 0x01 "txn" ${instance_id} "txn_info" ${db_id} ${txn_id}
3033
6
        if (!txn_info_pb.ParseFromArray(value.data(), value.size())) {
3034
0
            LOG(WARNING) << "failed to parse TxnInfoPB";
3035
0
            return -1;
3036
0
        }
3037
6
        auto txn_id = std::get<int64_t>(std::get<0>(out[4]));
3038
6
        auto it = txn_info_.find(txn_id);
3039
6
        if (it == txn_info_.end()) {
3040
0
            return 0;
3041
6
        } else {
3042
6
            if (it->second != txn_info_pb.label()) {
3043
1
                LOG(WARNING) << "txn_info_pb's txn_label not same with txn_label_pb's txn_label,"
3044
1
                             << " txn_info_pb's txn_label: " << txn_info_pb.label()
3045
1
                             << " txn_label_pb meta: " << txn_label_pb.ShortDebugString();
3046
1
                return 1;
3047
1
            }
3048
6
        }
3049
5
        return 0;
3050
6
    };
3051
3
    std::string_view k1 = key;
3052
3
    k1.remove_prefix(1);
3053
3
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
3054
3
    decode_key(&k1, &out);
3055
    // 0x01 "txn" ${instance_id} "txn_label" ${db_id} ${label}
3056
3
    if (!txn_label_pb.ParseFromArray(value.data(), value.size() - VERSION_STAMP_LEN)) {
3057
1
        LOG(WARNING) << "failed to parse TxnLabelPB";
3058
1
        return -1;
3059
1
    }
3060
2
    auto db_id = std::get<int64_t>(std::get<0>(out[3]));
3061
2
    auto label = std::get<std::string>(std::get<0>(out[4]));
3062
    // txn_id -> txn_label
3063
6
    for (const auto& txn_id : txn_label_pb.txn_ids()) {
3064
6
        txn_info_.insert({txn_id, label});
3065
6
    }
3066
2
    std::string txn_info_key_begin = txn_info_key({instance_id_, db_id, 0});
3067
2
    std::string txn_info_key_end = txn_info_key({instance_id_, db_id, INT64_MAX});
3068
2
    return scan_and_handle_kv(txn_info_key_begin, txn_info_key_end,
3069
6
                              [&](std::string_view k, std::string_view v) -> int {
3070
6
                                  return handle_check_txn_label_key(k, v);
3071
6
                              });
3072
3
}
3073
3074
6
int InstanceChecker::check_txn_label_key(std::string_view key, std::string_view value) {
3075
6
    TxnInfoPB txn_info_pb;
3076
6
    std::string_view k1 = key;
3077
6
    k1.remove_prefix(1);
3078
6
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
3079
6
    decode_key(&k1, &out);
3080
    // 0x01 "txn" ${instance_id} "txn_info" ${db_id} ${txn_id}
3081
6
    if (!txn_info_pb.ParseFromArray(value.data(), value.size())) {
3082
1
        LOG(WARNING) << "failed to parse TxnInfoPB";
3083
1
        return -1;
3084
1
    }
3085
5
    auto txn_id = std::get<int64_t>(std::get<0>(out[4]));
3086
5
    auto db_id = std::get<int64_t>(std::get<0>(out[3]));
3087
5
    auto label = txn_info_pb.label();
3088
5
    std::string txn_label = txn_label_key({instance_id_, db_id, label});
3089
5
    std::string txn_label_val;
3090
5
    TxnLabelPB txn_label_pb;
3091
5
    std::unique_ptr<Transaction> txn;
3092
5
    TxnErrorCode err = txn_kv_->create_txn(&txn);
3093
5
    if (err != TxnErrorCode::TXN_OK) {
3094
0
        LOG(WARNING) << "failed to init txn";
3095
0
        return -1;
3096
0
    }
3097
5
    if (txn->get(txn_label, &txn_label_val) != TxnErrorCode::TXN_OK) {
3098
1
        LOG(WARNING) << "failed to get txn label key, key=" << hex(txn_label);
3099
1
        return -1;
3100
1
    }
3101
4
    txn_label_pb.ParseFromString(txn_label_val);
3102
4
    auto txn_ids = txn_label_pb.txn_ids();
3103
4
    if (!std::count(txn_ids.begin(), txn_ids.end(), txn_id)) {
3104
        // clang-format off txn_info_pb
3105
1
        LOG(WARNING) << "txn_info_pb's txn_id not found in txn_label_pb info,"
3106
1
                     << " txn_id: " << txn_id
3107
1
                     << " txn_label_pb meta: " << txn_label_pb.ShortDebugString();
3108
        // clang-format on
3109
1
        return 1;
3110
1
    }
3111
3
    return 0;
3112
4
}
3113
3114
4
int InstanceChecker::check_txn_index_key(std::string_view key, std::string_view value) {
3115
4
    TxnInfoPB txn_info_pb;
3116
4
    std::string_view k1 = key;
3117
4
    k1.remove_prefix(1);
3118
4
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
3119
4
    decode_key(&k1, &out);
3120
    // 0x01 "txn" ${instance_id} "txn_info" ${db_id} ${txn_id}
3121
4
    if (!txn_info_pb.ParseFromArray(value.data(), value.size())) {
3122
1
        LOG(WARNING) << "failed to parse TxnInfoPB";
3123
1
        return -1;
3124
1
    }
3125
3
    auto txn_id = std::get<int64_t>(std::get<0>(out[4]));
3126
3
    auto db_id = std::get<int64_t>(std::get<0>(out[3]));
3127
    /// get tablet id
3128
3
    std::string txn_index = txn_index_key({instance_id_, txn_id});
3129
3
    std::string txn_index_val;
3130
3
    TxnIndexPB txn_index_pb;
3131
3
    std::unique_ptr<Transaction> txn;
3132
3
    TxnErrorCode err = txn_kv_->create_txn(&txn);
3133
3
    if (err != TxnErrorCode::TXN_OK) {
3134
0
        LOG(WARNING) << "failed to init txn";
3135
0
        return -1;
3136
0
    }
3137
3
    if (txn->get(txn_index, &txn_index_val) != TxnErrorCode::TXN_OK) {
3138
1
        LOG(WARNING) << "failed to get txn label key, key=" << hex(txn_index);
3139
1
        return -1;
3140
1
    }
3141
2
    txn_index_pb.ParseFromString(txn_index_val);
3142
2
    if (txn_index_pb.tablet_index().db_id() != db_id) {
3143
        // clang-format off txn_info_pb
3144
1
        LOG(WARNING) << "txn_index_pb's db_id not same with txn_info_pb's db_id,"
3145
1
                     << " txn_index_pb meta: " << txn_index_pb.ShortDebugString()
3146
1
                     << " txn_info_pb meta: " << txn_info_pb.ShortDebugString();
3147
        // clang-format on
3148
1
        return 1;
3149
1
    }
3150
1
    return 0;
3151
2
}
3152
3153
3
int InstanceChecker::check_txn_running_key(std::string_view key, std::string_view value) {
3154
3
    TxnRunningPB txn_running_pb;
3155
3
    int64_t current_time =
3156
3
            duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
3157
3
    if (!txn_running_pb.ParseFromArray(value.data(), value.size())) {
3158
1
        LOG(WARNING) << "failed to parse TxnRunningPB";
3159
1
        return -1;
3160
1
    }
3161
2
    if (txn_running_pb.timeout_time() <= current_time) {
3162
1
        LOG(WARNING) << "txn_running_pb.timeout_time() is less than current_time,"
3163
1
                     << " but txn_running_key exists, "
3164
1
                     << " txn_running_pb meta: " << txn_running_pb.ShortDebugString();
3165
1
        return 1;
3166
1
    }
3167
1
    return 0;
3168
2
}
3169
3170
0
int InstanceChecker::do_txn_key_check() {
3171
0
    int ret = 0;
3172
3173
    // check txn info key depend on txn label key
3174
0
    std::string begin = txn_label_key({instance_id_, 0, ""});
3175
0
    std::string end = txn_label_key({instance_id_, INT64_MAX, ""});
3176
0
    int64_t num_scanned = 0;
3177
0
    int64_t num_abnormal = 0;
3178
0
    LOG(INFO) << "begin check txn_label_key and txn_info_key";
3179
0
    ret = scan_and_handle_kv(begin, end, [&, this](std::string_view k, std::string_view v) -> int {
3180
0
        num_scanned++;
3181
0
        int ret = check_txn_info_key(k, v);
3182
0
        if (ret == 1) {
3183
0
            num_abnormal++;
3184
0
        }
3185
0
        return ret;
3186
0
    });
3187
3188
0
    if (ret == 1) {
3189
0
        LOG(WARNING) << "failed to check txn_info_key depending on txn_label_key, num_scanned="
3190
0
                     << num_scanned << ", num_abnormal=" << num_abnormal;
3191
0
        return 1;
3192
0
    } else if (ret == -1) {
3193
0
        LOG(WARNING) << "failed to check txn label key and txn info key";
3194
0
        return -1;
3195
0
    }
3196
3197
    // check txn label key depend on txn info key
3198
0
    begin = txn_info_key({instance_id_, 0, 0});
3199
0
    end = txn_info_key({instance_id_, INT64_MAX, 0});
3200
0
    num_scanned = 0;
3201
0
    num_abnormal = 0;
3202
0
    LOG(INFO) << "begin check txn_label_key and txn_info_key";
3203
0
    ret = scan_and_handle_kv(begin, end, [&, this](std::string_view k, std::string_view v) -> int {
3204
0
        num_scanned++;
3205
0
        int ret = check_txn_label_key(k, v);
3206
0
        if (ret == 1) {
3207
0
            num_abnormal++;
3208
0
        }
3209
0
        return ret;
3210
0
    });
3211
0
    if (ret == 1) {
3212
0
        LOG(WARNING) << "failed to check txn_label_key depending on txn_info_key, num_scanned="
3213
0
                     << num_scanned << ", num_abnormal=" << num_abnormal;
3214
0
        return 1;
3215
0
    } else if (ret == -1) {
3216
0
        LOG(WARNING) << "failed to inverted check txn label key and txn info key";
3217
0
        return -1;
3218
0
    }
3219
0
    LOG(INFO) << "finish check txn_label_key and txn_info_key, num_scanned=" << num_scanned
3220
0
              << ", num_abnormal=" << num_abnormal;
3221
3222
    // check txn index key depend on txn info key
3223
0
    begin = txn_info_key({instance_id_, 0, 0});
3224
0
    end = txn_info_key({instance_id_, INT64_MAX, 0});
3225
0
    num_scanned = 0;
3226
0
    num_abnormal = 0;
3227
0
    LOG(INFO) << "begin check txn_index_key and txn_info_key";
3228
0
    ret = scan_and_handle_kv(begin, end, [&, this](std::string_view k, std::string_view v) -> int {
3229
0
        num_scanned++;
3230
0
        int ret = check_txn_index_key(k, v);
3231
0
        if (ret == 1) {
3232
0
            num_abnormal++;
3233
0
        }
3234
0
        return ret;
3235
0
    });
3236
0
    if (ret == 1) {
3237
0
        LOG(WARNING) << "failed to check txn_idx_key depending on txn_info_key, num_scanned="
3238
0
                     << num_scanned << ", num_abnormal=" << num_abnormal;
3239
0
        return 1;
3240
0
    } else if (ret == -1) {
3241
0
        LOG(WARNING) << "failed to check txn index key";
3242
0
        return -1;
3243
0
    }
3244
0
    LOG(INFO) << "finish check txn_index_key and txn_info_key, num_scanned=" << num_scanned
3245
0
              << ", num_abnormal=" << num_abnormal;
3246
3247
    // check txn running key
3248
0
    begin = txn_running_key({instance_id_, 0, 0});
3249
0
    end = txn_running_key({instance_id_, INT64_MAX, 0});
3250
0
    num_scanned = 0;
3251
0
    num_abnormal = 0;
3252
0
    LOG(INFO) << "begin check txn_running_key";
3253
0
    ret = scan_and_handle_kv(begin, end, [&, this](std::string_view k, std::string_view v) -> int {
3254
0
        num_scanned++;
3255
0
        int ret = check_txn_running_key(k, v);
3256
0
        if (ret == 1) {
3257
0
            num_abnormal++;
3258
0
        }
3259
0
        return ret;
3260
0
    });
3261
0
    if (ret == 1) {
3262
0
        LOG(WARNING) << "failed to check txn_running_key, num_scanned=" << num_scanned
3263
0
                     << ", num_abnormal=" << num_abnormal;
3264
0
        return 1;
3265
0
    } else if (ret == -1) {
3266
0
        LOG(WARNING) << "failed to check txn running key";
3267
0
        return -1;
3268
0
    }
3269
0
    LOG(INFO) << "finish check txn_running_key, num_scanned=" << num_scanned
3270
0
              << ", num_abnormal=" << num_abnormal;
3271
0
    return 0;
3272
0
}
3273
3274
2
int InstanceChecker::check_meta_tmp_rowset_key(std::string_view key, std::string_view value) {
3275
2
    TxnInfoPB txn_info_pb;
3276
2
    std::string_view k1 = key;
3277
2
    k1.remove_prefix(1);
3278
2
    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
3279
2
    decode_key(&k1, &out);
3280
    // 0x01 "txn" ${instance_id} "txn_info" ${db_id} ${txn_id}
3281
2
    if (!txn_info_pb.ParseFromArray(value.data(), value.size())) {
3282
0
        LOG(WARNING) << "failed to parse TxnInfoPB";
3283
0
        return -1;
3284
0
    }
3285
    /// get tablet id
3286
2
    auto txn_id = std::get<int64_t>(std::get<0>(out[4]));
3287
2
    std::string txn_index = txn_index_key({instance_id_, txn_id});
3288
2
    std::string txn_index_val;
3289
2
    TxnIndexPB txn_index_pb;
3290
2
    std::unique_ptr<Transaction> txn;
3291
2
    TxnErrorCode err = txn_kv_->create_txn(&txn);
3292
2
    if (err != TxnErrorCode::TXN_OK) {
3293
0
        LOG(WARNING) << "failed to init txn";
3294
0
        return -1;
3295
0
    }
3296
2
    if (txn->get(txn_index, &txn_index_val) != TxnErrorCode::TXN_OK) {
3297
0
        LOG(WARNING) << "failed to get txn index key, key=" << txn_index;
3298
0
        return -1;
3299
0
    }
3300
2
    txn_index_pb.ParseFromString(txn_index_val);
3301
2
    auto tablet_id = txn_index_pb.tablet_index().tablet_id();
3302
2
    std::string meta_tmp_rowset_key = meta_rowset_tmp_key({instance_id_, txn_id, tablet_id});
3303
2
    int is_key_exist = key_exist(txn_kv_.get(), meta_tmp_rowset_key);
3304
2
    if (is_key_exist == 1) {
3305
0
        if (txn_info_pb.status() != TxnStatusPB::TXN_STATUS_VISIBLE) {
3306
            // clang-format off
3307
0
            LOG(INFO) << "meta tmp rowset key not exist but txn status != TXN_STATUS_VISIBLE"
3308
0
                        << "meta tmp rowset key=" << meta_tmp_rowset_key
3309
0
                        << "txn_info=" << txn_info_pb.ShortDebugString();
3310
            // clang-format on
3311
0
            return 1;
3312
0
        }
3313
2
    } else if (is_key_exist == 0) {
3314
2
        if (txn_info_pb.status() != TxnStatusPB::TXN_STATUS_PREPARED) {
3315
            // clang-format off
3316
1
            LOG(INFO) << "meta tmp rowset key exist but txn status != TXN_STATUS_PREPARED"
3317
1
                        << "meta tmp rowset key=" << meta_tmp_rowset_key
3318
1
                        << "txn_info=" << txn_info_pb.ShortDebugString();
3319
            // clang-format on
3320
1
            return 1;
3321
1
        }
3322
2
    } else {
3323
0
        LOG(WARNING) << "failed to get key, key=" << meta_tmp_rowset_key;
3324
0
        return -1;
3325
0
    }
3326
1
    return 0;
3327
2
}
3328
3329
2
int InstanceChecker::check_meta_rowset_key(std::string_view key, std::string_view value) {
3330
2
    RowsetMetaCloudPB meta_rowset_pb;
3331
2
    if (!meta_rowset_pb.ParseFromArray(value.data(), value.size())) {
3332
0
        LOG(WARNING) << "failed to parse RowsetMetaCloudPB";
3333
0
        return -1;
3334
0
    }
3335
2
    std::string tablet_index_key = meta_tablet_idx_key({instance_id_, meta_rowset_pb.tablet_id()});
3336
2
    if (key_exist(txn_kv_.get(), tablet_index_key) == 1) {
3337
1
        LOG(WARNING) << "rowset's tablet id not found in fdb"
3338
1
                     << "tablet_index_key: " << tablet_index_key
3339
1
                     << "rowset meta: " << meta_rowset_pb.ShortDebugString();
3340
1
        return 1;
3341
1
    }
3342
1
    return 0;
3343
2
}
3344
3345
0
int InstanceChecker::do_meta_rowset_key_check() {
3346
0
    int ret = 0;
3347
3348
0
    std::string begin = meta_rowset_key({instance_id_, 0, 0});
3349
0
    std::string end = meta_rowset_key({instance_id_, INT64_MAX, 0});
3350
0
    int64_t num_scanned = 0;
3351
0
    int64_t num_loss = 0;
3352
3353
0
    ret = scan_and_handle_kv(begin, end, [&](std::string_view k, std::string_view v) {
3354
0
        num_scanned++;
3355
0
        int ret = check_meta_rowset_key(k, v);
3356
0
        if (ret == 1) {
3357
0
            num_loss++;
3358
0
        }
3359
0
        return ret;
3360
0
    });
3361
0
    if (ret == -1) {
3362
0
        LOG(WARNING) << "failed to check meta rowset key,";
3363
0
        return -1;
3364
0
    } else if (ret == 1) {
3365
0
        LOG(WARNING) << "meta rowset key may be loss, num_scanned=" << num_scanned
3366
0
                     << ", num_loss=" << num_loss;
3367
0
    }
3368
0
    LOG(INFO) << "meta rowset key check finish, num_scanned=" << num_scanned
3369
0
              << ", num_loss=" << num_loss;
3370
3371
0
    begin = txn_info_key({instance_id_, 0, 0});
3372
0
    end = txn_info_key({instance_id_, INT64_MAX, 0});
3373
0
    num_scanned = 0;
3374
0
    num_loss = 0;
3375
3376
0
    ret = scan_and_handle_kv(begin, end, [&](std::string_view k, std::string_view v) {
3377
0
        num_scanned++;
3378
0
        int ret = check_meta_tmp_rowset_key(k, v);
3379
0
        if (ret == 1) {
3380
0
            num_loss++;
3381
0
        }
3382
0
        return ret;
3383
0
    });
3384
0
    if (ret == -1) {
3385
0
        LOG(WARNING) << "failed to check tmp meta rowset key";
3386
0
        return -1;
3387
0
    } else if (ret == 1) {
3388
0
        LOG(WARNING) << "meta tmp rowset key may be loss, num_scanned=" << num_scanned
3389
0
                     << ", num_loss=" << num_loss;
3390
0
    }
3391
0
    LOG(INFO) << "meta tmp rowset key check finish, num_scanned=" << num_scanned
3392
0
              << ", num_loss=" << num_loss;
3393
3394
0
    return ret;
3395
0
}
3396
3397
0
StorageVaultAccessor* InstanceChecker::get_accessor(const std::string& id) {
3398
0
    auto it = accessor_map_.find(id);
3399
0
    if (it == accessor_map_.end()) {
3400
0
        return nullptr;
3401
0
    }
3402
0
    return it->second.get();
3403
0
}
3404
3405
0
void InstanceChecker::get_all_accessor(std::vector<StorageVaultAccessor*>* accessors) {
3406
0
    for (const auto& [_, accessor] : accessor_map_) {
3407
0
        accessors->push_back(accessor.get());
3408
0
    }
3409
0
}
3410
3411
0
int InstanceChecker::do_packed_file_check() {
3412
0
    LOG(INFO) << "begin to check packed files, instance_id=" << instance_id_;
3413
0
    int check_ret = 0;
3414
0
    long num_scanned_rowsets = 0;
3415
0
    long num_scanned_packed_files = 0;
3416
0
    long num_packed_file_loss = 0;
3417
0
    long num_packed_file_leak = 0;
3418
0
    long num_ref_count_mismatch = 0;
3419
0
    long num_small_file_ref_mismatch = 0;
3420
0
    using namespace std::chrono;
3421
0
    auto start_time = steady_clock::now();
3422
0
    DORIS_CLOUD_DEFER {
3423
0
        auto cost = duration<float>(steady_clock::now() - start_time).count();
3424
0
        LOG(INFO) << "check packed files finished, cost=" << cost
3425
0
                  << "s. instance_id=" << instance_id_
3426
0
                  << " num_scanned_rowsets=" << num_scanned_rowsets
3427
0
                  << " num_scanned_packed_files=" << num_scanned_packed_files
3428
0
                  << " num_packed_file_loss=" << num_packed_file_loss
3429
0
                  << " num_packed_file_leak=" << num_packed_file_leak
3430
0
                  << " num_ref_count_mismatch=" << num_ref_count_mismatch
3431
0
                  << " num_small_file_ref_mismatch=" << num_small_file_ref_mismatch;
3432
0
    };
3433
3434
    // Map to track expected reference count for each packed file
3435
    // packed_file_path -> expected_ref_count (from rowset metas)
3436
0
    std::unordered_map<std::string, int64_t> expected_ref_counts;
3437
    // Map to track small files referenced in packed files
3438
    // packed_file_path -> set of small_file_paths
3439
0
    std::unordered_map<std::string, std::unordered_set<std::string>> packed_file_small_files;
3440
3441
    // Step 1: Scan all rowset metas to collect packed_slice_locations references
3442
    // Use efficient range scan instead of iterating through each tablet_id
3443
0
    auto collect_packed_refs = [&](const doris::RowsetMetaCloudPB& rs_meta) {
3444
0
        const auto& index_map = rs_meta.packed_slice_locations();
3445
0
        for (const auto& [small_file_path, index_pb] : index_map) {
3446
0
            if (!index_pb.has_packed_file_path() || index_pb.packed_file_path().empty()) {
3447
0
                continue;
3448
0
            }
3449
0
            const std::string& packed_file_path = index_pb.packed_file_path();
3450
0
            expected_ref_counts[packed_file_path]++;
3451
0
            packed_file_small_files[packed_file_path].insert(small_file_path);
3452
0
        }
3453
0
    };
3454
3455
0
    {
3456
0
        std::string start_key = meta_rowset_key({instance_id_, 0, 0});
3457
0
        std::string end_key = meta_rowset_key({instance_id_, INT64_MAX, 0});
3458
3459
0
        std::unique_ptr<RangeGetIterator> it;
3460
0
        while (it == nullptr /* may be not init */ || (it->more() && !stopped())) {
3461
0
            if (stopped()) {
3462
0
                return -1;
3463
0
            }
3464
3465
0
            std::unique_ptr<Transaction> txn;
3466
0
            TxnErrorCode err = txn_kv_->create_txn(&txn);
3467
0
            if (err != TxnErrorCode::TXN_OK) {
3468
0
                LOG(WARNING) << "failed to create txn for packed file check";
3469
0
                return -1;
3470
0
            }
3471
3472
0
            err = txn->get(start_key, end_key, &it);
3473
0
            if (err != TxnErrorCode::TXN_OK) {
3474
0
                LOG(WARNING) << "failed to scan rowset metas, err=" << err;
3475
0
                check_ret = -1;
3476
0
                break;
3477
0
            }
3478
3479
0
            while (it->has_next() && !stopped()) {
3480
0
                auto [k, v] = it->next();
3481
0
                if (!it->has_next()) {
3482
0
                    start_key = k;
3483
0
                }
3484
3485
0
                doris::RowsetMetaCloudPB rs_meta;
3486
0
                if (!rs_meta.ParseFromArray(v.data(), v.size())) {
3487
0
                    LOG(WARNING) << "malformed rowset meta, key=" << hex(k);
3488
0
                    check_ret = -1;
3489
0
                    continue;
3490
0
                }
3491
3492
0
                num_scanned_rowsets++;
3493
3494
0
                collect_packed_refs(rs_meta);
3495
0
            }
3496
0
            start_key.push_back('\x00'); // Update to next smallest key for iteration
3497
0
        }
3498
0
    }
3499
3500
    // Rowsets in recycle keys may still hold packed file references while ref count
3501
    // updates are pending, so include them when calculating expected references.
3502
0
    {
3503
0
        std::string start_key = recycle_rowset_key({instance_id_, 0, ""});
3504
0
        std::string end_key = recycle_rowset_key({instance_id_, INT64_MAX, "\xff"});
3505
3506
0
        std::unique_ptr<RangeGetIterator> it;
3507
0
        while (it == nullptr /* may be not init */ || it->more()) {
3508
0
            if (stopped()) {
3509
0
                return -1;
3510
0
            }
3511
0
            std::unique_ptr<Transaction> txn;
3512
0
            TxnErrorCode err = txn_kv_->create_txn(&txn);
3513
0
            if (err != TxnErrorCode::TXN_OK) {
3514
0
                LOG(WARNING) << "failed to create txn for recycle rowset scan in packed file check";
3515
0
                return -1;
3516
0
            }
3517
3518
0
            err = txn->get(start_key, end_key, &it);
3519
0
            if (err != TxnErrorCode::TXN_OK) {
3520
0
                LOG(WARNING) << "failed to scan recycle rowset metas, err=" << err;
3521
0
                check_ret = -1;
3522
0
                break;
3523
0
            }
3524
3525
0
            while (it->has_next() && !stopped()) {
3526
0
                auto [k, v] = it->next();
3527
0
                if (!it->has_next()) {
3528
0
                    start_key = k;
3529
0
                }
3530
3531
0
                RecycleRowsetPB recycle_rowset;
3532
0
                if (!recycle_rowset.ParseFromArray(v.data(), v.size())) {
3533
0
                    LOG(WARNING) << "malformed recycle rowset, key=" << hex(k);
3534
0
                    check_ret = -1;
3535
0
                    continue;
3536
0
                }
3537
3538
0
                if (!recycle_rowset.has_rowset_meta()) {
3539
0
                    continue;
3540
0
                }
3541
3542
0
                num_scanned_rowsets++;
3543
0
                collect_packed_refs(recycle_rowset.rowset_meta());
3544
0
            }
3545
0
            start_key.push_back('\x00'); // Update to next smallest key for iteration
3546
0
        }
3547
0
    }
3548
3549
    // Step 2: Scan all packed file metadata and verify
3550
    // Also collect all packed file paths from metadata for Step 3
3551
    // Map: resource_id -> set of packed_file_paths
3552
0
    std::unordered_map<std::string, std::unordered_set<std::string>> packed_files_in_metadata;
3553
0
    std::string begin = packed_file_key({instance_id_, ""});
3554
0
    std::string end = packed_file_key({instance_id_, "\xff"});
3555
0
    std::string scan_begin = begin;
3556
3557
0
    while (true) {
3558
0
        if (stopped()) {
3559
0
            return -1;
3560
0
        }
3561
3562
0
        std::unique_ptr<Transaction> txn;
3563
0
        TxnErrorCode err = txn_kv_->create_txn(&txn);
3564
0
        if (err != TxnErrorCode::TXN_OK) {
3565
0
            LOG(WARNING) << "failed to create txn for scanning packed files";
3566
0
            return -1;
3567
0
        }
3568
3569
0
        std::unique_ptr<RangeGetIterator> it;
3570
0
        err = txn->get(scan_begin, end, &it);
3571
0
        if (err != TxnErrorCode::TXN_OK) {
3572
0
            LOG(WARNING) << "failed to scan packed file keys, err=" << err;
3573
0
            return -1;
3574
0
        }
3575
0
        if (!it->has_next()) {
3576
0
            break;
3577
0
        }
3578
3579
0
        std::string last_key;
3580
0
        while (it->has_next()) {
3581
0
            auto [k, v] = it->next();
3582
0
            last_key.assign(k.data(), k.size());
3583
0
            num_scanned_packed_files++;
3584
3585
0
            std::string packed_file_path;
3586
0
            if (!InstanceRecycler::decode_packed_file_key(k, &packed_file_path)) {
3587
0
                LOG(WARNING) << "failed to decode packed file key, key=" << hex(k);
3588
0
                check_ret = -1;
3589
0
                continue;
3590
0
            }
3591
3592
0
            cloud::PackedFileInfoPB packed_info;
3593
0
            if (!packed_info.ParseFromArray(v.data(), v.size())) {
3594
0
                LOG(WARNING) << "failed to parse packed file info, packed_file_path="
3595
0
                             << packed_file_path;
3596
0
                check_ret = -1;
3597
0
                continue;
3598
0
            }
3599
3600
            // Step 2.1: Verify packed file exists in storage
3601
0
            if (!packed_info.resource_id().empty()) {
3602
                // Collect packed file path for Step 3
3603
0
                packed_files_in_metadata[packed_info.resource_id()].insert(packed_file_path);
3604
3605
0
                auto* accessor = get_accessor(packed_info.resource_id());
3606
0
                if (accessor == nullptr) {
3607
0
                    LOG(WARNING) << "accessor not found for packed file, resource_id="
3608
0
                                 << packed_info.resource_id()
3609
0
                                 << ", packed_file_path=" << packed_file_path;
3610
0
                    check_ret = -1;
3611
0
                    continue;
3612
0
                }
3613
3614
0
                int ret = accessor->exists(packed_file_path);
3615
0
                if (ret < 0) {
3616
0
                    LOG(WARNING) << "failed to check packed file existence, packed_file_path="
3617
0
                                 << packed_file_path << ", ret=" << ret;
3618
0
                    check_ret = -1;
3619
0
                    continue;
3620
0
                }
3621
3622
0
                if (ret != 0) {
3623
                    // ret == 1 means file not found, ret > 1 means other error
3624
                    // When packed file doesn't exist in storage, ref_cnt must be 0 and state must be RECYCLING
3625
0
                    bool ref_cnt_valid = (packed_info.ref_cnt() == 0);
3626
0
                    bool state_valid = (packed_info.state() == cloud::PackedFileInfoPB::RECYCLING);
3627
0
                    if (!ref_cnt_valid || !state_valid) {
3628
0
                        LOG(WARNING) << "packed file not found in storage but metadata is invalid, "
3629
0
                                        "packed_file_path="
3630
0
                                     << packed_file_path << ", ref_cnt=" << packed_info.ref_cnt()
3631
0
                                     << " (expected=0), state=" << packed_info.state()
3632
0
                                     << " (expected=RECYCLING), ret=" << ret;
3633
0
                        num_packed_file_loss++;
3634
0
                        check_ret = 1; // Data inconsistency identified
3635
0
                    }
3636
                    // If ref_cnt == 0 and state == RECYCLING, this is expected (file is being recycled)
3637
0
                }
3638
                // ret == 0 means file exists, which is expected
3639
0
            }
3640
3641
            // Step 2.2: Verify reference count matches expected count
3642
0
            int64_t expected_ref = expected_ref_counts[packed_file_path];
3643
0
            if (packed_info.ref_cnt() != expected_ref) {
3644
0
                LOG(WARNING) << "packed file ref count mismatch, packed_file_path="
3645
0
                             << packed_file_path << ", expected=" << expected_ref
3646
0
                             << ", actual=" << packed_info.ref_cnt();
3647
0
                num_ref_count_mismatch++;
3648
0
                check_ret = 1; // Data inconsistency identified
3649
0
            }
3650
3651
            // Step 2.3: Verify small files in packed_info match rowset references
3652
0
            std::unordered_set<std::string> small_files_in_meta;
3653
0
            for (const auto& small_file : packed_info.slices()) {
3654
0
                if (!small_file.deleted()) {
3655
0
                    small_files_in_meta.insert(small_file.path());
3656
0
                }
3657
0
            }
3658
3659
0
            const auto& expected_small_files = packed_file_small_files[packed_file_path];
3660
0
            if (small_files_in_meta != expected_small_files) {
3661
                // Check for missing small files
3662
0
                for (const auto& expected_path : expected_small_files) {
3663
0
                    if (small_files_in_meta.find(expected_path) == small_files_in_meta.end()) {
3664
0
                        LOG(WARNING) << "small file missing in packed file info, packed_file_path="
3665
0
                                     << packed_file_path << ", small_file_path=" << expected_path;
3666
0
                        num_small_file_ref_mismatch++;
3667
0
                        check_ret = 1;
3668
0
                    }
3669
0
                }
3670
                // Check for extra small files (may be deleted, so less critical)
3671
0
                for (const auto& meta_path : small_files_in_meta) {
3672
0
                    if (expected_small_files.find(meta_path) == expected_small_files.end()) {
3673
0
                        LOG(INFO) << "small file in packed file info not found in rowset metas, "
3674
0
                                     "may be deleted, packed_file_path="
3675
0
                                  << packed_file_path << ", small_file_path=" << meta_path;
3676
0
                    }
3677
0
                }
3678
0
            }
3679
0
        }
3680
3681
0
        if (!it->more()) {
3682
0
            break;
3683
0
        }
3684
0
        scan_begin = last_key;
3685
0
        scan_begin.push_back('\x00');
3686
0
    }
3687
3688
    // Step 3: Check for leaked packed files (exist in storage but not in metadata)
3689
    // Scan all storage vaults to find packed files and verify they are in metadata
3690
0
    {
3691
0
        std::vector<StorageVaultAccessor*> accessors;
3692
0
        get_all_accessor(&accessors);
3693
3694
0
        for (StorageVaultAccessor* accessor : accessors) {
3695
0
            if (stopped()) {
3696
0
                return -1;
3697
0
            }
3698
3699
            // Find resource_id for this accessor
3700
0
            std::string resource_id;
3701
0
            for (const auto& [id, acc] : accessor_map_) {
3702
0
                if (acc.get() == accessor) {
3703
0
                    resource_id = id;
3704
0
                    break;
3705
0
                }
3706
0
            }
3707
3708
0
            if (resource_id.empty()) {
3709
0
                continue;
3710
0
            }
3711
3712
            // List all files under data/packed_file/ directory
3713
0
            std::unique_ptr<ListIterator> list_it;
3714
0
            int ret = accessor->list_directory("data/packed_file", &list_it);
3715
0
            if (ret != 0) {
3716
                // Directory may not exist, which is fine
3717
0
                if (ret < 0) {
3718
0
                    LOG(WARNING) << "failed to list packed_file directory, resource_id="
3719
0
                                 << resource_id << ", ret=" << ret;
3720
0
                    check_ret = -1;
3721
0
                }
3722
0
                continue;
3723
0
            }
3724
3725
0
            const auto& expected_packed_files = packed_files_in_metadata[resource_id];
3726
0
            while (list_it->has_next()) {
3727
0
                if (stopped()) {
3728
0
                    return -1;
3729
0
                }
3730
3731
0
                auto file_meta = list_it->next();
3732
0
                if (!file_meta.has_value()) {
3733
0
                    break;
3734
0
                }
3735
3736
0
                const std::string& file_path = file_meta->path;
3737
                // Only check files (not directories), and ensure it's a packed file
3738
                // Skip directories (paths ending with '/') and non-packed-file paths
3739
0
                if (file_path.empty() || file_path.back() == '/' ||
3740
0
                    !file_path.starts_with("data/packed_file/")) {
3741
0
                    continue;
3742
0
                }
3743
3744
                // Check if this packed file is in metadata
3745
0
                if (expected_packed_files.find(file_path) == expected_packed_files.end()) {
3746
0
                    LOG(WARNING) << "packed file found in storage but not in metadata, "
3747
0
                                    "resource_id="
3748
0
                                 << resource_id << ", packed_file_path=" << file_path;
3749
0
                    num_packed_file_leak++;
3750
0
                    check_ret = 1; // Data leak identified
3751
0
                }
3752
0
            }
3753
0
        }
3754
0
    }
3755
3756
0
    if (num_packed_file_loss > 0 || num_packed_file_leak > 0 || num_ref_count_mismatch > 0 ||
3757
0
        num_small_file_ref_mismatch > 0) {
3758
0
        return 1; // Data loss or inconsistency identified
3759
0
    }
3760
3761
0
    if (check_ret < 0) {
3762
0
        return check_ret; // Temporary error
3763
0
    }
3764
3765
0
    return 0; // Success
3766
0
}
3767
} // namespace doris::cloud