Coverage Report

Created: 2026-08-25 14:37

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