blob: 69770e47546ce0c8dd8c214a792efeb8d46023e1 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <brpc/controller.h>
#include <gen_cpp/cloud.pb.h>
#include <gtest/gtest.h>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "common/config.h"
#include "common/defer.h"
#include "common/lexical_util.h"
#include "cpp/sync_point.h"
#include "meta-service/meta_service.h"
#include "meta-store/keys.h"
#include "meta-store/txn_kv.h"
#include "meta-store/txn_kv_error.h"
#include "meta-store/versioned_value.h"
#include "recycler/recycler.h"
#include "resource-manager/resource_manager.h"
#include "snapshot/snapshot_manager.h"
namespace doris::cloud {
extern std::unique_ptr<MetaServiceProxy> get_meta_service(bool mock_resource_mgr);
extern void add_tablet(CreateTabletsRequest& request, int64_t table_id, int64_t index_id,
int64_t partition_id, int64_t tablet_id);
extern doris::RowsetMetaCloudPB create_rowset(int64_t txn_id, int64_t tablet_id, int partition_id,
int64_t version, int num_rows);
extern TxnErrorCode read_operation_log(Transaction* txn, std::string_view log_key,
Versionstamp* log_version, OperationLogPB* operation_log);
class MetaServiceTableStreamTest : public ::testing::Test {
protected:
void SetUp() override {
service_ = get_meta_service(false);
instance_id_ = "table_stream_read_state_instance";
cloud_unique_id_ = "1:" + instance_id_ + ":test";
identity_.set_base_db_id(1001);
identity_.set_base_table_id(1002);
identity_.set_stream_db_id(1003);
identity_.set_stream_id(1004);
}
void set_multi_version_status(MultiVersionStatus mode) {
multi_version_status_ = mode;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
InstanceInfoPB instance;
instance.set_instance_id(instance_id_);
instance.set_multi_version_status(mode);
txn->put(instance_key({instance_id_}), instance.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
auto [code, message] = service_->resource_mgr()->refresh_instance(instance_id_);
ASSERT_EQ(code, MetaServiceCode::OK) << message;
}
void put_partition_mapping(Transaction* txn, int64_t partition_id) {
PartitionIndexPB partition;
partition.set_db_id(identity_.base_db_id());
partition.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({instance_id_, partition_id}),
partition.SerializeAsString());
}
void put_latest_partition_state(Transaction* txn, int64_t partition_id, int64_t visible_version,
int64_t commit_tso, std::optional<int64_t> offset_tso) {
VersionPB version;
version.set_version(visible_version);
version.set_commit_tso(commit_tso);
txn->put(partition_version_key({instance_id_, identity_.base_db_id(),
identity_.base_table_id(), partition_id}),
version.SerializeAsString());
if (offset_tso.has_value()) {
TableStreamOffsetPB offset;
offset.set_partition_id(partition_id);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(*offset_tso);
offset.set_last_consumption_time_ms(1234);
txn->put(table_stream_offset_key({instance_id_, identity_.base_db_id(),
identity_.base_table_id(), identity_.stream_db_id(),
identity_.stream_id(), partition_id}),
offset.SerializeAsString());
}
}
void put_versioned_partition_state(Transaction* txn, int64_t partition_id,
int64_t visible_version, int64_t commit_tso,
std::optional<int64_t> offset_tso) {
put_partition_mapping(txn, partition_id);
versioned_put(txn, versioned::meta_partition_key({instance_id_, partition_id}),
Versionstamp(41, 0), "");
VersionPB version;
version.set_version(visible_version);
version.set_commit_tso(commit_tso);
versioned_put(txn, versioned::partition_version_key({instance_id_, partition_id}),
Versionstamp(42, 0), version.SerializeAsString());
if (offset_tso.has_value()) {
TableStreamOffsetPB offset;
offset.set_partition_id(partition_id);
offset.set_state(TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING);
offset.set_offset_tso(*offset_tso);
versioned_put(txn,
versioned::table_stream_offset_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), partition_id}),
Versionstamp(43, 0), offset.SerializeAsString());
}
}
GetTableStreamOffsetResponse get_read_state(const std::vector<int64_t>& partitions) {
GetTableStreamOffsetRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
auto* binding = request.add_bindings();
binding->mutable_identity()->CopyFrom(identity_);
for (int64_t partition_id : partitions) {
binding->add_partition_ids(partition_id);
}
GetTableStreamOffsetResponse response;
brpc::Controller controller;
service_->get_table_stream_offset(&controller, &request, &response, nullptr);
return response;
}
GetTableStreamOffsetResponse get_read_state(std::initializer_list<int64_t> partitions) {
return get_read_state(std::vector<int64_t>(partitions));
}
int64_t begin_target_transaction(const std::string& label) {
return begin_transaction(cloud_unique_id_, label);
}
int64_t begin_transaction(const std::string& cloud_unique_id, const std::string& label) {
BeginTxnRequest request;
request.set_cloud_unique_id(cloud_unique_id);
TxnInfoPB* txn_info = request.mutable_txn_info();
txn_info->set_db_id(3001);
txn_info->set_label(label);
txn_info->add_table_ids(3002);
txn_info->set_timeout_ms(36000);
BeginTxnResponse response;
brpc::Controller controller;
service_->begin_txn(&controller, &request, &response, nullptr);
EXPECT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
return response.txn_id();
}
void register_instance(const std::string& instance_id, MultiVersionStatus mode) {
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
InstanceInfoPB instance;
instance.set_instance_id(instance_id);
instance.set_multi_version_status(mode);
txn->put(instance_key({instance_id}), instance.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
auto [code, message] = service_->resource_mgr()->refresh_instance(instance_id);
ASSERT_EQ(code, MetaServiceCode::OK) << message;
}
void mark_transaction_visible(const std::string& instance_id, int64_t txn_id,
Transaction* txn) {
std::string txn_info_value;
ASSERT_EQ(txn->get(txn_info_key({instance_id, 3001, txn_id}), &txn_info_value),
TxnErrorCode::TXN_OK);
TxnInfoPB txn_info;
ASSERT_TRUE(txn_info.ParseFromString(txn_info_value));
txn_info.set_status(TXN_STATUS_VISIBLE);
txn->put(txn_info_key({instance_id, 3001, txn_id}), txn_info.SerializeAsString());
}
CommitTxnRequest make_consume_request(int64_t txn_id, int64_t partition_id,
TableStreamOffsetStatePB expected_state,
int64_t expected_tso, int64_t next_tso) {
CommitTxnRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.set_db_id(3001);
request.set_txn_id(txn_id);
TableStreamUpdatePB* stream_update = request.add_table_stream_updates();
stream_update->mutable_identity()->CopyFrom(identity_);
TableStreamPartitionUpdatePB* partition_update = stream_update->add_partition_updates();
partition_update->set_partition_id(partition_id);
partition_update->set_expected_state(expected_state);
if (expected_state != TABLE_STREAM_OFFSET_UNKNOWN) {
partition_update->set_expected_offset_tso(expected_tso);
}
partition_update->set_next_offset_tso(next_tso);
return request;
}
CommitTxnResponse commit_transaction(const CommitTxnRequest& request) {
CommitTxnResponse response;
brpc::Controller controller;
service_->commit_txn(&controller, &request, &response, nullptr);
return response;
}
CommitTxnResponse consume_partition(int64_t txn_id, int64_t partition_id,
TableStreamOffsetStatePB expected_state,
int64_t expected_tso, int64_t next_tso) {
return commit_transaction(
make_consume_request(txn_id, partition_id, expected_state, expected_tso, next_tso));
}
void create_target_tablet(int64_t db_id, int64_t table_id, int64_t index_id,
int64_t partition_id, int64_t tablet_id) {
CreateTabletsRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.set_db_id(db_id);
add_tablet(request, table_id, index_id, partition_id, tablet_id);
CreateTabletsResponse response;
brpc::Controller controller;
service_->create_tablets(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
VersionPB version;
version.set_version(1);
txn->put(partition_version_key({instance_id_, db_id, table_id, partition_id}),
version.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
}
void stage_target_rowset(int64_t txn_id, int64_t partition_id, int64_t tablet_id) {
const doris::RowsetMetaCloudPB rowset =
create_rowset(txn_id, tablet_id, partition_id, -1, 100);
auto call = [&](bool prepare) {
CreateRowsetRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.mutable_rowset_meta()->CopyFrom(rowset);
CreateRowsetResponse response;
brpc::Controller controller;
if (prepare) {
service_->prepare_rowset(&controller, &request, &response, nullptr);
} else {
service_->commit_rowset(&controller, &request, &response, nullptr);
}
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
};
ASSERT_NO_FATAL_FAILURE(call(true));
ASSERT_NO_FATAL_FAILURE(call(false));
}
TableStreamOffsetPB get_latest_offset(const TableStreamIdentityPB& identity,
int64_t partition_id) {
std::unique_ptr<Transaction> txn;
EXPECT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string value;
EXPECT_EQ(txn->get(table_stream_offset_key(
{instance_id_, identity.base_db_id(), identity.base_table_id(),
identity.stream_db_id(), identity.stream_id(), partition_id}),
&value),
TxnErrorCode::TXN_OK);
TableStreamOffsetPB offset;
EXPECT_TRUE(offset.ParseFromString(value));
return offset;
}
TableStreamOffsetPB get_latest_offset(int64_t partition_id) {
return get_latest_offset(identity_, partition_id);
}
void set_clone_source(const std::string& source_instance_id, Versionstamp snapshot_version) {
multi_version_status_ = MULTI_VERSION_READ_WRITE;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
InstanceInfoPB instance;
instance.set_instance_id(instance_id_);
instance.set_multi_version_status(MULTI_VERSION_READ_WRITE);
instance.set_source_instance_id(source_instance_id);
instance.set_source_snapshot_id(SnapshotManager::serialize_snapshot_id(snapshot_version));
txn->put(instance_key({instance_id_}), instance.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
auto [code, message] = service_->resource_mgr()->refresh_instance(instance_id_);
ASSERT_EQ(code, MetaServiceCode::OK) << message;
}
Versionstamp put_auto_versioned_offset(const std::string& target_instance_id,
const TableStreamIdentityPB& identity,
int64_t partition_id, int64_t offset_tso,
bool write_latest = false) {
std::unique_ptr<Transaction> txn;
EXPECT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
TableStreamOffsetPB offset;
offset.set_partition_id(partition_id);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(offset_tso);
const TableStreamOffsetKeyInfo key_info {target_instance_id, identity.base_db_id(),
identity.base_table_id(), identity.stream_db_id(),
identity.stream_id(), partition_id};
const std::string value = offset.SerializeAsString();
if (write_latest) {
txn->put(table_stream_offset_key(key_info), value);
}
txn->enable_get_versionstamp();
versioned_put(txn.get(), versioned::table_stream_offset_key(key_info), value);
EXPECT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
Versionstamp version;
EXPECT_EQ(txn->get_versionstamp(&version), TxnErrorCode::TXN_OK);
return version;
}
std::unique_ptr<MetaServiceProxy> service_;
std::string instance_id_;
std::string cloud_unique_id_;
TableStreamIdentityPB identity_;
MultiVersionStatus multi_version_status_ = MULTI_VERSION_DISABLED;
};
TEST_F(MetaServiceTableStreamTest, ReadLatestAndUnknownOffsets) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
put_latest_partition_state(txn.get(), 2002, 9, 140, std::nullopt);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state({2001, 2002});
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings_size(), 1);
ASSERT_EQ(response.bindings(0).partition_states_size(), 2);
const auto& consumed = response.bindings(0).partition_states(0);
EXPECT_EQ(consumed.partition_id(), 2001);
EXPECT_EQ(consumed.offset_state(), TABLE_STREAM_OFFSET_CONSUMED);
EXPECT_EQ(consumed.offset_tso(), 100);
EXPECT_EQ(consumed.end_tso(), 130);
EXPECT_EQ(consumed.visible_version(), 8);
EXPECT_EQ(consumed.last_consumption_time_ms(), 1234);
const auto& unknown = response.bindings(0).partition_states(1);
EXPECT_EQ(unknown.partition_id(), 2002);
EXPECT_EQ(unknown.offset_state(), TABLE_STREAM_OFFSET_UNKNOWN);
EXPECT_FALSE(unknown.has_offset_tso());
EXPECT_EQ(unknown.end_tso(), 140);
EXPECT_EQ(unknown.visible_version(), 9);
}
TEST_F(MetaServiceTableStreamTest, ReadVersionedState) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_versioned_partition_state(txn.get(), 2001, 18, 230, 200);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state({2001});
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings_size(), 1);
ASSERT_EQ(response.bindings(0).partition_states_size(), 1);
const auto& state = response.bindings(0).partition_states(0);
EXPECT_EQ(state.offset_state(), TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING);
EXPECT_EQ(state.offset_tso(), 200);
EXPECT_EQ(state.end_tso(), 230);
EXPECT_EQ(state.visible_version(), 18);
}
TEST_F(MetaServiceTableStreamTest, ReadVersionedStateFromCloneChainInBatch) {
const std::string source_instance_id = "table_stream_batch_read_source";
set_clone_source(source_instance_id, Versionstamp(100, 0));
const std::vector<int64_t> partition_ids = {2001, 2002, 2003};
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
for (size_t i = 0; i < partition_ids.size(); ++i) {
int64_t partition_id = partition_ids[i];
PartitionIndexPB partition;
partition.set_db_id(identity_.base_db_id());
partition.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({source_instance_id, partition_id}),
partition.SerializeAsString());
versioned_put(txn.get(), versioned::meta_partition_key({source_instance_id, partition_id}),
Versionstamp(51 + i, 0), "");
VersionPB version;
version.set_version(10 + i);
version.set_commit_tso(1000 + i);
versioned_put(txn.get(),
versioned::partition_version_key({source_instance_id, partition_id}),
Versionstamp(60 + i, 0), version.SerializeAsString());
}
auto put_offset = [&](const std::string& target_instance_id, int64_t partition_id,
Versionstamp versionstamp, int64_t offset_tso) {
TableStreamOffsetPB offset;
offset.set_partition_id(partition_id);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(offset_tso);
versioned_put(
txn.get(),
versioned::table_stream_offset_key(
{target_instance_id, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), partition_id}),
versionstamp, offset.SerializeAsString());
};
put_offset(source_instance_id, 2001, Versionstamp(70, 0), 701);
put_offset(source_instance_id, 2002, Versionstamp(71, 0), 702);
put_offset(instance_id_, 2002, Versionstamp(200, 0), 802);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state(partition_ids);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings(0).partition_states_size(), partition_ids.size());
EXPECT_EQ(response.bindings(0).partition_states(0).offset_tso(), 701);
EXPECT_EQ(response.bindings(0).partition_states(1).offset_tso(), 802);
EXPECT_EQ(response.bindings(0).partition_states(2).offset_state(), TABLE_STREAM_OFFSET_UNKNOWN);
}
TEST_F(MetaServiceTableStreamTest, ReadLargePartitionBatch) {
set_multi_version_status(MULTI_VERSION_DISABLED);
constexpr int kPartitionCount = 1201;
std::vector<int64_t> partition_ids;
partition_ids.reserve(kPartitionCount);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
for (int i = 0; i < kPartitionCount; ++i) {
int64_t partition_id = 10000 + i;
partition_ids.push_back(partition_id);
put_latest_partition_state(txn.get(), partition_id, i + 1, 100000 + i, 90000 + i);
}
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state(partition_ids);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings(0).partition_states_size(), kPartitionCount);
EXPECT_EQ(response.bindings(0).partition_states(0).partition_id(), partition_ids.front());
EXPECT_EQ(response.bindings(0).partition_states(0).offset_tso(), 90000);
EXPECT_EQ(response.bindings(0).partition_states(kPartitionCount - 1).partition_id(),
partition_ids.back());
EXPECT_EQ(response.bindings(0).partition_states(kPartitionCount - 1).offset_tso(),
90000 + kPartitionCount - 1);
}
TEST_F(MetaServiceTableStreamTest, ReadMultipleBindingsInBatch) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
constexpr int kBindingCount = 101;
constexpr int64_t kPartitionId = 2001;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
PartitionIndexPB partition;
partition.set_db_id(identity_.base_db_id());
partition.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({instance_id_, kPartitionId}),
partition.SerializeAsString());
versioned_put(txn.get(), versioned::meta_partition_key({instance_id_, kPartitionId}),
Versionstamp(20, 0), "");
VersionPB version;
version.set_version(8);
version.set_commit_tso(130);
versioned_put(txn.get(), versioned::partition_version_key({instance_id_, kPartitionId}),
Versionstamp(21, 0), version.SerializeAsString());
GetTableStreamOffsetRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
for (int i = 0; i < kBindingCount; ++i) {
int64_t stream_id = identity_.stream_id() + i;
TableStreamPartitionSetPB* binding = request.add_bindings();
binding->mutable_identity()->CopyFrom(identity_);
binding->mutable_identity()->set_stream_id(stream_id);
binding->add_partition_ids(kPartitionId);
}
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response;
brpc::Controller controller;
service_->get_table_stream_offset(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings_size(), kBindingCount);
for (const TableStreamReadBindingResultPB& binding : response.bindings()) {
ASSERT_EQ(binding.partition_states_size(), 1);
EXPECT_EQ(binding.partition_states(0).offset_state(), TABLE_STREAM_OFFSET_UNKNOWN);
}
}
TEST_F(MetaServiceTableStreamTest, ReadWriteOnlyState) {
set_multi_version_status(MULTI_VERSION_WRITE_ONLY);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 28, 330, 300);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state({2001});
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.bindings_size(), 1);
ASSERT_EQ(response.bindings(0).partition_states_size(), 1);
const auto& state = response.bindings(0).partition_states(0);
EXPECT_EQ(state.offset_state(), TABLE_STREAM_OFFSET_CONSUMED);
EXPECT_EQ(state.offset_tso(), 300);
EXPECT_EQ(state.end_tso(), 330);
EXPECT_EQ(state.visible_version(), 28);
}
TEST_F(MetaServiceTableStreamTest, WaitsForPendingSourceTxnAndRereadsSnapshot) {
set_multi_version_status(MULTI_VERSION_DISABLED);
const int64_t pending_txn_id = begin_target_transaction("pending-source-version");
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string txn_info_value;
ASSERT_EQ(txn->get(txn_info_key({instance_id_, 3001, pending_txn_id}), &txn_info_value),
TxnErrorCode::TXN_OK);
TxnInfoPB txn_info;
ASSERT_TRUE(txn_info.ParseFromString(txn_info_value));
txn_info.set_status(TXN_STATUS_VISIBLE);
txn->put(txn_info_key({instance_id_, 3001, pending_txn_id}), txn_info.SerializeAsString());
VersionPB pending_version;
pending_version.set_commit_tso(130);
pending_version.add_pending_txn_ids(pending_txn_id);
txn->put(partition_version_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(), 2001}),
pending_version.SerializeAsString());
TableStreamOffsetPB offset;
offset.set_partition_id(2001);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(100);
txn->put(table_stream_offset_key({instance_id_, identity_.base_db_id(),
identity_.base_table_id(), identity_.stream_db_id(),
identity_.stream_id(), 2001}),
offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
bool after_wait_called = false;
auto* sync_point = SyncPoint::get_instance();
DORIS_CLOUD_DEFER {
sync_point->disable_processing();
sync_point->clear_all_call_backs();
};
sync_point->set_call_back("get_table_stream_offset::after_wait_for_pending_txns", [&](auto&&) {
std::unique_ptr<Transaction> update_txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&update_txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(update_txn.get(), 2001, 2, 130, 120);
ASSERT_EQ(update_txn->commit(), TxnErrorCode::TXN_OK);
after_wait_called = true;
});
sync_point->enable_processing();
GetTableStreamOffsetResponse response = get_read_state({2001});
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_TRUE(after_wait_called);
ASSERT_EQ(response.bindings_size(), 1);
ASSERT_EQ(response.bindings(0).partition_states_size(), 1);
const TableStreamPartitionReadStatePB& state = response.bindings(0).partition_states(0);
EXPECT_EQ(state.visible_version(), 2);
EXPECT_EQ(state.end_tso(), 130);
EXPECT_EQ(state.offset_tso(), 120);
}
TEST_F(MetaServiceTableStreamTest, WaitsForPendingVersionFromCloneSourceInstance) {
const std::string source_instance_id = "table_stream_pending_version_source";
register_instance(source_instance_id, MULTI_VERSION_READ_WRITE);
set_clone_source(source_instance_id, Versionstamp(100, 0));
const int64_t pending_txn_id = begin_transaction(fmt::format("1:{}:test", source_instance_id),
"pending-clone-source-version");
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
mark_transaction_visible(source_instance_id, pending_txn_id, txn.get());
PartitionIndexPB partition;
partition.set_db_id(identity_.base_db_id());
partition.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({source_instance_id, 2001}),
partition.SerializeAsString());
versioned_put(txn.get(), versioned::meta_partition_key({source_instance_id, 2001}),
Versionstamp(50, 0), "");
VersionPB pending_version;
pending_version.set_commit_tso(130);
pending_version.add_pending_txn_ids(pending_txn_id);
versioned_put(txn.get(), versioned::partition_version_key({source_instance_id, 2001}),
Versionstamp(60, 0), pending_version.SerializeAsString());
TableStreamOffsetPB offset;
offset.set_partition_id(2001);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(100);
versioned_put(txn.get(),
versioned::table_stream_offset_key(
{source_instance_id, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), 2001}),
Versionstamp(70, 0), offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
bool after_wait_called = false;
auto* sync_point = SyncPoint::get_instance();
DORIS_CLOUD_DEFER {
sync_point->disable_processing();
sync_point->clear_all_call_backs();
};
sync_point->set_call_back("get_table_stream_offset::after_wait_for_pending_txns", [&](auto&&) {
std::unique_ptr<Transaction> update_txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&update_txn), TxnErrorCode::TXN_OK);
VersionPB committed_version;
committed_version.set_version(2);
committed_version.set_commit_tso(130);
versioned_put(update_txn.get(),
versioned::partition_version_key({source_instance_id, 2001}),
Versionstamp(60, 0), committed_version.SerializeAsString());
offset.set_offset_tso(120);
versioned_put(
update_txn.get(),
versioned::table_stream_offset_key(
{source_instance_id, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), 2001}),
Versionstamp(70, 0), offset.SerializeAsString());
ASSERT_EQ(update_txn->commit(), TxnErrorCode::TXN_OK);
after_wait_called = true;
});
sync_point->enable_processing();
GetTableStreamOffsetResponse response = get_read_state({2001});
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_TRUE(after_wait_called);
ASSERT_EQ(response.bindings_size(), 1);
ASSERT_EQ(response.bindings(0).partition_states_size(), 1);
const TableStreamPartitionReadStatePB& state = response.bindings(0).partition_states(0);
EXPECT_EQ(state.visible_version(), 2);
EXPECT_EQ(state.end_tso(), 130);
EXPECT_EQ(state.offset_tso(), 120);
}
TEST_F(MetaServiceTableStreamTest, SingleGetVersionWaitsUsingCloneSourceInstance) {
const std::string source_instance_id = "table_stream_single_pending_version_source";
register_instance(source_instance_id, MULTI_VERSION_READ_WRITE);
set_clone_source(source_instance_id, Versionstamp(100, 0));
const int64_t pending_txn_id = begin_transaction(fmt::format("1:{}:test", source_instance_id),
"pending-clone-single-version");
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
mark_transaction_visible(source_instance_id, pending_txn_id, txn.get());
VersionPB pending_version;
pending_version.set_commit_tso(130);
pending_version.add_pending_txn_ids(pending_txn_id);
versioned_put(txn.get(), versioned::partition_version_key({source_instance_id, 2001}),
Versionstamp(60, 0), pending_version.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetVersionRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.set_db_id(identity_.base_db_id());
request.set_table_id(identity_.base_table_id());
request.set_partition_id(2001);
request.set_wait_for_pending_txn(true);
GetVersionResponse response;
brpc::Controller controller;
service_->get_version(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(response.version(), 2);
ASSERT_EQ(response.commit_tsos_size(), 1);
EXPECT_EQ(response.commit_tsos(0), 130);
}
TEST_F(MetaServiceTableStreamTest, BatchGetVersionWaitsUsingCloneSourceInstance) {
const std::string source_instance_id = "table_stream_batch_pending_version_source";
register_instance(source_instance_id, MULTI_VERSION_READ_WRITE);
set_clone_source(source_instance_id, Versionstamp(100, 0));
const int64_t pending_txn_id = begin_transaction(fmt::format("1:{}:test", source_instance_id),
"pending-clone-batch-version");
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
mark_transaction_visible(source_instance_id, pending_txn_id, txn.get());
PartitionIndexPB partition;
partition.set_db_id(identity_.base_db_id());
partition.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({source_instance_id, 2001}),
partition.SerializeAsString());
VersionPB pending_version;
pending_version.set_commit_tso(130);
pending_version.add_pending_txn_ids(pending_txn_id);
versioned_put(txn.get(), versioned::partition_version_key({source_instance_id, 2001}),
Versionstamp(60, 0), pending_version.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetVersionRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.set_batch_mode(true);
request.set_wait_for_pending_txn(true);
request.add_db_ids(identity_.base_db_id());
request.add_table_ids(identity_.base_table_id());
request.add_partition_ids(2001);
GetVersionResponse response;
brpc::Controller controller;
service_->get_version(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(response.versions_size(), 1);
ASSERT_EQ(response.commit_tsos_size(), 1);
EXPECT_EQ(response.versions(0), 2);
EXPECT_EQ(response.commit_tsos(0), 130);
}
TEST_F(MetaServiceTableStreamTest, RejectDuplicateBindingsAndPartitions) {
GetTableStreamOffsetRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
auto* binding = request.add_bindings();
binding->mutable_identity()->CopyFrom(identity_);
binding->add_partition_ids(2001);
binding->add_partition_ids(2001);
GetTableStreamOffsetResponse response;
brpc::Controller controller;
service_->get_table_stream_offset(&controller, &request, &response, nullptr);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
request.mutable_bindings()->Clear();
for (int i = 0; i < 2; ++i) {
binding = request.add_bindings();
binding->mutable_identity()->CopyFrom(identity_);
binding->add_partition_ids(2001 + i);
}
response.Clear();
brpc::Controller second_controller;
service_->get_table_stream_offset(&second_controller, &request, &response, nullptr);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
}
TEST_F(MetaServiceTableStreamTest, RejectEnabledModeAndRecyclingStream) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
InstanceInfoPB instance;
instance.set_instance_id(instance_id_);
instance.set_multi_version_status(MULTI_VERSION_ENABLED);
txn->put(instance_key({instance_id_}), instance.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
GetTableStreamOffsetResponse response = get_read_state({2001});
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
set_multi_version_status(MULTI_VERSION_DISABLED);
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
RecycleIndexPB recycle_index;
recycle_index.set_table_id(identity_.base_table_id());
recycle_index.set_state(RecycleIndexPB::RECYCLING);
recycle_index.set_object_type(TABLE_STREAM);
txn->put(recycle_index_key({instance_id_, identity_.stream_id()}),
recycle_index.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
response = get_read_state({2001});
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
}
TEST_F(MetaServiceTableStreamTest, CommitUpdatesLatestOffsetAndIsIdempotent) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
put_latest_partition_state(txn.get(), 2002, 9, 140, std::nullopt);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-existing-offset");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
TableStreamOffsetPB offset = get_latest_offset(2001);
EXPECT_EQ(offset.state(), TABLE_STREAM_OFFSET_CONSUMED);
EXPECT_EQ(offset.offset_tso(), 120);
EXPECT_GT(offset.last_consumption_time_ms(), 0);
response = consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 120);
txn_id = begin_target_transaction("consume-unknown-offset");
response = consume_partition(txn_id, 2002, TABLE_STREAM_OFFSET_UNKNOWN, 0, 140);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2002).offset_tso(), 140);
}
TEST_F(MetaServiceTableStreamTest, CommitMultipleStreamsAtomically) {
set_multi_version_status(MULTI_VERSION_DISABLED);
TableStreamIdentityPB second_identity = identity_;
second_identity.set_stream_db_id(1005);
second_identity.set_stream_id(1006);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
TableStreamOffsetPB second_offset;
second_offset.set_partition_id(2001);
second_offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
second_offset.set_offset_tso(105);
second_offset.set_last_consumption_time_ms(1234);
txn->put(table_stream_offset_key(
{instance_id_, second_identity.base_db_id(), second_identity.base_table_id(),
second_identity.stream_db_id(), second_identity.stream_id(), 2001}),
second_offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
auto make_request = [&](int64_t txn_id, int64_t second_expected_tso) {
CommitTxnRequest request =
make_consume_request(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
TableStreamUpdatePB* second_stream_update = request.add_table_stream_updates();
second_stream_update->mutable_identity()->CopyFrom(second_identity);
TableStreamPartitionUpdatePB* second_partition_update =
second_stream_update->add_partition_updates();
second_partition_update->set_partition_id(2001);
second_partition_update->set_expected_state(TABLE_STREAM_OFFSET_CONSUMED);
second_partition_update->set_expected_offset_tso(second_expected_tso);
second_partition_update->set_next_offset_tso(125);
return request;
};
CommitTxnResponse response = commit_transaction(
make_request(begin_target_transaction("consume-multiple-streams-stale"), 104));
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(identity_, 2001).offset_tso(), 100);
EXPECT_EQ(get_latest_offset(second_identity, 2001).offset_tso(), 105);
response = commit_transaction(
make_request(begin_target_transaction("consume-multiple-streams"), 105));
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(identity_, 2001).offset_tso(), 120);
EXPECT_EQ(get_latest_offset(second_identity, 2001).offset_tso(), 125);
}
TEST_F(MetaServiceTableStreamTest, CommitTargetRowsetAndOffsetAtomically) {
set_multi_version_status(MULTI_VERSION_DISABLED);
constexpr int64_t kSourcePartitionId = 2001;
constexpr int64_t kTargetDbId = 3001;
constexpr int64_t kTargetTableId = 3002;
constexpr int64_t kTargetIndexId = 3003;
constexpr int64_t kTargetPartitionId = 4001;
constexpr int64_t kTargetTabletId = 5001;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), kSourcePartitionId, 8, 130, 100);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
create_target_tablet(kTargetDbId, kTargetTableId, kTargetIndexId, kTargetPartitionId,
kTargetTabletId);
int64_t stale_txn_id = begin_target_transaction("consume-target-rowset-stale");
stage_target_rowset(stale_txn_id, kTargetPartitionId, kTargetTabletId);
CommitTxnRequest stale_request = make_consume_request(stale_txn_id, kSourcePartitionId,
TABLE_STREAM_OFFSET_CONSUMED, 99, 120);
CommitTxnResponse response = commit_transaction(stale_request);
ASSERT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 100);
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string value;
EXPECT_EQ(txn->get(meta_rowset_tmp_key({instance_id_, stale_txn_id, kTargetTabletId}), &value),
TxnErrorCode::TXN_OK);
EXPECT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 2}), &value),
TxnErrorCode::TXN_KEY_NOT_FOUND);
ASSERT_EQ(txn->get(partition_version_key(
{instance_id_, kTargetDbId, kTargetTableId, kTargetPartitionId}),
&value),
TxnErrorCode::TXN_OK);
VersionPB target_version;
ASSERT_TRUE(target_version.ParseFromString(value));
EXPECT_EQ(target_version.version(), 1);
int64_t committed_txn_id = begin_target_transaction("consume-target-rowset");
stage_target_rowset(committed_txn_id, kTargetPartitionId, kTargetTabletId);
CommitTxnRequest commit_request = make_consume_request(committed_txn_id, kSourcePartitionId,
TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
commit_request.set_commit_tso(999);
response = commit_transaction(commit_request);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 120);
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
EXPECT_EQ(txn->get(meta_rowset_tmp_key({instance_id_, committed_txn_id, kTargetTabletId}),
&value),
TxnErrorCode::TXN_KEY_NOT_FOUND);
ASSERT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 2}), &value),
TxnErrorCode::TXN_OK);
doris::RowsetMetaCloudPB committed_rowset;
ASSERT_TRUE(committed_rowset.ParseFromString(value));
EXPECT_EQ(committed_rowset.txn_id(), committed_txn_id);
ASSERT_EQ(txn->get(partition_version_key(
{instance_id_, kTargetDbId, kTargetTableId, kTargetPartitionId}),
&value),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(target_version.ParseFromString(value));
EXPECT_EQ(target_version.version(), 2);
EXPECT_EQ(target_version.commit_tso(), 999);
response = commit_transaction(commit_request);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 120);
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
ASSERT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 2}), &value),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(committed_rowset.ParseFromString(value));
EXPECT_EQ(committed_rowset.txn_id(), committed_txn_id);
ASSERT_EQ(txn->get(partition_version_key(
{instance_id_, kTargetDbId, kTargetTableId, kTargetPartitionId}),
&value),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(target_version.ParseFromString(value));
EXPECT_EQ(target_version.version(), 2);
EXPECT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 3}), &value),
TxnErrorCode::TXN_KEY_NOT_FOUND);
}
TEST_F(MetaServiceTableStreamTest, CommitLargePartitionBatch) {
set_multi_version_status(MULTI_VERSION_WRITE_ONLY);
constexpr int kPartitionCount = 1201;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
for (int i = 0; i < kPartitionCount; ++i) {
int64_t partition_id = 30000 + i;
put_latest_partition_state(txn.get(), partition_id, i + 1, 40000 + i, std::nullopt);
}
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
CommitTxnRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.set_db_id(3001);
request.set_txn_id(begin_target_transaction("consume-large-partition-batch"));
TableStreamUpdatePB* stream_update = request.add_table_stream_updates();
stream_update->mutable_identity()->CopyFrom(identity_);
for (int i = 0; i < kPartitionCount; ++i) {
int64_t partition_id = 30000 + i;
TableStreamPartitionUpdatePB* update = stream_update->add_partition_updates();
update->set_partition_id(partition_id);
update->set_expected_state(TABLE_STREAM_OFFSET_UNKNOWN);
update->set_next_offset_tso(40000 + i);
}
CommitTxnResponse response = commit_transaction(request);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(30000).offset_tso(), 40000);
EXPECT_EQ(get_latest_offset(30000 + kPartitionCount / 2).offset_tso(),
40000 + kPartitionCount / 2);
EXPECT_EQ(get_latest_offset(30000 + kPartitionCount - 1).offset_tso(),
40000 + kPartitionCount - 1);
}
TEST_F(MetaServiceTableStreamTest, CommitRejectsStaleAndInvalidOffsets) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-stale-offset");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 99, 120);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
txn_id = begin_target_transaction("consume-backward-offset");
response = consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 90);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
txn_id = begin_target_transaction("consume-beyond-visible-tso");
response = consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 131);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
}
TEST_F(MetaServiceTableStreamTest, CommitValidatesOffsetTsoDomain) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, -1);
put_latest_partition_state(txn.get(), 2002, 9, 140, std::nullopt);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
CommitTxnResponse response =
consume_partition(begin_target_transaction("consume-empty-boundary-existing"), 2001,
TABLE_STREAM_OFFSET_CONSUMED, -1, -1);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2001).offset_tso(), -1);
response = consume_partition(begin_target_transaction("consume-empty-boundary-unknown"), 2002,
TABLE_STREAM_OFFSET_UNKNOWN, 0, -1);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2002).offset_tso(), -1);
for (int64_t invalid_tso : {0, -2}) {
response = consume_partition(
begin_target_transaction("consume-invalid-expected-" + std::to_string(invalid_tso)),
2001, TABLE_STREAM_OFFSET_CONSUMED, invalid_tso, -1);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), -1);
response = consume_partition(
begin_target_transaction("consume-invalid-next-" + std::to_string(invalid_tso)),
2001, TABLE_STREAM_OFFSET_CONSUMED, -1, invalid_tso);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), -1);
}
}
TEST_F(MetaServiceTableStreamTest, CommitUsesCloneEffectiveOffset) {
const std::string source_instance_id = "table_stream_read_state_source";
set_clone_source(source_instance_id, Versionstamp(100, 0));
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
PartitionIndexPB partition_index;
partition_index.set_db_id(identity_.base_db_id());
partition_index.set_table_id(identity_.base_table_id());
txn->put(versioned::partition_index_key({source_instance_id, 2001}),
partition_index.SerializeAsString());
versioned_put(txn.get(), versioned::meta_partition_key({source_instance_id, 2001}),
Versionstamp(41, 0), "");
VersionPB version;
version.set_version(18);
version.set_commit_tso(230);
versioned_put(txn.get(), versioned::partition_version_key({source_instance_id, 2001}),
Versionstamp(42, 0), version.SerializeAsString());
TableStreamOffsetPB inherited_offset;
inherited_offset.set_partition_id(2001);
inherited_offset.set_state(TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING);
inherited_offset.set_offset_tso(200);
versioned_put(txn.get(),
versioned::table_stream_offset_key(
{source_instance_id, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), 2001}),
Versionstamp(43, 0), inherited_offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-inherited-offset-as-unknown");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_UNKNOWN, 0, 220);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
txn_id = begin_target_transaction("consume-inherited-offset");
response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING, 200, 220);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 220);
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string value;
Versionstamp offset_version;
ASSERT_EQ(
versioned_get(txn.get(),
versioned::table_stream_offset_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), 2001}),
&offset_version, &value),
TxnErrorCode::TXN_OK);
TableStreamOffsetPB versioned_offset;
ASSERT_TRUE(versioned_offset.ParseFromString(value));
EXPECT_EQ(versioned_offset.offset_tso(), 220);
OperationLogPB operation_log;
Versionstamp log_version;
ASSERT_EQ(read_operation_log(txn.get(), versioned::log_key({instance_id_}), &log_version,
&operation_log),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(operation_log.has_commit_txn());
ASSERT_EQ(operation_log.commit_txn().table_stream_offset_gc_size(), 1);
const TableStreamPartitionSetPB& offset_gc =
operation_log.commit_txn().table_stream_offset_gc(0);
EXPECT_EQ(offset_gc.identity().SerializeAsString(), identity_.SerializeAsString());
ASSERT_EQ(offset_gc.partition_ids_size(), 1);
EXPECT_EQ(offset_gc.partition_ids(0), 2001);
}
TEST_F(MetaServiceTableStreamTest, CommitRejectsLatestOffsetWithoutVersionedOffset) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_versioned_partition_state(txn.get(), 2001, 8, 130, std::nullopt);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-inconsistent-local-offset");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_UNKNOWN, 0, 120);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
}
TEST_F(MetaServiceTableStreamTest, CommitRejectsDifferentLatestAndVersionedOffsetValues) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_versioned_partition_state(txn.get(), 2001, 8, 130, std::nullopt);
TableStreamOffsetPB latest_offset;
latest_offset.set_partition_id(2001);
latest_offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
latest_offset.set_offset_tso(100);
latest_offset.set_last_consumption_time_ms(1234);
TableStreamOffsetKeyInfo key_info {instance_id_,
identity_.base_db_id(),
identity_.base_table_id(),
identity_.stream_db_id(),
identity_.stream_id(),
2001};
txn->put(table_stream_offset_key(key_info), latest_offset.SerializeAsString());
TableStreamOffsetPB versioned_offset = latest_offset;
versioned_offset.set_last_consumption_time_ms(1235);
versioned_put(txn.get(), versioned::table_stream_offset_key(key_info), Versionstamp(43, 0),
versioned_offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-inconsistent-offset-values");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).last_consumption_time_ms(), 1234);
}
TEST_F(MetaServiceTableStreamTest, CommitRejectsUnsupportedModeAndMissingCommitTso) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
VersionPB version;
version.set_version(8);
txn->put(partition_version_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(), 2001}),
version.SerializeAsString());
TableStreamOffsetPB offset;
offset.set_partition_id(2001);
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(100);
txn->put(table_stream_offset_key({instance_id_, identity_.base_db_id(),
identity_.base_table_id(), identity_.stream_db_id(),
identity_.stream_id(), 2001}),
offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
int64_t txn_id = begin_target_transaction("consume-missing-visible-tso");
CommitTxnResponse response =
consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 110);
EXPECT_EQ(response.status().code(), MetaServiceCode::VERSION_NOT_FOUND);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
set_multi_version_status(MULTI_VERSION_ENABLED);
txn_id = begin_target_transaction("consume-enabled-mode");
response = consume_partition(txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 110);
EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT);
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 100);
}
TEST_F(MetaServiceTableStreamTest, CommitRejectsUnsupportedTxnModesAndForcesImmediate) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
put_latest_partition_state(txn.get(), 2001, 8, 130, 100);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
CommitTxnRequest request = make_consume_request(begin_target_transaction("consume-2pc"), 2001,
TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
request.set_is_2pc(true);
EXPECT_EQ(commit_transaction(request).status().code(), MetaServiceCode::INVALID_ARGUMENT);
request = make_consume_request(begin_target_transaction("consume-txn-load"), 2001,
TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
request.set_is_txn_load(true);
EXPECT_EQ(commit_transaction(request).status().code(), MetaServiceCode::INVALID_ARGUMENT);
request = make_consume_request(begin_target_transaction("consume-sub-txn"), 2001,
TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
request.add_sub_txn_infos()->set_sub_txn_id(1);
EXPECT_EQ(commit_transaction(request).status().code(), MetaServiceCode::INVALID_ARGUMENT);
int old_fuzzy_possibility = config::cloud_txn_lazy_commit_fuzzy_possibility;
config::cloud_txn_lazy_commit_fuzzy_possibility = 100;
request = make_consume_request(begin_target_transaction("consume-immediate"), 2001,
TABLE_STREAM_OFFSET_CONSUMED, 100, 120);
request.set_enable_txn_lazy_commit(true);
CommitTxnResponse response = commit_transaction(request);
config::cloud_txn_lazy_commit_fuzzy_possibility = old_fuzzy_possibility;
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
EXPECT_EQ(get_latest_offset(2001).offset_tso(), 120);
}
TEST_F(MetaServiceTableStreamTest, DropWritesTypedRecycleIndexInDisabledMode) {
set_multi_version_status(MULTI_VERSION_DISABLED);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
IndexRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.add_index_ids(identity_.stream_id());
request.set_db_id(identity_.base_db_id());
request.set_table_id(identity_.base_table_id());
request.set_object_type(IndexObjectTypePB::TABLE_STREAM);
request.set_stream_db_id(identity_.stream_db_id());
request.set_expiration(0);
IndexResponse response;
brpc::Controller controller;
service_->drop_index(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string value;
ASSERT_EQ(txn->get(recycle_index_key({instance_id_, identity_.stream_id()}), &value),
TxnErrorCode::TXN_OK);
RecycleIndexPB recycle_index;
ASSERT_TRUE(recycle_index.ParseFromString(value));
EXPECT_EQ(recycle_index.state(), RecycleIndexPB::DROPPED);
EXPECT_EQ(recycle_index.object_type(), IndexObjectTypePB::TABLE_STREAM);
EXPECT_EQ(recycle_index.db_id(), identity_.base_db_id());
EXPECT_EQ(recycle_index.table_id(), identity_.base_table_id());
EXPECT_EQ(recycle_index.stream_db_id(), identity_.stream_db_id());
}
TEST_F(MetaServiceTableStreamTest, DropWritesTypedOperationLogInVersionedMode) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
IndexRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.add_index_ids(identity_.stream_id());
request.set_db_id(identity_.base_db_id());
request.set_table_id(identity_.base_table_id());
request.set_object_type(IndexObjectTypePB::TABLE_STREAM);
request.set_stream_db_id(identity_.stream_db_id());
request.set_expiration(0);
IndexResponse response;
brpc::Controller controller;
service_->drop_index(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
OperationLogPB operation_log;
Versionstamp log_version;
ASSERT_EQ(read_operation_log(txn.get(), versioned::log_key({instance_id_}), &log_version,
&operation_log),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(operation_log.has_drop_index());
ASSERT_TRUE(operation_log.has_min_timestamp());
EXPECT_EQ(operation_log.min_timestamp(),
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
const DropIndexLogPB& drop_index = operation_log.drop_index();
EXPECT_EQ(drop_index.object_type(), IndexObjectTypePB::TABLE_STREAM);
EXPECT_EQ(drop_index.db_id(), identity_.base_db_id());
EXPECT_EQ(drop_index.table_id(), identity_.base_table_id());
EXPECT_EQ(drop_index.stream_db_id(), identity_.stream_db_id());
ASSERT_EQ(drop_index.index_ids_size(), 1);
EXPECT_EQ(drop_index.index_ids(0), identity_.stream_id());
std::string value;
EXPECT_EQ(txn->get(recycle_index_key({instance_id_, identity_.stream_id()}), &value, true),
TxnErrorCode::TXN_KEY_NOT_FOUND);
}
TEST_F(MetaServiceTableStreamTest, DropUsesEarliestLocalVersionedOffset) {
const std::string source_instance_id = "table_stream_drop_source";
const Versionstamp source_version =
put_auto_versioned_offset(source_instance_id, identity_, 2001, 10);
set_clone_source(source_instance_id, Versionstamp(source_version.version() + 1, 0));
// Partition 2002 sorts after partition 2001 but contains the earliest local version.
const Versionstamp min_local_version =
put_auto_versioned_offset(instance_id_, identity_, 2002, 30);
put_auto_versioned_offset(instance_id_, identity_, 2001, 10);
put_auto_versioned_offset(instance_id_, identity_, 2001, 20);
IndexRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.add_index_ids(identity_.stream_id());
request.set_db_id(identity_.base_db_id());
request.set_table_id(identity_.base_table_id());
request.set_object_type(IndexObjectTypePB::TABLE_STREAM);
request.set_stream_db_id(identity_.stream_db_id());
request.set_expiration(0);
IndexResponse response;
brpc::Controller controller;
service_->drop_index(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
OperationLogPB operation_log;
Versionstamp log_version;
ASSERT_EQ(read_operation_log(txn.get(), versioned::log_key({instance_id_}), &log_version,
&operation_log),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(operation_log.has_drop_index());
ASSERT_TRUE(operation_log.has_min_timestamp());
EXPECT_LT(source_version.version(), min_local_version.version());
EXPECT_EQ(operation_log.min_timestamp(), min_local_version.version());
EXPECT_LT(operation_log.min_timestamp(), log_version.version());
}
TEST_F(MetaServiceTableStreamTest, DropFindsMinimumInLargeVersionedOffsetHistory) {
set_multi_version_status(MULTI_VERSION_READ_WRITE);
const int default_full_range_batch_size = FullRangeGetOptions().batch_limit;
std::unique_ptr<Transaction> txn;
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
TableStreamOffsetPB offset;
offset.set_state(TABLE_STREAM_OFFSET_CONSUMED);
offset.set_offset_tso(100);
for (int i = 0; i < default_full_range_batch_size; ++i) {
offset.set_partition_id(2001);
versioned_put(
txn.get(),
versioned::table_stream_offset_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), offset.partition_id()}),
Versionstamp(1000 + i, 0), offset.SerializeAsString());
}
// This key sorts after all partition 2001 history but carries the global minimum version.
offset.set_partition_id(2002);
versioned_put(txn.get(),
versioned::table_stream_offset_key(
{instance_id_, identity_.base_db_id(), identity_.base_table_id(),
identity_.stream_db_id(), identity_.stream_id(), offset.partition_id()}),
Versionstamp(50, 0), offset.SerializeAsString());
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
IndexRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.add_index_ids(identity_.stream_id());
request.set_db_id(identity_.base_db_id());
request.set_table_id(identity_.base_table_id());
request.set_object_type(IndexObjectTypePB::TABLE_STREAM);
request.set_stream_db_id(identity_.stream_db_id());
request.set_expiration(0);
IndexResponse response;
brpc::Controller controller;
service_->drop_index(&controller, &request, &response, nullptr);
ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
OperationLogPB operation_log;
Versionstamp log_version;
ASSERT_EQ(read_operation_log(txn.get(), versioned::log_key({instance_id_}), &log_version,
&operation_log),
TxnErrorCode::TXN_OK);
ASSERT_TRUE(operation_log.has_drop_index());
ASSERT_TRUE(operation_log.has_min_timestamp());
EXPECT_EQ(operation_log.min_timestamp(), 50);
}
TEST_F(MetaServiceTableStreamTest, DropAndRecycleCloneChildOffsets) {
const bool old_force_immediate_recycle = config::force_immediate_recycle;
config::force_immediate_recycle = true;
DORIS_CLOUD_DEFER {
config::force_immediate_recycle = old_force_immediate_recycle;
};
const std::string source_instance_id = "table_stream_drop_recycle_source";
TableStreamIdentityPB local_identity = identity_;
TableStreamIdentityPB inherited_identity = identity_;
inherited_identity.set_stream_id(identity_.stream_id() + 1);
put_auto_versioned_offset(source_instance_id, local_identity, 2001, 90);
const Versionstamp source_version =
put_auto_versioned_offset(source_instance_id, inherited_identity, 2001, 80);
const Versionstamp source_snapshot = Versionstamp::next(source_version);
set_clone_source(source_instance_id, source_snapshot);
put_auto_versioned_offset(instance_id_, local_identity, 2001, 100, true);
auto drop_stream = [&](const TableStreamIdentityPB& identity) {
IndexRequest request;
request.set_cloud_unique_id(cloud_unique_id_);
request.add_index_ids(identity.stream_id());
request.set_db_id(identity.base_db_id());
request.set_table_id(identity.base_table_id());
request.set_object_type(IndexObjectTypePB::TABLE_STREAM);
request.set_stream_db_id(identity.stream_db_id());
request.set_expiration(0);
IndexResponse response;
brpc::Controller controller;
service_->drop_index(&controller, &request, &response, nullptr);
EXPECT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg();
};
drop_stream(local_identity);
drop_stream(inherited_identity);
InstanceInfoPB child_instance;
child_instance.set_instance_id(instance_id_);
child_instance.set_status(InstanceInfoPB::NORMAL);
child_instance.set_multi_version_status(MULTI_VERSION_READ_WRITE);
child_instance.set_source_instance_id(source_instance_id);
child_instance.set_source_snapshot_id(SnapshotManager::serialize_snapshot_id(source_snapshot));
InstanceRecycler recycler(service_->txn_kv(), child_instance, RecyclerThreadPoolGroup {},
std::make_shared<TxnLazyCommitter>(service_->txn_kv()));
ASSERT_EQ(recycler.init(), 0);
ASSERT_EQ(recycler.recycle_operation_logs(), 0);
auto key_exists = [&](std::string_view key) {
std::unique_ptr<Transaction> txn;
EXPECT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string value;
return txn->get(key, &value, true) == TxnErrorCode::TXN_OK;
};
EXPECT_TRUE(key_exists(recycle_index_key({instance_id_, local_identity.stream_id()})));
EXPECT_TRUE(key_exists(recycle_index_key({instance_id_, inherited_identity.stream_id()})));
ASSERT_EQ(recycler.recycle_indexes(), 0);
const auto offset_count = [&](const std::string& prefix) {
std::unique_ptr<Transaction> txn;
EXPECT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::unique_ptr<RangeGetIterator> iter;
EXPECT_EQ(txn->get(prefix, lexical_end(prefix), &iter, true, 0), TxnErrorCode::TXN_OK);
return iter->size();
};
EXPECT_EQ(offset_count(table_stream_offset_key_prefix(
instance_id_, local_identity.base_db_id(), local_identity.base_table_id(),
local_identity.stream_db_id(), local_identity.stream_id())),
0);
EXPECT_EQ(offset_count(versioned::table_stream_offset_key_prefix(
instance_id_, local_identity.base_db_id(), local_identity.base_table_id(),
local_identity.stream_db_id(), local_identity.stream_id())),
0);
EXPECT_EQ(offset_count(versioned::table_stream_offset_key_prefix(
instance_id_, inherited_identity.base_db_id(),
inherited_identity.base_table_id(), inherited_identity.stream_db_id(),
inherited_identity.stream_id())),
0);
EXPECT_FALSE(key_exists(recycle_index_key({instance_id_, local_identity.stream_id()})));
EXPECT_FALSE(key_exists(recycle_index_key({instance_id_, inherited_identity.stream_id()})));
EXPECT_GT(
offset_count(versioned::table_stream_offset_key_prefix(
source_instance_id, local_identity.base_db_id(), local_identity.base_table_id(),
local_identity.stream_db_id(), local_identity.stream_id())),
0);
EXPECT_GT(offset_count(versioned::table_stream_offset_key_prefix(
source_instance_id, inherited_identity.base_db_id(),
inherited_identity.base_table_id(), inherited_identity.stream_db_id(),
inherited_identity.stream_id())),
0);
}
} // namespace doris::cloud