blob: 77823a2162c1f44915a9705835b28e0cc8343424 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <brpc/controller.h>
#include <fmt/format.h>
#include <gen_cpp/cloud.pb.h>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "cpp/sync_point.h"
#include "meta-service/meta_service_helper.h"
#include "meta-service/table_stream_metadata_reader.h"
#include "meta-store/clone_chain_reader.h"
#include "meta-store/txn_kv.h"
#include "meta-store/txn_kv_error.h"
#include "meta_service.h"
#include "resource-manager/resource_manager.h"
namespace doris::cloud {
namespace {
TableStreamReadResult validate_request(const GetTableStreamOffsetRequest& request) {
std::unordered_set<int64_t> stream_ids;
for (const TableStreamPartitionSetPB& binding : request.bindings()) {
if (!binding.has_identity() || !is_valid_table_stream_identity(binding.identity()) ||
binding.partition_ids().empty()) {
return {MetaServiceCode::INVALID_ARGUMENT, "invalid table stream binding"};
}
if (!stream_ids.insert(binding.identity().stream_id()).second) {
return {MetaServiceCode::INVALID_ARGUMENT, "duplicate table stream binding"};
}
std::unordered_set<int64_t> partition_ids;
for (int64_t partition_id : binding.partition_ids()) {
if (partition_id <= 0) {
return {MetaServiceCode::INVALID_ARGUMENT,
fmt::format("invalid partition id {}", partition_id)};
}
if (!partition_ids.insert(partition_id).second) {
return {MetaServiceCode::INVALID_ARGUMENT,
fmt::format("duplicate partition id {}", partition_id)};
}
}
}
return {};
}
TableStreamReadResult validate_table_streams(
const TableStreamMetadataReader& reader,
const std::vector<TableStreamPartitionSetPB>& bindings) {
std::vector<int64_t> stream_ids;
stream_ids.reserve(bindings.size());
for (const TableStreamPartitionSetPB& binding : bindings) {
stream_ids.push_back(binding.identity().stream_id());
}
std::unordered_set<int64_t> recycling_stream_ids;
TableStreamReadResult result = reader.read_recycling_streams(
stream_ids, TableStreamReadIntent::SNAPSHOT, &recycling_stream_ids);
if (!result.ok()) {
return result;
}
for (int64_t stream_id : stream_ids) {
if (recycling_stream_ids.contains(stream_id)) {
return {MetaServiceCode::INVALID_ARGUMENT,
fmt::format("table stream {} is being created or recycled", stream_id)};
}
}
return {};
}
TableStreamReadResult fill_partition_read_state(const TableStreamIdentityPB& identity,
int64_t partition_id, const VersionPB& version,
const TableStreamOffsetPB* offset,
TableStreamPartitionReadStatePB* state) {
state->set_partition_id(partition_id);
state->set_visible_version(version.version());
state->set_end_tso(version.commit_tso());
if (!offset) {
state->set_offset_state(TableStreamOffsetStatePB::TABLE_STREAM_OFFSET_UNKNOWN);
return {};
}
if (!offset->has_partition_id() || offset->partition_id() != partition_id ||
!offset->has_state() ||
offset->state() == TableStreamOffsetStatePB::TABLE_STREAM_OFFSET_UNKNOWN ||
!offset->has_offset_tso()) {
return {MetaServiceCode::PROTOBUF_PARSE_ERR,
fmt::format("invalid offset for table stream {} partition {}", identity.stream_id(),
partition_id)};
}
state->set_offset_state(offset->state());
state->set_offset_tso(offset->offset_tso());
if (offset->has_last_consumption_time_ms()) {
state->set_last_consumption_time_ms(offset->last_consumption_time_ms());
}
return {};
}
TableStreamReadResult read_bindings(
const TableStreamMetadataReader& reader,
const std::vector<TableStreamPartitionSetPB>& bindings,
std::unordered_map<std::string, std::vector<VersionPB>>* pending_versions,
GetTableStreamOffsetResponse* response) {
TableStreamPartitionVersionMap versions;
std::unordered_map<int64_t, std::string> source_instances;
TableStreamReadResult result = reader.read_and_validate_partitions(
bindings, TableStreamReadIntent::SNAPSHOT, &versions, &source_instances);
if (!result.ok()) {
return result;
}
std::unordered_map<std::string, std::unordered_set<int64_t>> pending_txn_ids;
for (const auto& stream_versions : versions) {
for (const auto& partition_version : stream_versions.second) {
const int64_t partition_id = partition_version.first;
const VersionPB& version = partition_version.second;
if (version.pending_txn_ids().empty()) {
continue;
}
DCHECK_EQ(version.pending_txn_ids_size(), 1)
<< "only support one pending txn id for now, version="
<< version.ShortDebugString();
DCHECK(source_instances.contains(partition_id));
const std::string& source_instance = source_instances.at(partition_id);
if (pending_txn_ids[source_instance].insert(version.pending_txn_ids(0)).second) {
(*pending_versions)[source_instance].push_back(version);
}
}
}
if (!pending_versions->empty()) {
return {};
}
TEST_SYNC_POINT("get_table_stream_offset::after_read_versions");
TableStreamOffsetMap offsets;
result = reader.read_effective_offsets(bindings, TableStreamReadIntent::SNAPSHOT, &offsets);
if (!result.ok()) {
return result;
}
for (const TableStreamPartitionSetPB& binding : bindings) {
const TableStreamIdentityPB& identity = binding.identity();
const int64_t stream_id = identity.stream_id();
TableStreamReadBindingResultPB* binding_result = response->add_bindings();
binding_result->mutable_identity()->CopyFrom(identity);
auto stream_offset_it = offsets.find(stream_id);
for (int64_t partition_id : binding.partition_ids()) {
const TableStreamOffsetPB* offset = nullptr;
if (stream_offset_it != offsets.end()) {
auto offset_it = stream_offset_it->second.find(partition_id);
if (offset_it != stream_offset_it->second.end()) {
offset = &offset_it->second;
}
}
result = fill_partition_read_state(identity, partition_id,
versions.at(stream_id).at(partition_id), offset,
binding_result->add_partition_states());
if (!result.ok()) {
return result;
}
}
}
return {};
}
} // namespace
void MetaServiceImpl::get_table_stream_offset(::google::protobuf::RpcController* controller,
const GetTableStreamOffsetRequest* request,
GetTableStreamOffsetResponse* response,
::google::protobuf::Closure* done) {
RPC_PREPROCESS(get_table_stream_offset, get);
if (!request->has_cloud_unique_id() || request->cloud_unique_id().empty()) {
code = MetaServiceCode::INVALID_ARGUMENT;
msg = "cloud_unique_id not set";
return;
}
if (request->bindings().empty()) {
code = MetaServiceCode::INVALID_ARGUMENT;
msg = "empty table stream bindings";
return;
}
TableStreamReadResult validation = validate_request(*request);
if (!validation.ok()) {
code = validation.code;
msg = std::move(validation.message);
return;
}
instance_id = get_instance_id(resource_mgr_, request->cloud_unique_id());
if (instance_id.empty()) {
code = MetaServiceCode::INVALID_ARGUMENT;
msg = "empty instance_id";
return;
}
RPC_RATE_LIMIT(get_table_stream_offset)
std::vector<TableStreamPartitionSetPB> bindings(request->bindings().begin(),
request->bindings().end());
while (true) {
TxnErrorCode err = txn_kv_->create_txn(&txn);
if (err != TxnErrorCode::TXN_OK) {
code = cast_as<ErrCategory::CREATE>(err);
msg = "failed to create read transaction";
return;
}
MultiVersionStatus multi_version_status;
TableStreamReadResult mode_result = read_table_stream_multi_version_status(
txn.get(), instance_id, TableStreamReadIntent::SNAPSHOT, &multi_version_status);
if (!mode_result.ok()) {
code = mode_result.code;
msg = std::move(mode_result.message);
return;
}
CloneChainReader clone_chain_reader(instance_id, resource_mgr_.get());
TableStreamMetadataReader reader(txn.get(), instance_id, multi_version_status,
&clone_chain_reader);
TableStreamReadResult stream_result = validate_table_streams(reader, bindings);
if (!stream_result.ok()) {
code = stream_result.code;
msg = std::move(stream_result.message);
return;
}
std::unordered_map<std::string, std::vector<VersionPB>> pending_versions;
GetTableStreamOffsetResponse read_response;
TableStreamReadResult read_result =
read_bindings(reader, bindings, &pending_versions, &read_response);
if (!read_result.ok()) {
code = read_result.code;
msg = std::move(read_result.message);
return;
}
if (pending_versions.empty()) {
response->mutable_bindings()->Swap(read_response.mutable_bindings());
return;
}
stats.get_bytes += txn->get_bytes();
stats.get_counter += txn->num_get_keys();
txn.reset();
for (auto& [source_instance, versions] : pending_versions) {
std::tie(code, msg) = wait_for_pending_txns(source_instance, versions);
if (code != MetaServiceCode::OK) {
return;
}
}
TEST_SYNC_POINT_CALLBACK("get_table_stream_offset::after_wait_for_pending_txns");
}
}
} // namespace doris::cloud