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


##########
cloud/src/recycler/recycler.cpp:
##########
@@ -4524,6 +4521,20 @@ int InstanceRecycler::delete_rowset_data(
             ret = -1;
             continue;
         }
+
+        // we have to treat tmp rowset as "orphans" that may not related to 
any existing tablets

Review Comment:
   [P1] Match the packed bitmap slice by its stored location
   
   Moving the recycled-tablet check below the two decrement calls makes 
retained recycle rowsets reliably depend on the existing bitmap helper. A 
resource-wide packed file can contain segment/index slices and the 
delete-bitmap slice for the same tablet/rowset, and slice order comes from an 
`unordered_map`. After `decrement_packed_file_ref_counts()` marks a segment 
deleted, the bitmap helper matches only tablet/rowset and stops at that first 
deleted segment, treating the bitmap as already deleted while its actual 
slice/refcount remains live. Once the bitmap blob and recycle key are removed, 
an already-corrected packed record is never rescanned, so the object/KV leaks 
permanently. Please match the bitmap's persisted packed path/offset/size and 
add a colocated-slices test.



##########
cloud/src/recycler/recycler.cpp:
##########
@@ -4524,6 +4521,20 @@ int InstanceRecycler::delete_rowset_data(
             ret = -1;
             continue;
         }
+
+        // we have to treat tmp rowset as "orphans" that may not related to 
any existing tablets
+        // due to aborted schema change.
+        if (is_formal_rowset) {
+            std::lock_guard lock(recycled_tablets_mtx_);
+            if (recycled_tablets_.contains(rs.tablet_id()) &&

Review Comment:
   [P1] Do not skip recycle rowsets from an unswept vault
   
   `recycled_tablets_` only means `recycle_tablet()` deleted the tablet prefix 
from resource IDs found in current formal/restore rowsets. In a cross-vault 
tablet, a compacted recycle rowset can still point to old vault A while 
surviving formal rowsets point only to vault B; tablet recycling sweeps B, this 
branch then skips deletion from A, and the caller removes the recycle key, 
leaking A's segment/index and standalone-bitmap files permanently. Please 
include retained recycle-rowset vaults in the tablet sweep, scope the marker to 
`(tablet_id, resource_id)`, or perform the idempotent rowset deletion here.



##########
cloud/src/recycler/recycler.cpp:
##########
@@ -5315,11 +5326,17 @@ int InstanceRecycler::recycle_tablet(int64_t tablet_id, 
RecyclerMetricsContext&
     std::string delete_bitmap_end = meta_delete_bitmap_key({instance_id_, 
tablet_id + 1, "", 0, 0});
     txn->remove(delete_bitmap_start, delete_bitmap_end);
 
-    std::string dbm_start_key = 
versioned::meta_delete_bitmap_key({instance_id_, tablet_id, ""});
-    std::string dbm_end_key = versioned::meta_delete_bitmap_key({instance_id_, 
tablet_id + 1, ""});
-    txn->remove(dbm_start_key, dbm_end_key);
-    LOG(INFO) << "remove delete bitmap kv, tablet=" << tablet_id << ", begin=" 
<< hex(dbm_start_key)
-              << " end=" << hex(dbm_end_key);
+    for (const auto& rowset_id : versioned_delete_bitmap_rowset_ids) {

Review Comment:
   [P1] Batch the per-rowset bitmap range clears
   
   Each iteration adds a separate clear range, including two encoded keys plus 
FDB mutation/conflict-range overhead, to the same final transaction. 
Time-series tablets can reach the default 20,000-version limit; with normal 
48-byte rowset IDs these clears alone can exceed Doris's 7-8 MiB safe 
transaction envelope and commit with `TXN_BYTES_TOO_LARGE`. Because the rowset 
ranges are removed in that same failed transaction, every retry reconstructs 
the identical oversized transaction and the dropped index/partition cannot 
finish recycling. Please commit these idempotent bitmap clears in bounded 
batches using `approximate_bytes()`/`max_txn_commit_byte`, then perform the 
final tablet metadata cleanup.



##########
cloud/test/recycler_test.cpp:
##########
@@ -3686,8 +3696,216 @@ TEST(RecyclerTest, 
recycle_tablet_packed_file_ref_count) {
     ASSERT_FALSE(list_iter->has_next());
 }
 
+TEST(RecyclerTest, recycle_tablet_recycle_rowset_packed_file_ref_count) {
+    const bool old_force_immediate_recycle = config::force_immediate_recycle;
+    DORIS_CLOUD_DEFER {
+        config::force_immediate_recycle = old_force_immediate_recycle;
+    };
+    config::force_immediate_recycle = true;
+    auto txn_kv = std::make_shared<MemTxnKv>();
+    ASSERT_EQ(txn_kv->init(), 0);
+
+    constexpr std::string_view kResourceId = 
"recycle_tablet_recycle_rowset_ref";
+    auto instance = create_recycler_test_instance(std::string(kResourceId));
+    InstanceRecycler recycler(txn_kv, instance, thread_group,
+                              std::make_shared<TxnLazyCommitter>(txn_kv));
+    ASSERT_EQ(recycler.init(), 0);
+    auto accessor = recycler.accessor_map_.begin()->second;
+
+    constexpr int64_t tablet_id = 40101;
+    constexpr int64_t index_id = 40102;
+    constexpr int64_t kSmallFileSize = 64;
+    doris::TabletSchemaCloudPB schema;
+    schema.set_schema_version(1);
+
+    auto formal_rowset = create_rowset(std::string(kResourceId), tablet_id, 
index_id, 1, schema,
+                                       RowsetStatePB::VISIBLE);
+    formal_rowset.set_end_version(1);
+
+    auto recycled_rowset = create_rowset(std::string(kResourceId), tablet_id, 
index_id, 1, schema,
+                                         RowsetStatePB::VISIBLE);
+    const std::string small_file_path = segment_path(tablet_id, 
recycled_rowset.rowset_id_v2(), 0);
+    const std::string packed_file_path = 
"data/packed_file/1/recycle_rowset.bin";
+    auto& packed_location = 
(*recycled_rowset.mutable_packed_slice_locations())[small_file_path];
+    packed_location.set_packed_file_path(packed_file_path);
+    packed_location.set_offset(0);
+    packed_location.set_size(kSmallFileSize);
+
+    RecycleRowsetPB recycle_rowset;
+    recycle_rowset.set_creation_time(current_time);
+    recycle_rowset.set_type(RecycleRowsetPB::COMPACT);
+    recycle_rowset.mutable_rowset_meta()->CopyFrom(recycled_rowset);
+    const std::string recycled_rowset_key =
+            recycle_rowset_key({instance_id, tablet_id, 
recycled_rowset.rowset_id_v2()});
+
+    // A corrected packed file will not have its rowset references scanned 
again.
+    PackedFileInfoPB packed_info;
+    packed_info.set_ref_cnt(1);
+    packed_info.set_total_slice_num(1);
+    packed_info.set_total_slice_bytes(kSmallFileSize);
+    packed_info.set_remaining_slice_bytes(kSmallFileSize);
+    packed_info.set_corrected(true);
+    packed_info.set_state(PackedFileInfoPB::NORMAL);
+    packed_info.set_resource_id(std::string(kResourceId));
+    auto* packed_slice = packed_info.add_slices();
+    packed_slice->set_path(small_file_path);
+    packed_slice->set_offset(0);
+    packed_slice->set_size(kSmallFileSize);
+    packed_slice->set_deleted(false);
+    packed_slice->set_corrected(true);
+    packed_slice->set_tablet_id(tablet_id);
+    packed_slice->set_rowset_id(recycled_rowset.rowset_id_v2());
+    packed_slice->set_txn_id(next_small_file_txn_id());
+    const std::string packed_key = packed_file_key({instance_id, 
packed_file_path});
+
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn_kv->create_txn(&txn));
+    txn->put(meta_rowset_key({instance_id, tablet_id, 
formal_rowset.end_version()}),
+             formal_rowset.SerializeAsString());
+    txn->put(recycled_rowset_key, recycle_rowset.SerializeAsString());
+    txn->put(packed_key, packed_info.SerializeAsString());
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn->commit());
+
+    ASSERT_EQ(0, accessor->put_file(packed_file_path, "payload"));
+    const std::string formal_segment_path =
+            segment_path(tablet_id, formal_rowset.rowset_id_v2(), 0);
+    ASSERT_EQ(0, accessor->put_file(formal_segment_path, "segment"));
+
+    ASSERT_EQ(0, recycler.recycle_tablet(tablet_id, ctx));
+    ASSERT_EQ(0, recycler.recycle_rowsets());
+    ASSERT_EQ(0, recycler.recycle_packed_files());
+
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn_kv->create_txn(&txn));
+    std::string value;
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND, txn->get(recycled_rowset_key, 
&value));
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND, txn->get(packed_key, &value));
+    EXPECT_EQ(1, accessor->exists(packed_file_path));
+}
+
+TEST(RecyclerTest, 
recycle_tablet_recycle_rowset_delete_bitmap_packed_file_ref_count) {
+    const bool old_force_immediate_recycle = config::force_immediate_recycle;
+    DORIS_CLOUD_DEFER {
+        config::force_immediate_recycle = old_force_immediate_recycle;
+    };
+    config::force_immediate_recycle = true;
+
+    auto txn_kv = std::make_shared<MemTxnKv>();
+    ASSERT_EQ(txn_kv->init(), 0);
+
+    constexpr std::string_view kResourceId = 
"recycle_tablet_recycle_rowset_dbm_ref";
+    auto instance = create_recycler_test_instance(std::string(kResourceId));
+    InstanceRecycler recycler(txn_kv, instance, thread_group,
+                              std::make_shared<TxnLazyCommitter>(txn_kv));
+    ASSERT_EQ(recycler.init(), 0);
+    auto accessor = recycler.accessor_map_.begin()->second;
+
+    constexpr int64_t tablet_id = 40103;
+    constexpr int64_t index_id = 40104;
+    constexpr int64_t kSmallFileSize = 64;
+    doris::TabletSchemaCloudPB schema;
+    schema.set_schema_version(1);
+
+    auto formal_rowset = create_rowset(std::string(kResourceId), tablet_id, 
index_id, 1, schema,
+                                       RowsetStatePB::VISIBLE);
+    formal_rowset.set_end_version(1);
+
+    auto restore_job_rowset = create_rowset(std::string(kResourceId), 
tablet_id, index_id, 1,
+                                            schema, RowsetStatePB::VISIBLE);
+    restore_job_rowset.set_end_version(2);
+    const std::string restore_job_rowset_key =
+            job_restore_rowset_key({instance_id, tablet_id, 
restore_job_rowset.end_version()});
+
+    auto recycled_rowset = create_rowset(std::string(kResourceId), tablet_id, 
index_id, 1, schema,
+                                         RowsetStatePB::VISIBLE);
+    RecycleRowsetPB recycle_rowset;
+    recycle_rowset.set_creation_time(current_time);
+    recycle_rowset.set_type(RecycleRowsetPB::COMPACT);
+    recycle_rowset.mutable_rowset_meta()->CopyFrom(recycled_rowset);
+    const std::string recycled_rowset_key =
+            recycle_rowset_key({instance_id, tablet_id, 
recycled_rowset.rowset_id_v2()});
+
+    const std::string packed_file_path = 
"data/packed_file/1/recycle_rowset_delete_bitmap.bin";
+    const std::string delete_bitmap_key = versioned::meta_delete_bitmap_key(
+            {instance_id, tablet_id, recycled_rowset.rowset_id_v2()});
+    const std::string formal_delete_bitmap_key = 
versioned::meta_delete_bitmap_key(
+            {instance_id, tablet_id, formal_rowset.rowset_id_v2()});
+    const std::string restore_job_delete_bitmap_key = 
versioned::meta_delete_bitmap_key(
+            {instance_id, tablet_id, restore_job_rowset.rowset_id_v2()});
+    DeleteBitmapStoragePB delete_bitmap_storage;
+    delete_bitmap_storage.set_store_in_fdb(false);
+    auto* packed_location = 
delete_bitmap_storage.mutable_packed_slice_location();
+    packed_location->set_packed_file_path(packed_file_path);
+    packed_location->set_offset(0);
+    packed_location->set_size(kSmallFileSize);
+
+    PackedFileInfoPB packed_info;
+    packed_info.set_ref_cnt(1);
+    packed_info.set_total_slice_num(1);
+    packed_info.set_total_slice_bytes(kSmallFileSize);
+    packed_info.set_remaining_slice_bytes(kSmallFileSize);
+    packed_info.set_corrected(true);
+    packed_info.set_state(PackedFileInfoPB::NORMAL);
+    packed_info.set_resource_id(std::string(kResourceId));
+    auto* packed_slice = packed_info.add_slices();
+    packed_slice->set_path(delete_bitmap_path(tablet_id, 
recycled_rowset.rowset_id_v2()));
+    packed_slice->set_offset(0);
+    packed_slice->set_size(kSmallFileSize);
+    packed_slice->set_deleted(false);
+    packed_slice->set_corrected(true);
+    packed_slice->set_tablet_id(tablet_id);
+    packed_slice->set_rowset_id(recycled_rowset.rowset_id_v2());
+    packed_slice->set_txn_id(next_small_file_txn_id());
+    const std::string packed_key = packed_file_key({instance_id, 
packed_file_path});
+
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn_kv->create_txn(&txn));
+    txn->put(meta_rowset_key({instance_id, tablet_id, 
formal_rowset.end_version()}),
+             formal_rowset.SerializeAsString());
+    txn->put(restore_job_rowset_key, restore_job_rowset.SerializeAsString());
+    txn->put(recycled_rowset_key, recycle_rowset.SerializeAsString());
+    DeleteBitmapStoragePB in_fdb_delete_bitmap_storage;
+    in_fdb_delete_bitmap_storage.set_store_in_fdb(true);
+    cloud::blob_put(txn.get(), formal_delete_bitmap_key, 
in_fdb_delete_bitmap_storage, 0);
+    cloud::blob_put(txn.get(), restore_job_delete_bitmap_key, 
in_fdb_delete_bitmap_storage, 0);
+    cloud::blob_put(txn.get(), delete_bitmap_key, delete_bitmap_storage, 0);
+    txn->put(packed_key, packed_info.SerializeAsString());
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn->commit());
+
+    ASSERT_EQ(0, accessor->put_file(packed_file_path, "delete bitmap 
payload"));
+    const std::string formal_segment_path =
+            segment_path(tablet_id, formal_rowset.rowset_id_v2(), 0);
+    ASSERT_EQ(0, accessor->put_file(formal_segment_path, "segment"));
+
+    ASSERT_EQ(0, recycler.recycle_tablet(tablet_id, ctx));
+
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn_kv->create_txn(&txn));
+    ValueBuf delete_bitmap_value;
+    EXPECT_EQ(TxnErrorCode::TXN_OK,
+              cloud::blob_get(txn.get(), delete_bitmap_key, 
&delete_bitmap_value));
+    ValueBuf removed_delete_bitmap_value;
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND,
+              cloud::blob_get(txn.get(), formal_delete_bitmap_key, 
&removed_delete_bitmap_value));
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND,
+              cloud::blob_get(txn.get(), restore_job_delete_bitmap_key,
+                              &removed_delete_bitmap_value));
+
+    ASSERT_EQ(0, recycler.recycle_rowsets());
+    ASSERT_EQ(0, recycler.recycle_packed_files());
+
+    ASSERT_EQ(TxnErrorCode::TXN_OK, txn_kv->create_txn(&txn));
+    std::string value;
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND, txn->get(recycled_rowset_key, 
&value));
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND, txn->get(packed_key, &value));
+    EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND, txn->get(delete_bitmap_key, 
&value));

Review Comment:
   [P2] Check the blob chunks instead of the unwritten prefix
   
   `cloud::blob_put` never writes `delete_bitmap_key` itself; it appends an 
encoded chunk suffix to every physical KV. Consequently this exact 
`txn->get(delete_bitmap_key, ...)` returns `TXN_KEY_NOT_FOUND` even immediately 
after setup, so the test still passes if deferred recycling leaves every 
delete-bitmap chunk behind.
   
   ```suggestion
   EXPECT_EQ(TxnErrorCode::TXN_KEY_NOT_FOUND,
             cloud::blob_get(txn.get(), delete_bitmap_key, 
&removed_delete_bitmap_value));
   ```



-- 
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