Coverage Report

Created: 2026-10-22 22:11

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