Coverage Report

Created: 2026-09-04 17:44

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