@@ -2006,23 +2006,68 @@ class TKqpDataExecuter : public TKqpExecuterBase<TKqpDataExecuter, EExecType::Da
2006
2006
TTopicTabletTxs& topicTxs) {
2007
2007
TDatashardTxs datashardTxs;
2008
2008
2009
- std::vector<ui64> affectedShardsSet;
2010
- affectedShardsSet.reserve (datashardTasks.size ());
2011
-
2012
2009
for (auto & [shardId, tasks]: datashardTasks) {
2013
2010
auto [it, success] = datashardTxs.emplace (
2014
2011
shardId,
2015
2012
TasksGraph.GetMeta ().Allocate <NKikimrTxDataShard::TKqpTransaction>());
2016
2013
2017
2014
YQL_ENSURE (success, " unexpected duplicates in datashard transactions" );
2018
- affectedShardsSet.emplace_back (shardId);
2019
2015
NKikimrTxDataShard::TKqpTransaction* dsTxs = it->second ;
2020
2016
dsTxs->MutableTasks ()->Reserve (tasks.size ());
2021
2017
for (auto & task: tasks) {
2022
2018
dsTxs->AddTasks ()->Swap (task);
2023
2019
}
2024
2020
}
2025
2021
2022
+ // Note: when locks map is present it will be mutated to avoid copying data
2023
+ auto & locksMap = Request.DataShardLocks ;
2024
+ if (!locksMap.empty ()) {
2025
+ YQL_ENSURE (Request.LocksOp == ELocksOp::Commit || Request.LocksOp == ELocksOp::Rollback);
2026
+ }
2027
+
2028
+ // Materialize (possibly empty) txs for all shards with locks (either commit or rollback)
2029
+ for (auto & [shardId, locksList] : locksMap) {
2030
+ YQL_ENSURE (!locksList.empty (), " unexpected empty locks list in DataShardLocks" );
2031
+
2032
+ auto it = datashardTxs.find (shardId);
2033
+ if (it == datashardTxs.end ()) {
2034
+ auto [emplaced, success] = datashardTxs.emplace (
2035
+ shardId,
2036
+ TasksGraph.GetMeta ().Allocate <NKikimrTxDataShard::TKqpTransaction>());
2037
+
2038
+ YQL_ENSURE (success, " unexpected failure to emplace a datashard transaction" );
2039
+ it = emplaced;
2040
+ }
2041
+
2042
+ NKikimrTxDataShard::TKqpTransaction* tx = it->second ;
2043
+ switch (Request.LocksOp ) {
2044
+ case ELocksOp::Commit:
2045
+ tx->MutableLocks ()->SetOp (NKikimrDataEvents::TKqpLocks::Commit);
2046
+ break ;
2047
+ case ELocksOp::Rollback:
2048
+ tx->MutableLocks ()->SetOp (NKikimrDataEvents::TKqpLocks::Rollback);
2049
+ break ;
2050
+ case ELocksOp::Unspecified:
2051
+ break ;
2052
+ }
2053
+
2054
+ // Move lock descriptions to the datashard tx
2055
+ auto * protoLocks = tx->MutableLocks ()->MutableLocks ();
2056
+ protoLocks->Reserve (locksList.size ());
2057
+ bool hasWrites = false ;
2058
+ for (auto & lock : locksList) {
2059
+ hasWrites = hasWrites || lock.GetHasWrites ();
2060
+ protoLocks->Add (std::move (lock));
2061
+ }
2062
+ locksList.clear ();
2063
+
2064
+ // When locks with writes are committed this commits accumulated effects
2065
+ if (Request.LocksOp == ELocksOp::Commit && hasWrites) {
2066
+ ShardsWithEffects.insert (shardId);
2067
+ YQL_ENSURE (!ReadOnlyTx);
2068
+ }
2069
+ }
2070
+
2026
2071
Request.TopicOperations .BuildTopicTxs (topicTxs);
2027
2072
2028
2073
const bool needRollback = Request.LocksOp == ELocksOp::Rollback;
@@ -2042,7 +2087,7 @@ class TKqpDataExecuter : public TKqpExecuterBase<TKqpDataExecuter, EExecType::Da
2042
2087
// TODO: add support in the future
2043
2088
topicTxs.empty () &&
2044
2089
// We only want to use volatile transactions for multiple shards
2045
- (affectedShardsSet .size () + topicTxs.size ()) > 1 &&
2090
+ (datashardTxs .size () + topicTxs.size ()) > 1 &&
2046
2091
// We cannot use volatile transactions with persistent channels
2047
2092
// Note: currently persistent channels are never used
2048
2093
!HasPersistentChannels);
@@ -2055,30 +2100,29 @@ class TKqpDataExecuter : public TKqpExecuterBase<TKqpDataExecuter, EExecType::Da
2055
2100
// Transactions with topics must always use generic readsets
2056
2101
!topicTxs.empty ());
2057
2102
2058
- if (auto locksMap = Request.DataShardLocks ;
2059
- !locksMap.empty () ||
2060
- VolatileTx ||
2103
+ if (!locksMap.empty () || VolatileTx ||
2061
2104
Request.TopicOperations .HasReadOperations ())
2062
2105
{
2063
2106
YQL_ENSURE (Request.LocksOp == ELocksOp::Commit || Request.LocksOp == ELocksOp::Rollback || VolatileTx);
2064
2107
2065
2108
bool needCommit = Request.LocksOp == ELocksOp::Commit || VolatileTx;
2066
2109
2067
- auto locksOp = needCommit
2068
- ? NKikimrDataEvents::TKqpLocks::Commit
2069
- : NKikimrDataEvents::TKqpLocks::Rollback;
2070
-
2071
2110
absl::flat_hash_set<ui64> sendingShardsSet;
2072
2111
absl::flat_hash_set<ui64> receivingShardsSet;
2073
2112
2074
2113
// Gather shards that need to send/receive readsets (shards with effects)
2075
2114
if (needCommit) {
2076
- for (auto & shardId: affectedShardsSet) {
2115
+ for (auto & [shardId, tx] : datashardTxs) {
2116
+ if (tx->HasLocks ()) {
2117
+ // Locks may be broken so shards with locks need to send readsets
2118
+ sendingShardsSet.insert (shardId);
2119
+ }
2077
2120
if (ShardsWithEffects.contains (shardId)) {
2078
2121
// Volatile transactions may abort effects, so they send readsets
2079
2122
if (VolatileTx) {
2080
2123
sendingShardsSet.insert (shardId);
2081
2124
}
2125
+ // Effects are only applied when all locks are valid
2082
2126
receivingShardsSet.insert (shardId);
2083
2127
}
2084
2128
}
@@ -2093,44 +2137,7 @@ class TKqpDataExecuter : public TKqpExecuterBase<TKqpDataExecuter, EExecType::Da
2093
2137
}
2094
2138
}
2095
2139
2096
- // Gather locks that need to be committed or erased
2097
- for (auto & [shardId, locksList] : locksMap) {
2098
- NKikimrTxDataShard::TKqpTransaction* tx = nullptr ;
2099
- auto it = datashardTxs.find (shardId);
2100
- if (it != datashardTxs.end ()) {
2101
- tx = it->second ;
2102
- } else {
2103
- auto [eIt, success] = datashardTxs.emplace (
2104
- shardId,
2105
- TasksGraph.GetMeta ().Allocate <NKikimrTxDataShard::TKqpTransaction>());
2106
- tx = eIt->second ;
2107
- }
2108
-
2109
- tx->MutableLocks ()->SetOp (locksOp);
2110
-
2111
- if (!locksList.empty ()) {
2112
- auto * protoLocks = tx->MutableLocks ()->MutableLocks ();
2113
- protoLocks->Reserve (locksList.size ());
2114
- bool hasWrites = false ;
2115
- for (auto & lock : locksList) {
2116
- hasWrites = hasWrites || lock.GetHasWrites ();
2117
- protoLocks->Add ()->Swap (&lock);
2118
- }
2119
-
2120
- if (needCommit) {
2121
- // We also send the result on commit
2122
- sendingShardsSet.insert (shardId);
2123
-
2124
- if (hasWrites) {
2125
- // Tx with uncommitted changes can be aborted due to conflicts,
2126
- // so shards with write locks should receive readsets
2127
- receivingShardsSet.insert (shardId);
2128
- YQL_ENSURE (!ReadOnlyTx);
2129
- }
2130
- }
2131
- }
2132
- }
2133
-
2140
+ // Encode sending/receiving shards in tx bodies
2134
2141
if (needCommit) {
2135
2142
NProtoBuf::RepeatedField<ui64> sendingShards (sendingShardsSet.begin (), sendingShardsSet.end ());
2136
2143
NProtoBuf::RepeatedField<ui64> receivingShards (receivingShardsSet.begin (), receivingShardsSet.end ());
@@ -2139,23 +2146,13 @@ class TKqpDataExecuter : public TKqpExecuterBase<TKqpDataExecuter, EExecType::Da
2139
2146
std::sort (receivingShards.begin (), receivingShards.end ());
2140
2147
2141
2148
for (auto & [shardId, shardTx] : datashardTxs) {
2142
- shardTx->MutableLocks ()->SetOp (locksOp );
2149
+ shardTx->MutableLocks ()->SetOp (NKikimrDataEvents::TKqpLocks::Commit );
2143
2150
*shardTx->MutableLocks ()->MutableSendingShards () = sendingShards;
2144
2151
*shardTx->MutableLocks ()->MutableReceivingShards () = receivingShards;
2145
2152
}
2146
2153
2147
2154
for (auto & [_, tx] : topicTxs) {
2148
- switch (locksOp) {
2149
- case NKikimrDataEvents::TKqpLocks::Commit:
2150
- tx.SetOp (NKikimrPQ::TDataTransaction::Commit);
2151
- break ;
2152
- case NKikimrDataEvents::TKqpLocks::Rollback:
2153
- tx.SetOp (NKikimrPQ::TDataTransaction::Rollback);
2154
- break ;
2155
- case NKikimrDataEvents::TKqpLocks::Unspecified:
2156
- break ;
2157
- }
2158
-
2155
+ tx.SetOp (NKikimrPQ::TDataTransaction::Commit);
2159
2156
*tx.MutableSendingShards () = sendingShards;
2160
2157
*tx.MutableReceivingShards () = receivingShards;
2161
2158
}
0 commit comments