github-actions[bot] commented on code in PR #69264:
URL: https://github.com/apache/doris/pull/69264#discussion_r4311131925


##########
cloud/src/recycler/checker.cpp:
##########
@@ -1249,6 +1249,135 @@ int InstanceChecker::collect_tablet_rowsets(
     return 0;
 }
 
+std::pair<MetaServiceCode, std::string> check_delete_bitmap_orphan(
+        const std::shared_ptr<TxnKv>& txn_kv, const std::string& instance_id, 
int64_t tablet_id,
+        int64_t max_scan_keys, DeleteBitmapOrphanCheckResult* result,
+        const std::function<bool()>& should_cancel) {
+    *result = {};
+    auto check_cancelled = [&] {
+        result->cancelled = should_cancel();
+        return result->cancelled;
+    };
+    std::unique_ptr<Transaction> txn;
+    auto err = txn_kv->create_txn(&txn);
+    if (err != TxnErrorCode::TXN_OK) {
+        return {cast_as<ErrCategory::CREATE>(err), "failed to create check 
transaction"};
+    }
+    std::string instance_value;
+    err = txn->get(instance_key({instance_id}), &instance_value);

Review Comment:
   [P2] Check cancellation around the initial instance lookup
   
   `should_cancel` is first invoked only after the first bitmap range read. An 
already-cancelled request therefore still reads the instance; when it is 
absent, the handler returns HTTP 400 before observing cancellation. 
Cancellation arriving during that lookup similarly returns `INVALID_ARGUMENT` 
instead of `OK` with `result.cancelled=true`. This directly contradicts both 
added tests `delete_bitmap_orphan_http_cancelled_before_scan` and 
`delete_bitmap_orphan_check_cancelled_during_instance_read`; an 
existing-instance request can also start a bitmap read after cancellation. 
Check cancellation before initial I/O and immediately after the instance 
lookup, before interpreting its result, so these paths return the intended 
cancelled result.



##########
cloud/src/recycler/checker.cpp:
##########
@@ -1249,6 +1249,135 @@ int InstanceChecker::collect_tablet_rowsets(
     return 0;
 }
 
+std::pair<MetaServiceCode, std::string> check_delete_bitmap_orphan(
+        const std::shared_ptr<TxnKv>& txn_kv, const std::string& instance_id, 
int64_t tablet_id,
+        int64_t max_scan_keys, DeleteBitmapOrphanCheckResult* result,
+        const std::function<bool()>& should_cancel) {
+    *result = {};
+    auto check_cancelled = [&] {
+        result->cancelled = should_cancel();
+        return result->cancelled;
+    };
+    std::unique_ptr<Transaction> txn;
+    auto err = txn_kv->create_txn(&txn);
+    if (err != TxnErrorCode::TXN_OK) {
+        return {cast_as<ErrCategory::CREATE>(err), "failed to create check 
transaction"};
+    }
+    std::string instance_value;
+    err = txn->get(instance_key({instance_id}), &instance_value);
+    if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
+        return {MetaServiceCode::INVALID_ARGUMENT, "instance not found"};
+    }
+    if (err != TxnErrorCode::TXN_OK) {
+        return {cast_as<ErrCategory::READ>(err), "failed to read instance"};
+    }
+
+    auto begin = meta_delete_bitmap_key({instance_id, tablet_id, "", 0, 0});
+    auto end = meta_delete_bitmap_key(
+            {instance_id, tablet_id == 0 ? INT64_MAX : tablet_id + 1, "", 0, 
0});
+    RangeGetOptions options;
+    options.reverse = true;
+    options.snapshot = true;
+    int64_t cached_tablet_id = -1;
+    std::unordered_set<std::string> rowsets;
+    while (true) {
+        if (max_scan_keys > 0 && result->scanned_kvs >= max_scan_keys) {
+            return {MetaServiceCode::OK, ""};
+        }
+        // Small pages bound bitmap value traffic before an early return. No 
next-page prefetch.
+        options.batch_limit = max_scan_keys > 0
+                                      ? std::min<int64_t>(128, max_scan_keys - 
result->scanned_kvs)
+                                      : 128;
+        std::unique_ptr<RangeGetIterator> it;
+        err = txn->get(begin, end, &it, options);
+        if (check_cancelled()) {
+            return {MetaServiceCode::OK, ""};
+        }
+        if (err != TxnErrorCode::TXN_OK) {
+            return {cast_as<ErrCategory::READ>(err), "failed to scan v1 delete 
bitmaps"};
+        }
+        // The cache is reused across pages, each with a new transaction. 
Track whether the
+        // current tablet's cache was refreshed at this transaction's read 
version, so a cache
+        // miss is rechecked for newly published rowsets before reporting a 
suspected orphan.
+        bool rowsets_cache_refreshed_in_txn = false;
+        while (!check_cancelled() && it->has_next()) {
+            auto [key, value] = it->next();
+            ++result->scanned_kvs;
+            auto encoded_key = key.substr(1);
+            std::vector<std::tuple<std::variant<int64_t, std::string>, int, 
int>> fields;
+            // Legacy keys have seven fields; blob keys have an additional 
encoded suffix.
+            if (decode_key(&encoded_key, &fields) != 0 ||
+                (fields.size() != 7 && fields.size() != 8)) {
+                return {MetaServiceCode::UNDEFINED_ERR,
+                        fmt::format("malformed delete bitmap key: {}", 
hex(key))};
+            }
+            const auto* current_tablet_id = 
std::get_if<int64_t>(&std::get<0>(fields[3]));
+            const auto* rowset_id = 
std::get_if<std::string>(&std::get<0>(fields[4]));
+            const auto* version = 
std::get_if<int64_t>(&std::get<0>(fields[5]));
+            const auto* segment_id = 
std::get_if<int64_t>(&std::get<0>(fields[6]));
+            if (current_tablet_id == nullptr || rowset_id == nullptr || 
version == nullptr ||
+                segment_id == nullptr) {
+                return {MetaServiceCode::UNDEFINED_ERR,
+                        fmt::format("invalid delete bitmap key fields: {}", 
hex(key))};
+            }
+
+            if (cached_tablet_id != *current_tablet_id ||
+                (!rowsets.contains(*rowset_id) && 
!rowsets_cache_refreshed_in_txn)) {
+                rowsets.clear();
+                FullRangeGetOptions rowset_options;
+                rowset_options.snapshot = true;
+                rowset_options.batch_limit = 128;
+                auto rowset_it = txn->full_range_get(

Review Comment:
   [P2] Renew transactions within the nested rowset scans
   
   `txn->full_range_get()` pins `opts.txn=this`, so all its 128-row pages keep 
using the bitmap transaction; that transaction is replaced only after the whole 
bitmap page is processed. A page containing one bitmap from each of 128 tablets 
with 1,900 formal rowsets requires at least 1,920 sequential rowset reads on 
one snapshot. At 3 ms per read this exceeds [FoundationDB's five-second read 
window](https://apple.github.io/foundationdb/known-limitations.html#long-running-transactions):
 a later rowset page returns `TXN_TOO_OLD`, and this endpoint errors out before 
reaching lower keys. These rowset counts are below the configured 2,000-version 
limit. Bound transaction lifetime within the nested scans and revalidate after 
renewal, rather than relying solely on the outer bitmap page boundary.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to