1
1
#pragma once
2
2
3
+ #include " kafka_init_producer_id_actor.h"
4
+ #include < ydb/core/kafka_proxy/kafka_events.h>
3
5
#include < ydb/library/actors/core/actor_bootstrapped.h>
6
+ #include < ydb/core/kafka_proxy/kqp_helper.h>
4
7
5
8
namespace NKafka {
6
9
/*
7
10
This class is responsible for one kafka transaction.
8
11
9
12
It accumulates transaction state (partitions in tx, offsets) and on commit submits transaction to KQP
10
13
*/
11
- class TKafkaTransactionActor : public NActors ::TActorBootstrapped <TKafkaTransactionActor> {
14
+ class TKafkaTransactionActor : public NActors ::TActor <TKafkaTransactionActor> {
12
15
13
- using TBase = NActors::TActorBootstrapped <TKafkaTransactionActor>;
16
+ using TBase = NActors::TActor <TKafkaTransactionActor>;
14
17
15
18
public:
16
- void Bootstrap (const NActors::TActorContext&) {
17
- TBase::Become (&TKafkaTransactionActor::StateWork);
18
- }
19
+ struct TTopicPartition {
20
+ TString TopicPath;
21
+ ui32 PartitionId;
22
+
23
+ bool operator ==(const TTopicPartition &other) const = default ;
24
+ };
25
+
26
+ struct TopicPartitionHashFn {
27
+ size_t operator ()(const TTopicPartition& partition) const {
28
+ return std::hash<TString>()(partition.TopicPath ) ^ std::hash<int64_t >()(partition.PartitionId );
29
+ }
30
+ };
31
+
32
+ struct TPartitionCommit {
33
+ i32 Partition;
34
+ i64 Offset;
35
+ TString ConsumerName;
36
+ i64 ConsumerGeneration;
37
+ };
38
+
39
+ enum EKafkaTxnKqpRequests : ui8 {
40
+ NO_REQUEST = 0 ,
41
+
42
+ // This request selects up-to-date producer and consumers states from relevant tables
43
+ // After this request a check will happen, that no transaction details has expired.
44
+ SELECT,
45
+ // This request sends to KQP a command to commit transaction
46
+ // Both these requests happen in same transaction
47
+ COMMIT
48
+ };
49
+
50
+ // we need to exlplicitly specify kqpActorId and txnCoordinatorActorId for unit tests
51
+ TKafkaTransactionActor (const TString& transactionalId, i64 producerId, i16 producerEpoch, const TString& DatabasePath, const TActorId& kqpActorId, const TActorId& txnCoordinatorActorId) :
52
+ TActor<TKafkaTransactionActor>(&TKafkaTransactionActor::StateFunc),
53
+ TransactionalId (transactionalId),
54
+ ProducerInstanceId ({producerId, producerEpoch}),
55
+ DatabasePath (DatabasePath),
56
+ TxnCoordinatorActorId (txnCoordinatorActorId),
57
+ KqpActorId (kqpActorId) {};
19
58
20
59
TStringBuilder LogPrefix () const {
21
- return TStringBuilder () << " KafkaTransactionActor" ;
60
+ return TStringBuilder () << " KafkaTransactionActor{TransactionalId= " << TransactionalId << " ; ProducerId= " << ProducerInstanceId. Id << " ; ProducerEpoch= " << ProducerInstanceId. Epoch << " }: " ;
22
61
}
23
62
24
63
private:
25
- STFUNC (StateWork) {
26
- switch (ev->GetTypeRewrite ()) {
27
- // will be eimplemented in a future PR
28
- // ToDo: add poison pill handler
64
+ STFUNC (StateFunc) {
65
+ try {
66
+ switch (ev->GetTypeRewrite ()) {
67
+ HFunc (TEvKafka::TEvAddPartitionsToTxnRequest, Handle);
68
+ HFunc (TEvKafka::TEvAddOffsetsToTxnRequest, Handle);
69
+ HFunc (TEvKafka::TEvTxnOffsetCommitRequest, Handle);
70
+ HFunc (TEvKafka::TEvEndTxnRequest, Handle);
71
+ HFunc (NKqp::TEvKqp::TEvCreateSessionResponse, Handle);
72
+ HFunc (NKqp::TEvKqp::TEvQueryResponse, Handle);
73
+ HFunc (TEvents::TEvPoison, Handle);
74
+ }
75
+ } catch (const yexception& y) {
76
+ KAFKA_LOG_CRIT (TStringBuilder () << " Critical error happened. Reason: " << y.what ());
77
+ Die (ActorContext ());
29
78
}
30
79
}
80
+
81
+ // Kafka API events
82
+ void Handle (TEvKafka::TEvAddPartitionsToTxnRequest::TPtr& ev, const TActorContext& ctx);
83
+ void Handle (TEvKafka::TEvAddOffsetsToTxnRequest::TPtr& ev, const TActorContext& ctx);
84
+ void Handle (TEvKafka::TEvTxnOffsetCommitRequest::TPtr& ev, const TActorContext& ctx);
85
+ void Handle (TEvKafka::TEvEndTxnRequest::TPtr& ev, const TActorContext& ctx);
86
+ // KQP events
87
+ void Handle (NKqp::TEvKqp::TEvCreateSessionResponse::TPtr& ev, const TActorContext& ctx);
88
+ void Handle (NKqp::TEvKqp::TEvQueryResponse::TPtr& ev, const TActorContext& ctx);
89
+
90
+ // Poison pill
91
+ void Handle (TEvents::TEvPoison::TPtr& ev, const TActorContext& ctx);
92
+
93
+ // Transaction commit logic
94
+ void StartKqpSession (const TActorContext& ctx);
95
+ void SendToKqpValidationRequests (const TActorContext& ctx);
96
+ void SendCommitTxnRequest (const TString& kqpTransactionId);
97
+
98
+ // Response senders
99
+ template <class ErrorResponseType , class EventType >
100
+ void SendFailResponse (TAutoPtr<TEventHandle<EventType>>& evHandle, EKafkaErrors errorCode, const TString& errorMessage = {});
101
+ template <class ResponseType , class EventType >
102
+ void SendOkResponse (TAutoPtr<TEventHandle<EventType>>& evHandle);
103
+
104
+ // helper methods
105
+ void Die (const TActorContext &ctx);
106
+ template <class EventType >
107
+ bool ProducerInRequestIsValid (TMessagePtr<EventType> kafkaRequest);
108
+ TString GetFullTopicPath (const TString& topicName);
109
+ TString GetYqlWithTablesNames (const TString& templateStr);
110
+ NYdb::TParams BuildSelectParams ();
111
+ THolder<NKikimr::NKqp::TEvKqp::TEvQueryRequest> BuildCommitTxnRequestToKqp (const TString& kqpTransactionId);
112
+ void HandleSelectResponse (const NKqp::TEvKqp::TEvQueryResponse& response, const TActorContext& ctx);
113
+ void HandleCommitResponse (const TActorContext& ctx);
114
+ TMaybe<TString> GetErrorFromYdbResponse (NKqp::TEvKqp::TEvQueryResponse::TPtr& ev);
115
+ TMaybe<TProducerState> ParseProducerState (const NKqp::TEvKqp::TEvQueryResponse& response);
116
+ TMaybe<TString> GetErrorInProducerState (const TMaybe<TProducerState>& producerState);
117
+ std::unordered_map<TString, i32 > ParseConsumersGenerations (const NKqp::TEvKqp::TEvQueryResponse& response);
118
+ TMaybe<TString> GetErrorInConsumersStates (const std::unordered_map<TString, i32 >& consumerGenerationByName);
119
+ TString GetAsStr (EKafkaTxnKqpRequests request);
120
+
121
+ // data from fields below will be sent to KQP on END_TXN request
122
+ std::unordered_map<TTopicPartition, TPartitionCommit, TopicPartitionHashFn> OffsetsToCommit = {};
123
+ std::unordered_set<TTopicPartition, TopicPartitionHashFn> PartitionsInTxn = {};
124
+ const TString TransactionalId;
125
+ const TEvKafka::TProducerInstanceId ProducerInstanceId;
126
+ // const i64 ProducerId;
127
+ // const i16 ProducerEpoch;
128
+
129
+ // helper fields
130
+ const TString DatabasePath;
131
+ // This field need to preserve request details between several requests to KQP
132
+ // In case something goes off road, we can always send error back to client
133
+ TAutoPtr<TEventHandle<TEvKafka::TEvEndTxnRequest>> EndTxnRequestPtr;
134
+ bool CommitStarted = false ;
135
+ const TActorId TxnCoordinatorActorId;
136
+
137
+ // communication with KQP
138
+ std::unique_ptr<TKqpTxHelper> Kqp;
139
+ TActorId KqpActorId;
140
+ TString KqpSessionId;
141
+ ui64 KqpCookie = 0 ;
142
+ EKafkaTxnKqpRequests LastSentToKqpRequest;
31
143
};
32
144
} // namespace NKafka
0 commit comments