| // 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 <chrono> | |
| #include <condition_variable> | |
| #include <cstdint> | |
| #include <initializer_list> | |
| #include <limits> | |
| #include <memory> | |
| #include <mutex> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <thread> | |
| #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(MetaServiceProxy* service, | |
| 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(const std::vector<int64_t>& partitions) { | |
| return get_read_state(service_.get(), partitions); | |
| } | |
| 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; | |
| } | |
| std::pair<CommitTxnResponse, CommitTxnResponse> commit_concurrently( | |
| const CommitTxnRequest& first_request, const CommitTxnRequest& second_request) { | |
| std::mutex mutex; | |
| std::condition_variable condition; | |
| int commits_ready = 0; | |
| bool release_commits = false; | |
| auto* sync_point = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| sync_point->clear_all_call_backs(); | |
| sync_point->clear_trace(); | |
| sync_point->disable_processing(); | |
| }; | |
| sync_point->set_call_back("commit_txn_immediately::before_commit", [&](auto&&) { | |
| std::unique_lock lock(mutex); | |
| ++commits_ready; | |
| condition.notify_all(); | |
| condition.wait(lock, [&] { return release_commits; }); | |
| }); | |
| sync_point->enable_processing(); | |
| CommitTxnResponse first_response; | |
| CommitTxnResponse second_response; | |
| std::thread first_thread([&] { first_response = commit_transaction(first_request); }); | |
| std::thread second_thread([&] { second_response = commit_transaction(second_request); }); | |
| bool both_commits_ready = false; | |
| { | |
| std::unique_lock lock(mutex); | |
| both_commits_ready = condition.wait_for(lock, std::chrono::seconds(10), | |
| [&] { return commits_ready == 2; }); | |
| release_commits = true; | |
| condition.notify_all(); | |
| } | |
| first_thread.join(); | |
| second_thread.join(); | |
| EXPECT_TRUE(both_commits_ready); | |
| EXPECT_EQ(commits_ready, 2); | |
| return {std::move(first_response), std::move(second_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); | |
| } | |
| size_t count_keys(std::string_view begin, std::string_view end) { | |
| 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(begin, end, &iter, true, 0), TxnErrorCode::TXN_OK); | |
| return iter->size(); | |
| } | |
| std::vector<std::pair<std::string, std::string>> read_keys(std::string_view begin, | |
| std::string_view end) { | |
| std::unique_ptr<Transaction> txn; | |
| EXPECT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| FullRangeGetOptions options; | |
| options.txn = txn.get(); | |
| auto iter = service_->txn_kv()->full_range_get(std::string(begin), std::string(end), | |
| std::move(options)); | |
| std::vector<std::pair<std::string, std::string>> entries; | |
| for (auto kv = iter->next(); kv.has_value(); kv = iter->next()) { | |
| entries.emplace_back(kv->first, kv->second); | |
| } | |
| EXPECT_TRUE(iter->is_valid()); | |
| return entries; | |
| } | |
| std::unique_ptr<MetaServiceProxy> restart_meta_service() { | |
| const std::shared_ptr<TxnKv> txn_kv = service_->txn_kv(); | |
| auto resource_manager = std::make_shared<ResourceManager>(txn_kv); | |
| auto [code, message] = resource_manager->refresh_instance(instance_id_); | |
| EXPECT_EQ(code, MetaServiceCode::OK) << message; | |
| auto rate_limiter = std::make_shared<RateLimiter>(); | |
| auto snapshot_manager = std::make_shared<SnapshotManager>(txn_kv); | |
| auto restarted_impl = std::make_unique<MetaServiceImpl>(txn_kv, resource_manager, | |
| rate_limiter, snapshot_manager); | |
| return std::make_unique<MetaServiceProxy>(std::move(restarted_impl)); | |
| } | |
| 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(versioned::TableStreamOffsetKeyInfo(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, ReadVersionAndOffsetFromOneSnapshot) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kPartitionId = 2001; | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kPartitionId, 8, 130, 100); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| bool updated = 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_read_versions", [&](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(), kPartitionId, 9, 140, 120); | |
| ASSERT_EQ(update_txn->commit(), TxnErrorCode::TXN_OK); | |
| updated = true; | |
| }); | |
| sync_point->enable_processing(); | |
| const GetTableStreamOffsetResponse response = get_read_state({kPartitionId}); | |
| ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg(); | |
| ASSERT_TRUE(updated); | |
| 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(), 8); | |
| EXPECT_EQ(state.end_tso(), 130); | |
| EXPECT_EQ(state.offset_tso(), 100); | |
| sync_point->disable_processing(); | |
| sync_point->clear_all_call_backs(); | |
| const GetTableStreamOffsetResponse next_response = get_read_state({kPartitionId}); | |
| ASSERT_EQ(next_response.status().code(), MetaServiceCode::OK) << next_response.status().msg(); | |
| const TableStreamPartitionReadStatePB& next_state = | |
| next_response.bindings(0).partition_states(0); | |
| EXPECT_EQ(next_state.visible_version(), 9); | |
| EXPECT_EQ(next_state.end_tso(), 140); | |
| EXPECT_EQ(next_state.offset_tso(), 120); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, ReadStateSurvivesMetaServiceRestartAndDoesNotWriteKv) { | |
| 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); | |
| const std::string all_keys_begin(1, '\x00'); | |
| const std::string all_keys_end(1, '\xff'); | |
| const auto keys_before = read_keys(all_keys_begin, all_keys_end); | |
| const GetTableStreamOffsetResponse first_response = get_read_state({2001, 2002}); | |
| ASSERT_EQ(first_response.status().code(), MetaServiceCode::OK) << first_response.status().msg(); | |
| EXPECT_EQ(read_keys(all_keys_begin, all_keys_end), keys_before); | |
| const std::shared_ptr<TxnKv> txn_kv = service_->txn_kv(); | |
| auto resource_manager = std::make_shared<ResourceManager>(txn_kv); | |
| auto [code, message] = resource_manager->refresh_instance(instance_id_); | |
| ASSERT_EQ(code, MetaServiceCode::OK) << message; | |
| auto rate_limiter = std::make_shared<RateLimiter>(); | |
| auto snapshot_manager = std::make_shared<SnapshotManager>(txn_kv); | |
| auto restarted_impl = std::make_unique<MetaServiceImpl>(txn_kv, resource_manager, rate_limiter, | |
| snapshot_manager); | |
| auto restarted_service = std::make_unique<MetaServiceProxy>(std::move(restarted_impl)); | |
| const GetTableStreamOffsetResponse restarted_response = | |
| get_read_state(restarted_service.get(), {2001, 2002}); | |
| ASSERT_EQ(restarted_response.status().code(), MetaServiceCode::OK) | |
| << restarted_response.status().msg(); | |
| EXPECT_EQ(restarted_response.SerializeAsString(), first_response.SerializeAsString()); | |
| EXPECT_EQ(read_keys(all_keys_begin, all_keys_end), keys_before); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, CreateResumesAcrossMetaServiceRestarts) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kFirstPartitionId = 2001; | |
| constexpr int64_t kSecondPartitionId = 2002; | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kFirstPartitionId, 8, 130, std::nullopt); | |
| put_latest_partition_state(txn.get(), kSecondPartitionId, 9, 140, std::nullopt); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| IndexRequest index_request; | |
| index_request.set_cloud_unique_id(cloud_unique_id_); | |
| index_request.set_db_id(identity_.base_db_id()); | |
| index_request.set_table_id(identity_.base_table_id()); | |
| index_request.add_index_ids(identity_.stream_id()); | |
| index_request.set_object_type(IndexObjectTypePB::TABLE_STREAM); | |
| index_request.set_stream_db_id(identity_.stream_db_id()); | |
| index_request.set_expiration(::time(nullptr) + 3600); | |
| IndexResponse index_response; | |
| brpc::Controller prepare_controller; | |
| service_->prepare_index(&prepare_controller, &index_request, &index_response, nullptr); | |
| ASSERT_EQ(index_response.status().code(), MetaServiceCode::OK) << index_response.status().msg(); | |
| auto make_partition_request = [&](int64_t partition_id, int64_t offset_tso) { | |
| PartitionRequest 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.add_index_ids(identity_.stream_id()); | |
| request.set_object_type(IndexObjectTypePB::TABLE_STREAM); | |
| request.set_stream_db_id(identity_.stream_db_id()); | |
| request.add_partition_ids(partition_id); | |
| TableStreamOffsetPB* offset = request.add_table_stream_offsets(); | |
| offset->set_partition_id(partition_id); | |
| offset->set_state(TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING); | |
| offset->set_offset_tso(offset_tso); | |
| return request; | |
| }; | |
| const PartitionRequest first_partition_request = make_partition_request(kFirstPartitionId, 100); | |
| const PartitionRequest second_partition_request = | |
| make_partition_request(kSecondPartitionId, 110); | |
| auto restarted_after_prepare = restart_meta_service(); | |
| PartitionResponse partition_response; | |
| brpc::Controller first_partition_controller; | |
| restarted_after_prepare->commit_partition(&first_partition_controller, &first_partition_request, | |
| &partition_response, nullptr); | |
| ASSERT_EQ(partition_response.status().code(), MetaServiceCode::OK) | |
| << partition_response.status().msg(); | |
| auto restarted_after_partial_commit = restart_meta_service(); | |
| partition_response.Clear(); | |
| brpc::Controller retry_partition_controller; | |
| restarted_after_partial_commit->commit_partition( | |
| &retry_partition_controller, &first_partition_request, &partition_response, nullptr); | |
| ASSERT_EQ(partition_response.status().code(), MetaServiceCode::OK) | |
| << partition_response.status().msg(); | |
| partition_response.Clear(); | |
| brpc::Controller second_partition_controller; | |
| restarted_after_partial_commit->commit_partition( | |
| &second_partition_controller, &second_partition_request, &partition_response, nullptr); | |
| ASSERT_EQ(partition_response.status().code(), MetaServiceCode::OK) | |
| << partition_response.status().msg(); | |
| index_response.Clear(); | |
| brpc::Controller commit_index_controller; | |
| restarted_after_partial_commit->commit_index(&commit_index_controller, &index_request, | |
| &index_response, nullptr); | |
| ASSERT_EQ(index_response.status().code(), MetaServiceCode::OK) << index_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kFirstPartitionId).offset_tso(), 100); | |
| EXPECT_EQ(get_latest_offset(kSecondPartitionId).offset_tso(), 110); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string value; | |
| EXPECT_EQ(txn->get(recycle_index_key({instance_id_, identity_.stream_id()}), &value, true), | |
| TxnErrorCode::TXN_KEY_NOT_FOUND); | |
| Versionstamp offset_version; | |
| EXPECT_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(), kFirstPartitionId}), | |
| &offset_version, &value), | |
| TxnErrorCode::TXN_KEY_NOT_FOUND); | |
| } | |
| 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, | |
| ReadVersionedStateFromCloneChainInBatchSurvivesRestartAndDoesNotWriteKv) { | |
| 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); | |
| const std::string all_keys_begin(1, '\x00'); | |
| const std::string all_keys_end(1, '\xff'); | |
| const auto keys_before = read_keys(all_keys_begin, all_keys_end); | |
| const GetTableStreamOffsetResponse response = get_read_state(partition_ids); | |
| ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg(); | |
| ASSERT_EQ(response.bindings_size(), 1); | |
| 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); | |
| EXPECT_EQ(read_keys(all_keys_begin, all_keys_end), keys_before); | |
| auto restarted_service = restart_meta_service(); | |
| const GetTableStreamOffsetResponse restarted_response = | |
| get_read_state(restarted_service.get(), partition_ids); | |
| ASSERT_EQ(restarted_response.status().code(), MetaServiceCode::OK) | |
| << restarted_response.status().msg(); | |
| EXPECT_EQ(restarted_response.SerializeAsString(), response.SerializeAsString()); | |
| EXPECT_EQ(read_keys(all_keys_begin, all_keys_end), keys_before); | |
| } | |
| 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, SourcePublishDoesNotConflictWithConsumptionCommit) { | |
| 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); | |
| const int64_t consume_txn_id = begin_target_transaction("consume-during-source-publish"); | |
| stage_target_rowset(consume_txn_id, kTargetPartitionId, kTargetTabletId); | |
| CommitTxnRequest consume_request = make_consume_request(consume_txn_id, kSourcePartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| consume_request.set_commit_tso(999); | |
| std::mutex mutex; | |
| std::condition_variable condition; | |
| bool consume_ready = false; | |
| bool release_consume = false; | |
| auto* sync_point = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| { | |
| std::lock_guard lock(mutex); | |
| release_consume = true; | |
| } | |
| condition.notify_all(); | |
| sync_point->disable_processing(); | |
| sync_point->clear_all_call_backs(); | |
| }; | |
| sync_point->set_call_back("commit_txn_immediately::before_commit", [&](auto&&) { | |
| std::unique_lock lock(mutex); | |
| consume_ready = true; | |
| condition.notify_all(); | |
| condition.wait(lock, [&] { return release_consume; }); | |
| }); | |
| sync_point->enable_processing(); | |
| CommitTxnResponse consume_response; | |
| std::thread consume_thread([&] { consume_response = commit_transaction(consume_request); }); | |
| bool reached_commit = false; | |
| { | |
| std::unique_lock lock(mutex); | |
| reached_commit = | |
| condition.wait_for(lock, std::chrono::seconds(10), [&] { return consume_ready; }); | |
| } | |
| TxnErrorCode create_publish_txn = TxnErrorCode::TXN_OK; | |
| TxnErrorCode commit_publish_txn = TxnErrorCode::TXN_OK; | |
| if (reached_commit) { | |
| create_publish_txn = service_->txn_kv()->create_txn(&txn); | |
| if (create_publish_txn == TxnErrorCode::TXN_OK) { | |
| VersionPB published_version; | |
| published_version.set_version(9); | |
| published_version.set_commit_tso(160); | |
| txn->put(partition_version_key({instance_id_, identity_.base_db_id(), | |
| identity_.base_table_id(), kSourcePartitionId}), | |
| published_version.SerializeAsString()); | |
| commit_publish_txn = txn->commit(); | |
| } | |
| } | |
| { | |
| std::lock_guard lock(mutex); | |
| release_consume = true; | |
| } | |
| condition.notify_all(); | |
| consume_thread.join(); | |
| ASSERT_TRUE(reached_commit); | |
| ASSERT_EQ(create_publish_txn, TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(commit_publish_txn, TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(consume_response.status().code(), MetaServiceCode::OK) | |
| << consume_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 120); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string value; | |
| ASSERT_EQ(txn->get(partition_version_key({instance_id_, identity_.base_db_id(), | |
| identity_.base_table_id(), kSourcePartitionId}), | |
| &value), | |
| TxnErrorCode::TXN_OK); | |
| VersionPB source_version; | |
| ASSERT_TRUE(source_version.ParseFromString(value)); | |
| EXPECT_EQ(source_version.version(), 9); | |
| EXPECT_EQ(source_version.commit_tso(), 160); | |
| EXPECT_EQ( | |
| txn->get(meta_rowset_tmp_key({instance_id_, consume_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(), consume_txn_id); | |
| 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(), 2); | |
| EXPECT_EQ(target_version.commit_tso(), 999); | |
| ASSERT_EQ(txn->get(txn_info_key({instance_id_, kTargetDbId, consume_txn_id}), &value), | |
| TxnErrorCode::TXN_OK); | |
| TxnInfoPB txn_info; | |
| ASSERT_TRUE(txn_info.ParseFromString(value)); | |
| EXPECT_EQ(txn_info.status(), TXN_STATUS_VISIBLE); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, CommitRetryAfterMetaServiceRestartIsExactlyOnce) { | |
| 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); | |
| const int64_t txn_id = begin_target_transaction("consume-retry-after-ms-restart"); | |
| stage_target_rowset(txn_id, kTargetPartitionId, kTargetTabletId); | |
| CommitTxnRequest request = make_consume_request(txn_id, kSourcePartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| request.set_commit_tso(999); | |
| // The commit succeeds, but its response is lost before the caller observes it. | |
| const CommitTxnResponse lost_response = commit_transaction(request); | |
| ASSERT_EQ(lost_response.status().code(), MetaServiceCode::OK) << lost_response.status().msg(); | |
| auto restarted_service = restart_meta_service(); | |
| CommitTxnResponse retry_response; | |
| brpc::Controller retry_controller; | |
| restarted_service->commit_txn(&retry_controller, &request, &retry_response, nullptr); | |
| ASSERT_EQ(retry_response.status().code(), MetaServiceCode::OK) << retry_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 120); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string value; | |
| EXPECT_EQ(txn->get(meta_rowset_tmp_key({instance_id_, 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(), txn_id); | |
| EXPECT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 3}), &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(), 2); | |
| EXPECT_EQ(target_version.commit_tso(), 999); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, CommitRejectsRecyclingStreamBeforePublishingRowsetAndOffset) { | |
| 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); | |
| const int64_t txn_id = begin_target_transaction("consume-recycling-stream"); | |
| stage_target_rowset(txn_id, kTargetPartitionId, kTargetTabletId); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| RecycleIndexPB recycle_index; | |
| recycle_index.set_db_id(identity_.base_db_id()); | |
| recycle_index.set_table_id(identity_.base_table_id()); | |
| recycle_index.set_stream_db_id(identity_.stream_db_id()); | |
| recycle_index.set_state(RecycleIndexPB::RECYCLING); | |
| recycle_index.set_object_type(IndexObjectTypePB::TABLE_STREAM); | |
| txn->put(recycle_index_key({instance_id_, identity_.stream_id()}), | |
| recycle_index.SerializeAsString()); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| const CommitTxnRequest request = make_consume_request(txn_id, kSourcePartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| const CommitTxnResponse response = commit_transaction(request); | |
| EXPECT_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_, 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); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, DropPartitionConflictsWithInFlightConsumption) { | |
| 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); | |
| const int64_t txn_id = begin_target_transaction("consume-while-dropping-source-partition"); | |
| stage_target_rowset(txn_id, kTargetPartitionId, kTargetTabletId); | |
| const CommitTxnRequest consume_request = make_consume_request( | |
| txn_id, kSourcePartitionId, TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| std::mutex mutex; | |
| std::condition_variable condition; | |
| bool commit_ready = false; | |
| bool release_commit = false; | |
| auto* sync_point = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| { | |
| std::lock_guard lock(mutex); | |
| release_commit = true; | |
| } | |
| condition.notify_all(); | |
| sync_point->disable_processing(); | |
| sync_point->clear_all_call_backs(); | |
| }; | |
| sync_point->set_call_back("commit_txn_immediately::before_commit", [&](auto&&) { | |
| std::unique_lock lock(mutex); | |
| commit_ready = true; | |
| condition.notify_all(); | |
| condition.wait(lock, [&] { return release_commit; }); | |
| }); | |
| sync_point->enable_processing(); | |
| CommitTxnResponse consume_response; | |
| std::thread consume_thread([&] { consume_response = commit_transaction(consume_request); }); | |
| bool reached_commit = false; | |
| { | |
| std::unique_lock lock(mutex); | |
| reached_commit = | |
| condition.wait_for(lock, std::chrono::seconds(10), [&] { return commit_ready; }); | |
| } | |
| PartitionRequest drop_request; | |
| PartitionResponse drop_response; | |
| if (reached_commit) { | |
| drop_request.set_cloud_unique_id(cloud_unique_id_); | |
| drop_request.set_db_id(identity_.base_db_id()); | |
| drop_request.set_table_id(identity_.base_table_id()); | |
| drop_request.add_index_ids(identity_.base_table_id() + 1); | |
| drop_request.add_partition_ids(kSourcePartitionId); | |
| drop_request.mutable_table_streams()->Add()->CopyFrom(identity_); | |
| drop_request.set_expiration(::time(nullptr) + 3600); | |
| brpc::Controller drop_controller; | |
| service_->drop_partition(&drop_controller, &drop_request, &drop_response, nullptr); | |
| } | |
| { | |
| std::lock_guard lock(mutex); | |
| release_commit = true; | |
| } | |
| condition.notify_all(); | |
| consume_thread.join(); | |
| ASSERT_TRUE(reached_commit); | |
| ASSERT_EQ(drop_response.status().code(), MetaServiceCode::OK) << drop_response.status().msg(); | |
| EXPECT_EQ(consume_response.status().code(), MetaServiceCode::KV_TXN_CONFLICT) | |
| << consume_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kSourcePartitionId).offset_tso(), 100); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string value; | |
| ASSERT_EQ(txn->get(recycle_partition_key({instance_id_, kSourcePartitionId}), &value), | |
| TxnErrorCode::TXN_OK); | |
| RecyclePartitionPB recycle_partition; | |
| ASSERT_TRUE(recycle_partition.ParseFromString(value)); | |
| EXPECT_EQ(recycle_partition.state(), RecyclePartitionPB::DROPPED); | |
| EXPECT_EQ(txn->get(meta_rowset_tmp_key({instance_id_, 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); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, ConcurrentOffsetCasAllowsOnlyOneCommit) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kPartitionId = 2001; | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kPartitionId, 8, 130, 100); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| const CommitTxnRequest first_request = | |
| make_consume_request(begin_target_transaction("concurrent-cas-first"), kPartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| const CommitTxnRequest second_request = | |
| make_consume_request(begin_target_transaction("concurrent-cas-second"), kPartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 125); | |
| auto [first_response, second_response] = commit_concurrently(first_request, second_request); | |
| const int successful_commits = (first_response.status().code() == MetaServiceCode::OK ? 1 : 0) + | |
| (second_response.status().code() == MetaServiceCode::OK ? 1 : 0); | |
| const int conflicted_commits = | |
| (first_response.status().code() == MetaServiceCode::KV_TXN_CONFLICT ? 1 : 0) + | |
| (second_response.status().code() == MetaServiceCode::KV_TXN_CONFLICT ? 1 : 0); | |
| EXPECT_EQ(successful_commits, 1); | |
| EXPECT_EQ(conflicted_commits, 1); | |
| const int64_t final_offset = get_latest_offset(kPartitionId).offset_tso(); | |
| EXPECT_TRUE(final_offset == 120 || final_offset == 125); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, ConcurrentUnknownOffsetCasAllowsOnlyOneFirstConsumer) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kPartitionId = 2001; | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kPartitionId, 8, 130, std::nullopt); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| const CommitTxnRequest first_request = | |
| make_consume_request(begin_target_transaction("concurrent-unknown-first"), kPartitionId, | |
| TABLE_STREAM_OFFSET_UNKNOWN, 0, 120); | |
| const CommitTxnRequest second_request = | |
| make_consume_request(begin_target_transaction("concurrent-unknown-second"), | |
| kPartitionId, TABLE_STREAM_OFFSET_UNKNOWN, 0, 125); | |
| auto [first_response, second_response] = commit_concurrently(first_request, second_request); | |
| const int successful_commits = (first_response.status().code() == MetaServiceCode::OK ? 1 : 0) + | |
| (second_response.status().code() == MetaServiceCode::OK ? 1 : 0); | |
| const int conflicted_commits = | |
| (first_response.status().code() == MetaServiceCode::KV_TXN_CONFLICT ? 1 : 0) + | |
| (second_response.status().code() == MetaServiceCode::KV_TXN_CONFLICT ? 1 : 0); | |
| EXPECT_EQ(successful_commits, 1); | |
| EXPECT_EQ(conflicted_commits, 1); | |
| const int64_t final_offset = get_latest_offset(kPartitionId).offset_tso(); | |
| EXPECT_TRUE(final_offset == 120 || final_offset == 125); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, ConcurrentIndependentOffsetUpdatesDoNotConflict) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kSharedPartitionId = 2101; | |
| constexpr int64_t kFirstPartitionId = 2201; | |
| constexpr int64_t kSecondPartitionId = 2202; | |
| TableStreamIdentityPB second_identity = identity_; | |
| second_identity.set_stream_db_id(identity_.stream_db_id() + 1); | |
| second_identity.set_stream_id(identity_.stream_id() + 1); | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kSharedPartitionId, 8, 130, std::nullopt); | |
| put_latest_partition_state(txn.get(), kFirstPartitionId, 9, 140, std::nullopt); | |
| put_latest_partition_state(txn.get(), kSecondPartitionId, 10, 150, std::nullopt); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| CommitTxnRequest first_request = | |
| make_consume_request(begin_target_transaction("concurrent-stream-first"), | |
| kSharedPartitionId, TABLE_STREAM_OFFSET_UNKNOWN, 0, 120); | |
| CommitTxnRequest second_request = | |
| make_consume_request(begin_target_transaction("concurrent-stream-second"), | |
| kSharedPartitionId, TABLE_STREAM_OFFSET_UNKNOWN, 0, 125); | |
| second_request.mutable_table_stream_updates(0)->mutable_identity()->CopyFrom(second_identity); | |
| auto [first_stream_response, second_stream_response] = | |
| commit_concurrently(first_request, second_request); | |
| ASSERT_EQ(first_stream_response.status().code(), MetaServiceCode::OK) | |
| << first_stream_response.status().msg(); | |
| ASSERT_EQ(second_stream_response.status().code(), MetaServiceCode::OK) | |
| << second_stream_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(identity_, kSharedPartitionId).offset_tso(), 120); | |
| EXPECT_EQ(get_latest_offset(second_identity, kSharedPartitionId).offset_tso(), 125); | |
| first_request = make_consume_request(begin_target_transaction("concurrent-partition-first"), | |
| kFirstPartitionId, TABLE_STREAM_OFFSET_UNKNOWN, 0, 135); | |
| second_request = make_consume_request(begin_target_transaction("concurrent-partition-second"), | |
| kSecondPartitionId, TABLE_STREAM_OFFSET_UNKNOWN, 0, 145); | |
| auto [first_partition_response, second_partition_response] = | |
| commit_concurrently(first_request, second_request); | |
| ASSERT_EQ(first_partition_response.status().code(), MetaServiceCode::OK) | |
| << first_partition_response.status().msg(); | |
| ASSERT_EQ(second_partition_response.status().code(), MetaServiceCode::OK) | |
| << second_partition_response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kFirstPartitionId).offset_tso(), 135); | |
| EXPECT_EQ(get_latest_offset(kSecondPartitionId).offset_tso(), 145); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, MultiVersionModeChangeConflictsWithInFlightConsumption) { | |
| set_multi_version_status(MULTI_VERSION_DISABLED); | |
| constexpr int64_t kPartitionId = 2001; | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_latest_partition_state(txn.get(), kPartitionId, 8, 130, 100); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| const CommitTxnRequest request = make_consume_request( | |
| begin_target_transaction("consume-while-changing-multi-version-mode"), kPartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| std::mutex mutex; | |
| std::condition_variable condition; | |
| bool commit_ready = false; | |
| bool release_commit = false; | |
| auto* sync_point = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| { | |
| std::lock_guard lock(mutex); | |
| release_commit = true; | |
| } | |
| condition.notify_all(); | |
| sync_point->disable_processing(); | |
| sync_point->clear_all_call_backs(); | |
| }; | |
| sync_point->set_call_back("commit_txn_immediately::before_commit", [&](auto&&) { | |
| std::unique_lock lock(mutex); | |
| commit_ready = true; | |
| condition.notify_all(); | |
| condition.wait(lock, [&] { return release_commit; }); | |
| }); | |
| sync_point->enable_processing(); | |
| CommitTxnResponse response; | |
| std::thread commit_thread([&] { response = commit_transaction(request); }); | |
| bool reached_commit = false; | |
| { | |
| std::unique_lock lock(mutex); | |
| reached_commit = | |
| condition.wait_for(lock, std::chrono::seconds(10), [&] { return commit_ready; }); | |
| } | |
| TxnErrorCode create_mode_txn = TxnErrorCode::TXN_OK; | |
| TxnErrorCode commit_mode_txn = TxnErrorCode::TXN_OK; | |
| if (reached_commit) { | |
| create_mode_txn = service_->txn_kv()->create_txn(&txn); | |
| if (create_mode_txn == TxnErrorCode::TXN_OK) { | |
| InstanceInfoPB instance; | |
| instance.set_instance_id(instance_id_); | |
| instance.set_multi_version_status(MULTI_VERSION_WRITE_ONLY); | |
| txn->put(instance_key({instance_id_}), instance.SerializeAsString()); | |
| commit_mode_txn = txn->commit(); | |
| } | |
| } | |
| { | |
| std::lock_guard lock(mutex); | |
| release_commit = true; | |
| } | |
| condition.notify_all(); | |
| commit_thread.join(); | |
| ASSERT_TRUE(reached_commit); | |
| ASSERT_EQ(create_mode_txn, TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(commit_mode_txn, TxnErrorCode::TXN_OK); | |
| EXPECT_EQ(response.status().code(), MetaServiceCode::KV_TXN_CONFLICT) | |
| << response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(kPartitionId).offset_tso(), 100); | |
| } | |
| 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(versioned::TableStreamOffsetKeyInfo(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); | |
| 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; | |
| create_target_tablet(kTargetDbId, kTargetTableId, kTargetIndexId, kTargetPartitionId, | |
| kTargetTabletId); | |
| const int old_rowset_threshold = config::txn_lazy_commit_rowsets_thresold; | |
| const int old_fuzzy_possibility = config::cloud_txn_lazy_commit_fuzzy_possibility; | |
| const bool old_lazy_commit_enabled = config::enable_cloud_txn_lazy_commit; | |
| config::txn_lazy_commit_rowsets_thresold = 0; | |
| config::cloud_txn_lazy_commit_fuzzy_possibility = 100; | |
| config::enable_cloud_txn_lazy_commit = true; | |
| DORIS_CLOUD_DEFER { | |
| config::txn_lazy_commit_rowsets_thresold = old_rowset_threshold; | |
| config::cloud_txn_lazy_commit_fuzzy_possibility = old_fuzzy_possibility; | |
| config::enable_cloud_txn_lazy_commit = old_lazy_commit_enabled; | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| SyncPoint::get_instance()->clear_trace(); | |
| SyncPoint::get_instance()->disable_processing(); | |
| }; | |
| bool eventually_committed = false; | |
| SyncPoint::get_instance()->set_call_back("commit_txn_eventually::finish", | |
| [&](auto&&) { eventually_committed = true; }); | |
| SyncPoint::get_instance()->enable_processing(); | |
| const int64_t immediate_txn_id = begin_target_transaction("consume-immediate"); | |
| stage_target_rowset(immediate_txn_id, kTargetPartitionId, kTargetTabletId); | |
| request = make_consume_request(immediate_txn_id, 2001, TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| request.set_enable_txn_lazy_commit(true); | |
| request.set_commit_tso(999); | |
| CommitTxnResponse response = commit_transaction(request); | |
| ASSERT_EQ(response.status().code(), MetaServiceCode::OK) << response.status().msg(); | |
| EXPECT_FALSE(response.is_lazy_commit()); | |
| EXPECT_FALSE(eventually_committed); | |
| EXPECT_EQ(get_latest_offset(2001).offset_tso(), 120); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| std::string value; | |
| EXPECT_EQ(txn->get(meta_rowset_tmp_key({instance_id_, immediate_txn_id, kTargetTabletId}), | |
| &value), | |
| TxnErrorCode::TXN_KEY_NOT_FOUND); | |
| EXPECT_EQ(txn->get(meta_rowset_key({instance_id_, kTargetTabletId, 2}), &value), | |
| TxnErrorCode::TXN_OK); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, CommitTxnTooLargeDoesNotFallBackToLazyCommit) { | |
| 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); | |
| const int64_t txn_id = begin_target_transaction("consume-too-large"); | |
| stage_target_rowset(txn_id, kTargetPartitionId, kTargetTabletId); | |
| const int old_rowset_threshold = config::txn_lazy_commit_rowsets_thresold; | |
| const int old_fuzzy_possibility = config::cloud_txn_lazy_commit_fuzzy_possibility; | |
| const bool old_lazy_commit_enabled = config::enable_cloud_txn_lazy_commit; | |
| config::txn_lazy_commit_rowsets_thresold = 0; | |
| config::cloud_txn_lazy_commit_fuzzy_possibility = 100; | |
| config::enable_cloud_txn_lazy_commit = true; | |
| DORIS_CLOUD_DEFER { | |
| config::txn_lazy_commit_rowsets_thresold = old_rowset_threshold; | |
| config::cloud_txn_lazy_commit_fuzzy_possibility = old_fuzzy_possibility; | |
| config::enable_cloud_txn_lazy_commit = old_lazy_commit_enabled; | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| SyncPoint::get_instance()->clear_trace(); | |
| SyncPoint::get_instance()->disable_processing(); | |
| }; | |
| bool immediate_commit_injected = false; | |
| bool eventually_committed = false; | |
| SyncPoint::get_instance()->set_call_back( | |
| "commit_txn_immediately::before_commit", [&](auto&& args) { | |
| auto* err = try_any_cast<TxnErrorCode*>(args[0]); | |
| *err = TxnErrorCode::TXN_BYTES_TOO_LARGE; | |
| auto* code = try_any_cast<MetaServiceCode*>(args[1]); | |
| *code = cast_as<ErrCategory::COMMIT>(*err); | |
| auto* pred = try_any_cast<bool*>(args.back()); | |
| *pred = true; | |
| immediate_commit_injected = true; | |
| }); | |
| SyncPoint::get_instance()->set_call_back("commit_txn_eventually::finish", | |
| [&](auto&&) { eventually_committed = true; }); | |
| SyncPoint::get_instance()->enable_processing(); | |
| CommitTxnRequest request = make_consume_request(txn_id, kSourcePartitionId, | |
| TABLE_STREAM_OFFSET_CONSUMED, 100, 120); | |
| request.set_enable_txn_lazy_commit(true); | |
| request.set_commit_tso(999); | |
| const CommitTxnResponse response = commit_transaction(request); | |
| EXPECT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT); | |
| EXPECT_NE(response.status().msg().find("table stream offset updates cannot use lazy commit"), | |
| std::string::npos); | |
| EXPECT_TRUE(immediate_commit_injected); | |
| EXPECT_FALSE(eventually_committed); | |
| EXPECT_FALSE(response.is_lazy_commit()); | |
| 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_, 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); | |
| } | |
| TEST_F(MetaServiceTableStreamTest, CommitWritesOffsetsForEachMultiVersionMode) { | |
| struct ModeCase { | |
| MultiVersionStatus mode; | |
| const char* label; | |
| bool succeeds; | |
| bool writes_versioned; | |
| }; | |
| const std::vector<ModeCase> mode_cases = { | |
| {MULTI_VERSION_DISABLED, "disabled", true, false}, | |
| {MULTI_VERSION_WRITE_ONLY, "write-only", true, true}, | |
| {MULTI_VERSION_READ_WRITE, "read-write", true, true}, | |
| {MULTI_VERSION_ENABLED, "enabled", false, false}, | |
| }; | |
| for (size_t i = 0; i < mode_cases.size(); ++i) { | |
| const ModeCase& mode_case = mode_cases[i]; | |
| SCOPED_TRACE(mode_case.label); | |
| const int64_t partition_id = 2101 + i; | |
| const std::string log_prefix = versioned::log_key({instance_id_}); | |
| if (mode_case.mode == MULTI_VERSION_READ_WRITE) { | |
| set_multi_version_status(mode_case.mode); | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| put_versioned_partition_state(txn.get(), partition_id, 8, 130, std::nullopt); | |
| TableStreamOffsetPB latest_offset; | |
| latest_offset.set_partition_id(partition_id); | |
| latest_offset.set_state(TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING); | |
| latest_offset.set_offset_tso(100); | |
| const TableStreamOffsetKeyInfo key_info {instance_id_, | |
| identity_.base_db_id(), | |
| identity_.base_table_id(), | |
| identity_.stream_db_id(), | |
| identity_.stream_id(), | |
| partition_id}; | |
| txn->put(table_stream_offset_key(key_info), latest_offset.SerializeAsString()); | |
| versioned_put(txn.get(), | |
| versioned::table_stream_offset_key( | |
| versioned::TableStreamOffsetKeyInfo(key_info)), | |
| latest_offset.SerializeAsString()); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| } else { | |
| 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(), partition_id, 8, 130, 100); | |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); | |
| if (mode_case.mode != MULTI_VERSION_DISABLED) { | |
| set_multi_version_status(mode_case.mode); | |
| } | |
| } | |
| const size_t log_count_before = count_keys(log_prefix, lexical_end(log_prefix)); | |
| const TableStreamOffsetStatePB expected_state = | |
| mode_case.mode == MULTI_VERSION_READ_WRITE | |
| ? TABLE_STREAM_OFFSET_INITIAL_SNAPSHOT_PENDING | |
| : TABLE_STREAM_OFFSET_CONSUMED; | |
| const CommitTxnResponse response = consume_partition( | |
| begin_target_transaction(fmt::format("consume-mode-{}", mode_case.label)), | |
| partition_id, expected_state, 100, 120); | |
| EXPECT_EQ(response.status().code(), | |
| mode_case.succeeds ? MetaServiceCode::OK : MetaServiceCode::INVALID_ARGUMENT) | |
| << response.status().msg(); | |
| EXPECT_EQ(get_latest_offset(partition_id).offset_tso(), mode_case.succeeds ? 120 : 100); | |
| std::unique_ptr<Transaction> txn; | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| Versionstamp value_version; | |
| std::string value; | |
| const TxnErrorCode versioned_err = 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(), partition_id}), | |
| &value_version, &value); | |
| if (mode_case.writes_versioned) { | |
| ASSERT_EQ(versioned_err, TxnErrorCode::TXN_OK); | |
| TableStreamOffsetPB versioned_offset; | |
| ASSERT_TRUE(versioned_offset.ParseFromString(value)); | |
| EXPECT_EQ(versioned_offset.state(), TABLE_STREAM_OFFSET_CONSUMED); | |
| EXPECT_EQ(versioned_offset.offset_tso(), 120); | |
| } else { | |
| EXPECT_EQ(versioned_err, TxnErrorCode::TXN_KEY_NOT_FOUND); | |
| } | |
| EXPECT_EQ(count_keys(log_prefix, lexical_end(log_prefix)), | |
| log_count_before + (mode_case.writes_versioned ? 1 : 0)); | |
| } | |
| } | |
| 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, DropRetryAfterMetaServiceRestartConverges) { | |
| 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; | |
| }; | |
| set_multi_version_status(MULTI_VERSION_READ_WRITE); | |
| constexpr int64_t kPartitionId = 2001; | |
| put_auto_versioned_offset(instance_id_, identity_, kPartitionId, 100, true); | |
| 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); | |
| // The first DROP succeeds, but its response is lost before the caller observes it. | |
| IndexResponse lost_response; | |
| brpc::Controller first_drop_controller; | |
| service_->drop_index(&first_drop_controller, &request, &lost_response, nullptr); | |
| ASSERT_EQ(lost_response.status().code(), MetaServiceCode::OK) << lost_response.status().msg(); | |
| const std::string operation_log_prefix = versioned::log_key({instance_id_}); | |
| const size_t first_drop_log_count = | |
| count_keys(operation_log_prefix, lexical_end(operation_log_prefix)); | |
| ASSERT_GT(first_drop_log_count, 0); | |
| auto restarted_service = restart_meta_service(); | |
| IndexResponse retry_response; | |
| brpc::Controller retry_drop_controller; | |
| restarted_service->drop_index(&retry_drop_controller, &request, &retry_response, nullptr); | |
| ASSERT_EQ(retry_response.status().code(), MetaServiceCode::OK) << retry_response.status().msg(); | |
| EXPECT_GE(count_keys(operation_log_prefix, lexical_end(operation_log_prefix)), | |
| first_drop_log_count); | |
| InstanceInfoPB instance; | |
| instance.set_instance_id(instance_id_); | |
| instance.set_status(InstanceInfoPB::NORMAL); | |
| instance.set_multi_version_status(MULTI_VERSION_READ_WRITE); | |
| InstanceRecycler recycler(service_->txn_kv(), instance, RecyclerThreadPoolGroup {}, | |
| std::make_shared<TxnLazyCommitter>(service_->txn_kv())); | |
| ASSERT_EQ(recycler.init(), 0); | |
| ASSERT_EQ(recycler.recycle_operation_logs(), 0); | |
| EXPECT_EQ(count_keys(operation_log_prefix, lexical_end(operation_log_prefix)), 0); | |
| std::unique_ptr<Transaction> txn; | |
| 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, true), | |
| 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); | |
| ASSERT_EQ(recycler.recycle_indexes(), 0); | |
| ASSERT_EQ(service_->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK); | |
| EXPECT_EQ(txn->get(recycle_index_key({instance_id_, identity_.stream_id()}), &value, true), | |
| TxnErrorCode::TXN_KEY_NOT_FOUND); | |
| const std::string latest_offset_prefix = table_stream_offset_key_prefix( | |
| instance_id_, identity_.base_db_id(), identity_.base_table_id(), | |
| identity_.stream_db_id(), identity_.stream_id()); | |
| const std::string versioned_offset_prefix = versioned::table_stream_offset_key_prefix( | |
| instance_id_, identity_.base_db_id(), identity_.base_table_id(), | |
| identity_.stream_db_id(), identity_.stream_id()); | |
| EXPECT_EQ(count_keys(latest_offset_prefix, lexical_end(latest_offset_prefix)), 0); | |
| EXPECT_EQ(count_keys(versioned_offset_prefix, lexical_end(versioned_offset_prefix)), 0); | |
| } | |
| 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 |