2
2
3
3
namespace NKikimr ::NReplication::NController {
4
4
5
+ THolder<TEvTxUserProxy::TEvProposeTransaction> MakeCommitProposal (ui64 writeTxId, const TVector<TString>& tables) {
6
+ auto ev = MakeHolder<TEvTxUserProxy::TEvProposeTransaction>();
7
+ auto & tx = *ev->Record .MutableTransaction ()->MutableCommitWrites ();
8
+
9
+ tx.SetWriteTxId (writeTxId);
10
+ for (const auto & path : tables) {
11
+ tx.AddTables ()->SetTablePath (path);
12
+ }
13
+
14
+ return ev;
15
+ }
16
+
5
17
class TController ::TTxHeartbeat: public TTxBase {
18
+ THolder<TEvTxUserProxy::TEvProposeTransaction> CommitProposal;
19
+
6
20
public:
7
21
explicit TTxHeartbeat (TController* self)
8
22
: TTxBase(" TxHeartbeat" , self)
@@ -22,24 +36,37 @@ class TController::TTxHeartbeat: public TTxBase {
22
36
return true ;
23
37
}
24
38
25
- const auto prevMinVersion = !Self->WorkersByHeartbeat .empty ()
26
- ? std::make_optional<TRowVersion>(Self->WorkersByHeartbeat .begin ()->first )
27
- : std::nullopt;
39
+ auto replication = Self->GetSingle ();
40
+ if (!replication) {
41
+ CLOG_E (ctx, " Ambiguous replication instance" );
42
+ return true ;
43
+ }
28
44
29
45
NIceDb::TNiceDb db (txc.DB );
30
46
31
- for (const auto & [id, version] : Self->PendingHeartbeats ) {
47
+ while (!Self->PendingHeartbeats .empty ()) {
48
+ auto it = Self->PendingHeartbeats .begin ();
49
+ const auto & id = it->first ;
50
+ const auto & version = it->second ;
51
+
32
52
if (!Self->Workers .contains (id)) {
53
+ Self->PendingHeartbeats .erase (it);
33
54
continue ;
34
55
}
35
56
36
57
auto & worker = Self->Workers [id];
37
58
if (worker.HasHeartbeat ()) {
38
- auto it = Self->WorkersByHeartbeat .find (worker.GetHeartbeat ());
39
- if (it != Self->WorkersByHeartbeat .end ()) {
40
- it->second .erase (id);
41
- if (it->second .empty ()) {
42
- Self->WorkersByHeartbeat .erase (it);
59
+ const auto & prevVersion = worker.GetHeartbeat ();
60
+ if (version < prevVersion) {
61
+ Self->PendingHeartbeats .erase (it);
62
+ continue ;
63
+ }
64
+
65
+ auto jt = Self->WorkersByHeartbeat .find (prevVersion);
66
+ if (jt != Self->WorkersByHeartbeat .end ()) {
67
+ jt->second .erase (id);
68
+ if (jt->second .empty ()) {
69
+ Self->WorkersByHeartbeat .erase (jt);
43
70
}
44
71
}
45
72
}
@@ -52,31 +79,43 @@ class TController::TTxHeartbeat: public TTxBase {
52
79
NIceDb::TUpdate<Schema::Workers::HeartbeatVersionStep>(version.Step ),
53
80
NIceDb::TUpdate<Schema::Workers::HeartbeatVersionTxId>(version.TxId )
54
81
);
82
+
83
+ Self->PendingHeartbeats .erase (it);
55
84
}
56
85
57
86
if (Self->Workers .size () != Self->WorkersWithHeartbeat .size ()) {
58
- return true ;
87
+ return true ; // no quorum
59
88
}
60
89
61
- Y_ABORT_UNLESS (!Self->WorkersByHeartbeat .empty ());
62
- const auto newMinVersion = Self->WorkersByHeartbeat .begin ()->first ;
90
+ if (Self->CommittingTxId ) {
91
+ return true ; // another commit in progress
92
+ }
63
93
64
- if (newMinVersion <= prevMinVersion. value_or ( TRowVersion::Min () )) {
65
- return true ;
94
+ if (Self-> AssignedTxIds . empty ( )) {
95
+ return true ; // nothing to commit
66
96
}
67
97
68
- CLOG_N (ctx, " Min version has been changed"
69
- << " : prev# " << prevMinVersion.value_or (TRowVersion::Min ())
70
- << " , new# " << newMinVersion);
98
+ Y_ABORT_UNLESS (!Self->WorkersByHeartbeat .empty ());
99
+ if (Self->WorkersByHeartbeat .begin ()->first < Self->AssignedTxIds .begin ()->first ) {
100
+ return true ; // version has not been changed
101
+ }
102
+
103
+ Self->CommittingTxId = Self->AssignedTxIds .begin ()->second ;
104
+ CommitProposal = MakeCommitProposal (Self->CommittingTxId , replication->GetTargetTablePaths ());
71
105
72
- // TODO: run commit
73
106
return true ;
74
107
}
75
108
76
109
void Complete (const TActorContext& ctx) override {
77
110
CLOG_D (ctx, " Complete"
78
111
<< " : pending# " << Self->PendingHeartbeats .size ());
79
112
113
+ if (auto & ev = CommitProposal) {
114
+ CLOG_N (ctx, " Propose commit"
115
+ << " : writeTxId# " << Self->CommittingTxId );
116
+ ctx.Send (MakeTxProxyID (), std::move (ev), 0 , Self->CommittingTxId );
117
+ }
118
+
80
119
if (Self->PendingHeartbeats ) {
81
120
Self->Execute (new TTxHeartbeat (Self), ctx);
82
121
} else {
@@ -93,4 +132,85 @@ void TController::RunTxHeartbeat(const TActorContext& ctx) {
93
132
}
94
133
}
95
134
135
+ class TController ::TTxCommitChanges: public TTxBase {
136
+ TEvTxUserProxy::TEvProposeTransactionStatus::TPtr Status;
137
+ THolder<TEvTxUserProxy::TEvProposeTransaction> CommitProposal;
138
+
139
+ public:
140
+ explicit TTxCommitChanges (TController* self, TEvTxUserProxy::TEvProposeTransactionStatus::TPtr& ev)
141
+ : TTxBase(" TxCommitChanges" , self)
142
+ , Status(ev)
143
+ {
144
+ }
145
+
146
+ TTxType GetTxType () const override {
147
+ return TXTYPE_COMMIT_CHANGES;
148
+ }
149
+
150
+ bool Execute (TTransactionContext& txc, const TActorContext& ctx) override {
151
+ CLOG_D (ctx, " Execute"
152
+ << " : writeTxId# " << Self->CommittingTxId );
153
+
154
+ auto replication = Self->GetSingle ();
155
+ if (!replication) {
156
+ CLOG_E (ctx, " Ambiguous replication instance" );
157
+ return true ;
158
+ }
159
+
160
+ auto it = Self->AssignedTxIds .begin ();
161
+ Y_ABORT_UNLESS (it != Self->AssignedTxIds .end ());
162
+ Y_ABORT_UNLESS (it->second == Self->CommittingTxId );
163
+
164
+ const auto & record = Status->Get ()->Record ;
165
+ const auto status = static_cast <TEvTxUserProxy::TEvProposeTransactionStatus::EStatus>(record.GetStatus ());
166
+ if (status != TEvTxUserProxy::TEvProposeTransactionStatus::EStatus::ExecComplete) {
167
+ CommitProposal = MakeCommitProposal (Self->CommittingTxId , replication->GetTargetTablePaths ());
168
+ return true ;
169
+ }
170
+
171
+ NIceDb::TNiceDb db (txc.DB );
172
+
173
+ db.Table <Schema::TxIds>().Key (it->first .Step , it->first .TxId ).Delete ();
174
+ it = Self->AssignedTxIds .erase (it);
175
+ Self->CommittingTxId = 0 ;
176
+
177
+ if (it == Self->AssignedTxIds .end () || Self->WorkersByHeartbeat .empty ()) {
178
+ return true ;
179
+ }
180
+
181
+ if (Self->WorkersByHeartbeat .begin ()->first < it->first ) {
182
+ return true ;
183
+ }
184
+
185
+ Self->CommittingTxId = Self->AssignedTxIds .begin ()->second ;
186
+ CommitProposal = MakeCommitProposal (Self->CommittingTxId , replication->GetTargetTablePaths ());
187
+
188
+ return true ;
189
+ }
190
+
191
+ void Complete (const TActorContext& ctx) override {
192
+ CLOG_D (ctx, " Complete" );
193
+
194
+ if (auto & ev = CommitProposal) {
195
+ CLOG_N (ctx, " Propose commit"
196
+ << " : writeTxId# " << Self->CommittingTxId );
197
+ ctx.Send (MakeTxProxyID (), std::move (ev), 0 , Self->CommittingTxId );
198
+ }
199
+ }
200
+
201
+ }; // TTxCommitChanges
202
+
203
+ void TController::Handle (TEvTxUserProxy::TEvProposeTransactionStatus::TPtr& ev, const TActorContext& ctx) {
204
+ CLOG_T (ctx, " Handle " << ev->Get ()->ToString ());
205
+
206
+ if (ev->Cookie != CommittingTxId) {
207
+ CLOG_E (ctx, " Cookie mismatch"
208
+ << " : expected# " << CommittingTxId
209
+ << " , got# " << ev->Cookie );
210
+ return ;
211
+ }
212
+
213
+ Execute (new TTxCommitChanges (this , ev), ctx);
214
+ }
215
+
96
216
}
0 commit comments