You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ga...@apache.org on 2024/09/10 18:29:38 UTC
(doris) 01/11: [feature](cloud) Add lazy commit mechanism for `commit_txn` (#38243)
This is an automated email from the ASF dual-hosted git repository.
gavinchou pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git
commit bf8e88aaf6059b97464b4c797ae383e91b323850
Author: Lei Zhang <27...@users.noreply.github.com>
AuthorDate: Mon Sep 2 00:50:37 2024 +0800
[feature](cloud) Add lazy commit mechanism for `commit_txn` (#38243)
Motivation:
* In cloud mode we use `foundationdb` for storing rowset meta, when we
load data with many rowsets, `commit_txn` will failed because
`foundationdb` currently limits all transactions to be below 10 MB in
size, So we spilt `commit_txn` into several sub fdb txn to solve the
problem
How:
Now `commit_txn` main flow like this
sub txn 1:
1. update partition `VersionPB` with txn_id
2. submit async task to txn_lazy_committer
txn_lazy_committer:
sub txn 2:
convert tmp rowset meta per batch
sub txn 3:
convert tmp rowset meta per batch
......
sub txn n:
make txn visible and remove txn_id in `VersionPB`
---
be/src/cloud/cloud_meta_mgr.cpp | 1 +
be/src/cloud/config.cpp | 4 +
be/src/cloud/config.h | 3 +
cloud/src/common/config.h | 7 +
cloud/src/meta-service/CMakeLists.txt | 1 +
cloud/src/meta-service/meta_service.cpp | 250 ++--
cloud/src/meta-service/meta_service.h | 2 +
cloud/src/meta-service/meta_service_txn.cpp | 1248 ++++++++++++++------
cloud/src/meta-service/txn_lazy_committer.cpp | 476 ++++++++
cloud/src/meta-service/txn_lazy_committer.h | 68 ++
cloud/src/recycler/recycler.cpp | 282 ++++-
cloud/src/recycler/recycler.h | 10 +-
cloud/src/recycler/recycler_service.cpp | 18 +-
cloud/src/recycler/recycler_service.h | 5 +-
cloud/test/CMakeLists.txt | 4 +
cloud/test/meta_service_test.cpp | 2 +-
cloud/test/recycler_test.cpp | 100 +-
cloud/test/txn_lazy_commit_test.cpp | 216 ++++
.../main/java/org/apache/doris/common/Config.java | 4 +
.../org/apache/doris/alter/CloudRollupJobV2.java | 1 +
.../apache/doris/alter/CloudSchemaChangeJobV2.java | 1 +
.../cloud/datasource/CloudInternalCatalog.java | 3 +-
.../transaction/CloudGlobalTransactionMgr.java | 6 +-
gensrc/proto/cloud.proto | 4 +
.../test_insert_with_many_buckets.out | 4 +
.../test_insert_with_many_partitions.out | 4 +
...est_insert_with_many_partitions_and_buckets.out | 4 +
.../test_insert_with_many_buckets.groovy | 57 +
.../test_insert_with_many_partitions.groovy | 73 ++
..._insert_with_many_partitions_and_buckets.groovy | 79 ++
30 files changed, 2394 insertions(+), 543 deletions(-)
diff --git a/be/src/cloud/cloud_meta_mgr.cpp b/be/src/cloud/cloud_meta_mgr.cpp
index 4ca44f67cec..3f5f8df8de8 100644
--- a/be/src/cloud/cloud_meta_mgr.cpp
+++ b/be/src/cloud/cloud_meta_mgr.cpp
@@ -855,6 +855,7 @@ Status CloudMetaMgr::commit_txn(const StreamLoadContext& ctx, bool is_2pc) {
req.set_db_id(ctx.db_id);
req.set_txn_id(ctx.txn_id);
req.set_is_2pc(is_2pc);
+ req.set_enable_txn_lazy_commit(config::enable_cloud_txn_lazy_commit);
auto st = retry_rpc("commit txn", req, &res, &MetaService_Stub::commit_txn);
if (st.ok()) {
diff --git a/be/src/cloud/config.cpp b/be/src/cloud/config.cpp
index 82c466120e9..1b8256bf932 100644
--- a/be/src/cloud/config.cpp
+++ b/be/src/cloud/config.cpp
@@ -60,4 +60,8 @@ DEFINE_mBool(save_load_error_log_to_s3, "false");
DEFINE_mInt32(sync_load_for_tablets_thread, "32");
+DEFINE_mBool(enable_new_tablet_do_compaction, "false");
+
+DEFINE_Bool(enable_cloud_txn_lazy_commit, "false");
+
} // namespace doris::config
diff --git a/be/src/cloud/config.h b/be/src/cloud/config.h
index 02e7014801e..560c3de97cc 100644
--- a/be/src/cloud/config.h
+++ b/be/src/cloud/config.h
@@ -94,4 +94,7 @@ DECLARE_mBool(save_load_error_log_to_s3);
// the theads which sync the datas which loaded in other clusters
DECLARE_mInt32(sync_load_for_tablets_thread);
+// enable large txn lazy commit in meta-service `commit_txn`
+DECLARE_mBool(enable_cloud_txn_lazy_commit);
+
} // namespace doris::config
diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h
index a28143bb705..ee3c4ba81fc 100644
--- a/cloud/src/common/config.h
+++ b/cloud/src/common/config.h
@@ -211,4 +211,11 @@ CONF_mInt64(max_s3_client_retry, "10");
// Max aborted txn num for the same label name
CONF_mInt64(max_num_aborted_txn, "100");
+CONF_Bool(enable_cloud_txn_lazy_commit, "true");
+CONF_Int32(txn_lazy_commit_rowsets_thresold, "1000");
+CONF_Int32(txn_lazy_commit_num_threads, "8");
+CONF_Int32(txn_lazy_max_rowsets_per_batch, "1000");
+
+// max TabletIndexPB num for batch get
+CONF_Int32(max_tablet_index_num_per_batch, "1000");
} // namespace doris::cloud::config
diff --git a/cloud/src/meta-service/CMakeLists.txt b/cloud/src/meta-service/CMakeLists.txt
index 33dd9b6c0ce..c7c4887a068 100644
--- a/cloud/src/meta-service/CMakeLists.txt
+++ b/cloud/src/meta-service/CMakeLists.txt
@@ -24,4 +24,5 @@ add_library(MetaService
doris_txn.cpp
mem_txn_kv.cpp
http_encode_key.cpp
+ txn_lazy_committer.cpp
)
diff --git a/cloud/src/meta-service/meta_service.cpp b/cloud/src/meta-service/meta_service.cpp
index 2213bb48387..5f3d64b24c9 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -75,6 +75,7 @@ MetaServiceImpl::MetaServiceImpl(std::shared_ptr<TxnKv> txn_kv,
resource_mgr_ = resource_mgr;
rate_limiter_ = rate_limiter;
rate_limiter_->init(this);
+ txn_lazy_committer_ = std::make_shared<TxnLazyCommitter>(txn_kv_);
}
MetaServiceImpl::~MetaServiceImpl() = default;
@@ -237,6 +238,7 @@ void MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
partition_version_key({instance_id, db_id, table_id, partition_id}, &ver_key);
}
+ code = MetaServiceCode::OK;
std::unique_ptr<Transaction> txn;
TxnErrorCode err = txn_kv_->create_txn(&txn);
if (err != TxnErrorCode::TXN_OK) {
@@ -265,6 +267,12 @@ void MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
msg = "malformed version value";
return;
}
+ if (!version_pb.has_version()) {
+ msg = "not found";
+ code = MetaServiceCode::VERSION_NOT_FOUND;
+ return;
+ }
+
response->set_version(version_pb.version());
response->add_version_update_time_ms(version_pb.update_time_ms());
}
@@ -329,6 +337,7 @@ void MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
std::vector<std::optional<std::string>> version_values;
version_keys.reserve(BATCH_SIZE);
version_values.reserve(BATCH_SIZE);
+
while ((code == MetaServiceCode::OK || code == MetaServiceCode::KV_TXN_TOO_OLD) &&
response->versions_size() < num_acquired) {
std::unique_ptr<Transaction> txn;
@@ -390,8 +399,15 @@ void MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
msg = "malformed version value";
break;
}
- response->add_versions(version_pb.version());
- response->add_version_update_time_ms(version_pb.update_time_ms());
+
+ if (!version_pb.has_version()) {
+ // return -1 if the target version is not exists.
+ response->add_versions(-1);
+ response->add_version_update_time_ms(-1);
+ } else {
+ response->add_versions(version_pb.version());
+ response->add_version_update_time_ms(version_pb.update_time_ms());
+ }
}
}
}
@@ -403,9 +419,9 @@ void MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
}
}
-void internal_create_tablet(MetaServiceCode& code, std::string& msg,
- const doris::TabletMetaCloudPB& meta, std::shared_ptr<TxnKv> txn_kv,
- const std::string& instance_id,
+void internal_create_tablet(const CreateTabletsRequest* request, MetaServiceCode& code,
+ std::string& msg, const doris::TabletMetaCloudPB& meta,
+ std::shared_ptr<TxnKv> txn_kv, const std::string& instance_id,
std::set<std::pair<int64_t, int32_t>>& saved_schema) {
doris::TabletMetaCloudPB tablet_meta(meta);
bool has_first_rowset = tablet_meta.rs_metas_size() > 0;
@@ -496,7 +512,9 @@ void internal_create_tablet(MetaServiceCode& code, std::string& msg,
MetaTabletIdxKeyInfo key_info1 {instance_id, tablet_id};
meta_tablet_idx_key(key_info1, &key1);
TabletIndexPB tablet_table;
- // tablet_table.set_db_id(db_id);
+ if (request->has_db_id()) {
+ tablet_table.set_db_id(request->db_id());
+ }
tablet_table.set_table_id(table_id);
tablet_table.set_index_id(index_id);
tablet_table.set_partition_id(partition_id);
@@ -618,11 +636,12 @@ void MetaServiceImpl::create_tablets(::google::protobuf::RpcController* controll
msg = fmt::format("failed to get vault id, vault name={}", name);
return;
}
+
// [index_id, schema_version]
std::set<std::pair<int64_t, int32_t>> saved_schema;
TEST_SYNC_POINT_RETURN_WITH_VOID("create_tablets");
for (auto& tablet_meta : request->tablet_metas()) {
- internal_create_tablet(code, msg, tablet_meta, txn_kv_, instance_id, saved_schema);
+ internal_create_tablet(request, code, msg, tablet_meta, txn_kv_, instance_id, saved_schema);
if (code != MetaServiceCode::OK) {
return;
}
@@ -1420,104 +1439,157 @@ void MetaServiceImpl::get_rowset(::google::protobuf::RpcController* controller,
int64_t req_cc_cnt = request->cumulative_compaction_cnt();
int64_t req_cp = request->cumulative_point();
- std::unique_ptr<Transaction> txn;
- TxnErrorCode err = txn_kv_->create_txn(&txn);
- if (err != TxnErrorCode::TXN_OK) {
- code = cast_as<ErrCategory::CREATE>(err);
- msg = "failed to create txn";
- return;
- }
+ do {
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv_->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ msg = "failed to create txn";
+ LOG(WARNING) << msg;
+ return;
+ }
- TabletIndexPB idx(request->idx());
- // Get tablet id index from kv
- if (!idx.has_table_id() || !idx.has_index_id() || !idx.has_partition_id()) {
+ TabletIndexPB idx;
+ // Get tablet id index from kv
get_tablet_idx(code, msg, txn.get(), instance_id, tablet_id, idx);
- if (code != MetaServiceCode::OK) return;
- }
- // TODO(plat1ko): Judge if tablet has been dropped (in dropped index/partition)
+ if (code != MetaServiceCode::OK) {
+ return;
+ }
+ DCHECK(request->has_idx());
- TabletStatsPB tablet_stat;
- internal_get_tablet_stats(code, msg, txn.get(), instance_id, idx, tablet_stat, true);
- if (code != MetaServiceCode::OK) return;
- VLOG_DEBUG << "tablet_id=" << tablet_id << " stats=" << proto_to_json(tablet_stat);
+ if (idx.has_db_id()) {
+ // there is maybe a lazy commit txn when call get_rowset
+ // we need advance lazy commit txn here
+ std::string ver_val;
+ std::string ver_key = partition_version_key(
+ {instance_id, idx.db_id(), idx.table_id(), idx.partition_id()});
+ err = txn->get(ver_key, &ver_val);
+ if (TxnErrorCode::TXN_OK != err && TxnErrorCode::TXN_KEY_NOT_FOUND != err) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get partiton version, tablet_id=" << tablet_id
+ << " key=" << hex(ver_key) << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ if (TxnErrorCode::TXN_OK == err) {
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(ver_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse version pb db_id=" << idx.db_id()
+ << " table_id=" << idx.table_id() << " partition_id" << idx.partition_id()
+ << " key=" << hex(ver_key);
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ if (version_pb.pending_txn_ids_size() > 0) {
+ DCHECK(version_pb.pending_txn_ids_size() == 1);
+ txn.reset();
+ std::shared_ptr<TxnLazyCommitTask> task =
+ txn_lazy_committer_->submit(instance_id, version_pb.pending_txn_ids(0));
+
+ std::tie(code, msg) = task->wait();
+ if (code != MetaServiceCode::OK) {
+ LOG(WARNING) << "advance_last_txn failed last_txn="
+ << version_pb.pending_txn_ids(0) << " code=" << code
+ << "msg=" << msg;
+ return;
+ }
+ continue;
+ }
+ }
+ }
- int64_t bc_cnt = tablet_stat.base_compaction_cnt();
- int64_t cc_cnt = tablet_stat.cumulative_compaction_cnt();
- int64_t cp = tablet_stat.cumulative_point();
+ // TODO(plat1ko): Judge if tablet has been dropped (in dropped index/partition)
- response->mutable_stats()->CopyFrom(tablet_stat);
+ TabletStatsPB tablet_stat;
+ internal_get_tablet_stats(code, msg, txn.get(), instance_id, idx, tablet_stat, true);
+ if (code != MetaServiceCode::OK) return;
+ VLOG_DEBUG << "tablet_id=" << tablet_id << " stats=" << proto_to_json(tablet_stat);
- int64_t req_start = request->start_version();
- int64_t req_end = request->end_version();
- req_end = req_end < 0 ? std::numeric_limits<int64_t>::max() - 1 : req_end;
+ int64_t bc_cnt = tablet_stat.base_compaction_cnt();
+ int64_t cc_cnt = tablet_stat.cumulative_compaction_cnt();
+ int64_t cp = tablet_stat.cumulative_point();
- //==========================================================================
- // Find version ranges to be synchronized due to compaction
- //==========================================================================
- if (req_bc_cnt > bc_cnt || req_cc_cnt > cc_cnt || req_cp > cp) {
- code = MetaServiceCode::INVALID_ARGUMENT;
- ss << "no valid compaction_cnt or cumulative_point given. req_bc_cnt=" << req_bc_cnt
- << ", bc_cnt=" << bc_cnt << ", req_cc_cnt=" << req_cc_cnt << ", cc_cnt=" << cc_cnt
- << ", req_cp=" << req_cp << ", cp=" << cp;
- msg = ss.str();
- return;
- }
- auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp,
- req_start, req_end);
- for (auto [start, end] : versions) {
- internal_get_rowset(txn.get(), start, end, instance_id, tablet_id, code, msg, response);
- if (code != MetaServiceCode::OK) {
- return;
- }
- }
+ response->mutable_stats()->CopyFrom(tablet_stat);
- // get referenced schema
- std::unordered_map<int32_t, doris::TabletSchemaCloudPB*> version_to_schema;
- for (auto& rowset_meta : *response->mutable_rowset_meta()) {
- if (rowset_meta.has_tablet_schema()) {
- version_to_schema.emplace(rowset_meta.tablet_schema().schema_version(),
- rowset_meta.mutable_tablet_schema());
- rowset_meta.set_schema_version(rowset_meta.tablet_schema().schema_version());
- }
- rowset_meta.set_index_id(idx.index_id());
- }
- bool need_read_schema_dict = false;
- auto arena = response->GetArena();
- for (auto& rowset_meta : *response->mutable_rowset_meta()) {
- if (rowset_meta.has_schema_dict_key_list()) {
- need_read_schema_dict = true;
- }
- if (rowset_meta.has_tablet_schema()) continue;
- if (!rowset_meta.has_schema_version()) {
+ int64_t req_start = request->start_version();
+ int64_t req_end = request->end_version();
+ req_end = req_end < 0 ? std::numeric_limits<int64_t>::max() - 1 : req_end;
+
+ //==========================================================================
+ // Find version ranges to be synchronized due to compaction
+ //==========================================================================
+ if (req_bc_cnt > bc_cnt || req_cc_cnt > cc_cnt || req_cp > cp) {
code = MetaServiceCode::INVALID_ARGUMENT;
- msg = fmt::format(
- "rowset_meta must have either schema or schema_version, "
- "rowset_version=[{}-{}]",
- rowset_meta.start_version(), rowset_meta.end_version());
+ ss << "no valid compaction_cnt or cumulative_point given. req_bc_cnt=" << req_bc_cnt
+ << ", bc_cnt=" << bc_cnt << ", req_cc_cnt=" << req_cc_cnt << ", cc_cnt=" << cc_cnt
+ << ", req_cp=" << req_cp << ", cp=" << cp;
+ msg = ss.str();
return;
}
- if (auto it = version_to_schema.find(rowset_meta.schema_version());
- it != version_to_schema.end()) {
- if (arena != nullptr) {
- rowset_meta.set_allocated_tablet_schema(it->second);
- } else {
- rowset_meta.mutable_tablet_schema()->CopyFrom(*it->second);
+ auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp,
+ req_start, req_end);
+ for (auto [start, end] : versions) {
+ internal_get_rowset(txn.get(), start, end, instance_id, tablet_id, code, msg, response);
+ if (code != MetaServiceCode::OK) {
+ return;
}
- } else {
- auto key = meta_schema_key({instance_id, idx.index_id(), rowset_meta.schema_version()});
- if (!try_fetch_and_parse_schema(txn.get(), rowset_meta, key, code, msg)) {
+ }
+
+ // get referenced schema
+ std::unordered_map<int32_t, doris::TabletSchemaCloudPB*> version_to_schema;
+ for (auto& rowset_meta : *response->mutable_rowset_meta()) {
+ if (rowset_meta.has_tablet_schema()) {
+ version_to_schema.emplace(rowset_meta.tablet_schema().schema_version(),
+ rowset_meta.mutable_tablet_schema());
+ rowset_meta.set_schema_version(rowset_meta.tablet_schema().schema_version());
+ }
+ rowset_meta.set_index_id(idx.index_id());
+ }
+ bool need_read_schema_dict = false;
+ auto arena = response->GetArena();
+ for (auto& rowset_meta : *response->mutable_rowset_meta()) {
+ if (rowset_meta.has_schema_dict_key_list()) {
+ need_read_schema_dict = true;
+ }
+ if (rowset_meta.has_tablet_schema()) continue;
+ if (!rowset_meta.has_schema_version()) {
+ code = MetaServiceCode::INVALID_ARGUMENT;
+ msg = fmt::format(
+ "rowset_meta must have either schema or schema_version, "
+ "rowset_version=[{}-{}]",
+ rowset_meta.start_version(), rowset_meta.end_version());
return;
}
- version_to_schema.emplace(rowset_meta.schema_version(),
- rowset_meta.mutable_tablet_schema());
+ if (auto it = version_to_schema.find(rowset_meta.schema_version());
+ it != version_to_schema.end()) {
+ if (arena != nullptr) {
+ rowset_meta.set_allocated_tablet_schema(it->second);
+ } else {
+ rowset_meta.mutable_tablet_schema()->CopyFrom(*it->second);
+ }
+ } else {
+ auto key = meta_schema_key(
+ {instance_id, idx.index_id(), rowset_meta.schema_version()});
+ if (!try_fetch_and_parse_schema(txn.get(), rowset_meta, key, code, msg)) {
+ return;
+ }
+ version_to_schema.emplace(rowset_meta.schema_version(),
+ rowset_meta.mutable_tablet_schema());
+ }
}
- }
- if (need_read_schema_dict) {
- read_schema_from_dict(code, msg, instance_id, idx.index_id(), txn.get(),
- response->mutable_rowset_meta());
- if (code != MetaServiceCode::OK) return;
- }
+ if (need_read_schema_dict) {
+ read_schema_from_dict(code, msg, instance_id, idx.index_id(), txn.get(),
+ response->mutable_rowset_meta());
+ if (code != MetaServiceCode::OK) return;
+ }
+ break;
+ } while (true);
}
void MetaServiceImpl::get_tablet_stats(::google::protobuf::RpcController* controller,
diff --git a/cloud/src/meta-service/meta_service.h b/cloud/src/meta-service/meta_service.h
index c64a55b89a2..5d2ae55ffeb 100644
--- a/cloud/src/meta-service/meta_service.h
+++ b/cloud/src/meta-service/meta_service.h
@@ -30,6 +30,7 @@
#include "common/config.h"
#include "cpp/sync_point.h"
#include "meta-service/txn_kv.h"
+#include "meta-service/txn_lazy_committer.h"
#include "rate-limiter/rate_limiter.h"
#include "resource-manager/resource_manager.h"
@@ -291,6 +292,7 @@ private:
std::shared_ptr<TxnKv> txn_kv_;
std::shared_ptr<ResourceManager> resource_mgr_;
std::shared_ptr<RateLimiter> rate_limiter_;
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
};
class MetaServiceProxy final : public MetaService {
diff --git a/cloud/src/meta-service/meta_service_txn.cpp b/cloud/src/meta-service/meta_service_txn.cpp
index 63f7030e076..2c5b4d6ec9a 100644
--- a/cloud/src/meta-service/meta_service_txn.cpp
+++ b/cloud/src/meta-service/meta_service_txn.cpp
@@ -765,7 +765,7 @@ void MetaServiceImpl::reset_rl_progress(::google::protobuf::RpcController* contr
}
void scan_tmp_rowset(
- const std::string& instance_id, int64_t txn_id, std::shared_ptr<TxnKv>& txn_kv,
+ const std::string& instance_id, int64_t txn_id, std::shared_ptr<TxnKv> txn_kv,
MetaServiceCode& code, std::string& msg, int64_t* db_id,
std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>* tmp_rowsets_meta) {
// Create a readonly txn for scan tmp rowset
@@ -774,7 +774,7 @@ void scan_tmp_rowset(
TxnErrorCode err = txn_kv->create_txn(&txn);
if (err != TxnErrorCode::TXN_OK) {
code = cast_as<ErrCategory::CREATE>(err);
- ss << "filed to create txn, txn_id=" << txn_id << " err=" << err;
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
msg = ss.str();
LOG(WARNING) << msg;
return;
@@ -909,6 +909,7 @@ void update_tablet_stats(const StatsTabletKeyInfo& info, const TabletStats& stat
LOG(INFO) << "put stats_tablet_key key=" << hex(key);
}
}
+
/**
* 0. Extract txn_id from request
* 1. Get db id from TxnKv with txn_id
@@ -925,94 +926,482 @@ void update_tablet_stats(const StatsTabletKeyInfo& info, const TabletStats& stat
*/
void commit_txn_immediately(
const CommitTxnRequest* request, CommitTxnResponse* response,
- std::shared_ptr<TxnKv>& txn_kv, MetaServiceCode& code, std::string& msg,
- const std::string& instance_id, int64_t db_id,
+ std::shared_ptr<TxnKv>& txn_kv, std::shared_ptr<TxnLazyCommitter>& txn_lazy_committer,
+ MetaServiceCode& code, std::string& msg, const std::string& instance_id, int64_t db_id,
std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>& tmp_rowsets_meta) {
std::stringstream ss;
int64_t txn_id = request->txn_id();
- std::unique_ptr<Transaction> txn;
- TxnErrorCode err = txn_kv->create_txn(&txn);
- if (err != TxnErrorCode::TXN_OK) {
- code = cast_as<ErrCategory::CREATE>(err);
- ss << "filed to create txn, txn_id=" << txn_id << " err=" << err;
- msg = ss.str();
- LOG(WARNING) << msg;
- return;
- }
+ do {
+ int64_t last_pending_txn_id = 0;
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
- // Get txn info with db_id and txn_id
- std::string info_val; // Will be reused when saving updated txn
- const std::string info_key = txn_info_key({instance_id, db_id, txn_id});
- err = txn->get(info_key, &info_val);
- if (err != TxnErrorCode::TXN_OK) {
- code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
- : cast_as<ErrCategory::READ>(err);
- if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
- ss << "transaction [" << txn_id << "] not found, db_id=" << db_id;
- } else {
- ss << "failed to get txn_info, db_id=" << db_id << " txn_id=" << txn_id
- << " err=" << err;
+ // Get txn info with db_id and txn_id
+ std::string info_val; // Will be reused when saving updated txn
+ const std::string info_key = txn_info_key({instance_id, db_id, txn_id});
+ err = txn->get(info_key, &info_val);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
+ ss << "transaction [" << txn_id << "] not found, db_id=" << db_id;
+ } else {
+ ss << "failed to get txn_info, db_id=" << db_id << " txn_id=" << txn_id
+ << " err=" << err;
+ }
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
}
- msg = ss.str();
- LOG(WARNING) << msg;
- return;
- }
- TxnInfoPB txn_info;
- if (!txn_info.ParseFromString(info_val)) {
- code = MetaServiceCode::PROTOBUF_PARSE_ERR;
- ss << "failed to parse txn_info, db_id=" << db_id << " txn_id=" << txn_id;
- msg = ss.str();
- LOG(WARNING) << msg;
- return;
- }
+ TxnInfoPB txn_info;
+ if (!txn_info.ParseFromString(info_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse txn_info, db_id=" << db_id << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
- // TODO: do more check like txn state, 2PC etc.
- DCHECK(txn_info.txn_id() == txn_id);
- if (txn_info.status() == TxnStatusPB::TXN_STATUS_ABORTED) {
- code = MetaServiceCode::TXN_ALREADY_ABORTED;
- ss << "transaction [" << txn_id << "] is already aborted, db_id=" << db_id;
- msg = ss.str();
- LOG(WARNING) << msg;
- return;
- }
+ // TODO: do more check like txn state, 2PC etc.
+ DCHECK(txn_info.txn_id() == txn_id);
+ if (txn_info.status() == TxnStatusPB::TXN_STATUS_ABORTED) {
+ code = MetaServiceCode::TXN_ALREADY_ABORTED;
+ ss << "transaction [" << txn_id << "] is already aborted, db_id=" << db_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
- if (txn_info.status() == TxnStatusPB::TXN_STATUS_VISIBLE) {
- if (request->has_is_2pc() && request->is_2pc()) {
- code = MetaServiceCode::TXN_ALREADY_VISIBLE;
- ss << "transaction [" << txn_id << "] is already visible, not pre-committed.";
+ if (txn_info.status() == TxnStatusPB::TXN_STATUS_VISIBLE) {
+ if (request->has_is_2pc() && request->is_2pc()) {
+ code = MetaServiceCode::TXN_ALREADY_VISIBLE;
+ ss << "transaction [" << txn_id << "] is already visible, not pre-committed.";
+ msg = ss.str();
+ LOG(INFO) << msg;
+ response->mutable_txn_info()->CopyFrom(txn_info);
+ return;
+ }
+ code = MetaServiceCode::OK;
+ ss << "transaction is already visible: db_id=" << db_id << " txn_id=" << txn_id;
msg = ss.str();
LOG(INFO) << msg;
response->mutable_txn_info()->CopyFrom(txn_info);
return;
}
- code = MetaServiceCode::OK;
- ss << "transaction is already visible: db_id=" << db_id << " txn_id=" << txn_id;
- msg = ss.str();
- LOG(INFO) << msg;
- response->mutable_txn_info()->CopyFrom(txn_info);
- return;
- }
- if (request->has_is_2pc() && request->is_2pc() &&
- txn_info.status() == TxnStatusPB::TXN_STATUS_PREPARED) {
- code = MetaServiceCode::TXN_INVALID_STATUS;
- ss << "transaction is prepare, not pre-committed: db_id=" << db_id << " txn_id" << txn_id;
- msg = ss.str();
- LOG(WARNING) << msg;
- return;
- }
+ if (request->has_is_2pc() && request->is_2pc() &&
+ txn_info.status() == TxnStatusPB::TXN_STATUS_PREPARED) {
+ code = MetaServiceCode::TXN_INVALID_STATUS;
+ ss << "transaction is prepare, not pre-committed: db_id=" << db_id << " txn_id"
+ << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
- LOG(INFO) << "txn_id=" << txn_id << " txn_info=" << txn_info.ShortDebugString();
+ LOG(INFO) << "txn_id=" << txn_id << " txn_info=" << txn_info.ShortDebugString();
- // Prepare rowset meta and new_versions
+ // Prepare rowset meta and new_versions
+ // Read tablet indexes in batch.
+ std::vector<std::string> tablet_idx_keys;
+ for (auto& [_, i] : tmp_rowsets_meta) {
+ tablet_idx_keys.push_back(meta_tablet_idx_key({instance_id, i.tablet_id()}));
+ }
+ std::vector<std::optional<std::string>> tablet_idx_values;
+ err = txn->batch_get(&tablet_idx_values, tablet_idx_keys,
+ Transaction::BatchGetOptions(false));
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get tablet table index ids, err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+
+ size_t total_rowsets = tmp_rowsets_meta.size();
+ // tablet_id -> {table/index/partition}_id
+ std::unordered_map<int64_t, TabletIndexPB> tablet_ids;
+ // table_id -> tablets_ids
+ std::unordered_map<int64_t, std::vector<int64_t>> table_id_tablet_ids;
+ for (size_t i = 0; i < total_rowsets; i++) {
+ uint64_t tablet_id = tmp_rowsets_meta[i].second.tablet_id();
+ if (!tablet_idx_values[i].has_value()) [[unlikely]] {
+ // The value must existed
+ code = MetaServiceCode::KV_TXN_GET_ERR;
+ ss << "failed to get tablet table index ids, err=not found"
+ << " tablet_id=" << tablet_id << " key=" << hex(tablet_idx_keys[i]);
+ msg = ss.str();
+ LOG(WARNING) << msg << " err=" << err << " txn_id=" << txn_id;
+ return;
+ }
+ if (!tablet_ids[tablet_id].ParseFromString(tablet_idx_values[i].value())) [[unlikely]] {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "malformed tablet index value tablet_id=" << tablet_id
+ << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+ table_id_tablet_ids[tablet_ids[tablet_id].table_id()].push_back(tablet_id);
+ VLOG_DEBUG << "tablet_id:" << tablet_id
+ << " value:" << tablet_ids[tablet_id].ShortDebugString();
+ }
+
+ tablet_idx_keys.clear();
+ tablet_idx_values.clear();
+
+ // {table/partition} -> version
+ std::unordered_map<std::string, uint64_t> new_versions;
+ std::vector<std::string> version_keys;
+ for (auto& [_, i] : tmp_rowsets_meta) {
+ int64_t tablet_id = i.tablet_id();
+ int64_t table_id = tablet_ids[tablet_id].table_id();
+ int64_t partition_id = i.partition_id();
+ std::string ver_key =
+ partition_version_key({instance_id, db_id, table_id, partition_id});
+ if (new_versions.count(ver_key) == 0) {
+ new_versions.insert({ver_key, 0});
+ version_keys.push_back(std::move(ver_key));
+ }
+ }
+ std::vector<std::optional<std::string>> version_values;
+ err = txn->batch_get(&version_values, version_keys);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get partition versions, err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+ size_t total_versions = version_keys.size();
+ for (size_t i = 0; i < total_versions; i++) {
+ int64_t version;
+ if (version_values[i].has_value()) {
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(version_values[i].value())) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse version pb txn_id=" << txn_id
+ << " key=" << hex(version_keys[i]);
+ msg = ss.str();
+ return;
+ }
+ if (version_pb.pending_txn_ids_size() > 0) {
+ DCHECK(version_pb.pending_txn_ids_size() == 1);
+ last_pending_txn_id = version_pb.pending_txn_ids(0);
+ DCHECK(last_pending_txn_id > 0);
+ break;
+ }
+ version = version_pb.version();
+ } else {
+ version = 1;
+ }
+ new_versions[version_keys[i]] = version + 1;
+ last_pending_txn_id = 0;
+ }
+ version_keys.clear();
+ version_values.clear();
+
+ if (last_pending_txn_id > 0) {
+ txn.reset();
+ std::shared_ptr<TxnLazyCommitTask> task =
+ txn_lazy_committer->submit(instance_id, last_pending_txn_id);
+
+ std::tie(code, msg) = task->wait();
+ if (code != MetaServiceCode::OK) {
+ LOG(WARNING) << "advance_last_txn failed last_txn=" << last_pending_txn_id
+ << " code=" << code << "msg=" << msg;
+ return;
+ }
+ last_pending_txn_id = 0;
+ continue;
+ }
+
+ std::vector<std::pair<std::string, std::string>> rowsets;
+ std::unordered_map<int64_t, TabletStats> tablet_stats; // tablet_id -> stats
+ rowsets.reserve(tmp_rowsets_meta.size());
+ for (auto& [_, i] : tmp_rowsets_meta) {
+ int64_t tablet_id = i.tablet_id();
+ int64_t table_id = tablet_ids[tablet_id].table_id();
+ int64_t partition_id = i.partition_id();
+ std::string ver_key =
+ partition_version_key({instance_id, db_id, table_id, partition_id});
+ if (new_versions[ver_key] == 0) [[unlikely]] {
+ // it is impossible.
+ code = MetaServiceCode::UNDEFINED_ERR;
+ ss << "failed to get partition version key, the target version not exists in "
+ "new_versions."
+ << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(ERROR) << msg;
+ return;
+ }
+
+ // Update rowset version
+ int64_t new_version = new_versions[ver_key];
+ i.set_start_version(new_version);
+ i.set_end_version(new_version);
+
+ std::string key = meta_rowset_key({instance_id, tablet_id, i.end_version()});
+ std::string val;
+ if (!i.SerializeToString(&val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize rowset_meta, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+ rowsets.emplace_back(std::move(key), std::move(val));
+
+ // Accumulate affected rows
+ auto& stats = tablet_stats[tablet_id];
+ stats.data_size += i.data_disk_size();
+ stats.num_rows += i.num_rows();
+ ++stats.num_rowsets;
+ stats.num_segs += i.num_segments();
+ } // for tmp_rowsets_meta
+
+ // process mow table, check lock and remove pending key
+ std::vector<std::string> lock_keys;
+ lock_keys.reserve(request->mow_table_ids().size());
+ for (auto table_id : request->mow_table_ids()) {
+ lock_keys.push_back(meta_delete_bitmap_update_lock_key({instance_id, table_id, -1}));
+ }
+ std::vector<std::optional<std::string>> lock_values;
+ err = txn->batch_get(&lock_values, lock_keys);
+ if (err != TxnErrorCode::TXN_OK) {
+ ss << "failed to get delete bitmap update lock key info, instance_id=" << instance_id
+ << " err=" << err;
+ msg = ss.str();
+ code = cast_as<ErrCategory::READ>(err);
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+ size_t total_locks = lock_keys.size();
+ for (size_t i = 0; i < total_locks; i++) {
+ int64_t table_id = request->mow_table_ids(i);
+ // When the key does not exist, it means the lock has been acquired
+ // by another transaction and successfully committed.
+ if (!lock_values[i].has_value()) {
+ ss << "get delete bitmap update lock info, lock is expired"
+ << " table_id=" << table_id << " key=" << hex(lock_keys[i]);
+ code = MetaServiceCode::LOCK_EXPIRED;
+ msg = ss.str();
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+
+ DeleteBitmapUpdateLockPB lock_info;
+ if (!lock_info.ParseFromString(lock_values[i].value())) [[unlikely]] {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ msg = "failed to parse DeleteBitmapUpdateLockPB";
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+ if (lock_info.lock_id() != request->txn_id()) {
+ msg = "lock is expired";
+ code = MetaServiceCode::LOCK_EXPIRED;
+ return;
+ }
+ txn->remove(lock_keys[i]);
+ LOG(INFO) << "xxx remove delete bitmap lock, lock_key=" << hex(lock_keys[i])
+ << " txn_id=" << txn_id;
+
+ for (auto tablet_id : table_id_tablet_ids[table_id]) {
+ std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id});
+ txn->remove(pending_key);
+ LOG(INFO) << "xxx remove delete bitmap pending key, pending_key="
+ << hex(pending_key) << " txn_id=" << txn_id;
+ }
+ }
+ lock_keys.clear();
+ lock_values.clear();
+
+ // Save rowset meta
+ for (auto& i : rowsets) {
+ size_t rowset_size = i.first.size() + i.second.size();
+ txn->put(i.first, i.second);
+ LOG(INFO) << "xxx put rowset_key=" << hex(i.first) << " txn_id=" << txn_id
+ << " rowset_size=" << rowset_size;
+ }
+
+ // Save versions
+ int64_t version_update_time_ms =
+ duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+ response->set_version_update_time_ms(version_update_time_ms);
+ for (auto& i : new_versions) {
+ std::string ver_val;
+ VersionPB version_pb;
+ version_pb.set_version(i.second);
+ version_pb.set_update_time_ms(version_update_time_ms);
+ if (!version_pb.SerializeToString(&ver_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize version_pb when saving, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+
+ txn->put(i.first, ver_val);
+ LOG(INFO) << "xxx put partition_version_key=" << hex(i.first) << " version:" << i.second
+ << " txn_id=" << txn_id << " update_time=" << version_update_time_ms;
+
+ std::string_view ver_key = i.first;
+ ver_key.remove_prefix(1); // Remove key space
+ // PartitionVersionKeyInfo {instance_id, db_id, table_id, partition_id}
+ std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
+ int ret = decode_key(&ver_key, &out);
+ if (ret != 0) [[unlikely]] {
+ // decode version key error means this is something wrong,
+ // we can not continue this txn
+ LOG(WARNING) << "failed to decode key, ret=" << ret << " key=" << hex(ver_key);
+ code = MetaServiceCode::UNDEFINED_ERR;
+ msg = "decode version key error";
+ return;
+ }
+
+ int64_t table_id = std::get<int64_t>(std::get<0>(out[4]));
+ int64_t partition_id = std::get<int64_t>(std::get<0>(out[5]));
+ VLOG_DEBUG << " table_id=" << table_id << " partition_id=" << partition_id;
+
+ response->add_table_ids(table_id);
+ response->add_partition_ids(partition_id);
+ response->add_versions(i.second);
+ }
+
+ // Save table versions
+ for (auto& i : table_id_tablet_ids) {
+ std::string ver_key = table_version_key({instance_id, db_id, i.first});
+ txn->atomic_add(ver_key, 1);
+ LOG(INFO) << "xxx atomic add table_version_key=" << hex(ver_key)
+ << " txn_id=" << txn_id;
+ }
+
+ LOG(INFO) << " before update txn_info=" << txn_info.ShortDebugString();
+
+ // Update txn_info
+ txn_info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
+
+ auto now_time = system_clock::now();
+ uint64_t commit_time = duration_cast<milliseconds>(now_time.time_since_epoch()).count();
+ if ((txn_info.prepare_time() + txn_info.timeout_ms()) < commit_time) {
+ code = MetaServiceCode::UNDEFINED_ERR;
+ msg = fmt::format("txn is expired, not allow to commit txn_id={}", txn_id);
+ LOG(INFO) << msg << " prepare_time=" << txn_info.prepare_time()
+ << " timeout_ms=" << txn_info.timeout_ms() << " commit_time=" << commit_time;
+ return;
+ }
+ txn_info.set_commit_time(commit_time);
+ txn_info.set_finish_time(commit_time);
+ if (request->has_commit_attachment()) {
+ txn_info.mutable_commit_attachment()->CopyFrom(request->commit_attachment());
+ }
+ LOG(INFO) << "after update txn_info=" << txn_info.ShortDebugString();
+ info_val.clear();
+ if (!txn_info.SerializeToString(&info_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize txn_info when saving, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+ txn->put(info_key, info_val);
+ LOG(INFO) << "xxx put info_key=" << hex(info_key) << " txn_id=" << txn_id;
+
+ // Update stats of affected tablet
+ for (auto& [tablet_id, stats] : tablet_stats) {
+ DCHECK(tablet_ids.count(tablet_id));
+ auto& tablet_idx = tablet_ids[tablet_id];
+ StatsTabletKeyInfo info {instance_id, tablet_idx.table_id(), tablet_idx.index_id(),
+ tablet_idx.partition_id(), tablet_id};
+ update_tablet_stats(info, stats, txn, code, msg);
+ if (code != MetaServiceCode::OK) return;
+ }
+ // Remove tmp rowset meta
+ for (auto& [k, _] : tmp_rowsets_meta) {
+ txn->remove(k);
+ LOG(INFO) << "xxx remove tmp_rowset_key=" << hex(k) << " txn_id=" << txn_id;
+ }
+
+ const std::string running_key = txn_running_key({instance_id, db_id, txn_id});
+ LOG(INFO) << "xxx remove running_key=" << hex(running_key) << " txn_id=" << txn_id;
+ txn->remove(running_key);
+
+ std::string recycle_val;
+ std::string recycle_key = recycle_txn_key({instance_id, db_id, txn_id});
+ RecycleTxnPB recycle_pb;
+ recycle_pb.set_creation_time(commit_time);
+ recycle_pb.set_label(txn_info.label());
+
+ if (!recycle_pb.SerializeToString(&recycle_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize recycle_pb, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+ txn->put(recycle_key, recycle_val);
+
+ if (txn_info.load_job_source_type() ==
+ LoadJobSourceTypePB::LOAD_JOB_SRC_TYPE_ROUTINE_LOAD_TASK) {
+ put_routine_load_progress(code, msg, instance_id, request, txn.get(), db_id);
+ }
+
+ LOG(INFO) << "xxx commit_txn put recycle_key key=" << hex(recycle_key)
+ << " txn_id=" << txn_id;
+ LOG(INFO) << "commit_txn put_size=" << txn->put_bytes()
+ << " del_size=" << txn->delete_bytes() << " num_put_keys=" << txn->num_put_keys()
+ << " num_del_keys=" << txn->num_del_keys()
+ << " txn_size=" << txn->approximate_bytes() << " txn_id=" << txn_id;
+
+ // Finally we are done...
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ return;
+ }
+
+ // calculate table stats from tablets stats
+ std::map<int64_t /*table_id*/, TableStats> table_stats;
+ std::vector<int64_t> base_tablet_ids(request->base_tablet_ids().begin(),
+ request->base_tablet_ids().end());
+ calc_table_stats(tablet_ids, tablet_stats, table_stats, base_tablet_ids);
+ for (const auto& pair : table_stats) {
+ TableStatsPB* stats_pb = response->add_table_stats();
+ auto table_id = pair.first;
+ auto stats = pair.second;
+ get_pb_from_tablestats(stats, stats_pb);
+ stats_pb->set_table_id(table_id);
+ VLOG_DEBUG << "Add TableStats to CommitTxnResponse. txn_id=" << txn_id
+ << " table_id=" << table_id
+ << " updated_row_count=" << stats_pb->updated_row_count();
+ }
+ response->mutable_txn_info()->CopyFrom(txn_info);
+ break;
+ } while (true);
+} // end commit_txn_immediately
+
+void get_tablet_indexes(
+ const std::string& instance_id, int64_t txn_id,
+ const std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>& tmp_rowsets_meta,
+ std::unique_ptr<Transaction>& txn, MetaServiceCode& code, std::string& msg,
+ std::unordered_map<int64_t, TabletIndexPB>* tablet_ids,
+ std::unordered_map<int64_t, std::vector<int64_t>>* table_id_tablet_ids,
+ bool* need_repair_tablet_idx) {
// Read tablet indexes in batch.
+ std::stringstream ss;
std::vector<std::string> tablet_idx_keys;
for (auto& [_, i] : tmp_rowsets_meta) {
tablet_idx_keys.push_back(meta_tablet_idx_key({instance_id, i.tablet_id()}));
}
std::vector<std::optional<std::string>> tablet_idx_values;
- err = txn->batch_get(&tablet_idx_values, tablet_idx_keys, Transaction::BatchGetOptions(false));
+ TxnErrorCode err =
+ txn->batch_get(&tablet_idx_values, tablet_idx_keys, Transaction::BatchGetOptions(true));
if (err != TxnErrorCode::TXN_OK) {
code = cast_as<ErrCategory::READ>(err);
ss << "failed to get tablet table index ids, err=" << err;
@@ -1021,12 +1410,7 @@ void commit_txn_immediately(
return;
}
- size_t total_rowsets = tmp_rowsets_meta.size();
- // tablet_id -> {table/index/partition}_id
- std::unordered_map<int64_t, TabletIndexPB> tablet_ids;
- // table_id -> tablets_ids
- std::unordered_map<int64_t, std::vector<int64_t>> table_id_tablet_ids;
- for (size_t i = 0; i < total_rowsets; i++) {
+ for (size_t i = 0; i < tmp_rowsets_meta.size(); i++) {
uint64_t tablet_id = tmp_rowsets_meta[i].second.tablet_id();
if (!tablet_idx_values[i].has_value()) [[unlikely]] {
// The value must existed
@@ -1037,353 +1421,488 @@ void commit_txn_immediately(
LOG(WARNING) << msg << " err=" << err << " txn_id=" << txn_id;
return;
}
- if (!tablet_ids[tablet_id].ParseFromString(tablet_idx_values[i].value())) [[unlikely]] {
+ if (!(*tablet_ids)[tablet_id].ParseFromString(tablet_idx_values[i].value())) [[unlikely]] {
code = MetaServiceCode::PROTOBUF_PARSE_ERR;
ss << "malformed tablet index value tablet_id=" << tablet_id << " txn_id=" << txn_id;
msg = ss.str();
LOG(WARNING) << msg;
return;
}
- table_id_tablet_ids[tablet_ids[tablet_id].table_id()].push_back(tablet_id);
+ if (!(*tablet_ids)[tablet_id].has_db_id()) {
+ *need_repair_tablet_idx = true;
+ }
+ (*table_id_tablet_ids)[(*tablet_ids)[tablet_id].table_id()].push_back(tablet_id);
VLOG_DEBUG << "tablet_id:" << tablet_id
- << " value:" << tablet_ids[tablet_id].ShortDebugString();
+ << " value:" << (*tablet_ids)[tablet_id].ShortDebugString();
}
tablet_idx_keys.clear();
tablet_idx_values.clear();
+}
- // {table/partition} -> version
- std::unordered_map<std::string, uint64_t> new_versions;
- std::vector<std::string> version_keys;
+// rewrite TabletIndexPB for fill db_id, in case of historical reasons
+// TabletIndexPB missing db_id
+void repair_tablet_index(
+ std::shared_ptr<TxnKv>& txn_kv, MetaServiceCode& code, std::string& msg,
+ const std::string& instance_id, int64_t db_id, int64_t txn_id,
+ const std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>& tmp_rowsets_meta) {
+ std::stringstream ss;
+ std::vector<std::string> tablet_idx_keys;
for (auto& [_, i] : tmp_rowsets_meta) {
- int64_t tablet_id = i.tablet_id();
- int64_t table_id = tablet_ids[tablet_id].table_id();
- int64_t partition_id = i.partition_id();
- std::string ver_key = partition_version_key({instance_id, db_id, table_id, partition_id});
- if (new_versions.count(ver_key) == 0) {
- new_versions.insert({ver_key, 0});
- version_keys.push_back(std::move(ver_key));
- }
- }
- std::vector<std::optional<std::string>> version_values;
- err = txn->batch_get(&version_values, version_keys);
- if (err != TxnErrorCode::TXN_OK) {
- code = cast_as<ErrCategory::READ>(err);
- ss << "failed to get partition versions, err=" << err;
- msg = ss.str();
- LOG(WARNING) << msg << " txn_id=" << txn_id;
- return;
+ tablet_idx_keys.push_back(meta_tablet_idx_key({instance_id, i.tablet_id()}));
}
- size_t total_versions = version_keys.size();
- for (size_t i = 0; i < total_versions; i++) {
- int64_t version;
- if (version_values[i].has_value()) {
- VersionPB version_pb;
- if (!version_pb.ParseFromString(version_values[i].value())) {
+
+ for (size_t i = 0; i < tablet_idx_keys.size(); i += config::max_tablet_index_num_per_batch) {
+ size_t end = (i + config::max_tablet_index_num_per_batch) > tablet_idx_keys.size()
+ ? tablet_idx_keys.size()
+ : i + config::max_tablet_index_num_per_batch;
+ const std::vector<std::string> sub_tablet_idx_keys(tablet_idx_keys.begin() + i,
+ tablet_idx_keys.begin() + end);
+
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ std::vector<std::optional<std::string>> tablet_idx_values;
+ // batch get snapshot is false
+ err = txn->batch_get(&tablet_idx_values, sub_tablet_idx_keys,
+ Transaction::BatchGetOptions(false));
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get tablet table index ids, err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+ DCHECK(tablet_idx_values.size() <= config::max_tablet_index_num_per_batch);
+
+ for (size_t j = 0; j < sub_tablet_idx_keys.size(); j++) {
+ if (!tablet_idx_values[j].has_value()) [[unlikely]] {
+ // The value must existed
+ code = MetaServiceCode::KV_TXN_GET_ERR;
+ ss << "failed to get tablet table index ids, err=not found"
+ << " key=" << hex(tablet_idx_keys[j]);
+ msg = ss.str();
+ LOG(WARNING) << msg << " err=" << err << " txn_id=" << txn_id;
+ return;
+ }
+ TabletIndexPB tablet_idx_pb;
+ if (!tablet_idx_pb.ParseFromString(tablet_idx_values[j].value())) [[unlikely]] {
code = MetaServiceCode::PROTOBUF_PARSE_ERR;
- ss << "failed to parse version pb txn_id=" << txn_id
- << " key=" << hex(version_keys[i]);
+ ss << "malformed tablet index value key=" << hex(tablet_idx_keys[j])
+ << " txn_id=" << txn_id;
msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ if (!tablet_idx_pb.has_db_id()) {
+ tablet_idx_pb.set_db_id(db_id);
+ std::string idx_val;
+ if (!tablet_idx_pb.SerializeToString(&idx_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize tablet index value key=" << hex(tablet_idx_keys[j])
+ << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+ txn->put(sub_tablet_idx_keys[j], idx_val);
+ LOG(INFO) << " repaire tablet index txn_id=" << txn_id
+ << " tablet_idx_pb:" << tablet_idx_pb.ShortDebugString()
+ << " key=" << hex(sub_tablet_idx_keys[j]);
+ }
+ }
+
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+ }
+}
+
+void commit_txn_eventually(
+ const CommitTxnRequest* request, CommitTxnResponse* response,
+ std::shared_ptr<TxnKv>& txn_kv, std::shared_ptr<TxnLazyCommitter>& txn_lazy_committer,
+ MetaServiceCode& code, std::string& msg, const std::string& instance_id, int64_t db_id,
+ const std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>& tmp_rowsets_meta) {
+ std::stringstream ss;
+ TxnErrorCode err = TxnErrorCode::TXN_OK;
+ int64_t txn_id = request->txn_id();
+
+ do {
+ int64_t last_pending_txn_id = 0;
+ std::unique_ptr<Transaction> txn;
+ err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ // tablet_id -> {table/index/partition}_id
+ std::unordered_map<int64_t, TabletIndexPB> tablet_ids;
+ // table_id -> tablets_ids
+ std::unordered_map<int64_t, std::vector<int64_t>> table_id_tablet_ids;
+ bool need_repair_tablet_idx = false;
+ get_tablet_indexes(instance_id, txn_id, tmp_rowsets_meta, txn, code, msg, &tablet_ids,
+ &table_id_tablet_ids, &need_repair_tablet_idx);
+ if (code != MetaServiceCode::OK) {
+ LOG(WARNING) << "get_tablet_indexes failed, txn_id=" << txn_id << " code=" << code;
+ return;
+ }
+
+ if (need_repair_tablet_idx) {
+ txn.reset();
+ repair_tablet_index(txn_kv, code, msg, instance_id, db_id, txn_id, tmp_rowsets_meta);
+ if (code != MetaServiceCode::OK) {
+ LOG(WARNING) << "repair_tablet_index failed, txn_id=" << txn_id << " code=" << code;
return;
}
- version = version_pb.version();
- } else {
- version = 1;
+ continue;
+ }
+
+ // <partition_version_key, version>
+ std::unordered_map<std::string, uint64_t> new_versions;
+ std::vector<std::string> version_keys;
+ for (auto& [_, i] : tmp_rowsets_meta) {
+ int64_t tablet_id = i.tablet_id();
+ int64_t table_id = tablet_ids[tablet_id].table_id();
+ int64_t partition_id = i.partition_id();
+ std::string ver_key =
+ partition_version_key({instance_id, db_id, table_id, partition_id});
+ if (new_versions.count(ver_key) == 0) {
+ new_versions.insert({ver_key, 0});
+ version_keys.push_back(std::move(ver_key));
+ }
}
- new_versions[version_keys[i]] = version + 1;
- }
- version_keys.clear();
- version_values.clear();
- std::vector<std::pair<std::string, std::string>> rowsets;
- std::unordered_map<int64_t, TabletStats> tablet_stats; // tablet_id -> stats
- rowsets.reserve(tmp_rowsets_meta.size());
- for (auto& [_, i] : tmp_rowsets_meta) {
- int64_t tablet_id = i.tablet_id();
- int64_t table_id = tablet_ids[tablet_id].table_id();
- int64_t partition_id = i.partition_id();
- std::string ver_key = partition_version_key({instance_id, db_id, table_id, partition_id});
- if (new_versions[ver_key] == 0) [[unlikely]] {
- // it is impossible.
- code = MetaServiceCode::UNDEFINED_ERR;
- ss << "failed to get partition version key, the target version not exists in "
- "new_versions."
- << " txn_id=" << txn_id;
+ std::vector<std::optional<std::string>> version_values;
+ err = txn->batch_get(&version_values, version_keys);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get partition versions, err=" << err;
msg = ss.str();
- LOG(ERROR) << msg;
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
return;
}
- // Update rowset version
- int64_t new_version = new_versions[ver_key];
- i.set_start_version(new_version);
- i.set_end_version(new_version);
+ for (size_t i = 0; i < version_keys.size(); i++) {
+ int64_t version;
+ if (version_values[i].has_value()) {
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(version_values[i].value())) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse version pb txn_id=" << txn_id
+ << " key=" << hex(version_keys[i]);
+ msg = ss.str();
+ return;
+ }
+ if (version_pb.pending_txn_ids_size() > 0) {
+ DCHECK(version_pb.pending_txn_ids_size() == 1);
+ last_pending_txn_id = version_pb.pending_txn_ids(0);
+ DCHECK(last_pending_txn_id > 0);
+ break;
+ }
+ version = version_pb.version();
+ } else {
+ version = 1;
+ }
+ new_versions[version_keys[i]] = version;
+ last_pending_txn_id = 0;
+ }
+
+ if (last_pending_txn_id > 0) {
+ txn.reset();
+ std::shared_ptr<TxnLazyCommitTask> task =
+ txn_lazy_committer->submit(instance_id, last_pending_txn_id);
- std::string key = meta_rowset_key({instance_id, tablet_id, i.end_version()});
- std::string val;
- if (!i.SerializeToString(&val)) {
- code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
- ss << "failed to serialize rowset_meta, txn_id=" << txn_id;
+ std::tie(code, msg) = task->wait();
+ if (code != MetaServiceCode::OK) {
+ LOG(WARNING) << "advance_last_txn failed last_txn=" << last_pending_txn_id
+ << " code=" << code << "msg=" << msg;
+ return;
+ }
+
+ last_pending_txn_id = 0;
+ // there maybe concurrent commit_txn_eventually, so we need continue to make sure
+ // partition versionPB has no txn_id
+ continue;
+ }
+
+ std::string info_val;
+ const std::string info_key = txn_info_key({instance_id, db_id, txn_id});
+ err = txn->get(info_key, &info_val);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ ss << "failed to get txn_info, db_id=" << db_id << " txn_id=" << txn_id
+ << " err=" << err;
msg = ss.str();
+ LOG(WARNING) << msg;
return;
}
- rowsets.emplace_back(std::move(key), std::move(val));
- // Accumulate affected rows
- auto& stats = tablet_stats[tablet_id];
- stats.data_size += i.data_disk_size();
- stats.num_rows += i.num_rows();
- ++stats.num_rowsets;
- stats.num_segs += i.num_segments();
- } // for tmp_rowsets_meta
-
- // process mow table, check lock and remove pending key
- std::vector<std::string> lock_keys;
- lock_keys.reserve(request->mow_table_ids().size());
- for (auto table_id : request->mow_table_ids()) {
- lock_keys.push_back(meta_delete_bitmap_update_lock_key({instance_id, table_id, -1}));
- }
- std::vector<std::optional<std::string>> lock_values;
- err = txn->batch_get(&lock_values, lock_keys);
- if (err != TxnErrorCode::TXN_OK) {
- ss << "failed to get delete bitmap update lock key info, instance_id=" << instance_id
- << " err=" << err;
- msg = ss.str();
- code = cast_as<ErrCategory::READ>(err);
- LOG(WARNING) << msg << " txn_id=" << txn_id;
- return;
- }
- size_t total_locks = lock_keys.size();
- for (size_t i = 0; i < total_locks; i++) {
- int64_t table_id = request->mow_table_ids(i);
- // When the key does not exist, it means the lock has been acquired
- // by another transaction and successfully committed.
- if (!lock_values[i].has_value()) {
- ss << "get delete bitmap update lock info, lock is expired"
- << " table_id=" << table_id << " key=" << hex(lock_keys[i]);
- code = MetaServiceCode::LOCK_EXPIRED;
+ TxnInfoPB txn_info;
+ if (!txn_info.ParseFromString(info_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse txn_info, db_id=" << db_id << " txn_id=" << txn_id;
msg = ss.str();
- LOG(WARNING) << msg << " txn_id=" << txn_id;
+ LOG(WARNING) << msg;
return;
}
+ LOG(INFO) << "txn_id=" << txn_id << " txn_info=" << txn_info.ShortDebugString();
- DeleteBitmapUpdateLockPB lock_info;
- if (!lock_info.ParseFromString(lock_values[i].value())) [[unlikely]] {
- code = MetaServiceCode::PROTOBUF_PARSE_ERR;
- msg = "failed to parse DeleteBitmapUpdateLockPB";
- LOG(WARNING) << msg << " txn_id=" << txn_id;
+ DCHECK(txn_info.txn_id() == txn_id);
+ if (txn_info.status() == TxnStatusPB::TXN_STATUS_ABORTED) {
+ code = MetaServiceCode::TXN_ALREADY_ABORTED;
+ ss << "transaction is already aborted: db_id=" << db_id << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
return;
}
- if (lock_info.lock_id() != request->txn_id()) {
- msg = "lock is expired";
- code = MetaServiceCode::LOCK_EXPIRED;
+
+ if (txn_info.status() == TxnStatusPB::TXN_STATUS_VISIBLE) {
+ if (request->has_is_2pc() && request->is_2pc()) {
+ code = MetaServiceCode::TXN_ALREADY_VISIBLE;
+ ss << "transaction [" << txn_id << "] is already visible, not pre-committed.";
+ msg = ss.str();
+ LOG(INFO) << msg;
+ response->mutable_txn_info()->CopyFrom(txn_info);
+ return;
+ }
+ code = MetaServiceCode::OK;
+ ss << "transaction is already visible: db_id=" << db_id << " txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(INFO) << msg;
+ response->mutable_txn_info()->CopyFrom(txn_info);
return;
}
- txn->remove(lock_keys[i]);
- LOG(INFO) << "xxx remove delete bitmap lock, lock_key=" << hex(lock_keys[i])
- << " txn_id=" << txn_id;
- for (auto tablet_id : table_id_tablet_ids[table_id]) {
- std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id});
- txn->remove(pending_key);
- LOG(INFO) << "xxx remove delete bitmap pending key, pending_key=" << hex(pending_key)
- << " txn_id=" << txn_id;
+ if (request->has_is_2pc() && request->is_2pc() &&
+ txn_info.status() == TxnStatusPB::TXN_STATUS_PREPARED) {
+ code = MetaServiceCode::TXN_INVALID_STATUS;
+ ss << "transaction is prepare, not pre-committed: db_id=" << db_id << " txn_id"
+ << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
}
- }
- lock_keys.clear();
- lock_values.clear();
- // Save rowset meta
- for (auto& i : rowsets) {
- size_t rowset_size = i.first.size() + i.second.size();
- txn->put(i.first, i.second);
- LOG(INFO) << "xxx put rowset_key=" << hex(i.first) << " txn_id=" << txn_id
- << " rowset_size=" << rowset_size;
- }
+ auto now_time = system_clock::now();
+ uint64_t commit_time = duration_cast<milliseconds>(now_time.time_since_epoch()).count();
+ if ((txn_info.prepare_time() + txn_info.timeout_ms()) < commit_time) {
+ code = MetaServiceCode::UNDEFINED_ERR;
+ msg = fmt::format("txn is expired, not allow to commit txn_id={}", txn_id);
+ LOG(INFO) << msg << " prepare_time=" << txn_info.prepare_time()
+ << " timeout_ms=" << txn_info.timeout_ms() << " commit_time=" << commit_time;
+ return;
+ }
+ txn_info.set_commit_time(commit_time);
+ txn_info.set_finish_time(commit_time);
+ if (request->has_commit_attachment()) {
+ txn_info.mutable_commit_attachment()->CopyFrom(request->commit_attachment());
+ }
+ DCHECK(txn_info.status() != TxnStatusPB::TXN_STATUS_COMMITTED);
+ // set status TXN_STATUS_COMMITTED not TXN_STATUS_VISIBLE !!!
+ // lazy commit task will advance txn to make txn visible
+ txn_info.set_status(TxnStatusPB::TXN_STATUS_COMMITTED);
- // Save versions
- int64_t version_update_time_ms =
- duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
- response->set_version_update_time_ms(version_update_time_ms);
- for (auto& i : new_versions) {
- std::string ver_val;
- VersionPB version_pb;
- version_pb.set_version(i.second);
- version_pb.set_update_time_ms(version_update_time_ms);
- if (!version_pb.SerializeToString(&ver_val)) {
+ LOG(INFO) << "after update txn_id= " << txn_id
+ << " txn_info=" << txn_info.ShortDebugString();
+ info_val.clear();
+ if (!txn_info.SerializeToString(&info_val)) {
code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
- ss << "failed to serialize version_pb when saving, txn_id=" << txn_id;
+ ss << "failed to serialize txn_info when saving, txn_id=" << txn_id;
msg = ss.str();
return;
}
- txn->put(i.first, ver_val);
- LOG(INFO) << "xxx put partition_version_key=" << hex(i.first) << " version:" << i.second
- << " txn_id=" << txn_id << " update_time=" << version_update_time_ms;
+ txn->put(info_key, info_val);
+ LOG(INFO) << "put info_key=" << hex(info_key) << " txn_id=" << txn_id;
- std::string_view ver_key = i.first;
- ver_key.remove_prefix(1); // Remove key space
- // PartitionVersionKeyInfo {instance_id, db_id, table_id, partition_id}
- std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
- int ret = decode_key(&ver_key, &out);
- if (ret != 0) [[unlikely]] {
- // decode version key error means this is something wrong,
- // we can not continue this txn
- LOG(WARNING) << "failed to decode key, ret=" << ret << " key=" << hex(ver_key);
- code = MetaServiceCode::UNDEFINED_ERR;
- msg = "decode version key error";
- return;
+ if (txn_info.load_job_source_type() ==
+ LoadJobSourceTypePB::LOAD_JOB_SRC_TYPE_ROUTINE_LOAD_TASK) {
+ put_routine_load_progress(code, msg, instance_id, request, txn.get(), db_id);
}
- int64_t table_id = std::get<int64_t>(std::get<0>(out[4]));
- int64_t partition_id = std::get<int64_t>(std::get<0>(out[5]));
- VLOG_DEBUG << " table_id=" << table_id << " partition_id=" << partition_id;
+ // save versions for partition
+ int64_t version_update_time_ms =
+ duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+ response->set_version_update_time_ms(version_update_time_ms);
+ for (auto& i : new_versions) {
+ std::string ver_val;
+ VersionPB version_pb;
+ version_pb.add_pending_txn_ids(txn_id);
+ version_pb.set_update_time_ms(version_update_time_ms);
+ if (i.second > 1) {
+ version_pb.set_version(i.second);
+ }
- response->add_table_ids(table_id);
- response->add_partition_ids(partition_id);
- response->add_versions(i.second);
- }
+ if (!version_pb.SerializeToString(&ver_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize version_pb when saving, txn_id=" << txn_id
+ << " partiton_key=" << hex(i.first);
+ msg = ss.str();
+ return;
+ }
- // Save table versions
- for (auto& i : table_id_tablet_ids) {
- std::string ver_key = table_version_key({instance_id, db_id, i.first});
- txn->atomic_add(ver_key, 1);
- LOG(INFO) << "xxx atomic add table_version_key=" << hex(ver_key) << " txn_id=" << txn_id;
- }
+ txn->put(i.first, ver_val);
+ LOG(INFO) << "put partition_version_key=" << hex(i.first) << " version:" << i.second
+ << " txn_id=" << txn_id << " update_time=" << version_update_time_ms;
+
+ std::string_view ver_key = i.first;
+ ver_key.remove_prefix(1); // Remove key space
+ // PartitionVersionKeyInfo {instance_id, db_id, table_id, partition_id}
+ std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
+ int ret = decode_key(&ver_key, &out);
+ if (ret != 0) [[unlikely]] {
+ // decode version key error means this is something wrong,
+ // we can not continue this txn
+ LOG(WARNING) << "failed to decode key, ret=" << ret << " key=" << hex(ver_key);
+ code = MetaServiceCode::UNDEFINED_ERR;
+ msg = "decode version key error";
+ return;
+ }
- LOG(INFO) << " before update txn_info=" << txn_info.ShortDebugString();
+ int64_t table_id = std::get<int64_t>(std::get<0>(out[4]));
+ int64_t partition_id = std::get<int64_t>(std::get<0>(out[5]));
+ VLOG_DEBUG << "txn_id=" << txn_id << " table_id=" << table_id
+ << " partition_id=" << partition_id << " version=" << i.second;
- // Update txn_info
- txn_info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
+ response->add_table_ids(table_id);
+ response->add_partition_ids(partition_id);
+ response->add_versions(i.second + 1);
+ }
- auto now_time = system_clock::now();
- uint64_t commit_time = duration_cast<milliseconds>(now_time.time_since_epoch()).count();
- if ((txn_info.prepare_time() + txn_info.timeout_ms()) < commit_time) {
- code = MetaServiceCode::UNDEFINED_ERR;
- msg = fmt::format("txn is expired, not allow to commit txn_id={}", txn_id);
- LOG(INFO) << msg << " prepare_time=" << txn_info.prepare_time()
- << " timeout_ms=" << txn_info.timeout_ms() << " commit_time=" << commit_time;
- return;
- }
- txn_info.set_commit_time(commit_time);
- txn_info.set_finish_time(commit_time);
- if (request->has_commit_attachment()) {
- txn_info.mutable_commit_attachment()->CopyFrom(request->commit_attachment());
- }
- LOG(INFO) << "after update txn_info=" << txn_info.ShortDebugString();
- info_val.clear();
- if (!txn_info.SerializeToString(&info_val)) {
- code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
- ss << "failed to serialize txn_info when saving, txn_id=" << txn_id;
- msg = ss.str();
- return;
- }
- txn->put(info_key, info_val);
- LOG(INFO) << "xxx put info_key=" << hex(info_key) << " txn_id=" << txn_id;
+ // process mow table, check lock and remove pending key
+ std::vector<std::string> lock_keys;
+ lock_keys.reserve(request->mow_table_ids().size());
+ for (auto table_id : request->mow_table_ids()) {
+ lock_keys.push_back(meta_delete_bitmap_update_lock_key({instance_id, table_id, -1}));
+ }
+ std::vector<std::optional<std::string>> lock_values;
+ err = txn->batch_get(&lock_values, lock_keys);
+ if (err != TxnErrorCode::TXN_OK) {
+ ss << "failed to get delete bitmap update lock key info, instance_id=" << instance_id
+ << " err=" << err;
+ msg = ss.str();
+ code = cast_as<ErrCategory::READ>(err);
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
- // Update stats of affected tablet
- for (auto& [tablet_id, stats] : tablet_stats) {
- DCHECK(tablet_ids.count(tablet_id));
- auto& tablet_idx = tablet_ids[tablet_id];
- StatsTabletKeyInfo info {instance_id, tablet_idx.table_id(), tablet_idx.index_id(),
- tablet_idx.partition_id(), tablet_id};
- update_tablet_stats(info, stats, txn, code, msg);
- if (code != MetaServiceCode::OK) return;
- }
- // Remove tmp rowset meta
- for (auto& [k, _] : tmp_rowsets_meta) {
- txn->remove(k);
- LOG(INFO) << "xxx remove tmp_rowset_key=" << hex(k) << " txn_id=" << txn_id;
- }
+ for (size_t i = 0; i < lock_keys.size(); i++) {
+ int64_t table_id = request->mow_table_ids(i);
+ // When the key does not exist, it means the lock has been acquired
+ // by another transaction and successfully committed.
+ if (!lock_values[i].has_value()) {
+ ss << "get delete bitmap update lock info, lock is expired"
+ << " table_id=" << table_id << " key=" << hex(lock_keys[i]);
+ code = MetaServiceCode::LOCK_EXPIRED;
+ msg = ss.str();
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
- const std::string running_key = txn_running_key({instance_id, db_id, txn_id});
- LOG(INFO) << "xxx remove running_key=" << hex(running_key) << " txn_id=" << txn_id;
- txn->remove(running_key);
+ DeleteBitmapUpdateLockPB lock_info;
+ if (!lock_info.ParseFromString(lock_values[i].value())) [[unlikely]] {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ msg = "failed to parse DeleteBitmapUpdateLockPB";
+ LOG(WARNING) << msg << " txn_id=" << txn_id;
+ return;
+ }
+ if (lock_info.lock_id() != request->txn_id()) {
+ msg = "lock is expired";
+ code = MetaServiceCode::LOCK_EXPIRED;
+ return;
+ }
+ txn->remove(lock_keys[i]);
+ LOG(INFO) << "xxx remove delete bitmap lock, lock_key=" << hex(lock_keys[i])
+ << " txn_id=" << txn_id;
- std::string recycle_val;
- std::string recycle_key = recycle_txn_key({instance_id, db_id, txn_id});
- RecycleTxnPB recycle_pb;
- recycle_pb.set_creation_time(commit_time);
- recycle_pb.set_label(txn_info.label());
+ for (auto tablet_id : table_id_tablet_ids[table_id]) {
+ std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id});
+ txn->remove(pending_key);
+ LOG(INFO) << "xxx remove delete bitmap pending key, pending_key="
+ << hex(pending_key) << " txn_id=" << txn_id;
+ }
+ }
+ lock_keys.clear();
+ lock_values.clear();
- if (!recycle_pb.SerializeToString(&recycle_val)) {
- code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
- ss << "failed to serialize recycle_pb, txn_id=" << txn_id;
- msg = ss.str();
- return;
- }
- txn->put(recycle_key, recycle_val);
+ // Save table versions
+ for (auto& i : table_id_tablet_ids) {
+ std::string ver_key = table_version_key({instance_id, db_id, i.first});
+ txn->atomic_add(ver_key, 1);
+ LOG(INFO) << "xxx atomic add table_version_key=" << hex(ver_key)
+ << " txn_id=" << txn_id;
+ }
- if (txn_info.load_job_source_type() ==
- LoadJobSourceTypePB::LOAD_JOB_SRC_TYPE_ROUTINE_LOAD_TASK) {
- put_routine_load_progress(code, msg, instance_id, request, txn.get(), db_id);
- }
+ LOG(INFO) << "commit_txn_eventually put_size=" << txn->put_bytes()
+ << " del_size=" << txn->delete_bytes() << " num_put_keys=" << txn->num_put_keys()
+ << " num_del_keys=" << txn->num_del_keys()
+ << " txn_size=" << txn->approximate_bytes() << " txn_id=" << txn_id;
- LOG(INFO) << "xxx commit_txn put recycle_key key=" << hex(recycle_key) << " txn_id=" << txn_id;
- LOG(INFO) << "commit_txn put_size=" << txn->put_bytes() << " del_size=" << txn->delete_bytes()
- << " num_put_keys=" << txn->num_put_keys() << " num_del_keys=" << txn->num_del_keys()
- << " txn_size=" << txn->approximate_bytes() << " txn_id=" << txn_id;
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ return;
+ }
- // Finally we are done...
- err = txn->commit();
- if (err != TxnErrorCode::TXN_OK) {
- if ((err == TxnErrorCode::TXN_VALUE_TOO_LARGE ||
- err == TxnErrorCode::TXN_BYTES_TOO_LARGE) &&
- !tmp_rowsets_meta.empty()) {
- size_t max_size = 0, max_idx = 0, max_num_segments = 0,
- min_num_segments = std::numeric_limits<size_t>::max(), avg_num_segments = 0;
- for (size_t i = 0; i < tmp_rowsets_meta.size(); i++) {
- auto& [k, v] = tmp_rowsets_meta[i];
- if (v.ByteSizeLong() > max_size) {
- max_size = v.ByteSizeLong();
- max_idx = i;
- }
- if (v.num_segments() > max_num_segments) {
- max_num_segments = v.num_segments();
- }
- if (v.num_segments() < min_num_segments) {
- min_num_segments = v.num_segments();
- }
- avg_num_segments += v.num_segments();
- }
- if (!tmp_rowsets_meta.empty()) {
- avg_num_segments /= tmp_rowsets_meta.size();
- }
- LOG(WARNING) << "failed to commit kv txn"
- << ", err=" << err << ", txn_id=" << txn_id
- << ", total_rowsets=" << tmp_rowsets_meta.size()
- << ", avg_num_segments=" << avg_num_segments
- << ", min_num_segments=" << min_num_segments
- << ", max_num_segments=" << max_num_segments
- << ", largest_rowset_size=" << max_size
- << ", largest_rowset_key=" << hex(tmp_rowsets_meta[max_idx].first)
- << ", largest_rowset_value="
- << tmp_rowsets_meta[max_idx].second.ShortDebugString();
+ std::shared_ptr<TxnLazyCommitTask> task = txn_lazy_committer->submit(instance_id, txn_id);
+ std::pair<MetaServiceCode, std::string> ret = task->wait();
+ if (ret.first != MetaServiceCode::OK) {
+ LOG(WARNING) << "txn lazy commit failed txn_id=" << txn_id << " code=" << ret.first
+ << "msg=" << ret.second;
}
- code = cast_as<ErrCategory::COMMIT>(err);
- ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
- msg = ss.str();
- return;
- }
- // calculate table stats from tablets stats
- std::map<int64_t /*table_id*/, TableStats> table_stats;
- std::vector<int64_t> base_tablet_ids(request->base_tablet_ids().begin(),
- request->base_tablet_ids().end());
- calc_table_stats(tablet_ids, tablet_stats, table_stats, base_tablet_ids);
- for (const auto& pair : table_stats) {
- TableStatsPB* stats_pb = response->add_table_stats();
- auto table_id = pair.first;
- auto stats = pair.second;
- get_pb_from_tablestats(stats, stats_pb);
- stats_pb->set_table_id(table_id);
- VLOG_DEBUG << "Add TableStats to CommitTxnResponse. txn_id=" << txn_id
- << " table_id=" << table_id
- << " updated_row_count=" << stats_pb->updated_row_count();
- }
+ std::unordered_map<int64_t, TabletStats> tablet_stats; // tablet_id -> stats
+ for (auto& [_, i] : tmp_rowsets_meta) {
+ // Accumulate affected rows
+ auto& stats = tablet_stats[i.tablet_id()];
+ stats.data_size += i.data_disk_size();
+ stats.num_rows += i.num_rows();
+ ++stats.num_rowsets;
+ stats.num_segs += i.num_segments();
+ }
- response->mutable_txn_info()->CopyFrom(txn_info);
-} // end commit_txn
+ // calculate table stats from tablets stats
+ std::map<int64_t /*table_id*/, TableStats> table_stats;
+ std::vector<int64_t> base_tablet_ids(request->base_tablet_ids().begin(),
+ request->base_tablet_ids().end());
+ calc_table_stats(tablet_ids, tablet_stats, table_stats, base_tablet_ids);
+ for (const auto& pair : table_stats) {
+ TableStatsPB* stats_pb = response->add_table_stats();
+ auto table_id = pair.first;
+ auto stats = pair.second;
+ get_pb_from_tablestats(stats, stats_pb);
+ stats_pb->set_table_id(table_id);
+ VLOG_DEBUG << "Add TableStats to CommitTxnResponse. txn_id=" << txn_id
+ << " table_id=" << table_id
+ << " updated_row_count=" << stats_pb->updated_row_count();
+ }
+
+ // txn set visible for fe callback
+ txn_info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
+ response->mutable_txn_info()->CopyFrom(txn_info);
+ break;
+ } while (true);
+}
/**
* This process is generally the same as commit_txn, the difference is that
@@ -2009,8 +2528,19 @@ void MetaServiceImpl::commit_txn(::google::protobuf::RpcController* controller,
LOG(WARNING) << "scan_tmp_rowset failed, txn_id=" << txn_id << " code=" << code;
return;
}
- commit_txn_immediately(request, response, txn_kv_, code, msg, instance_id, db_id,
- tmp_rowsets_meta);
+
+ if (request->has_is_2pc() && !request->is_2pc() && request->has_enable_txn_lazy_commit() &&
+ request->enable_txn_lazy_commit() && config::enable_cloud_txn_lazy_commit &&
+ (tmp_rowsets_meta.size() >= config::txn_lazy_commit_rowsets_thresold)) {
+ LOG(INFO) << "txn_id=" << txn_id << " commit_txn_eventually"
+ << " size=" << tmp_rowsets_meta.size();
+ commit_txn_eventually(request, response, txn_kv_, txn_lazy_committer_, code, msg,
+ instance_id, db_id, tmp_rowsets_meta);
+ return;
+ }
+
+ commit_txn_immediately(request, response, txn_kv_, txn_lazy_committer_, code, msg, instance_id,
+ db_id, tmp_rowsets_meta);
}
static void _abort_txn(const std::string& instance_id, const AbortTxnRequest* request,
diff --git a/cloud/src/meta-service/txn_lazy_committer.cpp b/cloud/src/meta-service/txn_lazy_committer.cpp
new file mode 100644
index 00000000000..5273a5c9683
--- /dev/null
+++ b/cloud/src/meta-service/txn_lazy_committer.cpp
@@ -0,0 +1,476 @@
+// 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 "txn_lazy_committer.h"
+
+#include <chrono>
+
+#include "common/logging.h"
+#include "common/util.h"
+#include "meta-service/keys.h"
+#include "meta-service/meta_service_helper.h"
+#include "meta-service/meta_service_tablet_stats.h"
+
+using namespace std::chrono;
+
+namespace doris::cloud {
+
+void scan_tmp_rowset(
+ const std::string& instance_id, int64_t txn_id, std::shared_ptr<TxnKv> txn_kv,
+ MetaServiceCode& code, std::string& msg, int64_t* db_id,
+ std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>* tmp_rowsets_meta);
+
+void update_tablet_stats(const StatsTabletKeyInfo& info, const TabletStats& stats,
+ std::unique_ptr<Transaction>& txn, MetaServiceCode& code,
+ std::string& msg);
+
+void convert_tmp_rowsets(
+ const std::string& instance_id, int64_t txn_id, std::shared_ptr<TxnKv> txn_kv,
+ MetaServiceCode& code, std::string& msg, int64_t db_id,
+ std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>& tmp_rowsets_meta,
+ std::unordered_map<int64_t, TabletIndexPB>& tablet_ids) {
+ std::stringstream ss;
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ // partition_id -> VersionPB
+ std::unordered_map<int64_t, VersionPB> partition_versions;
+ // tablet_id -> stats
+ std::unordered_map<int64_t, TabletStats> tablet_stats;
+
+ for (auto& [tmp_rowset_key, tmp_rowset_pb] : tmp_rowsets_meta) {
+ std::string tmp_rowst_data;
+ err = txn->get(tmp_rowset_key, &tmp_rowst_data);
+ if (TxnErrorCode::TXN_KEY_NOT_FOUND == err) {
+ // the tmp rowset has been converted
+ VLOG_DEBUG << "tmp rowset has been converted, key=" << hex(tmp_rowset_key);
+ continue;
+ }
+
+ if (TxnErrorCode::TXN_OK != err) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get tmp_rowset_key, txn_id=" << txn_id
+ << " key=" << hex(tmp_rowset_key) << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ if (!tablet_ids.contains(tmp_rowset_pb.tablet_id())) {
+ std::string tablet_idx_key =
+ meta_tablet_idx_key({instance_id, tmp_rowset_pb.tablet_id()});
+ std::string tablet_idx_val;
+ err = txn->get(tablet_idx_key, &tablet_idx_val, true);
+ if (TxnErrorCode::TXN_OK != err) {
+ code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ ss << "failed to get tablet idx, txn_id=" << txn_id
+ << " key=" << hex(tablet_idx_key) << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ TabletIndexPB tablet_idx_pb;
+ if (!tablet_idx_pb.ParseFromString(tablet_idx_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse tablet idx pb txn_id=" << txn_id
+ << " key=" << hex(tablet_idx_key);
+ msg = ss.str();
+ return;
+ }
+ tablet_ids.emplace(tmp_rowset_pb.tablet_id(), tablet_idx_pb);
+ }
+ const TabletIndexPB& tablet_idx_pb = tablet_ids[tmp_rowset_pb.tablet_id()];
+
+ if (!partition_versions.contains(tmp_rowset_pb.partition_id())) {
+ std::string ver_val;
+ std::string ver_key = partition_version_key(
+ {instance_id, db_id, tablet_idx_pb.table_id(), tmp_rowset_pb.partition_id()});
+ err = txn->get(ver_key, &ver_val);
+ if (TxnErrorCode::TXN_OK != err) {
+ code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ ss << "failed to get partiton version, txn_id=" << txn_id << " key=" << hex(ver_key)
+ << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(ver_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse version pb txn_id=" << txn_id << " key=" << hex(ver_key);
+ msg = ss.str();
+ return;
+ }
+ LOG(INFO) << "txn_id=" << txn_id << " key=" << hex(ver_key)
+ << " version_pb:" << version_pb.ShortDebugString();
+ partition_versions.emplace(tmp_rowset_pb.partition_id(), version_pb);
+ }
+
+ const VersionPB& version_pb = partition_versions[tmp_rowset_pb.partition_id()];
+ DCHECK(version_pb.pending_txn_ids_size() > 0);
+ DCHECK_EQ(version_pb.pending_txn_ids(0), txn_id);
+
+ int64_t version = version_pb.has_version() ? (version_pb.version() + 1) : 2;
+
+ std::string rowset_key = meta_rowset_key({instance_id, tmp_rowset_pb.tablet_id(), version});
+ std::string rowset_val;
+ err = txn->get(rowset_key, &rowset_val);
+ if (TxnErrorCode::TXN_OK == err) {
+ // tmp rowset key has been converted
+ continue;
+ }
+
+ if (err != TxnErrorCode::TXN_KEY_NOT_FOUND) {
+ code = cast_as<ErrCategory::READ>(err);
+ ss << "failed to get rowset_key, txn_id=" << txn_id << " key=" << hex(rowset_key)
+ << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+ DCHECK(err == TxnErrorCode::TXN_KEY_NOT_FOUND);
+
+ tmp_rowset_pb.set_start_version(version);
+ tmp_rowset_pb.set_end_version(version);
+
+ rowset_val.clear();
+ if (!tmp_rowset_pb.SerializeToString(&rowset_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize rowset_meta, txn_id=" << txn_id
+ << " key=" << hex(rowset_key);
+ msg = ss.str();
+ return;
+ }
+
+ txn->put(rowset_key, rowset_val);
+ LOG(INFO) << "put rowset_key=" << hex(rowset_key) << " txn_id=" << txn_id
+ << " rowset_size=" << rowset_key.size() + rowset_val.size();
+
+ // Accumulate affected rows
+ auto& stats = tablet_stats[tmp_rowset_pb.tablet_id()];
+ stats.data_size += tmp_rowset_pb.data_disk_size();
+ stats.num_rows += tmp_rowset_pb.num_rows();
+ ++stats.num_rowsets;
+ stats.num_segs += tmp_rowset_pb.num_segments();
+ }
+
+ DCHECK(partition_versions.size() == 1);
+
+ for (auto& [tablet_id, stats] : tablet_stats) {
+ DCHECK(tablet_ids.count(tablet_id));
+ auto& tablet_idx = tablet_ids[tablet_id];
+ StatsTabletKeyInfo info {instance_id, tablet_idx.table_id(), tablet_idx.index_id(),
+ tablet_idx.partition_id(), tablet_id};
+ update_tablet_stats(info, stats, txn, code, msg);
+ if (code != MetaServiceCode::OK) return;
+ }
+
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ return;
+ }
+}
+
+void make_committed_txn_visible(const std::string& instance_id, int64_t db_id, int64_t txn_id,
+ std::shared_ptr<TxnKv> txn_kv, MetaServiceCode& code,
+ std::string& msg) {
+ // 1. visible txn info
+ // 2. remove running key and put recycle txn key
+
+ std::stringstream ss;
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ std::string info_val;
+ const std::string info_key = txn_info_key({instance_id, db_id, txn_id});
+ err = txn->get(info_key, &info_val);
+ if (err != TxnErrorCode::TXN_OK) {
+ code = err == TxnErrorCode::TXN_KEY_NOT_FOUND ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ ss << "failed to get txn_info, db_id=" << db_id << " txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ TxnInfoPB txn_info;
+ if (!txn_info.ParseFromString(info_val)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse txn_info, txn_id=" << txn_id;
+ msg = ss.str();
+ LOG(WARNING) << msg;
+ return;
+ }
+
+ VLOG_DEBUG << "txn_info:" << txn_info.ShortDebugString();
+ DCHECK((txn_info.status() == TxnStatusPB::TXN_STATUS_COMMITTED) ||
+ (txn_info.status() == TxnStatusPB::TXN_STATUS_VISIBLE));
+
+ if (txn_info.status() == TxnStatusPB::TXN_STATUS_COMMITTED) {
+ txn_info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
+
+ if (!txn_info.SerializeToString(&info_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize txn_info when saving, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+ txn->put(info_key, info_val);
+ LOG(INFO) << "put info_key=" << hex(info_key) << " txn_id=" << txn_id;
+
+ const std::string running_key = txn_running_key({instance_id, db_id, txn_id});
+ LOG(INFO) << "remove running_key=" << hex(running_key) << " txn_id=" << txn_id;
+ txn->remove(running_key);
+
+ std::string recycle_val;
+ std::string recycle_key = recycle_txn_key({instance_id, db_id, txn_id});
+ RecycleTxnPB recycle_pb;
+ auto now_time = system_clock::now();
+ uint64_t visible_time = duration_cast<milliseconds>(now_time.time_since_epoch()).count();
+ recycle_pb.set_creation_time(visible_time);
+ recycle_pb.set_label(txn_info.label());
+
+ if (!recycle_pb.SerializeToString(&recycle_val)) {
+ code = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize recycle_pb, txn_id=" << txn_id;
+ msg = ss.str();
+ return;
+ }
+
+ txn->put(recycle_key, recycle_val);
+ LOG(INFO) << "put recycle_key=" << hex(recycle_key) << " txn_id=" << txn_id;
+
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id << " err=" << err;
+ msg = ss.str();
+ return;
+ }
+ }
+}
+
+TxnLazyCommitTask::TxnLazyCommitTask(const std::string& instance_id, int64_t txn_id,
+ std::shared_ptr<TxnKv> txn_kv,
+ TxnLazyCommitter* txn_lazy_committer)
+ : instance_id_(instance_id),
+ txn_id_(txn_id),
+ txn_kv_(txn_kv),
+ txn_lazy_committer_(txn_lazy_committer) {
+ DCHECK(txn_id > 0);
+}
+
+void TxnLazyCommitTask::commit() {
+ std::stringstream ss;
+ int retry_times = 0;
+ do {
+ LOG(INFO) << "lazy task commit txn_id=" << txn_id_ << " retry_times=" << retry_times;
+ do {
+ code_ = MetaServiceCode::OK;
+ msg_.clear();
+ int64_t db_id;
+ std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>> tmp_rowset_metas;
+ scan_tmp_rowset(instance_id_, txn_id_, txn_kv_, code_, msg_, &db_id, &tmp_rowset_metas);
+ if (code_ != MetaServiceCode::OK) {
+ LOG(WARNING) << "scan_tmp_rowset failed, txn_id=" << txn_id_ << " code=" << code_;
+ break;
+ }
+
+ VLOG_DEBUG << "txn_id=" << txn_id_
+ << " tmp_rowset_metas.size()=" << tmp_rowset_metas.size();
+ if (tmp_rowset_metas.size() == 0) {
+ LOG(INFO) << "empty tmp_rowset_metas, txn_id=" << txn_id_;
+ }
+
+ // <partition_id, tmp_rowsets>
+ std::unordered_map<int64_t,
+ std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>>
+ partition_to_tmp_rowset_metas;
+ for (auto& [tmp_rowset_key, tmp_rowset_pb] : tmp_rowset_metas) {
+ partition_to_tmp_rowset_metas[tmp_rowset_pb.partition_id()].emplace_back();
+ partition_to_tmp_rowset_metas[tmp_rowset_pb.partition_id()].back().first =
+ tmp_rowset_key;
+ partition_to_tmp_rowset_metas[tmp_rowset_pb.partition_id()].back().second =
+ tmp_rowset_pb;
+ }
+
+ // tablet_id -> TabletIndexPB
+ std::unordered_map<int64_t, TabletIndexPB> tablet_ids;
+ for (auto& [partition_id, tmp_rowset_metas] : partition_to_tmp_rowset_metas) {
+ for (size_t i = 0; i < tmp_rowset_metas.size();
+ i += config::txn_lazy_max_rowsets_per_batch) {
+ size_t end =
+ (i + config::txn_lazy_max_rowsets_per_batch) > tmp_rowset_metas.size()
+ ? tmp_rowset_metas.size()
+ : i + config::txn_lazy_max_rowsets_per_batch;
+ std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>
+ sub_partition_tmp_rowset_metas(tmp_rowset_metas.begin() + i,
+ tmp_rowset_metas.begin() + end);
+ convert_tmp_rowsets(instance_id_, txn_id_, txn_kv_, code_, msg_, db_id,
+ sub_partition_tmp_rowset_metas, tablet_ids);
+ if (code_ != MetaServiceCode::OK) break;
+ }
+ if (code_ != MetaServiceCode::OK) break;
+
+ DCHECK(tmp_rowset_metas.size() > 0);
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv_->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ code_ = cast_as<ErrCategory::CREATE>(err);
+ ss << "failed to create txn, txn_id=" << txn_id_ << " err=" << err;
+ msg_ = ss.str();
+ LOG(WARNING) << msg_;
+ break;
+ }
+
+ int64_t table_id = -1;
+ for (auto& [tmp_rowset_key, tmp_rowset_pb] : tmp_rowset_metas) {
+ if (table_id <= 0) {
+ table_id = tablet_ids[tmp_rowset_pb.tablet_id()].table_id();
+ }
+ txn->remove(tmp_rowset_key);
+ LOG(INFO) << "remove tmp_rowset_key=" << hex(tmp_rowset_key)
+ << " txn_id=" << txn_id_;
+ }
+
+ DCHECK(table_id > 0);
+ DCHECK(partition_id > 0);
+
+ std::string ver_val;
+ std::string ver_key =
+ partition_version_key({instance_id_, db_id, table_id, partition_id});
+ err = txn->get(ver_key, &ver_val);
+ if (TxnErrorCode::TXN_OK != err) {
+ code_ = err == TxnErrorCode::TXN_KEY_NOT_FOUND
+ ? MetaServiceCode::TXN_ID_NOT_FOUND
+ : cast_as<ErrCategory::READ>(err);
+ ss << "failed to get partiton version, txn_id=" << txn_id_
+ << " key=" << hex(ver_key) << " err=" << err;
+ msg_ = ss.str();
+ LOG(WARNING) << msg_;
+ break;
+ }
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(ver_val)) {
+ code_ = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ ss << "failed to parse version pb txn_id=" << txn_id_
+ << " key=" << hex(ver_key);
+ msg_ = ss.str();
+ break;
+ }
+
+ if (version_pb.pending_txn_ids_size() > 0 &&
+ version_pb.pending_txn_ids(0) == txn_id_) {
+ DCHECK(version_pb.pending_txn_ids_size() == 1);
+ version_pb.clear_pending_txn_ids();
+ ver_val.clear();
+
+ if (version_pb.has_version()) {
+ version_pb.set_version(version_pb.version() + 1);
+ } else {
+ // first commit txn version is 2
+ version_pb.set_version(2);
+ }
+ if (!version_pb.SerializeToString(&ver_val)) {
+ code_ = MetaServiceCode::PROTOBUF_SERIALIZE_ERR;
+ ss << "failed to serialize version_pb when saving, txn_id=" << txn_id_;
+ msg_ = ss.str();
+ return;
+ }
+ txn->put(ver_key, ver_val);
+ LOG(INFO) << "put ver_key=" << hex(ver_key) << " txn_id=" << txn_id_
+ << " version_pb=" << version_pb.ShortDebugString();
+
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ code_ = cast_as<ErrCategory::COMMIT>(err);
+ ss << "failed to commit kv txn, txn_id=" << txn_id_ << " err=" << err;
+ msg_ = ss.str();
+ break;
+ }
+ }
+ }
+ make_committed_txn_visible(instance_id_, db_id, txn_id_, txn_kv_, code_, msg_);
+ } while (false);
+ } while (code_ == MetaServiceCode::KV_TXN_CONFLICT &&
+ retry_times++ < config::txn_store_retry_times);
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ this->finished_ = true;
+ }
+ this->cond_.notify_all();
+}
+
+std::pair<MetaServiceCode, std::string> TxnLazyCommitTask::wait() {
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ cond_.wait(lock, [this]() { return this->finished_ == true; });
+ }
+ txn_lazy_committer_->remove(txn_id_);
+ return std::make_pair(this->code_, this->msg_);
+}
+
+TxnLazyCommitter::TxnLazyCommitter(std::shared_ptr<TxnKv> txn_kv) : txn_kv_(txn_kv) {
+ worker_pool_ = std::make_unique<SimpleThreadPool>(config::txn_lazy_commit_num_threads);
+ worker_pool_->start();
+}
+
+std::shared_ptr<TxnLazyCommitTask> TxnLazyCommitter::submit(const std::string& instance_id,
+ int64_t txn_id) {
+ std::shared_ptr<TxnLazyCommitTask> task;
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ auto iter = running_tasks_.find(txn_id);
+ if (iter != running_tasks_.end()) {
+ return iter->second;
+ }
+
+ task = std::make_shared<TxnLazyCommitTask>(instance_id, txn_id, txn_kv_, this);
+ running_tasks_.emplace(txn_id, task);
+ }
+
+ worker_pool_->submit([task]() { task->commit(); });
+ DCHECK(task != nullptr);
+ return task;
+}
+
+void TxnLazyCommitter::remove(int64_t txn_id) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ running_tasks_.erase(txn_id);
+}
+
+} // namespace doris::cloud
\ No newline at end of file
diff --git a/cloud/src/meta-service/txn_lazy_committer.h b/cloud/src/meta-service/txn_lazy_committer.h
new file mode 100644
index 00000000000..dca618739ec
--- /dev/null
+++ b/cloud/src/meta-service/txn_lazy_committer.h
@@ -0,0 +1,68 @@
+// 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.
+
+#pragma once
+
+#include <gen_cpp/cloud.pb.h>
+
+#include <atomic>
+
+#include "common/simple_thread_pool.h"
+#include "meta-service/txn_kv.h"
+
+namespace doris::cloud {
+
+class TxnLazyCommitter;
+
+class TxnLazyCommitTask {
+public:
+ TxnLazyCommitTask(const std::string& instance_id, int64_t txn_id, std::shared_ptr<TxnKv> txn_kv,
+ TxnLazyCommitter* txn_lazy_committer);
+ void commit();
+
+ std::pair<MetaServiceCode, std::string> wait();
+
+private:
+ friend class TxnLazyCommitter;
+
+ std::string instance_id_;
+ int64_t txn_id_;
+ std::shared_ptr<TxnKv> txn_kv_;
+ std::mutex mutex_;
+ std::condition_variable cond_;
+ bool finished_ = false;
+ MetaServiceCode code_ = MetaServiceCode::OK;
+ std::string msg_;
+ TxnLazyCommitter* txn_lazy_committer_;
+};
+
+class TxnLazyCommitter {
+public:
+ TxnLazyCommitter(std::shared_ptr<TxnKv> txn_kv);
+ std::shared_ptr<TxnLazyCommitTask> submit(const std::string& instance_id, int64_t txn_id);
+ void remove(int64_t txn_id);
+
+private:
+ std::shared_ptr<TxnKv> txn_kv_;
+
+ std::unique_ptr<SimpleThreadPool> worker_pool_;
+
+ std::mutex mutex_;
+ // <txn_id, TxnLazyCommitTask>
+ std::unordered_map<int64_t, std::shared_ptr<TxnLazyCommitTask>> running_tasks_;
+};
+} // namespace doris::cloud
\ No newline at end of file
diff --git a/cloud/src/recycler/recycler.cpp b/cloud/src/recycler/recycler.cpp
index 486e143f30b..76d4a7ca767 100644
--- a/cloud/src/recycler/recycler.cpp
+++ b/cloud/src/recycler/recycler.cpp
@@ -167,6 +167,7 @@ static inline void check_recycle_task(const std::string& instance_id, const std:
Recycler::Recycler(std::shared_ptr<TxnKv> txn_kv) : txn_kv_(std::move(txn_kv)) {
ip_port_ = std::string(butil::my_ip_cstr()) + ":" + std::to_string(config::brpc_listen_port);
+
auto s3_producer_pool = std::make_shared<SimpleThreadPool>(config::recycle_pool_parallelism);
s3_producer_pool->start();
auto recycle_tablet_pool = std::make_shared<SimpleThreadPool>(config::recycle_pool_parallelism);
@@ -177,6 +178,8 @@ Recycler::Recycler(std::shared_ptr<TxnKv> txn_kv) : txn_kv_(std::move(txn_kv)) {
_thread_pool_group =
RecyclerThreadPoolGroup(std::move(s3_producer_pool), std::move(recycle_tablet_pool),
std::move(group_recycle_function_pool));
+
+ txn_lazy_committer_ = std::make_shared<TxnLazyCommitter>(txn_kv_);
}
Recycler::~Recycler() {
@@ -241,8 +244,9 @@ void Recycler::recycle_callback() {
// skip instance in recycling
if (recycling_instance_map_.count(instance_id)) continue;
}
- auto instance_recycler =
- std::make_shared<InstanceRecycler>(txn_kv_, instance, _thread_pool_group);
+ auto instance_recycler = std::make_shared<InstanceRecycler>(
+ txn_kv_, instance, _thread_pool_group, txn_lazy_committer_);
+
if (instance_recycler->init() != 0) {
LOG(WARNING) << "failed to init instance recycler, instance_id=" << instance_id;
continue;
@@ -342,7 +346,8 @@ int Recycler::start(brpc::Server* server) {
if (server) {
// Add service
- auto recycler_service = new RecyclerServiceImpl(txn_kv_, this, checker_.get());
+ auto recycler_service =
+ new RecyclerServiceImpl(txn_kv_, this, checker_.get(), txn_lazy_committer_);
server->AddService(recycler_service, brpc::SERVER_OWNS_SERVICE);
}
@@ -459,12 +464,14 @@ private:
};
InstanceRecycler::InstanceRecycler(std::shared_ptr<TxnKv> txn_kv, const InstanceInfoPB& instance,
- RecyclerThreadPoolGroup thread_pool_group)
+ RecyclerThreadPoolGroup thread_pool_group,
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer)
: txn_kv_(std::move(txn_kv)),
instance_id_(instance.instance_id()),
instance_info_(instance),
inverted_index_id_cache_(std::make_unique<InvertedIndexIdCache>(instance_id_, txn_kv_)),
- _thread_pool_group(std::move(thread_pool_group)) {}
+ _thread_pool_group(std::move(thread_pool_group)),
+ txn_lazy_committer_(std::move(txn_lazy_committer)) {};
InstanceRecycler::~InstanceRecycler() = default;
@@ -829,6 +836,79 @@ int InstanceRecycler::recycle_indexes() {
return scan_and_recycle(index_key0, index_key1, std::move(recycle_func), std::move(loop_done));
}
+bool check_lazy_txn_finished(std::shared_ptr<TxnKv> txn_kv, const std::string instance_id,
+ int64_t tablet_id) {
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ LOG(WARNING) << "failed to create txn, instance_id=" << instance_id
+ << " tablet_id=" << tablet_id << " err=" << err;
+ return false;
+ }
+
+ std::string tablet_idx_key = meta_tablet_idx_key({instance_id, tablet_id});
+ std::string tablet_idx_val;
+ err = txn->get(tablet_idx_key, &tablet_idx_val);
+ if (TxnErrorCode::TXN_OK != err) {
+ LOG(WARNING) << "failed to get tablet index, instance_id=" << instance_id
+ << " tablet_id=" << tablet_id << " err=" << err
+ << " key=" << hex(tablet_idx_key);
+ return false;
+ }
+
+ TabletIndexPB tablet_idx_pb;
+ if (!tablet_idx_pb.ParseFromString(tablet_idx_val)) {
+ LOG(WARNING) << "failed to parse tablet_idx_pb, instance_id=" << instance_id
+ << " tablet_id=" << tablet_id;
+ return false;
+ }
+
+ if (!tablet_idx_pb.has_db_id()) {
+ return true;
+ }
+
+ std::string ver_val;
+ std::string ver_key =
+ partition_version_key({instance_id, tablet_idx_pb.db_id(), tablet_idx_pb.table_id(),
+ tablet_idx_pb.partition_id()});
+ err = txn->get(ver_key, &ver_val);
+
+ if (TxnErrorCode::TXN_KEY_NOT_FOUND == err) {
+ return true;
+ }
+
+ if (TxnErrorCode::TXN_OK != err) {
+ LOG(WARNING) << "failed to get partition version, instance_id=" << instance_id
+ << " db_id=" << tablet_idx_pb.db_id()
+ << " table_id=" << tablet_idx_pb.table_id()
+ << " partition_id=" << tablet_idx_pb.partition_id()
+ << " tablet_id=" << tablet_id << " key=" << hex(ver_key) << " err=" << err;
+ return false;
+ }
+
+ VersionPB version_pb;
+ if (!version_pb.ParseFromString(ver_val)) {
+ LOG(WARNING) << "failed to parse version_pb, instance_id=" << instance_id
+ << " db_id=" << tablet_idx_pb.db_id()
+ << " table_id=" << tablet_idx_pb.table_id()
+ << " partition_id=" << tablet_idx_pb.partition_id()
+ << " tablet_id=" << tablet_id << " key=" << hex(ver_key);
+ return false;
+ }
+
+ if (version_pb.pending_txn_ids_size() > 0) {
+ DCHECK(version_pb.pending_txn_ids_size() == 1);
+ LOG(WARNING) << "lazy txn not finished, instance_id=" << instance_id
+ << " db_id=" << tablet_idx_pb.db_id()
+ << " table_id=" << tablet_idx_pb.table_id()
+ << " partition_id=" << tablet_idx_pb.partition_id()
+ << " tablet_id=" << tablet_id << " txn_id=" << version_pb.pending_txn_ids(0)
+ << " key=" << hex(ver_key);
+ return false;
+ }
+ return true;
+}
+
int InstanceRecycler::recycle_partitions() {
const std::string task_name = "recycle_partitions";
int num_scanned = 0;
@@ -928,6 +1008,7 @@ int InstanceRecycler::recycle_partitions() {
return -1;
}
}
+
int ret = 0;
for (int64_t index_id : part_pb.index_id()) {
if (recycle_tablets(part_pb.table_id(), index_id, partition_id, is_empty_tablet) != 0) {
@@ -1040,7 +1121,8 @@ int InstanceRecycler::recycle_versions() {
partition_version_key({instance_id_, db_id, table_id, INT64_MAX});
txn->remove(partition_version_key_begin, partition_version_key_end);
LOG(WARNING) << "remove partition version kv, begin=" << hex(partition_version_key_begin)
- << " end=" << hex(partition_version_key_end);
+ << " end=" << hex(partition_version_key_end) << " db_id=" << db_id
+ << " table_id=" << table_id;
// 2. Remove the table version kv of this table
auto tbl_version_key = table_version_key({instance_id_, db_id, table_id});
txn->remove(tbl_version_key);
@@ -1124,6 +1206,12 @@ int InstanceRecycler::recycle_tablets(int64_t table_id, int64_t index_id, int64_
return -1;
}
int64_t tablet_id = tablet_meta_pb.tablet_id();
+
+ if (!check_lazy_txn_finished(txn_kv_, instance_id_, tablet_meta_pb.tablet_id())) {
+ LOG(WARNING) << "lazy txn not finished tablet_id=" << tablet_meta_pb.tablet_id();
+ return -1;
+ }
+
tablet_idx_keys.push_back(meta_tablet_idx_key({instance_id_, tablet_id}));
if (!is_empty_tablet) {
sync_executor.add([this, &num_recycled, tid = tablet_id, range_move = use_range_remove,
@@ -1227,6 +1315,10 @@ int InstanceRecycler::recycle_tablets(int64_t table_id, int64_t index_id, int64_
int ret = scan_and_recycle(tablet_key_begin, tablet_key_end, std::move(recycle_func),
std::move(loop_done));
+ if (ret != 0) {
+ LOG(WARNING) << "failed to scan_and_recycle, instance_id=" << instance_id_;
+ return ret;
+ }
// directly remove tablet stats and tablet jobs of these dropped index or partition
std::unique_ptr<Transaction> txn;
@@ -1716,6 +1808,64 @@ int InstanceRecycler::recycle_rowsets() {
return ret;
}
+bool check_txn_abort(std::shared_ptr<TxnKv> txn_kv, const std::string& instance_id,
+ int64_t txn_id) {
+ std::unique_ptr<Transaction> txn;
+ TxnErrorCode err = txn_kv->create_txn(&txn);
+ if (err != TxnErrorCode::TXN_OK) {
+ LOG(WARNING) << "failed to create txn, txn_id=" << txn_id << " instance_id=" << instance_id;
+ return false;
+ }
+
+ std::string index_val;
+ const std::string index_key = txn_index_key({instance_id, txn_id});
+ err = txn->get(index_key, &index_val);
+ if (err != TxnErrorCode::TXN_OK) {
+ if (TxnErrorCode::TXN_KEY_NOT_FOUND == err) {
+ // txn has been recycled;
+ return true;
+ }
+ LOG(WARNING) << "failed to get txn index key, txn_id=" << txn_id
+ << " instance_id=" << instance_id << " key=" << hex(index_key)
+ << " err=" << err;
+ return false;
+ }
+
+ TxnIndexPB index_pb;
+ if (!index_pb.ParseFromString(index_val)) {
+ LOG(WARNING) << "failed to parse txn_index_pb, txn_id=" << txn_id
+ << " instance_id=" << instance_id;
+ return false;
+ }
+
+ DCHECK(index_pb.has_tablet_index() == true);
+ DCHECK(index_pb.tablet_index().has_db_id() == true);
+ int64_t db_id = index_pb.tablet_index().db_id();
+
+ std::string info_val;
+ const std::string info_key = txn_info_key({instance_id, db_id, txn_id});
+ err = txn->get(info_key, &info_val);
+ if (err != TxnErrorCode::TXN_OK) {
+ DCHECK(err != TxnErrorCode::TXN_KEY_NOT_FOUND);
+ LOG(WARNING) << "failed to get txn info key, txn_id=" << txn_id
+ << " instance_id=" << instance_id << " key=" << hex(info_key)
+ << " err=" << err;
+ return false;
+ }
+
+ TxnInfoPB txn_info;
+ if (!txn_info.ParseFromString(info_val)) {
+ LOG(WARNING) << "failed to parse txn_info, txn_id=" << txn_id
+ << " instance_id=" << instance_id;
+ return false;
+ }
+ DCHECK(txn_info.txn_id() == txn_id);
+ if (TxnStatusPB::TXN_STATUS_ABORTED == txn_info.status()) {
+ return true;
+ }
+ return false;
+}
+
int InstanceRecycler::recycle_tmp_rowsets() {
const std::string task_name = "recycle_tmp_rowsets";
int num_scanned = 0;
@@ -1777,6 +1927,11 @@ int InstanceRecycler::recycle_tmp_rowsets() {
// not expired
return 0;
}
+
+ if (!check_txn_abort(txn_kv_, instance_id_, rowset.txn_id())) {
+ return 0;
+ }
+
++num_expired;
expired_rowset_size += v.size();
if (!rowset.has_resource_id()) {
@@ -1861,6 +2016,7 @@ int InstanceRecycler::abort_timeout_txn() {
int num_scanned = 0;
int num_timeout = 0;
int num_abort = 0;
+ int num_advance = 0;
TxnRunningKeyInfo txn_running_key_info0 {instance_id_, 0, 0};
TxnRunningKeyInfo txn_running_key_info1 {instance_id_, INT64_MAX, INT64_MAX};
@@ -1882,24 +2038,17 @@ int InstanceRecycler::abort_timeout_txn() {
.tag("instance_id", instance_id_)
.tag("num_scanned", num_scanned)
.tag("num_timeout", num_timeout)
- .tag("num_abort", num_abort);
+ .tag("num_abort", num_abort)
+ .tag("num_advance", num_advance);
});
int64_t current_time =
duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
- auto handle_txn_running_kv = [&num_scanned, &num_timeout, &num_abort, ¤t_time, this](
- std::string_view k, std::string_view v) -> int {
+ auto handle_txn_running_kv = [&num_scanned, &num_timeout, &num_abort, &num_advance,
+ ¤t_time,
+ this](std::string_view k, std::string_view v) -> int {
++num_scanned;
- TxnRunningPB txn_running_pb;
- if (!txn_running_pb.ParseFromArray(v.data(), v.size())) {
- LOG_WARNING("malformed txn_running_pb").tag("key", hex(k));
- return -1;
- }
- if (txn_running_pb.timeout_time() > current_time) {
- return 0;
- }
- ++num_timeout;
std::unique_ptr<Transaction> txn;
TxnErrorCode err = txn_kv_->create_txn(&txn);
@@ -1931,42 +2080,69 @@ int InstanceRecycler::abort_timeout_txn() {
LOG_WARNING("failed to parse txn info").tag("key", hex(k));
return -1;
}
- txn_info.set_status(TxnStatusPB::TXN_STATUS_ABORTED);
- txn_info.set_finish_time(current_time);
- txn_info.set_reason("timeout");
- VLOG_DEBUG << "txn_info=" << txn_info.DebugString();
- txn_inf_val.clear();
- if (!txn_info.SerializeToString(&txn_inf_val)) {
- LOG_WARNING("failed to serialize txn info").tag("key", hex(k));
- return -1;
- }
- txn->put(txn_inf_key, txn_inf_val);
- VLOG_DEBUG << "txn->put, txn_inf_key=" << hex(txn_inf_key);
- // Put recycle txn key
- std::string recyc_txn_key, recyc_txn_val;
- recycle_txn_key({instance_id_, db_id, txn_id}, &recyc_txn_key);
- RecycleTxnPB recycle_txn_pb;
- recycle_txn_pb.set_creation_time(current_time);
- recycle_txn_pb.set_label(txn_info.label());
- if (!recycle_txn_pb.SerializeToString(&recyc_txn_val)) {
- LOG_WARNING("failed to serialize txn recycle info")
- .tag("key", hex(k))
- .tag("db_id", db_id)
- .tag("txn_id", txn_id);
- return -1;
- }
- txn->put(recyc_txn_key, recyc_txn_val);
- // Remove txn running key
- txn->remove(k);
- err = txn->commit();
- if (err != TxnErrorCode::TXN_OK) {
- LOG_WARNING("failed to commit txn err={}", err)
- .tag("key", hex(k))
- .tag("db_id", db_id)
- .tag("txn_id", txn_id);
- return -1;
+
+ if (TxnStatusPB::TXN_STATUS_COMMITTED == txn_info.status()) {
+ txn.reset();
+ std::shared_ptr<TxnLazyCommitTask> task =
+ txn_lazy_committer_->submit(instance_id_, txn_info.txn_id());
+ std::pair<MetaServiceCode, std::string> ret = task->wait();
+ if (ret.first != MetaServiceCode::OK) {
+ LOG(WARNING) << "lazy commit txn failed txn_id=" << txn_id << " code=" << ret.first
+ << "msg=" << ret.second;
+ return -1;
+ }
+ ++num_advance;
+ return 0;
+ } else {
+ TxnRunningPB txn_running_pb;
+ if (!txn_running_pb.ParseFromArray(v.data(), v.size())) {
+ LOG_WARNING("malformed txn_running_pb").tag("key", hex(k));
+ return -1;
+ }
+ if (txn_running_pb.timeout_time() > current_time) {
+ return 0;
+ }
+ ++num_timeout;
+
+ DCHECK(txn_info.status() != TxnStatusPB::TXN_STATUS_VISIBLE);
+ txn_info.set_status(TxnStatusPB::TXN_STATUS_ABORTED);
+ txn_info.set_finish_time(current_time);
+ txn_info.set_reason("timeout");
+ VLOG_DEBUG << "txn_info=" << txn_info.DebugString();
+ txn_inf_val.clear();
+ if (!txn_info.SerializeToString(&txn_inf_val)) {
+ LOG_WARNING("failed to serialize txn info").tag("key", hex(k));
+ return -1;
+ }
+ txn->put(txn_inf_key, txn_inf_val);
+ VLOG_DEBUG << "txn->put, txn_inf_key=" << hex(txn_inf_key);
+ // Put recycle txn key
+ std::string recyc_txn_key, recyc_txn_val;
+ recycle_txn_key({instance_id_, db_id, txn_id}, &recyc_txn_key);
+ RecycleTxnPB recycle_txn_pb;
+ recycle_txn_pb.set_creation_time(current_time);
+ recycle_txn_pb.set_label(txn_info.label());
+ if (!recycle_txn_pb.SerializeToString(&recyc_txn_val)) {
+ LOG_WARNING("failed to serialize txn recycle info")
+ .tag("key", hex(k))
+ .tag("db_id", db_id)
+ .tag("txn_id", txn_id);
+ return -1;
+ }
+ txn->put(recyc_txn_key, recyc_txn_val);
+ // Remove txn running key
+ txn->remove(k);
+ err = txn->commit();
+ if (err != TxnErrorCode::TXN_OK) {
+ LOG_WARNING("failed to commit txn err={}", err)
+ .tag("key", hex(k))
+ .tag("db_id", db_id)
+ .tag("txn_id", txn_id);
+ return -1;
+ }
+ ++num_abort;
}
- ++num_abort;
+
return 0;
};
diff --git a/cloud/src/recycler/recycler.h b/cloud/src/recycler/recycler.h
index 950c0193f77..91a461f474f 100644
--- a/cloud/src/recycler/recycler.h
+++ b/cloud/src/recycler/recycler.h
@@ -28,6 +28,7 @@
#include <string_view>
#include <thread>
+#include "meta-service/txn_lazy_committer.h"
#include "recycler/storage_vault_accessor.h"
#include "recycler/white_black_list.h"
@@ -101,13 +102,17 @@ private:
WhiteBlackList instance_filter_;
std::unique_ptr<Checker> checker_;
+
RecyclerThreadPoolGroup _thread_pool_group;
+
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
};
class InstanceRecycler {
public:
explicit InstanceRecycler(std::shared_ptr<TxnKv> txn_kv, const InstanceInfoPB& instance,
- RecyclerThreadPoolGroup thread_pool_group);
+ RecyclerThreadPoolGroup thread_pool_group,
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer);
~InstanceRecycler();
// returns 0 for success otherwise error
@@ -239,7 +244,10 @@ private:
std::mutex recycle_tasks_mutex;
// <task_name, start_time>>
std::map<std::string, int64_t> running_recycle_tasks;
+
RecyclerThreadPoolGroup _thread_pool_group;
+
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
};
} // namespace doris::cloud
diff --git a/cloud/src/recycler/recycler_service.cpp b/cloud/src/recycler/recycler_service.cpp
index f9ffe750fdf..08e937a4106 100644
--- a/cloud/src/recycler/recycler_service.cpp
+++ b/cloud/src/recycler/recycler_service.cpp
@@ -42,8 +42,12 @@ extern int reset_s3_rate_limiter(S3RateLimitType type, size_t max_speed, size_t
extern std::tuple<int, std::string_view> convert_ms_code_to_http_code(MetaServiceCode ret);
RecyclerServiceImpl::RecyclerServiceImpl(std::shared_ptr<TxnKv> txn_kv, Recycler* recycler,
- Checker* checker)
- : txn_kv_(std::move(txn_kv)), recycler_(recycler), checker_(checker) {}
+ Checker* checker,
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer)
+ : txn_kv_(std::move(txn_kv)),
+ recycler_(recycler),
+ checker_(checker),
+ txn_lazy_committer_(std::move(txn_lazy_committer)) {}
RecyclerServiceImpl::~RecyclerServiceImpl() = default;
@@ -152,7 +156,8 @@ void RecyclerServiceImpl::check_instance(const std::string& instance_id, MetaSer
void recycle_copy_jobs(const std::shared_ptr<TxnKv>& txn_kv, const std::string& instance_id,
MetaServiceCode& code, std::string& msg,
- RecyclerThreadPoolGroup thread_pool_group) {
+ RecyclerThreadPoolGroup thread_pool_group,
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer) {
std::unique_ptr<Transaction> txn;
TxnErrorCode err = txn_kv->create_txn(&txn);
if (err != TxnErrorCode::TXN_OK) {
@@ -190,7 +195,8 @@ void recycle_copy_jobs(const std::shared_ptr<TxnKv>& txn_kv, const std::string&
}
}
- auto recycler = std::make_unique<InstanceRecycler>(txn_kv, instance, thread_pool_group);
+ auto recycler = std::make_unique<InstanceRecycler>(txn_kv, instance, thread_pool_group,
+ txn_lazy_committer);
if (recycler->init() != 0) {
LOG(WARNING) << "failed to init InstanceRecycler recycle_copy_jobs on instance "
<< instance_id;
@@ -333,7 +339,9 @@ void RecyclerServiceImpl::http(::google::protobuf::RpcController* controller,
status_code = 400;
return;
}
- recycle_copy_jobs(txn_kv_, *instance_id, code, msg, recycler_->_thread_pool_group);
+ recycle_copy_jobs(txn_kv_, *instance_id, code, msg, recycler_->_thread_pool_group,
+ txn_lazy_committer_);
+
response_body = msg;
return;
}
diff --git a/cloud/src/recycler/recycler_service.h b/cloud/src/recycler/recycler_service.h
index 8f9faec1efe..5ece69c86ba 100644
--- a/cloud/src/recycler/recycler_service.h
+++ b/cloud/src/recycler/recycler_service.h
@@ -20,6 +20,7 @@
#include <gen_cpp/cloud.pb.h>
#include "meta-service/txn_kv.h"
+#include "meta-service/txn_lazy_committer.h"
namespace doris::cloud {
@@ -28,7 +29,8 @@ class Checker;
class RecyclerServiceImpl : public cloud::RecyclerService {
public:
- RecyclerServiceImpl(std::shared_ptr<TxnKv> txn_kv, Recycler* recycler, Checker* checker);
+ RecyclerServiceImpl(std::shared_ptr<TxnKv> txn_kv, Recycler* recycler, Checker* checker,
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer);
~RecyclerServiceImpl() override;
void recycle_instance(::google::protobuf::RpcController* controller,
@@ -48,6 +50,7 @@ private:
std::shared_ptr<TxnKv> txn_kv_;
Recycler* recycler_; // Ref
Checker* checker_; // Ref
+ std::shared_ptr<TxnLazyCommitter> txn_lazy_committer_;
};
} // namespace doris::cloud
diff --git a/cloud/test/CMakeLists.txt b/cloud/test/CMakeLists.txt
index ba5e2909918..b504a882534 100644
--- a/cloud/test/CMakeLists.txt
+++ b/cloud/test/CMakeLists.txt
@@ -56,6 +56,8 @@ add_executable(util_test util_test.cpp)
add_executable(network_util_test network_util_test.cpp)
+add_executable(txn_lazy_commit_test txn_lazy_commit_test.cpp)
+
message("Meta-service test dependencies: ${TEST_LINK_LIBS}")
#target_link_libraries(sync_point_test ${TEST_LINK_LIBS})
@@ -91,6 +93,8 @@ target_link_libraries(util_test ${TEST_LINK_LIBS})
target_link_libraries(network_util_test ${TEST_LINK_LIBS})
+target_link_libraries(txn_lazy_commit_test ${TEST_LINK_LIBS})
+
# FDB related tests need to be linked with libfdb_c
set(FDB_LINKER_FLAGS "-lfdb_c -L${THIRDPARTY_DIR}/lib")
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 3a04122e487..c7d27713c9b 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -163,7 +163,7 @@ static void commit_txn(MetaServiceProxy* meta_service, int64_t db_id, int64_t tx
}
static doris::RowsetMetaCloudPB create_rowset(int64_t txn_id, int64_t tablet_id,
- int partition_id = 0, int64_t version = -1,
+ int partition_id = 10, int64_t version = -1,
int num_rows = 100) {
doris::RowsetMetaCloudPB rowset;
rowset.set_rowset_id(0); // required
diff --git a/cloud/test/recycler_test.cpp b/cloud/test/recycler_test.cpp
index ae4f35a4605..14687354839 100644
--- a/cloud/test/recycler_test.cpp
+++ b/cloud/test/recycler_test.cpp
@@ -261,17 +261,27 @@ static int create_tablet(TxnKv* txn_kv, int64_t table_id, int64_t index_id, int6
if (txn_kv->create_txn(&txn) != TxnErrorCode::TXN_OK) {
return -1;
}
- auto key = meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
doris::TabletMetaCloudPB tablet_meta;
tablet_meta.set_tablet_id(tablet_id);
auto val = tablet_meta.SerializeAsString();
+ auto key = meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
txn->put(key, val);
- key = meta_tablet_idx_key({instance_id, tablet_id});
- txn->put(key, val); // val is not necessary
key = stats_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
txn->put(key, val); // val is not necessary
key = job_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
txn->put(key, val); // val is not necessary
+
+ // mock tablet index
+ TabletIndexPB tablet_idx_pb;
+ tablet_idx_pb.set_db_id(db_id);
+ tablet_idx_pb.set_table_id(table_id);
+ tablet_idx_pb.set_partition_id(partition_id);
+ tablet_idx_pb.set_tablet_id(tablet_id);
+ auto idx_val = tablet_idx_pb.SerializeAsString();
+ key = meta_tablet_idx_key({instance_id, tablet_id});
+ txn->put(key, idx_val);
+ LOG(INFO) << "tablet_idx_pb:" << tablet_idx_pb.DebugString() << " key=" << hex(key);
+
if (txn->commit() != TxnErrorCode::TXN_OK) {
return -1;
}
@@ -632,7 +642,8 @@ TEST(RecyclerTest, recycle_empty) {
obj_info->set_bucket(config::test_s3_bucket);
obj_info->set_prefix("recycle_empty");
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
ASSERT_EQ(recycler.recycle_rowsets(), 0);
@@ -665,7 +676,8 @@ TEST(RecyclerTest, recycle_rowsets) {
sp->set_call_back("InvertedIndexIdCache::insert2", [&](auto&&) { ++insert_inverted_index; });
sp->enable_processing();
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
std::vector<doris::TabletSchemaCloudPB> schemas;
@@ -730,7 +742,8 @@ TEST(RecyclerTest, bench_recycle_rowsets) {
config::instance_recycler_worker_pool_size = 10;
config::recycle_task_threshold_seconds = 0;
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto sp = SyncPoint::get_instance();
@@ -813,7 +826,8 @@ TEST(RecyclerTest, recycle_tmp_rowsets) {
sp->set_call_back("InvertedIndexIdCache::insert2", [&](auto&&) { ++insert_inverted_index; });
sp->enable_processing();
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
std::vector<doris::TabletSchemaCloudPB> schemas;
@@ -875,7 +889,8 @@ TEST(RecyclerTest, recycle_tablet) {
obj_info->set_bucket(config::test_s3_bucket);
obj_info->set_prefix("recycle_tablet");
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
std::vector<doris::TabletSchemaCloudPB> schemas;
@@ -902,6 +917,8 @@ TEST(RecyclerTest, recycle_tablet) {
create_committed_rowset(txn_kv.get(), accessor.get(), "recycle_tablet", tablet_id, i);
}
+ ASSERT_EQ(create_partition_version_kv(txn_kv.get(), table_id, partition_id), 0);
+
ASSERT_EQ(0, recycler.recycle_tablets(table_id, index_id));
// check rowset does not exist on s3
@@ -950,7 +967,8 @@ TEST(RecyclerTest, recycle_indexes) {
obj_info->set_bucket(config::test_s3_bucket);
obj_info->set_prefix("recycle_indexes");
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
std::vector<doris::TabletSchemaCloudPB> schemas;
@@ -985,6 +1003,8 @@ TEST(RecyclerTest, recycle_indexes) {
create_committed_rowset(txn_kv.get(), accessor.get(), "recycle_indexes", tablet_id, j);
}
}
+
+ ASSERT_EQ(create_partition_version_kv(txn_kv.get(), table_id, partition_id), 0);
create_recycle_index(txn_kv.get(), table_id, index_id);
ASSERT_EQ(recycler.recycle_indexes(), 0);
@@ -1062,7 +1082,8 @@ TEST(RecyclerTest, recycle_partitions) {
obj_info->set_bucket(config::test_s3_bucket);
obj_info->set_prefix("recycle_partitions");
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
std::vector<doris::TabletSchemaCloudPB> schemas;
@@ -1084,8 +1105,8 @@ TEST(RecyclerTest, recycle_partitions) {
int64_t tablet_id_base = 10100;
for (auto index_id : index_ids) {
for (int i = 0; i < 20; ++i) {
- int64_t tablet_id = tablet_id_base + i;
- create_tablet(txn_kv.get(), table_id, index_id, partition_id, tablet_id);
+ int64_t tablet_id = tablet_id_base++;
+ ASSERT_EQ(create_tablet(txn_kv.get(), table_id, index_id, partition_id, tablet_id), 0);
for (int j = 0; j < 10; ++j) {
auto rowset =
create_rowset("recycle_tablet", tablet_id, index_id, 5, schemas[j % 5]);
@@ -1099,6 +1120,9 @@ TEST(RecyclerTest, recycle_partitions) {
}
}
}
+
+ ASSERT_EQ(create_partition_version_kv(txn_kv.get(), table_id, partition_id), 0);
+
create_recycle_partiton(txn_kv.get(), table_id, partition_id, index_ids);
ASSERT_EQ(recycler.recycle_partitions(), 0);
@@ -1154,7 +1178,7 @@ TEST(RecyclerTest, recycle_versions) {
std::vector<int64_t> index_ids {20001, 20002, 20003, 20004, 20005};
std::vector<int64_t> partition_ids {30001, 30002, 30003, 30004, 30005, 30006};
- constexpr int64_t table_id = 10000;
+ constexpr int table_id = 10000;
int64_t tablet_id = 40000;
for (auto index_id : index_ids) {
@@ -1173,7 +1197,8 @@ TEST(RecyclerTest, recycle_versions) {
InstanceInfoPB instance;
instance.set_instance_id(instance_id);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
// Recycle all partitions in table except 30006
ASSERT_EQ(recycler.recycle_partitions(), 0);
@@ -1211,7 +1236,7 @@ TEST(RecyclerTest, recycle_versions) {
ASSERT_EQ(iter->size(), 0);
}
-TEST(RecyclerTest, abort_timeout_txn) {
+TEST(RecyclerTest, advance_pending_txn) {
auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
ASSERT_NE(txn_kv.get(), nullptr);
auto rs = std::make_shared<MockResourceManager>(txn_kv);
@@ -1229,7 +1254,7 @@ TEST(RecyclerTest, abort_timeout_txn) {
req.set_cloud_unique_id("test_cloud_unique_id");
TxnInfoPB txn_info_pb;
txn_info_pb.set_db_id(db_id);
- txn_info_pb.set_label("abort_timeout_txn");
+ txn_info_pb.set_label("advance_pending_txn");
txn_info_pb.add_table_ids(table_id);
txn_info_pb.set_timeout_ms(1);
req.mutable_txn_info()->CopyFrom(txn_info_pb);
@@ -1242,7 +1267,8 @@ TEST(RecyclerTest, abort_timeout_txn) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
sleep(1);
ASSERT_EQ(recycler.abort_timeout_txn(), 0);
@@ -1251,7 +1277,7 @@ TEST(RecyclerTest, abort_timeout_txn) {
ASSERT_EQ(txn_info_pb.status(), TxnStatusPB::TXN_STATUS_ABORTED);
}
-TEST(RecyclerTest, abort_timeout_txn_and_rebegin) {
+TEST(RecyclerTest, advance_pending_txn_and_rebegin) {
config::label_keep_max_second = 0;
auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
ASSERT_NE(txn_kv.get(), nullptr);
@@ -1264,7 +1290,7 @@ TEST(RecyclerTest, abort_timeout_txn_and_rebegin) {
int64_t table_id = 1234;
int64_t txn_id = -1;
std::string cloud_unique_id = "test_cloud_unique_id22131";
- std::string label = "abort_timeout_txn_and_rebegin";
+ std::string label = "advance_pending_txn_and_rebegin";
{
brpc::Controller cntl;
BeginTxnRequest req;
@@ -1285,7 +1311,8 @@ TEST(RecyclerTest, abort_timeout_txn_and_rebegin) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
sleep(1);
ASSERT_EQ(recycler.abort_timeout_txn(), 0);
@@ -1352,7 +1379,8 @@ TEST(RecyclerTest, recycle_expired_txn_label) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
recycler.abort_timeout_txn();
TxnInfoPB txn_info_pb;
@@ -1403,7 +1431,8 @@ TEST(RecyclerTest, recycle_expired_txn_label) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
sleep(1);
recycler.abort_timeout_txn();
@@ -1455,7 +1484,8 @@ TEST(RecyclerTest, recycle_expired_txn_label) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
sleep(1);
recycler.abort_timeout_txn();
@@ -1514,7 +1544,8 @@ TEST(RecyclerTest, recycle_expired_txn_label) {
}
InstanceInfoPB instance;
instance.set_instance_id(mock_instance);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
sleep(1);
recycler.abort_timeout_txn();
@@ -1651,7 +1682,8 @@ TEST(RecyclerTest, recycle_copy_jobs) {
InstanceInfoPB instance_info;
create_instance(internal_stage_id, external_stage_id, instance_info);
- InstanceRecycler recycler(txn_kv, instance_info, thread_group);
+ InstanceRecycler recycler(txn_kv, instance_info, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto internal_accessor = recycler.accessor_map_.find(internal_stage_id)->second;
@@ -1810,7 +1842,8 @@ TEST(RecyclerTest, recycle_batch_copy_jobs) {
InstanceInfoPB instance_info;
create_instance(internal_stage_id, external_stage_id, instance_info);
- InstanceRecycler recycler(txn_kv, instance_info, thread_group);
+ InstanceRecycler recycler(txn_kv, instance_info, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
const auto& internal_accessor = recycler.accessor_map_.find(internal_stage_id)->second;
@@ -1924,7 +1957,8 @@ TEST(RecyclerTest, recycle_stage) {
instance.set_instance_id(mock_instance);
instance.add_obj_info()->CopyFrom(object_info);
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto accessor = recycler.accessor_map_.begin()->second;
for (int i = 0; i < 10; ++i) {
@@ -1984,7 +2018,8 @@ TEST(RecyclerTest, recycle_deleted_instance) {
InstanceInfoPB instance_info;
create_instance(internal_stage_id, external_stage_id, instance_info);
- InstanceRecycler recycler(txn_kv, instance_info, thread_group);
+ InstanceRecycler recycler(txn_kv, instance_info, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
// create txn key
for (size_t i = 0; i < 100; i++) {
@@ -2569,7 +2604,8 @@ TEST(RecyclerTest, delete_rowset_data) {
}
{
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto accessor = recycler.accessor_map_.begin()->second;
int64_t txn_id_base = 114115;
@@ -2603,7 +2639,8 @@ TEST(RecyclerTest, delete_rowset_data) {
tmp_obj_info->set_bucket(config::test_s3_bucket);
tmp_obj_info->set_prefix(resource_id);
- InstanceRecycler recycler(txn_kv, tmp_instance, thread_group);
+ InstanceRecycler recycler(txn_kv, tmp_instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto accessor = recycler.accessor_map_.begin()->second;
// Delete multiple rowset files using one series of RowsetPB
@@ -2623,7 +2660,8 @@ TEST(RecyclerTest, delete_rowset_data) {
ASSERT_FALSE(list_iter->has_next());
}
{
- InstanceRecycler recycler(txn_kv, instance, thread_group);
+ InstanceRecycler recycler(txn_kv, instance, thread_group,
+ std::make_shared<TxnLazyCommitter>(txn_kv));
ASSERT_EQ(recycler.init(), 0);
auto accessor = recycler.accessor_map_.begin()->second;
// Delete multiple rowset files using one series of RowsetPB
diff --git a/cloud/test/txn_lazy_commit_test.cpp b/cloud/test/txn_lazy_commit_test.cpp
new file mode 100644
index 00000000000..6b4518c09ab
--- /dev/null
+++ b/cloud/test/txn_lazy_commit_test.cpp
@@ -0,0 +1,216 @@
+// 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 <bvar/window.h>
+#include <fmt/core.h>
+#include <gen_cpp/cloud.pb.h>
+#include <gen_cpp/olap_file.pb.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <cstdint>
+#include <memory>
+#include <random>
+#include <string>
+#include <thread>
+
+#include "common/config.h"
+#include "common/logging.h"
+#include "common/util.h"
+#include "cpp/sync_point.h"
+#include "meta-service/keys.h"
+#include "meta-service/mem_txn_kv.h"
+#include "meta-service/meta_service.h"
+#include "meta-service/meta_service_helper.h"
+#include "meta-service/txn_kv_error.h"
+#include "mock_resource_manager.h"
+#include "rate-limiter/rate_limiter.h"
+#include "resource-manager/resource_manager.h"
+
+int main(int argc, char** argv) {
+ const std::string conf_file = "doris_cloud.conf";
+ if (!doris::cloud::config::init(conf_file.c_str(), true)) {
+ std::cerr << "failed to init config file, conf=" << conf_file << std::endl;
+ return -1;
+ }
+
+ config::enable_cloud_txn_lazy_commit = true;
+ config::txn_lazy_commit_rowsets_thresold = 1;
+ config::txn_lazy_max_rowsets_per_batch = 1;
+ config::txn_lazy_commit_num_threads = 2;
+
+ if (!doris::cloud::init_glog("txn_lazy_commit_test")) {
+ std::cerr << "failed to init glog" << std::endl;
+ return -1;
+ }
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+namespace doris::cloud {
+
+std::unique_ptr<MetaServiceProxy> get_meta_service(std::shared_ptr<TxnKv> txn_kv,
+ bool mock_resource_mgr) {
+ std::unique_ptr<Transaction> txn;
+ EXPECT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
+ txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set
+ EXPECT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+ auto rs = mock_resource_mgr ? std::make_shared<MockResourceManager>(txn_kv)
+ : std::make_shared<ResourceManager>(txn_kv);
+ auto rl = std::make_shared<RateLimiter>();
+ auto meta_service = std::make_unique<MetaServiceImpl>(txn_kv, rs, rl);
+ return std::make_unique<MetaServiceProxy>(std::move(meta_service));
+}
+
+static std::string next_rowset_id() {
+ static int cnt = 0;
+ return std::to_string(++cnt);
+}
+
+static void add_tablet(CreateTabletsRequest& req, int64_t table_id, int64_t index_id,
+ int64_t partition_id, int64_t tablet_id) {
+ auto tablet = req.add_tablet_metas();
+ tablet->set_table_id(table_id);
+ tablet->set_index_id(index_id);
+ tablet->set_partition_id(partition_id);
+ tablet->set_tablet_id(tablet_id);
+ auto schema = tablet->mutable_schema();
+ schema->set_schema_version(0);
+ auto first_rowset = tablet->add_rs_metas();
+ first_rowset->set_rowset_id(0); // required
+ first_rowset->set_rowset_id_v2(next_rowset_id());
+ first_rowset->set_start_version(0);
+ first_rowset->set_end_version(1);
+ first_rowset->mutable_tablet_schema()->CopyFrom(*schema);
+}
+
+static void create_tablet(MetaServiceProxy* meta_service, int64_t table_id, int64_t index_id,
+ int64_t partition_id, int64_t tablet_id) {
+ brpc::Controller cntl;
+ CreateTabletsRequest req;
+ CreateTabletsResponse res;
+ add_tablet(req, table_id, index_id, partition_id, tablet_id);
+ meta_service->create_tablets(&cntl, &req, &res, nullptr);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << tablet_id;
+}
+
+static doris::RowsetMetaCloudPB create_rowset(int64_t txn_id, int64_t tablet_id,
+ int partition_id = 10, int64_t version = -1,
+ int num_rows = 100) {
+ doris::RowsetMetaCloudPB rowset;
+ rowset.set_rowset_id(0); // required
+ rowset.set_rowset_id_v2(next_rowset_id());
+ rowset.set_tablet_id(tablet_id);
+ rowset.set_partition_id(partition_id);
+ rowset.set_txn_id(txn_id);
+ if (version > 0) {
+ rowset.set_start_version(version);
+ rowset.set_end_version(version);
+ }
+ rowset.set_num_segments(1);
+ rowset.set_num_rows(num_rows);
+ rowset.set_data_disk_size(num_rows * 100);
+ rowset.mutable_tablet_schema()->set_schema_version(0);
+ rowset.set_txn_expiration(::time(nullptr)); // Required by DCHECK
+ return rowset;
+}
+
+static void commit_rowset(MetaServiceProxy* meta_service, const doris::RowsetMetaCloudPB& rowset,
+ CreateRowsetResponse& res) {
+ brpc::Controller cntl;
+ auto arena = res.GetArena();
+ auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
+ req->mutable_rowset_meta()->CopyFrom(rowset);
+ meta_service->commit_rowset(&cntl, req, &res, nullptr);
+ if (!arena) {
+ delete req;
+ }
+}
+
+TEST(TxnLazyCommitTest, CommitTxnEventuallyTest) {
+ int ret = 0;
+ // MemKv
+ auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
+ if (txn_kv != nullptr) {
+ ret = txn_kv->init();
+ [&] { ASSERT_EQ(ret, 0); }();
+ }
+ [&] { ASSERT_NE(txn_kv.get(), nullptr); }();
+
+ int64_t db_id = 3213235;
+ int64_t table_id = 93213131;
+
+ auto meta_service = get_meta_service(txn_kv, true);
+ brpc::Controller cntl;
+ BeginTxnRequest req;
+ req.set_cloud_unique_id("test_cloud_unique_id");
+ TxnInfoPB txn_info_pb;
+ txn_info_pb.set_db_id(db_id);
+ txn_info_pb.set_label("test_label");
+ txn_info_pb.add_table_ids(table_id);
+ txn_info_pb.set_timeout_ms(36000);
+ req.mutable_txn_info()->CopyFrom(txn_info_pb);
+ BeginTxnResponse res;
+ meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
+ nullptr);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+ int64_t txn_id = res.txn_id();
+
+ // mock rowset and tablet
+ int64_t tablet_id_base = 1103;
+ for (int i = 0; i < 5; ++i) {
+ create_tablet(meta_service.get(), table_id, 1235, 1236, tablet_id_base + i);
+ auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
+ CreateRowsetResponse res;
+ commit_rowset(meta_service.get(), tmp_rowset, res);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+ }
+
+ {
+ brpc::Controller cntl;
+ CommitTxnRequest req;
+ req.set_cloud_unique_id("test_cloud_unique_id");
+ req.set_db_id(db_id);
+ req.set_txn_id(txn_id);
+ req.set_is_2pc(false);
+ req.set_enable_txn_lazy_commit(true);
+ CommitTxnResponse res;
+ meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
+ &res, nullptr);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+ }
+
+ {
+ std::unique_ptr<Transaction> txn;
+ ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
+ std::string mock_instance = "test_instance";
+ int64_t tablet_id_base = 1103;
+ for (int i = 0; i < 5; ++i) {
+ int64_t tablet_id = tablet_id_base + i;
+ std::string key = meta_tablet_idx_key({mock_instance, tablet_id});
+ std::string val;
+ ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
+ TabletIndexPB tablet_idx_pb;
+ tablet_idx_pb.ParseFromString(val);
+ ASSERT_EQ(tablet_idx_pb.db_id(), db_id);
+ }
+ }
+}
+} // namespace doris::cloud
\ No newline at end of file
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index db80a265a3e..eb70a71a9c3 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -3010,6 +3010,10 @@ public class Config extends ConfigBase {
@ConfField(mutable = true, description = {"存算分离模式下commit阶段等锁超时时间,默认5s"})
public static int try_commit_lock_timeout_seconds = 5;
+
+ @ConfField(mutable = true, description = {"存算分离模式下是否开启大事务提交,默认false"})
+ public static boolean enable_cloud_txn_lazy_commit = false;
+
// ATTN: DONOT add any config not related to cloud mode here
// ATTN: DONOT add any config not related to cloud mode here
// ATTN: DONOT add any config not related to cloud mode here
diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/CloudRollupJobV2.java b/fe/fe-core/src/main/java/org/apache/doris/alter/CloudRollupJobV2.java
index 9c21bb3a3ad..52846353a8a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/CloudRollupJobV2.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/CloudRollupJobV2.java
@@ -215,6 +215,7 @@ public class CloudRollupJobV2 extends RollupJobV2 {
tbl.variantEnableFlattenNested());
requestBuilder.addTabletMetas(builder);
} // end for rollupTablets
+ requestBuilder.setDbId(dbId);
((CloudInternalCatalog) Env.getCurrentInternalCatalog())
.sendCreateTabletsRpc(requestBuilder);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/CloudSchemaChangeJobV2.java b/fe/fe-core/src/main/java/org/apache/doris/alter/CloudSchemaChangeJobV2.java
index c33e10bb4a5..7061478440f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/CloudSchemaChangeJobV2.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/CloudSchemaChangeJobV2.java
@@ -236,6 +236,7 @@ public class CloudSchemaChangeJobV2 extends SchemaChangeJobV2 {
tbl.variantEnableFlattenNested());
requestBuilder.addTabletMetas(builder);
} // end for rollupTablets
+ requestBuilder.setDbId(dbId);
((CloudInternalCatalog) Env.getCurrentInternalCatalog())
.sendCreateTabletsRpc(requestBuilder);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/datasource/CloudInternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/datasource/CloudInternalCatalog.java
index fb520d4295b..ef461c74b21 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/datasource/CloudInternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/datasource/CloudInternalCatalog.java
@@ -181,6 +181,7 @@ public class CloudInternalCatalog extends InternalCatalog {
if (!storageVaultIdSet && ((CloudEnv) Env.getCurrentEnv()).getEnableStorageVault()) {
requestBuilder.setStorageVaultName(storageVaultName);
}
+ requestBuilder.setDbId(dbId);
LOG.info("create tablets, dbId: {}, tableId: {}, tableName: {}, partitionId: {}, partitionName: {}, "
+ "indexId: {}, vault name {}",
@@ -682,7 +683,7 @@ public class CloudInternalCatalog extends InternalCatalog {
sendCreateTabletsRpc(Cloud.CreateTabletsRequest.Builder requestBuilder) throws DdlException {
requestBuilder.setCloudUniqueId(Config.cloud_unique_id);
Cloud.CreateTabletsRequest createTabletsReq = requestBuilder.build();
-
+ Preconditions.checkState(createTabletsReq.hasDbId(), "createTabletsReq must set dbId");
if (LOG.isDebugEnabled()) {
LOG.debug("send create tablets rpc, createTabletsReq: {}", createTabletsReq);
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
index e8be1ac133e..f224d2929a6 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
@@ -472,7 +472,8 @@ public class CloudGlobalTransactionMgr implements GlobalTransactionMgrIface {
.setTxnId(transactionId)
.setIs2Pc(is2PC)
.setCloudUniqueId(Config.cloud_unique_id)
- .addAllBaseTabletIds(getBaseTabletsFromTables(tableList, tabletCommitInfos));
+ .addAllBaseTabletIds(getBaseTabletsFromTables(tableList, tabletCommitInfos))
+ .setEnableTxnLazyCommit(Config.enable_cloud_txn_lazy_commit);
// if tablet commit info is empty, no need to pass mowTableList to meta service.
if (tabletCommitInfos != null && !tabletCommitInfos.isEmpty()) {
@@ -933,7 +934,8 @@ public class CloudGlobalTransactionMgr implements GlobalTransactionMgrIface {
.setTxnId(transactionId)
.setIs2Pc(false)
.setCloudUniqueId(Config.cloud_unique_id)
- .setIsTxnLoad(true);
+ .setIsTxnLoad(true)
+ .setEnableTxnLazyCommit(Config.enable_cloud_txn_lazy_commit);
// add sub txn infos
for (SubTransactionState subTransactionState : subTransactionStates) {
builder.addSubTxnInfos(SubTxnInfo.newBuilder().setSubTxnId(subTransactionState.getSubTransactionId())
diff --git a/gensrc/proto/cloud.proto b/gensrc/proto/cloud.proto
index 9c81c33c575..87948fdeea9 100644
--- a/gensrc/proto/cloud.proto
+++ b/gensrc/proto/cloud.proto
@@ -404,6 +404,8 @@ message TxnRunningPB {
message VersionPB {
optional int64 version = 1;
optional int64 update_time_ms = 2;
+ // now only allow one element
+ repeated int64 pending_txn_ids = 3;
}
message RecycleTxnPB {
@@ -664,6 +666,7 @@ message CommitTxnRequest {
// for transaction load
optional bool is_txn_load = 9;
repeated SubTxnInfo sub_txn_infos = 10;
+ optional bool enable_txn_lazy_commit = 11;
}
message SubTxnInfo {
@@ -891,6 +894,7 @@ message CreateTabletsRequest {
optional string cloud_unique_id = 1; // For auth
repeated doris.TabletMetaCloudPB tablet_metas = 2;
optional string storage_vault_name = 3;
+ optional int64 db_id = 4;
}
message CreateTabletsResponse {
diff --git a/regression-test/data/large_txn_load_p1/test_insert_with_many_buckets.out b/regression-test/data/large_txn_load_p1/test_insert_with_many_buckets.out
new file mode 100644
index 00000000000..a507ec71566
--- /dev/null
+++ b/regression-test/data/large_txn_load_p1/test_insert_with_many_buckets.out
@@ -0,0 +1,4 @@
+-- This file is automatically generated. You should know what you did if you want to edit this
+-- !sql --
+999
+
diff --git a/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions.out b/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions.out
new file mode 100644
index 00000000000..ce114af904d
--- /dev/null
+++ b/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions.out
@@ -0,0 +1,4 @@
+-- This file is automatically generated. You should know what you did if you want to edit this
+-- !sql --
+1000
+
diff --git a/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.out b/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.out
new file mode 100644
index 00000000000..475b9ff01e9
--- /dev/null
+++ b/regression-test/data/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.out
@@ -0,0 +1,4 @@
+-- This file is automatically generated. You should know what you did if you want to edit this
+-- !sql --
+400
+
diff --git a/regression-test/suites/large_txn_load_p1/test_insert_with_many_buckets.groovy b/regression-test/suites/large_txn_load_p1/test_insert_with_many_buckets.groovy
new file mode 100644
index 00000000000..ddfabe468e4
--- /dev/null
+++ b/regression-test/suites/large_txn_load_p1/test_insert_with_many_buckets.groovy
@@ -0,0 +1,57 @@
+// 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.
+
+suite("test_insert_with_many_buckets") {
+ def tableName = 'test_insert_with_many_buckets'
+
+ // [start, end)
+ def makeInsertStmt = { start, end ->
+ def stmt = ""
+ for (int i = start; i < end; i++) {
+ if (i == (end - 1)) {
+ stmt += """(${i}, false, 1, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10")\n"""
+ } else {
+ stmt += """(${i}, false, 1, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ }
+ }
+ return stmt
+ }
+
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
+ sql """ CREATE TABLE IF NOT EXISTS ${tableName} (
+ `c1` bigint(20) not null,
+ `c2` boolean REPLACE_IF_NOT_NULL null,
+ `c3` tinyint(4) REPLACE_IF_NOT_NULL null,
+ `c4` decimalv3(9, 3) REPLACE_IF_NOT_NULL null,
+ `c5` char(36) REPLACE_IF_NOT_NULL null,
+ `c6` date REPLACE_IF_NOT_NULL null,
+ `c7` datetime REPLACE_IF_NOT_NULL null,
+ `c8` varchar(64) REPLACE_IF_NOT_NULL null,
+ `c9` double REPLACE_IF_NOT_NULL null,
+ `c10` string REPLACE_IF_NOT_NULL null
+ ) engine=olap
+ AGGREGATE KEY(`c1`)
+ DISTRIBUTED BY HASH(`c1`) BUCKETS 1000
+ PROPERTIES (
+ "replication_num" = "1"
+ );
+ """
+
+ sql """ insert into ${tableName} values ${makeInsertStmt(1 , 1000)};"""
+ qt_sql """ select count(*) from ${tableName};"""
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
+}
diff --git a/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions.groovy b/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions.groovy
new file mode 100644
index 00000000000..a530f797a0c
--- /dev/null
+++ b/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions.groovy
@@ -0,0 +1,73 @@
+// 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.
+
+suite("test_insert_with_many_parititions") {
+ def tableName = 'test_insert_with_many_parititions'
+
+ def makePartitionsStmt = { partitionNum ->
+ def stmt = ""
+ for (int i = 0; i < partitionNum; i++) {
+ if (i == (partitionNum - 1)) {
+ stmt += """partition `p${i}` values [("${i}"), ("${i + 1}"))"""
+ } else {
+ stmt += """partition `p${i}` values [("${i}"), ("${i + 1}")),\n\t\t\t"""
+ }
+ }
+ return stmt
+ }
+
+ // [start, end)
+ def makeInsertStmt = { start, end ->
+ def stmt = ""
+ for (int i = start; i < end; i++) {
+ if (i == (end - 1)) {
+ stmt += """(${i}, false, 1, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10")\n"""
+ } else {
+ stmt += """(${i}, false, 1, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ }
+ }
+ return stmt
+ }
+
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
+ sql """ CREATE TABLE IF NOT EXISTS ${tableName} (
+ `c1` bigint(20) not null,
+ `c2` boolean REPLACE_IF_NOT_NULL null,
+ `c3` tinyint(4) REPLACE_IF_NOT_NULL null,
+ `c4` decimalv3(9, 3) REPLACE_IF_NOT_NULL null,
+ `c5` char(36) REPLACE_IF_NOT_NULL null,
+ `c6` date REPLACE_IF_NOT_NULL null,
+ `c7` datetime REPLACE_IF_NOT_NULL null,
+ `c8` varchar(64) REPLACE_IF_NOT_NULL null,
+ `c9` double REPLACE_IF_NOT_NULL null,
+ `c10` string REPLACE_IF_NOT_NULL null
+ ) engine=olap
+ AGGREGATE KEY(`c1`)
+ PARTITION BY RANGE(`c1`)
+ (
+ ${makePartitionsStmt(1000)}
+ )
+ DISTRIBUTED BY HASH(`c1`) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1"
+ );
+ """
+
+ sql """ insert into ${tableName} values ${makeInsertStmt(0, 1000)};"""
+ qt_sql """ select count(*) from ${tableName};"""
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
+}
diff --git a/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.groovy b/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.groovy
new file mode 100644
index 00000000000..b303ce13027
--- /dev/null
+++ b/regression-test/suites/large_txn_load_p1/test_insert_with_many_partitions_and_buckets.groovy
@@ -0,0 +1,79 @@
+// 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.
+
+suite("test_insert_with_many_parititions_and_buckets") {
+ def tableName = 'test_insert_with_many_parititions_and_buckets'
+
+ def makePartitionsStmt = { partitionNum ->
+ def stmt = ""
+ for (int i = 0; i < partitionNum; i++) {
+ if (i == (partitionNum - 1)) {
+ stmt += """partition `p${i}` values [("${i}"), ("${i + 1}"))"""
+ } else {
+ stmt += """partition `p${i}` values [("${i}"), ("${i + 1}")),\n\t\t\t"""
+ }
+ }
+ return stmt
+ }
+
+ // [start, end)
+ def makeInsertStmt = { start, end ->
+ def stmt = ""
+ for (int i = start; i < end; i++) {
+ if (i == (end - 1)) {
+ stmt += """(${i}, 1, false,null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 2, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 3, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 4, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10")\n"""
+ } else {
+ stmt += """(${i}, 1, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 2, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 3, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ stmt += """(${i}, 4, false, null, "c5", "2023-09-01", null, "c8", 1.1234, "c10"),\n"""
+ }
+ }
+ return stmt
+ }
+
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
+ sql """ CREATE TABLE IF NOT EXISTS ${tableName} (
+ `c1` bigint(20) not null,
+ `c2` int(6) not null,
+ `c3` boolean REPLACE_IF_NOT_NULL null,
+ `c4` decimalv3(9, 3) REPLACE_IF_NOT_NULL null,
+ `c5` char(36) REPLACE_IF_NOT_NULL null,
+ `c6` date REPLACE_IF_NOT_NULL null,
+ `c7` datetime REPLACE_IF_NOT_NULL null,
+ `c8` varchar(64) REPLACE_IF_NOT_NULL null,
+ `c9` double REPLACE_IF_NOT_NULL null,
+ `c10` string REPLACE_IF_NOT_NULL null
+ ) engine=olap
+ AGGREGATE KEY(`c1`, `c2`)
+ PARTITION BY RANGE(`c1`)
+ (
+ ${makePartitionsStmt(100)}
+ )
+ DISTRIBUTED BY HASH(`c2`) BUCKETS 16
+ PROPERTIES (
+ "replication_num" = "1"
+ );
+ """
+
+ sql """ insert into ${tableName} values ${makeInsertStmt(0, 100)};"""
+ qt_sql """ select count(*) from ${tableName};"""
+ sql """ DROP TABLE IF EXISTS ${tableName} FORCE;"""
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org