This is an automated email from the ASF dual-hosted git repository.
hello-stephen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 883bf91da0c [fix](cloud) Avoid caching partition versions with pending
transactions (#68104)
883bf91da0c is described below
commit 883bf91da0c88ce6d9436dd1e0f3d1cc48ed6d00
Author: meiyi <[email protected]>
AuthorDate: Fri Oct 23 15:20:01 2026 +0800
[fix](cloud) Avoid caching partition versions with pending transactions
(#68104)
Non-waiting version reads could cache an older partition version while a
lazy commit was still pending.
Return per-partition pending state alongside scalar and batch versions.
FE keeps pending responses uncached and restores cache validity only for
the epoch captured before the RPC.
---------
Co-authored-by: Gavin Chou <[email protected]>
Co-authored-by: Gavin Chou <[email protected]>
---
cloud/src/meta-service/meta_service.cpp | 14 +-
cloud/test/meta_service_versioned_read_test.cpp | 84 ++++++
.../apache/doris/cloud/catalog/CloudPartition.java | 78 ++++--
.../cloud/catalog/CloudSyncVersionDaemon.java | 26 +-
.../transaction/CloudGlobalTransactionMgr.java | 3 +-
.../doris/common/proc/PartitionsProcDir.java | 2 +-
.../java/org/apache/doris/qe/SessionVariable.java | 7 +-
.../catalog/CloudPartitionPendingVersionTest.java | 303 +++++++++++++++++++++
.../doris/cloud/catalog/CloudPartitionTest.java | 5 +
.../transaction/CloudGlobalTransactionMgrTest.java | 37 ++-
.../org/apache/doris/qe/SessionVariablesTest.java | 18 ++
gensrc/proto/cloud.proto | 4 +
12 files changed, 547 insertions(+), 34 deletions(-)
diff --git a/cloud/src/meta-service/meta_service.cpp
b/cloud/src/meta-service/meta_service.cpp
index 3b691083730..452ccc8252e 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -293,6 +293,7 @@ void
MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
TxnErrorCode err = reader.get_partition_version(partition_id,
&partition_version,
nullptr, false,
&source_instance);
if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
+ response->add_has_pending_txns(false);
msg = "partition version not found";
code = MetaServiceCode::VERSION_NOT_FOUND;
return;
@@ -311,6 +312,7 @@ void
MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
}
partition_version = std::move(pending_versions.front());
}
+
response->add_has_pending_txns(!partition_version.pending_txn_ids().empty());
if (!partition_version.has_version()) {
msg = "partition version not found";
code = MetaServiceCode::VERSION_NOT_FOUND;
@@ -369,6 +371,7 @@ void
MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
}
version_pb = std::move(pending_versions.front());
}
+
response->add_has_pending_txns(!version_pb.pending_txn_ids().empty());
if (!version_pb.has_version()) {
msg = "not found";
code = MetaServiceCode::VERSION_NOT_FOUND;
@@ -382,6 +385,9 @@ void
MetaServiceImpl::get_version(::google::protobuf::RpcController* controller,
TEST_SYNC_POINT_CALLBACK("get_version_code", &code);
return;
} else if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
+ if (!is_table_version) {
+ response->add_has_pending_txns(false);
+ }
msg = "not found";
code = MetaServiceCode::VERSION_NOT_FOUND;
return;
@@ -441,6 +447,7 @@ void
MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
response->clear_table_ids();
response->clear_versions();
response->clear_db_ids();
+ response->clear_has_pending_txns();
}
return;
}
@@ -545,7 +552,8 @@ void
MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
}
}
for (auto& version_pb : partition_versions) {
- response->add_versions(version_pb.version());
+ response->add_versions(version_pb.has_version() ?
version_pb.version() : -1);
+
response->add_has_pending_txns(!version_pb.pending_txn_ids().empty());
response->add_version_update_time_ms(version_pb.update_time_ms());
response->add_commit_tsos(version_pb.commit_tso());
}
@@ -558,6 +566,7 @@ void
MetaServiceImpl::batch_get_version(::google::protobuf::RpcController* contr
response->clear_versions();
response->clear_version_update_time_ms();
response->clear_commit_tsos();
+ response->clear_has_pending_txns();
}
}
@@ -707,7 +716,8 @@ std::pair<MetaServiceCode, std::string>
MetaServiceImpl::batch_get_partition_ver
}
}
for (auto& version_pb : partition_versions) {
- response->add_versions(version_pb.version());
+ response->add_versions(version_pb.has_version() ?
version_pb.version() : -1);
+
response->add_has_pending_txns(!version_pb.pending_txn_ids().empty());
response->add_version_update_time_ms(version_pb.update_time_ms());
response->add_commit_tsos(version_pb.commit_tso());
}
diff --git a/cloud/test/meta_service_versioned_read_test.cpp
b/cloud/test/meta_service_versioned_read_test.cpp
index 7ba77a1d3aa..3fc8f833a91 100644
--- a/cloud/test/meta_service_versioned_read_test.cpp
+++ b/cloud/test/meta_service_versioned_read_test.cpp
@@ -591,6 +591,90 @@ TEST(MetaServiceVersionedReadTest, GetVersion) {
}
}
+// Exercise both storage layouts and both scalar/batch wire responses.
+TEST(MetaServiceVersionedReadTest, GetVersionPendingState) {
+ for (bool versioned_read : {false, true}) {
+ SCOPED_TRACE(versioned_read);
+ auto service = get_meta_service(false);
+ std::string instance_id = "pending_version_instance";
+ MOCK_GET_INSTANCE_ID(instance_id);
+ if (versioned_read) {
+ create_and_refresh_instance(service.get(), instance_id);
+ }
+ std::unique_ptr<Transaction> txn;
+ ASSERT_EQ(service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+ for (int64_t partition_id : {1, 2, 3}) {
+ VersionPB version;
+ if (partition_id != 3) {
+ version.set_version(partition_id == 1 ? 12 : 7);
+ }
+ if (partition_id != 2) {
+ version.add_pending_txn_ids(100);
+ }
+ version.set_update_time_ms(2000);
+ version.set_commit_tso(3000);
+ std::string value = version.SerializeAsString();
+ txn->put(partition_version_key({instance_id, 1, 1, partition_id}),
value);
+ versioned_put(txn.get(),
versioned::partition_version_key({instance_id, partition_id}),
+ value);
+ }
+ // Model waiting on an observed pending version after its transaction
has become visible.
+ TxnIndexPB index;
+ index.mutable_tablet_index()->set_db_id(1);
+ txn->put(txn_index_key({instance_id, 100}), index.SerializeAsString());
+ TxnInfoPB info;
+ info.set_txn_id(100);
+ info.set_db_id(1);
+ info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
+ txn->put(txn_info_key({instance_id, 1, 100}),
info.SerializeAsString());
+ ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+ for (bool wait : {false, true}) {
+ SCOPED_TRACE(wait);
+ // Mix pending, non-pending, first-write pending, and absent
partitions in request order.
+ std::vector<int64_t> ids {2, 1, 3, 4};
+ std::vector<int64_t> expected_versions {7, wait ? 13 : 12, wait ?
2 : -1, -1};
+ std::vector<bool> expected_pending {false, !wait, !wait, false};
+ GetVersionRequest batch;
+ batch.set_cloud_unique_id("test_cloud_unique_id");
+ batch.set_batch_mode(true);
+ batch.set_wait_for_pending_txn(wait);
+ for (size_t i = 0; i < ids.size(); ++i) {
+ GetVersionRequest req;
+ req.set_cloud_unique_id("test_cloud_unique_id");
+ req.set_db_id(1);
+ req.set_table_id(1);
+ req.set_partition_id(ids[i]);
+ req.set_wait_for_pending_txn(wait);
+ brpc::Controller ctrl;
+ GetVersionResponse resp;
+ service->get_version(&ctrl, &req, &resp, nullptr);
+ ASSERT_EQ(resp.status().code(), expected_versions[i] == -1
+ ?
MetaServiceCode::VERSION_NOT_FOUND
+ : MetaServiceCode::OK);
+ ASSERT_EQ(resp.has_pending_txns_size(), 1);
+ EXPECT_EQ(resp.has_pending_txns(0), expected_pending[i]);
+ if (expected_versions[i] != -1) {
+ EXPECT_EQ(resp.version(), expected_versions[i]);
+ }
+ batch.add_db_ids(1);
+ batch.add_table_ids(1);
+ batch.add_partition_ids(ids[i]);
+ }
+ brpc::Controller ctrl;
+ GetVersionResponse resp;
+ service->get_version(&ctrl, &batch, &resp, nullptr);
+ ASSERT_EQ(resp.status().code(), MetaServiceCode::OK) <<
resp.DebugString();
+ ASSERT_EQ(resp.versions_size(), ids.size());
+ ASSERT_EQ(resp.has_pending_txns_size(), ids.size());
+ for (size_t i = 0; i < ids.size(); ++i) {
+ EXPECT_EQ(resp.versions(i), expected_versions[i]);
+ EXPECT_EQ(resp.has_pending_txns(i), expected_pending[i]);
+ }
+ }
+ }
+}
+
TEST(MetaServiceVersionedReadTest, BatchGetVersion) {
struct TestCase {
std::vector<int64_t> table_ids;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
index 9c788a9ab7f..5bbc3a89646 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
@@ -160,10 +160,16 @@ public class CloudPartition extends Partition {
return getCachedVisibleVersion();
}
- return getVisibleVersionFromMs(false);
+ return getVisibleVersionFromMs(shouldWaitForPendingTxns());
}
- private long getVisibleVersionFromMs(boolean waitForPendingTxns) {
+ private static boolean shouldWaitForPendingTxns() {
+ ConnectContext ctx = ConnectContext.get();
+ return ctx == null ?
VariableMgr.getDefaultSessionVariable().cloudGetVersionWaitForPendingTxn
+ : ctx.getSessionVariable().cloudGetVersionWaitForPendingTxn;
+ }
+
+ public long getVisibleVersionFromMs(boolean waitForPendingTxns) {
long cacheEpoch = versionCacheEpoch.get();
if (LOG.isDebugEnabled()) {
LOG.debug("getVisibleVersionFromMs use CloudPartition {},
waitForPendingTxns: {}",
@@ -192,8 +198,7 @@ public class CloudPartition extends Partition {
LOG.debug("get version from meta service, version: {},
tso: {}, partition: {}",
version, tso, super.getId());
}
- setCachedVisibleVersion(version, mTime, tso);
- refreshedVersionCacheEpoch.accumulateAndGet(cacheEpoch,
Math::max);
+ cacheVisibleVersion(version, mTime, tso, cacheEpoch,
isVersionCacheable(resp, 0, 1));
return version;
} else {
assert resp.getStatus().getCode() ==
MetaServiceCode.VERSION_NOT_FOUND;
@@ -203,14 +208,37 @@ public class CloudPartition extends Partition {
if (LOG.isDebugEnabled()) {
LOG.debug("get version from meta service, version: {},
partition: {}", version, super.getId());
}
- setCachedVisibleVersion(version, mTime);
- refreshedVersionCacheEpoch.accumulateAndGet(cacheEpoch, Math::max);
+ cacheVisibleVersion(version, mTime, -1, cacheEpoch,
isVersionCacheable(resp, 0, 1));
return version;
} catch (RpcException e) {
throw new RuntimeException("get version from meta service failed");
}
}
+ private void cacheVisibleVersion(long version, long updateTimeMs, long
tso, long cacheEpoch, boolean cacheable) {
+ if (!cacheable) {
+ // Invalidate before touching the timestamp, even when the cache
was valid before this read.
+ invalidateCachedVisibleVersion();
+ }
+ setCachedVisibleVersion(version, updateTimeMs, tso);
+ if (cacheable) {
+ // A response must not acknowledge an invalidation that happened
after its request started.
+ refreshedVersionCacheEpoch.accumulateAndGet(cacheEpoch, Math::max);
+ }
+ }
+
+ private static boolean isVersionCacheable(Cloud.GetVersionResponse
response, int index, int size) {
+ if (response.getHasPendingTxnsCount() == 0) {
+ // Older MS does not report pending state. Preserve its existing
cache behavior.
+ if (index == 0 && LOG.isDebugEnabled()) {
+ LOG.debug("MS version response has no pending transaction
state; using legacy cache behavior"
+ + " for {} partitions", size);
+ }
+ return true;
+ }
+ return response.getHasPendingTxnsCount() == size &&
!response.getHasPendingTxns(index);
+ }
+
// Select the non-empty partitions and return the ids.
public static List<Long> selectNonEmptyPartitionIds(List<CloudPartition>
partitions) {
return selectNonEmptyPartitionIds(partitions, false);
@@ -261,6 +289,10 @@ public class CloudPartition extends Partition {
// Get visible version from the specified partitions;
//
// Return the visible version in order of the specified partition ids
+ public static List<Long>
getSnapshotVisibleVersionFromMs(List<CloudPartition> partitions) throws
RpcException {
+ return getSnapshotVisibleVersionFromMs(partitions,
shouldWaitForPendingTxns());
+ }
+
public static List<Long> getSnapshotVisibleVersionFromMs(
List<CloudPartition> partitions, boolean waitForPendingTxns)
throws RpcException {
return getSnapshotVisibleVersionFromMs(
@@ -278,6 +310,7 @@ public class CloudPartition extends Partition {
List<Long> partitionIds = new ArrayList<>();
List<Long> versionUpdateTimesMs = new ArrayList<>();
List<Long> commitTsos = new ArrayList<>();
+ List<Boolean> cacheableVersions = new ArrayList<>();
List<Long> cacheEpochs = new ArrayList<>();
for (CloudPartition partition : partitions) {
dbIds.add(partition.getDbId());
@@ -287,7 +320,8 @@ public class CloudPartition extends Partition {
}
List<Long> versions = getSnapshotVisibleVersion(
- dbIds, tableIds, partitionIds, versionUpdateTimesMs,
commitTsos, waitForPendingTxns, maxAttempts);
+ dbIds, tableIds, partitionIds, versionUpdateTimesMs,
commitTsos, cacheableVersions,
+ waitForPendingTxns, maxAttempts);
// Cache visible version, see hasData() for details.
int size = versions.size();
@@ -303,12 +337,12 @@ public class CloudPartition extends Partition {
// For compatibility, the existing partitions may not have
mtime
long mTime = versions.size() ==
versionUpdateTimesMs.size() ? versionUpdateTimesMs.get(i) : 0;
long tso = hasCommitTsos ? commitTsos.get(i) : -1;
- partitions.get(i).setCachedVisibleVersion(versions.get(i),
mTime, tso);
+ partitions.get(i).cacheVisibleVersion(version, mTime, tso,
+ cacheEpochs.get(i), cacheableVersions.get(i));
} else { // No data has been written to this partition
-
partitions.get(i).setCachedVisibleVersion(Partition.PARTITION_INIT_VERSION,
- System.currentTimeMillis());
+
partitions.get(i).cacheVisibleVersion(Partition.PARTITION_INIT_VERSION,
+ System.currentTimeMillis(), -1,
cacheEpochs.get(i), cacheableVersions.get(i));
}
-
partitions.get(i).refreshedVersionCacheEpoch.accumulateAndGet(cacheEpochs.get(i),
Math::max);
}
} finally {
for (int i = tables.size() - 1; i >= 0; i--) {
@@ -353,7 +387,7 @@ public class CloudPartition extends Partition {
?
VariableMgr.getDefaultSessionVariable().cloudPartitionVersionCacheTtlMs
: ctx.getSessionVariable().cloudPartitionVersionCacheTtlMs;
if (cloudPartitionVersionCacheTtlMs <= 0) { // No cached versions will
be used
- return getSnapshotVisibleVersionFromMs(partitions, false);
+ return getSnapshotVisibleVersionFromMs(partitions);
}
// partitionId -> cachedVersion
@@ -384,8 +418,7 @@ public class CloudPartition extends Partition {
List<Long> versions = null;
if (!expiredPartitions.isEmpty()) { // Not all partition versions are
from cache
- versions = getSnapshotVisibleVersionFromMs(
- expiredPartitions, /*waitForPendingTxns=*/false); // Get
the rest versions from meta-service
+ versions = getSnapshotVisibleVersionFromMs(expiredPartitions); //
Get the rest versions from meta-service
}
int verMsIdx = 0;
for (Pair<Long, Long> v : allVersions) { // ATTN: keep the assigning
order!!!
@@ -405,7 +438,8 @@ public class CloudPartition extends Partition {
//
// Return the visible version in order of the specified partition ids
private static List<Long> getSnapshotVisibleVersion(List<Long> dbIds,
List<Long> tableIds, List<Long> partitionIds,
- List<Long> versionUpdateTimesMs, List<Long> commitTsos, boolean
waitForPendingTxns, int maxAttempts)
+ List<Long> versionUpdateTimesMs, List<Long> commitTsos,
List<Boolean> cacheableVersions,
+ boolean waitForPendingTxns, int maxAttempts)
throws RpcException {
assert dbIds.size() == partitionIds.size() :
"partition ids size: " + partitionIds.size() + " should equals
to db ids size: " + dbIds.size();
@@ -449,11 +483,15 @@ public class CloudPartition extends Partition {
commitTsos.addAll(resp.getCommitTsosList());
}
- ArrayList<Long> news = new ArrayList<>();
- for (Long v : versions) { // -1 means version NOT FOUND ==> no data
has been written
- news.add(v == -1 ? Partition.PARTITION_INIT_VERSION : v);
- }
- return news;
+ int size = versions.size();
+ List<Long> normalizedVersions = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ cacheableVersions.add(isVersionCacheable(resp, i, size));
+ long version = versions.get(i);
+ // -1 means version NOT FOUND.
+ normalizedVersions.add(version == -1 ?
Partition.PARTITION_INIT_VERSION : version);
+ }
+ return normalizedVersions;
}
@Override
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java
index 6d485d959bc..4b9ab39d8bf 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java
@@ -135,13 +135,7 @@ public class CloudSyncVersionDaemon extends MasterDaemon {
OlapTable table = tables.get(i);
long version = versions.get(i);
if (version > table.getCachedTableVersion()) {
- tableVersionMap.compute(table, (k, v) -> {
- if (v == null || version > v) {
- return version;
- } else {
- return v;
- }
- });
+ prepareTableForVersionSync(tableVersionMap, table,
version);
} else {
// update lastTableVersionCachedTimeMs
table.setCachedTableVersion(version);
@@ -154,6 +148,24 @@ public class CloudSyncVersionDaemon extends MasterDaemon {
});
}
+ private void prepareTableForVersionSync(Map<OlapTable, Long>
tableVersionMap, OlapTable table, long version) {
+ // Stop serving potentially stale partition caches while refresh is
queued or fails.
+ table.readLock();
+ try {
+ table.versionWriteLock();
+ try {
+ for (Partition partition : table.getAllPartitions()) {
+ ((CloudPartition)
partition).invalidateCachedVisibleVersion();
+ }
+ } finally {
+ table.versionWriteUnlock();
+ }
+ } finally {
+ table.readUnlock();
+ }
+ tableVersionMap.merge(table, version, Math::max);
+ }
+
private void syncPartitionVersion(Map<OlapTable, Long> tableVersionMap) {
Set<Long> failedTables = ConcurrentHashMap.newKeySet();
List<Future<Void>> futures = new ArrayList<>();
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 7716e51599d..ba5afbc2f6c 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
@@ -1192,7 +1192,8 @@ public class CloudGlobalTransactionMgr implements
GlobalTransactionMgrIface {
if (!Config.calc_delete_bitmap_get_versions_in_batch) {
Map<Long, Long> partitionToVersions = Maps.newHashMap();
partitionMap.forEach((key, value) -> {
- long visibleVersion = value.getVisibleVersion();
+ long visibleVersion = ((CloudPartition)
value).getVisibleVersionFromMs(
+
Config.calc_delete_bitmap_get_versions_waiting_for_pending_txns);
long newVersion = visibleVersion <= 0 ? 2 : visibleVersion + 1;
partitionToVersions.put(key, newVersion);
});
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
index e0a179cdb8f..83cbd31e9e1 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
@@ -422,7 +422,7 @@ public class PartitionsProcDir implements ProcDirInterface {
for (int start = 0; start < partitions.size(); start +=
batchSize) {
int end = Math.min(start + batchSize, partitions.size());
List<CloudPartition> batch = partitions.subList(start,
end);
-
partitionVersions.addAll(CloudPartition.getSnapshotVisibleVersionFromMs(batch,
false));
+
partitionVersions.addAll(CloudPartition.getSnapshotVisibleVersionFromMs(batch,
true));
}
} catch (RpcException e) {
LOG.warn("get partition versions failed for table: {}",
olapTable, e);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 542ddbc5174..6c65b9ddaf4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -880,6 +880,7 @@ public class SessionVariable implements Serializable,
Writable {
public static final String COMPUTE_GROUP = "compute_group";
public static final String DISABLE_EMPTY_PARTITION_PRUNE =
"disable_empty_partition_prune";
public static final String CLOUD_FORCE_SYNC_VERSION =
"cloud_force_sync_version";
+ public static final String CLOUD_GET_VERSION_WAIT_FOR_PENDING_TXN =
"cloud_get_version_wait_for_pending_txn";
public static final String CLOUD_PARTITION_VERSION_CACHE_TTL_MS =
"cloud_partition_version_cache_ttl_ms";
public static final String CLOUD_TABLE_VERSION_CACHE_TTL_MS =
@@ -3301,11 +3302,15 @@ public class SessionVariable implements Serializable,
Writable {
@VarAttrDef.VarAttr(name = CLOUD_FORCE_SYNC_TABLET_STATS, needForward =
true)
public boolean cloudForceSyncTabletStats = false;
@VarAttrDef.VarAttr(name = CLOUD_PARTITION_VERSION_CACHE_TTL_MS)
- public long cloudPartitionVersionCacheTtlMs = Long.MAX_VALUE;
+ public long cloudPartitionVersionCacheTtlMs = 10 * 60 * 1000L;
@VarAttrDef.VarAttr(name = CLOUD_TABLE_VERSION_CACHE_TTL_MS)
public long cloudTableVersionCacheTtlMs = Long.MAX_VALUE;
@VarAttrDef.VarAttr(name = CLOUD_FORCE_SYNC_VERSION, needForward = true)
public boolean cloudForceSyncVersion = false;
+ @VarAttrDef.VarAttr(name = CLOUD_GET_VERSION_WAIT_FOR_PENDING_TXN,
needForward = true,
+ description = "Whether Cloud partition version RPCs wait for
pending transactions. "
+ + "Does not bypass the version cache or override
explicitly specified waiting policies.")
+ public boolean cloudGetVersionWaitForPendingTxn = false;
// CLOUD_VARIABLES_END
// fetch remote schema rpc timeout
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionPendingVersionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionPendingVersionTest.java
new file mode 100644
index 00000000000..a6af0c5cd25
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionPendingVersionTest.java
@@ -0,0 +1,303 @@
+// 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.
+
+package org.apache.doris.cloud.catalog;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.cloud.proto.Cloud;
+import org.apache.doris.cloud.proto.Cloud.MetaServiceCode;
+import org.apache.doris.cloud.rpc.VersionHelper;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.qe.VariableMgr;
+import org.apache.doris.rpc.RpcException;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CloudPartitionPendingVersionTest {
+ private ConnectContext previousContext;
+ private MockedStatic<Env> env;
+ private MockedStatic<VersionHelper> versions;
+ private CloudPartition partition;
+ private Cloud.GetVersionResponse response;
+ private Cloud.GetVersionRequest lastRequest;
+ private Runnable beforeResponse;
+ private int requests;
+
+ @BeforeEach
+ public void setUp() {
+ previousContext = ConnectContext.get();
+ ConnectContext ctx = new ConnectContext();
+ ctx.setSessionVariable(new SessionVariable());
+ ctx.getSessionVariable().cloudPartitionVersionCacheTtlMs =
Long.MAX_VALUE;
+ ctx.setThreadLocalInfo();
+ env = Mockito.mockStatic(Env.class);
+
env.when(Env::getCurrentInternalCatalog).thenReturn(Mockito.mock(InternalCatalog.class));
+ partition = CloudPartitionTest.createPartition(1, 2, 3);
+ partition.setCachedVisibleVersion(12, 1000);
+ versions = Mockito.mockStatic(VersionHelper.class);
+ versions.when(() ->
VersionHelper.getVersionFromMeta(Mockito.any(Cloud.GetVersionRequest.class)))
+ .thenAnswer(invocation ->
nextResponse(invocation.getArgument(0)));
+ versions.when(() -> VersionHelper.getVersionFromMeta(
+ Mockito.any(Cloud.GetVersionRequest.class), Mockito.anyInt()))
+ .thenAnswer(invocation ->
nextResponse(invocation.getArgument(0)));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ versions.close();
+ env.close();
+ if (previousContext == null) {
+ ConnectContext.remove();
+ } else {
+ previousContext.setThreadLocalInfo();
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testPendingReadCannotRestoreInvalidatedCache(boolean batch)
throws Exception {
+ partition.invalidateCachedVisibleVersion();
+ response = response(12).addHasPendingTxns(true).build();
+ Assertions.assertEquals(12, read(batch));
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ // A later ordinary commit must not hide the pending update by
refreshing the cache timestamp.
+ partition.setCachedVisibleVersion(12, 2000);
+ Assertions.assertEquals(12, read(batch));
+ Assertions.assertEquals(2, requests);
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+
+ response = response(13).addHasPendingTxns(false).build();
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertEquals(3, requests, "A non-pending query response may
restore caching without a daemon");
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testPendingReadInvalidatesPreviouslyValidCache(boolean batch)
throws Exception {
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+
ConnectContext.get().getSessionVariable().cloudPartitionVersionCacheTtlMs = 0;
+ response = response(12).addHasPendingTxns(true).build();
+ Assertions.assertEquals(12, read(batch));
+
ConnectContext.get().getSessionVariable().cloudPartitionVersionCacheTtlMs =
Long.MAX_VALUE;
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ }
+
+ @Test
+ public void testBatchInvalidatesOnlyPendingPartitions() throws Exception {
+ CloudPartition other = CloudPartitionTest.createPartition(4, 2, 3);
+ other.setCachedVisibleVersion(7, 1000);
+ response =
response(12).addVersions(8).addHasPendingTxns(true).addHasPendingTxns(false).build();
+ // Forced MS reads may observe pending while the previous cache was
still valid.
+ Assertions.assertEquals(List.of(12L, 8L),
+
CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition, other),
false));
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ Assertions.assertFalse(other.isCachedVersionExpired());
+ response = response(13).addHasPendingTxns(false).build();
+ Assertions.assertEquals(List.of(13L, 8L),
+ CloudPartition.getSnapshotVisibleVersion(List.of(partition,
other)));
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testFirstPendingWriteDoesNotCacheEmptyPartition(boolean batch)
throws Exception {
+ partition = CloudPartitionTest.createPartition(1, 2, 3);
+ response =
response(-1).setStatus(Cloud.MetaServiceResponseStatus.newBuilder()
+ .setCode(batch ? MetaServiceCode.OK :
MetaServiceCode.VERSION_NOT_FOUND))
+ .addHasPendingTxns(true).build();
+ Assertions.assertEquals(1, read(batch));
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ response = response(2).addHasPendingTxns(false).build();
+ Assertions.assertEquals(2, read(batch));
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testNonPendingResponsePreservesConcurrentInvalidation(boolean
batch) throws Exception {
+ partition.invalidateCachedVisibleVersion();
+ response = response(13).addHasPendingTxns(false).build();
+ beforeResponse = partition::invalidateCachedVisibleVersion;
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ beforeResponse = null;
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testOlderMsPreservesLegacyCacheBehavior(boolean batch) throws
Exception {
+ partition.invalidateCachedVisibleVersion();
+ response = response(13).build();
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertEquals(1, requests, "An older MS response must still
allow cache hits without waiting");
+
+ partition.invalidateCachedVisibleVersion();
+ beforeResponse = partition::invalidateCachedVisibleVersion;
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertTrue(partition.isCachedVersionExpired(),
+ "Legacy compatibility must not acknowledge invalidation after
the RPC started");
+ }
+
+ @Test
+ public void testExplicitPendingOverridesWaitFlag() throws Exception {
+ response = response(12).addHasPendingTxns(true).build();
+ CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition),
true);
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ response = response(13).addHasPendingTxns(false).build();
+ CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition),
true);
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ }
+
+ @ParameterizedTest
+ @CsvSource({"false,false,false", "false,true,false", "true,false,false",
"true,true,false",
+ "false,false,true", "false,true,true", "true,false,true",
"true,true,true"})
+ public void testSessionWaitFlagPreservesCachePolicy(
+ boolean batch, boolean waitForPendingTxns, boolean cacheEnabled)
throws Exception {
+ SessionVariable session = ConnectContext.get().getSessionVariable();
+ session.cloudGetVersionWaitForPendingTxn = waitForPendingTxns;
+ session.cloudPartitionVersionCacheTtlMs = cacheEnabled ?
Long.MAX_VALUE : 0;
+ response = response(13).addHasPendingTxns(false).build();
+ if (cacheEnabled) {
+ Assertions.assertEquals(12, read(batch));
+ Assertions.assertEquals(0, requests, "Enabling waiting must not
bypass a valid cache");
+ partition.invalidateCachedVisibleVersion();
+ }
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertEquals(1, requests);
+ Assertions.assertEquals(batch, lastRequest.getBatchMode());
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ Assertions.assertEquals(13, read(batch));
+ Assertions.assertEquals(cacheEnabled ? 1 : 2, requests);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testForcedReadUsesSessionWaitFlag(boolean waitForPendingTxns)
throws Exception {
+
ConnectContext.get().getSessionVariable().cloudGetVersionWaitForPendingTxn =
waitForPendingTxns;
+ response = response(13).addHasPendingTxns(false).build();
+ Assertions.assertEquals(List.of(13L),
CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition)));
+ Assertions.assertEquals(1, requests);
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testExplicitWaitFlagOverridesSession(boolean
waitForPendingTxns) throws Exception {
+
ConnectContext.get().getSessionVariable().cloudGetVersionWaitForPendingTxn =
!waitForPendingTxns;
+ response = response(13).addHasPendingTxns(false).build();
+ Assertions.assertEquals(13,
partition.getVisibleVersionFromMs(waitForPendingTxns));
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ Assertions.assertEquals(List.of(13L),
+
CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition),
waitForPendingTxns));
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testReadWithoutSessionUsesGlobalWaitFlag(boolean
waitForPendingTxns) throws Exception {
+ SessionVariable defaults = VariableMgr.getDefaultSessionVariable();
+ boolean previousWait = defaults.cloudGetVersionWaitForPendingTxn;
+ try {
+ defaults.cloudGetVersionWaitForPendingTxn = waitForPendingTxns;
+
ConnectContext.get().getSessionVariable().cloudGetVersionWaitForPendingTxn =
!waitForPendingTxns;
+ response = response(13).addHasPendingTxns(false).build();
+ CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition));
+ Assertions.assertEquals(!waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+
+ ConnectContext.remove();
+ Assertions.assertEquals(List.of(13L),
CloudPartition.getSnapshotVisibleVersionFromMs(List.of(partition)));
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ partition.invalidateCachedVisibleVersion();
+ Assertions.assertEquals(13, partition.getVisibleVersion());
+ Assertions.assertEquals(waitForPendingTxns,
lastRequest.getWaitForPendingTxn());
+ } finally {
+ defaults.cloudGetVersionWaitForPendingTxn = previousWait;
+ }
+ }
+
+ @Test
+ public void testDaemonPreparesTableForVersionSync() throws Exception {
+ CloudPartition other = CloudPartitionTest.createPartition(4, 2, 3);
+ other.setCachedVisibleVersion(20, 1000);
+ OlapTable table = Mockito.mock(OlapTable.class);
+ Mockito.when(table.getAllPartitions()).thenReturn(List.of(partition,
other));
+ Map<OlapTable, Long> tablesToSync = new HashMap<>();
+ Method prepare = CloudSyncVersionDaemon.class.getDeclaredMethod(
+ "prepareTableForVersionSync", Map.class, OlapTable.class,
long.class);
+ prepare.setAccessible(true);
+ prepare.invoke(new CloudSyncVersionDaemon(), tablesToSync, table, 3L);
+
+ Assertions.assertTrue(partition.isCachedVersionExpired());
+ Assertions.assertTrue(other.isCachedVersionExpired());
+ Assertions.assertEquals(Map.of(table, 3L), tablesToSync);
+ Assertions.assertEquals(12, partition.getCachedVisibleVersion());
+ Assertions.assertEquals(20, other.getCachedVisibleVersion());
+ versions.verifyNoInteractions();
+ Mockito.verify(table,
Mockito.never()).setCachedTableVersion(Mockito.anyLong());
+ versions.when(() ->
VersionHelper.getVersionFromMeta(Mockito.any(Cloud.GetVersionRequest.class)))
+ .thenThrow(new RpcException("get version", "injected refresh
failure"));
+ Assertions.assertThrows(RuntimeException.class, () ->
partition.getVisibleVersion());
+ Assertions.assertTrue(partition.isCachedVersionExpired(), "A failed
refresh must leave the cache invalid");
+ Assertions.assertTrue(other.isCachedVersionExpired());
+ versions.when(() ->
VersionHelper.getVersionFromMeta(Mockito.any(Cloud.GetVersionRequest.class)))
+ .thenReturn(response(13).addHasPendingTxns(false).build());
+ Assertions.assertEquals(13, partition.getVisibleVersion());
+ Assertions.assertFalse(partition.isCachedVersionExpired());
+ }
+
+ private long read(boolean batch) throws RpcException {
+ return batch ?
CloudPartition.getSnapshotVisibleVersion(List.of(partition)).get(0)
+ : partition.getVisibleVersion();
+ }
+
+ private Cloud.GetVersionResponse nextResponse(Cloud.GetVersionRequest
request) {
+ lastRequest = request;
+ requests++;
+ if (beforeResponse != null) {
+ beforeResponse.run();
+ }
+ return response;
+ }
+
+ private Cloud.GetVersionResponse.Builder response(long version) {
+ return Cloud.GetVersionResponse.newBuilder()
+
.setStatus(Cloud.MetaServiceResponseStatus.newBuilder().setCode(MetaServiceCode.OK))
+
.setVersion(version).addVersions(version).addVersionUpdateTimeMs(2000).addCommitTsos(3000);
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionTest.java
index 9a33dcf70af..63889a798b9 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/cloud/catalog/CloudPartitionTest.java
@@ -127,6 +127,11 @@ public class CloudPartitionTest {
Cloud.GetVersionResponse.Builder builder =
Cloud.GetVersionResponse.newBuilder();
builder.setVersion(singleVersions.get(callCount[0]));
builder.addAllVersions(batchVersions.get(callCount[0]));
+ Cloud.GetVersionRequest request = invocation.getArgument(0);
+ int size = request.getBatchMode() ? builder.getVersionsCount()
: 1;
+ for (int i = 0; i < size; i++) {
+ builder.addHasPendingTxns(false);
+ }
++callCount[0];
return builder.build();
};
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgrTest.java
b/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgrTest.java
index 8f617c8f219..e7902076eea 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgrTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgrTest.java
@@ -88,6 +88,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
@@ -1109,7 +1110,8 @@ public class CloudGlobalTransactionMgrTest {
AtomicReference<CompletableFuture<Void>> snapshotReader = new
AtomicReference<>();
try (MockedStatic<VersionHelper> versions = mockVersionHelper()) {
versions.when(() ->
VersionHelper.getVersionFromMeta(Mockito.any())).thenReturn(
-
partitionVersion(4).toBuilder().addVersions(4).addVersionUpdateTimeMs(40).addCommitTsos(400).build());
+
partitionVersion(4).toBuilder().addVersions(4).addVersionUpdateTimeMs(40).addCommitTsos(400)
+ .addHasPendingTxns(false).build());
Mockito.doAnswer(invocation -> {
CountDownLatch started = new CountDownLatch(1);
CompletableFuture<Void> reader = CompletableFuture.runAsync(()
-> {
@@ -1213,6 +1215,37 @@ public class CloudGlobalTransactionMgrTest {
}
}
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ public void testNonBatchPartitionVersionsBypassCache(boolean
waitForPendingTxns) throws Exception {
+ boolean previousBatch =
Config.calc_delete_bitmap_get_versions_in_batch;
+ boolean previousWait =
Config.calc_delete_bitmap_get_versions_waiting_for_pending_txns;
+ useVersionCaches();
+ CloudPartition partition = addCloudPartition(1000);
+ try (MockedStatic<VersionHelper> versions = mockVersionHelper()) {
+ Config.calc_delete_bitmap_get_versions_in_batch = false;
+ Config.calc_delete_bitmap_get_versions_waiting_for_pending_txns =
waitForPendingTxns;
+ Assertions.assertEquals(2, partition.getVisibleVersion());
+ versions.verifyNoInteractions();
+ versions.when(() ->
VersionHelper.getVersionFromMeta(Mockito.any())).thenAnswer(invocation -> {
+ Cloud.GetVersionRequest request = invocation.getArgument(0);
+ Assertions.assertFalse(request.getBatchMode());
+ Assertions.assertEquals(partition.getId(),
request.getPartitionId());
+ Assertions.assertEquals(waitForPendingTxns,
request.getWaitForPendingTxn());
+ return partitionVersion(4);
+ });
+ Method getVersions =
CloudGlobalTransactionMgr.class.getDeclaredMethod("getPartitionVersions",
Map.class);
+ getVersions.setAccessible(true);
+ Assertions.assertEquals(Map.of(partition.getId(), 5L),
+ getVersions.invoke(masterTransMgr,
Map.of(partition.getId(), partition)));
+ Assertions.assertEquals(4, partition.getCachedVisibleVersion());
+ versions.verify(() ->
VersionHelper.getVersionFromMeta(Mockito.any()));
+ } finally {
+ Config.calc_delete_bitmap_get_versions_in_batch = previousBatch;
+ Config.calc_delete_bitmap_get_versions_waiting_for_pending_txns =
previousWait;
+ }
+ }
+
private CloudPartition addCloudPartition(long tableId) throws Exception {
OlapTable table = new OlapTable(tableId, "version_cache_" + tableId,
List.of(), KeysType.DUP_KEYS,
new PartitionInfo(), new RandomDistributionInfo(1));
@@ -1242,7 +1275,7 @@ public class CloudGlobalTransactionMgrTest {
private Cloud.GetVersionResponse partitionVersion(long version) {
return
Cloud.GetVersionResponse.newBuilder().setVersion(version).addVersions(version)
- .addVersionUpdateTimeMs(version * 10).addCommitTsos(version *
100).build();
+ .addVersionUpdateTimeMs(version * 10).addCommitTsos(version *
100).addHasPendingTxns(false).build();
}
private OlapTable getCloudTable(CloudPartition partition) throws Exception
{
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/qe/SessionVariablesTest.java
b/fe/fe-core/src/test/java/org/apache/doris/qe/SessionVariablesTest.java
index 5760ea5fb10..02d523522c9 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/SessionVariablesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/SessionVariablesTest.java
@@ -129,6 +129,24 @@ public class SessionVariablesTest extends
TestWithFeService {
Assertions.assertEquals(64, master.getQueryCacheEntryMaxRows());
}
+ @Test
+ public void testCloudGetVersionWaitForPendingTxn() throws Exception {
+ SessionVariable session = new SessionVariable();
+ SessionVariable forwarded = new SessionVariable();
+ Assertions.assertFalse(session.cloudGetVersionWaitForPendingTxn);
+ for (boolean waitForPendingTxn : new boolean[] {true, false}) {
+ VariableMgr.setVar(session, new SetVar(SetType.SESSION,
+ SessionVariable.CLOUD_GET_VERSION_WAIT_FOR_PENDING_TXN,
+ new StringLiteral(Boolean.toString(waitForPendingTxn))));
+ Assertions.assertEquals(waitForPendingTxn,
session.cloudGetVersionWaitForPendingTxn);
+ Map<String, String> vars = session.getForwardVariables();
+ Assertions.assertEquals(Boolean.toString(waitForPendingTxn),
+
vars.get(SessionVariable.CLOUD_GET_VERSION_WAIT_FOR_PENDING_TXN));
+ forwarded.setForwardedSessionVariables(vars);
+ Assertions.assertEquals(waitForPendingTxn,
forwarded.cloudGetVersionWaitForPendingTxn);
+ }
+ }
+
@Test
public void testInsertVisibleTimeoutReturnMode() throws Exception {
connectContext.setThreadLocalInfo();
diff --git a/gensrc/proto/cloud.proto b/gensrc/proto/cloud.proto
index 2e1463e5460..6d96eba78c0 100644
--- a/gensrc/proto/cloud.proto
+++ b/gensrc/proto/cloud.proto
@@ -1324,6 +1324,10 @@ message GetVersionResponse {
repeated int64 versions = 6;
repeated int64 version_update_time_ms = 7;
repeated int64 commit_tsos = 8;
+
+ // Pending state for partition versions, after any requested wait.
+ // One entry for scalar reads (including VERSION_NOT_FOUND), or per
partition in batch order.
+ repeated bool has_pending_txns = 9;
};
message GetObjStoreInfoRequest {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]