| // 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 <gen_cpp/cloud.pb.h> |
| #include <gtest/gtest.h> |
| |
| #include "cpp/sync_point.h" |
| #include "meta-store/blob_message.h" |
| #include "meta-store/keys.h" |
| #include "meta-store/mem_txn_kv.h" |
| #include "meta-store/versioned_value.h" |
| #include "recycler/checker.h" |
| |
| namespace doris::cloud { |
| |
| class TableStreamCheckerTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| txn_kv_ = std::make_shared<MemTxnKv>(); |
| ASSERT_EQ(txn_kv_->init(), 0); |
| } |
| |
| InstanceInfoPB write_only_instance(std::string_view instance_id) { |
| InstanceInfoPB instance; |
| instance.set_instance_id(std::string(instance_id)); |
| instance.set_multi_version_status(MULTI_VERSION_WRITE_ONLY); |
| return instance; |
| } |
| |
| TableStreamOffsetPB offset(int64_t partition_id, int64_t tso) { |
| TableStreamOffsetPB value; |
| value.set_partition_id(partition_id); |
| value.set_state(TABLE_STREAM_OFFSET_CONSUMED); |
| value.set_offset_tso(tso); |
| value.set_last_consumption_time_ms(1000); |
| return value; |
| } |
| |
| void put_offsets(Transaction* txn, std::string_view instance_id, int64_t base_db_id, |
| int64_t base_table_id, int64_t stream_db_id, int64_t stream_id, |
| int64_t partition_id, const TableStreamOffsetPB& latest, |
| const TableStreamOffsetPB& versioned) { |
| txn->put(table_stream_offset_key({instance_id, base_db_id, base_table_id, stream_db_id, |
| stream_id, partition_id}), |
| latest.SerializeAsString()); |
| versioned_put(txn, |
| versioned::table_stream_offset_key({instance_id, base_db_id, base_table_id, |
| stream_db_id, stream_id, partition_id}), |
| Versionstamp(10, 1), versioned.SerializeAsString()); |
| } |
| |
| std::shared_ptr<MemTxnKv> txn_kv_; |
| }; |
| |
| TEST_F(TableStreamCheckerTest, ConsistentOffsets) { |
| const std::string instance_id = "checker-consistent"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| auto value = offset(5, 100); |
| put_offsets(txn.get(), instance_id, 1, 2, 3, 4, 5, value, value); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| InstanceChecker checker(txn_kv_, instance_id); |
| ASSERT_EQ(checker.init(write_only_instance(instance_id)), 0); |
| EXPECT_EQ(checker.do_table_stream_check(), 0); |
| } |
| |
| TEST_F(TableStreamCheckerTest, DetectsLatestVersionedMismatch) { |
| const std::string instance_id = "checker-offset-mismatch"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| put_offsets(txn.get(), instance_id, 1, 2, 3, 4, 5, offset(5, 100), offset(5, 99)); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| InstanceChecker checker(txn_kv_, instance_id); |
| ASSERT_EQ(checker.init(write_only_instance(instance_id)), 0); |
| EXPECT_EQ(checker.do_table_stream_check(), 1); |
| } |
| |
| TEST_F(TableStreamCheckerTest, RechecksConcurrentProjectionChanges) { |
| const std::string instance_id = "checker-concurrent-offset-update"; |
| constexpr int64_t base_db_id = 1; |
| constexpr int64_t base_table_id = 2; |
| constexpr int64_t stream_db_id = 3; |
| constexpr int64_t stream_id = 4; |
| constexpr int64_t partition_id = 5; |
| constexpr int64_t created_stream_id = 6; |
| constexpr int64_t created_partition_id = 7; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| auto initial = offset(partition_id, 100); |
| put_offsets(txn.get(), instance_id, base_db_id, base_table_id, stream_db_id, stream_id, |
| partition_id, initial, initial); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| bool updated = false; |
| auto* sync_point = SyncPoint::get_instance(); |
| SyncPoint::CallbackGuard guard; |
| sync_point->set_call_back( |
| "InstanceChecker::do_table_stream_check::after_latest_scan", |
| [&](auto&&) { |
| std::unique_ptr<Transaction> update_txn; |
| ASSERT_EQ(txn_kv_->create_txn(&update_txn), TxnErrorCode::TXN_OK); |
| auto current = offset(partition_id, 120); |
| update_txn->put(table_stream_offset_key({instance_id, base_db_id, base_table_id, |
| stream_db_id, stream_id, partition_id}), |
| current.SerializeAsString()); |
| versioned_put( |
| update_txn.get(), |
| versioned::table_stream_offset_key({instance_id, base_db_id, base_table_id, |
| stream_db_id, stream_id, partition_id}), |
| Versionstamp(20, 1), current.SerializeAsString()); |
| auto created = offset(created_partition_id, 130); |
| update_txn->put(table_stream_offset_key({instance_id, base_db_id, base_table_id, |
| stream_db_id, created_stream_id, |
| created_partition_id}), |
| created.SerializeAsString()); |
| versioned_put(update_txn.get(), |
| versioned::table_stream_offset_key( |
| {instance_id, base_db_id, base_table_id, stream_db_id, |
| created_stream_id, created_partition_id}), |
| Versionstamp(20, 2), created.SerializeAsString()); |
| ASSERT_EQ(update_txn->commit(), TxnErrorCode::TXN_OK); |
| updated = true; |
| }, |
| &guard); |
| sync_point->enable_processing(); |
| |
| InstanceChecker checker(txn_kv_, instance_id); |
| ASSERT_EQ(checker.init(write_only_instance(instance_id)), 0); |
| EXPECT_EQ(checker.do_table_stream_check(), 0); |
| EXPECT_TRUE(updated); |
| } |
| |
| TEST_F(TableStreamCheckerTest, DetectsMalformedOffsetValue) { |
| const std::string instance_id = "checker-malformed-offset"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| TableStreamOffsetPB value; |
| value.set_partition_id(5); |
| value.set_state(TABLE_STREAM_OFFSET_CONSUMED); |
| put_offsets(txn.get(), instance_id, 1, 2, 3, 4, 5, value, value); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| InstanceChecker checker(txn_kv_, instance_id); |
| ASSERT_EQ(checker.init(write_only_instance(instance_id)), 0); |
| EXPECT_EQ(checker.do_table_stream_check(), 1); |
| } |
| |
| TEST_F(TableStreamCheckerTest, CollectsPendingTableStreamDrop) { |
| const std::string instance_id = "checker-pending-stream-drop"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| OperationLogPB operation_log; |
| DropIndexLogPB* drop_index = operation_log.mutable_drop_index(); |
| drop_index->set_db_id(1); |
| drop_index->set_table_id(2); |
| drop_index->set_object_type(TABLE_STREAM); |
| drop_index->set_stream_db_id(3); |
| drop_index->add_index_ids(4); |
| versioned::blob_put(txn.get(), versioned::log_key(instance_id), Versionstamp(10, 1), |
| operation_log); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| std::unordered_map<int64_t, PendingTableStreamDrop> pending_drops; |
| ASSERT_EQ(collect_pending_table_stream_drops(txn_kv_, instance_id, &pending_drops), |
| TxnErrorCode::TXN_OK); |
| ASSERT_EQ(pending_drops.size(), 1); |
| ASSERT_TRUE(pending_drops.contains(4)); |
| |
| EXPECT_TRUE(pending_drops.at(4).matches(1, 2, 3)); |
| EXPECT_FALSE(pending_drops.at(4).matches(1, 2, 5)); |
| } |
| |
| TEST_F(TableStreamCheckerTest, DoesNotCollectPhysicalIndexDrop) { |
| const std::string instance_id = "checker-pending-physical-index-drop"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| OperationLogPB operation_log; |
| DropIndexLogPB* drop_index = operation_log.mutable_drop_index(); |
| drop_index->set_db_id(1); |
| drop_index->set_table_id(2); |
| drop_index->add_index_ids(4); |
| versioned::blob_put(txn.get(), versioned::log_key(instance_id), Versionstamp(10, 1), |
| operation_log); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| std::unordered_map<int64_t, PendingTableStreamDrop> pending_drops; |
| EXPECT_EQ(collect_pending_table_stream_drops(txn_kv_, instance_id, &pending_drops), |
| TxnErrorCode::TXN_OK); |
| EXPECT_TRUE(pending_drops.empty()); |
| } |
| |
| TEST_F(TableStreamCheckerTest, RejectsConflictingPendingTableStreamDrops) { |
| const std::string instance_id = "checker-conflicting-stream-drops"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| for (int64_t stream_db_id : {3, 5}) { |
| OperationLogPB operation_log; |
| DropIndexLogPB* drop_index = operation_log.mutable_drop_index(); |
| drop_index->set_db_id(1); |
| drop_index->set_table_id(2); |
| drop_index->set_object_type(TABLE_STREAM); |
| drop_index->set_stream_db_id(stream_db_id); |
| drop_index->add_index_ids(4); |
| versioned::blob_put(txn.get(), versioned::log_key(instance_id), |
| Versionstamp(10 + stream_db_id, 1), operation_log); |
| } |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| std::unordered_map<int64_t, PendingTableStreamDrop> pending_drops; |
| EXPECT_EQ(collect_pending_table_stream_drops(txn_kv_, instance_id, &pending_drops), |
| TxnErrorCode::TXN_INVALID_DATA); |
| } |
| |
| TEST_F(TableStreamCheckerTest, RejectsMalformedPendingOperationLog) { |
| const std::string instance_id = "checker-malformed-pending-drop"; |
| std::unique_ptr<Transaction> txn; |
| ASSERT_EQ(txn_kv_->create_txn(&txn), TxnErrorCode::TXN_OK); |
| versioned::blob_put(txn.get(), versioned::log_key(instance_id), Versionstamp(10, 1), |
| "malformed"); |
| ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK); |
| |
| std::unordered_map<int64_t, PendingTableStreamDrop> pending_drops; |
| EXPECT_EQ(collect_pending_table_stream_drops(txn_kv_, instance_id, &pending_drops), |
| TxnErrorCode::TXN_INVALID_DATA); |
| } |
| |
| } // namespace doris::cloud |