Skip to content

Commit ba77f6b

Browse files
[YQL-17174] added spilling library for compute actors (#1544)
[YQL-17174] added spilling library for compute actors
1 parent 73b4f84 commit ba77f6b

File tree

9 files changed

+487
-0
lines changed

9 files changed

+487
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "compute_storage.h"
2+
3+
#include <util/generic/guid.h>
4+
5+
namespace NYql::NDq {
6+
7+
using namespace NActors;
8+
9+
TDqComputeStorage::TDqComputeStorage(TTxId txId, std::function<void()> wakeUpCallback, TActorSystem* actorSystem) : ActorSystem_(actorSystem) {
10+
TStringStream spillerName;
11+
spillerName << "Spiller" << "_" << CreateGuidAsString();
12+
ComputeStorageActor_ = CreateDqComputeStorageActor(txId, spillerName.Str(), wakeUpCallback, ActorSystem_);
13+
ComputeStorageActorId_ = ActorSystem_->Register(ComputeStorageActor_->GetActor());
14+
}
15+
16+
TDqComputeStorage::~TDqComputeStorage() {
17+
ActorSystem_->Send(ComputeStorageActorId_, new TEvents::TEvPoison);
18+
}
19+
20+
NThreading::TFuture<NKikimr::NMiniKQL::ISpiller::TKey> TDqComputeStorage::Put(TRope&& blob) {
21+
return ComputeStorageActor_->Put(std::move(blob));
22+
}
23+
24+
std::optional<NThreading::TFuture<TRope>> TDqComputeStorage::Get(TKey key) {
25+
return ComputeStorageActor_->Get(key);
26+
}
27+
28+
NThreading::TFuture<void> TDqComputeStorage::Delete(TKey key) {
29+
return ComputeStorageActor_->Delete(key);
30+
}
31+
32+
std::optional<NThreading::TFuture<TRope>> TDqComputeStorage::Extract(TKey key) {
33+
return ComputeStorageActor_->Extract(key);
34+
}
35+
36+
} // namespace NYql::NDq
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include "compute_storage_actor.h"
4+
5+
#include <ydb/library/yql/dq/common/dq_common.h>
6+
#include <ydb/library/yql/minikql/computation/mkql_spiller.h>
7+
#include <ydb/library/actors/core/actor.h>
8+
9+
namespace NActors {
10+
class TActorSystem;
11+
};
12+
13+
namespace NYql::NDq {
14+
15+
// This class will be refactored to be non-actor spiller part
16+
class TDqComputeStorage : public NKikimr::NMiniKQL::ISpiller
17+
{
18+
public:
19+
20+
TDqComputeStorage(TTxId txId, std::function<void()> wakeUpCallback, NActors::TActorSystem* actorSystem);
21+
22+
~TDqComputeStorage();
23+
24+
NThreading::TFuture<TKey> Put(TRope&& blob);
25+
26+
std::optional<NThreading::TFuture<TRope>> Get(TKey key);
27+
28+
std::optional<NThreading::TFuture<TRope>> Extract(TKey key);
29+
30+
NThreading::TFuture<void> Delete(TKey key);
31+
32+
private:
33+
NActors::TActorSystem* ActorSystem_;
34+
IDqComputeStorageActor* ComputeStorageActor_;
35+
NActors::TActorId ComputeStorageActorId_;
36+
};
37+
38+
} // namespace NYql::NDq
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
#include "compute_storage_actor.h"
2+
3+
#include "spilling.h"
4+
#include "spilling_file.h"
5+
6+
#include <ydb/library/services/services.pb.h>
7+
8+
#include <ydb/library/actors/core/actor_bootstrapped.h>
9+
#include <ydb/library/actors/core/hfunc.h>
10+
#include <ydb/library/actors/core/log.h>
11+
12+
13+
namespace NYql::NDq {
14+
15+
using namespace NActors;
16+
17+
namespace {
18+
19+
#define LOG_D(s) \
20+
LOG_DEBUG_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId_ << ". " << s)
21+
#define LOG_I(s) \
22+
LOG_INFO_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId << ". " << s)
23+
#define LOG_E(s) \
24+
LOG_ERROR_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId_ << ". " << s)
25+
#define LOG_C(s) \
26+
LOG_CRIT_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId << ". " << s)
27+
#define LOG_W(s) \
28+
LOG_WARN_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId << ". " << s)
29+
#define LOG_T(s) \
30+
LOG_TRACE_S(*TlsActivationContext, NKikimrServices::KQP_COMPUTE, "TxId: " << TxId_ << ". " << s)
31+
32+
class TDqComputeStorageActor : public NActors::TActorBootstrapped<TDqComputeStorageActor>,
33+
public IDqComputeStorageActor
34+
{
35+
using TBase = TActorBootstrapped<TDqComputeStorageActor>;
36+
// size + promise with key
37+
using TWritingBlobInfo = std::pair<ui64, NThreading::TPromise<IDqComputeStorageActor::TKey>>;
38+
// remove after read + promise with blob
39+
using TLoadingBlobInfo = std::pair<bool, NThreading::TPromise<TRope>>;
40+
// void promise that completes when block is removed
41+
using TDeletingBlobInfo = NThreading::TPromise<void>;
42+
public:
43+
TDqComputeStorageActor(TTxId txId, const TString& spillerName, std::function<void()> wakeupCallback, TActorSystem* actorSystem)
44+
: TxId_(txId),
45+
ActorSystem_(actorSystem),
46+
SpillerName_(spillerName),
47+
WakeupCallback_(wakeupCallback)
48+
{
49+
}
50+
51+
void Bootstrap() {
52+
Become(&TDqComputeStorageActor::WorkState);
53+
}
54+
55+
static constexpr char ActorName[] = "DQ_COMPUTE_STORAGE";
56+
57+
IActor* GetActor() override {
58+
return this;
59+
}
60+
61+
NThreading::TFuture<IDqComputeStorageActor::TKey> Put(TRope&& blob) override {
62+
InitializeIfNot();
63+
// Use lock to prevent race when state is changed on event processing and on Put call
64+
std::lock_guard lock(Mutex_);
65+
66+
FailOnError();
67+
68+
ui64 size = blob.size();
69+
70+
ActorSystem_->Send(SpillingActorId_, new TEvDqSpilling::TEvWrite(NextBlobId, std::move(blob)));
71+
72+
auto it = WritingBlobs_.emplace(NextBlobId, std::make_pair(size, NThreading::NewPromise<IDqComputeStorageActor::TKey>())).first;
73+
WritingBlobsSize_ += size;
74+
75+
++NextBlobId;
76+
77+
auto& promise = it->second.second;
78+
79+
return promise.GetFuture();
80+
}
81+
82+
std::optional<NThreading::TFuture<TRope>> Get(IDqComputeStorageActor::TKey blobId) override {
83+
return GetInternal(blobId, false);
84+
}
85+
86+
std::optional<NThreading::TFuture<TRope>> Extract(IDqComputeStorageActor::TKey blobId) override {
87+
return GetInternal(blobId, true);
88+
}
89+
90+
NThreading::TFuture<void> Delete(IDqComputeStorageActor::TKey blobId) override {
91+
InitializeIfNot();
92+
// Use lock to prevent race when state is changed on event processing and on Delete call
93+
std::lock_guard lock(Mutex_);
94+
95+
FailOnError();
96+
97+
auto promise = NThreading::NewPromise<void>();
98+
auto future = promise.GetFuture();
99+
100+
if (!StoredBlobs_.contains(blobId)) {
101+
promise.SetValue();
102+
return future;
103+
}
104+
105+
DeletingBlobs_.emplace(blobId, std::move(promise));
106+
107+
ActorSystem_->Send(SpillingActorId_, new TEvDqSpilling::TEvRead(blobId, true));
108+
109+
return future;
110+
}
111+
112+
protected:
113+
std::optional<NThreading::TFuture<TRope>>GetInternal(IDqComputeStorageActor::TKey blobId, bool removeAfterRead) {
114+
InitializeIfNot();
115+
// Use lock to prevent race when state is changed on event processing and on Get call
116+
std::lock_guard lock(Mutex_);
117+
118+
FailOnError();
119+
120+
if (!StoredBlobs_.contains(blobId)) return std::nullopt;
121+
122+
TLoadingBlobInfo loadingblobInfo = std::make_pair(removeAfterRead, NThreading::NewPromise<TRope>());
123+
auto it = LoadingBlobs_.emplace(blobId, std::move(loadingblobInfo)).first;
124+
125+
ActorSystem_->Send(SpillingActorId_, new TEvDqSpilling::TEvRead(blobId, false));
126+
127+
auto& promise = it->second.second;
128+
return promise.GetFuture();
129+
}
130+
131+
void PassAway() override {
132+
InitializeIfNot();
133+
ActorSystem_->Send(SpillingActorId_, new TEvents::TEvPoison);
134+
TBase::PassAway();
135+
}
136+
137+
void FailOnError() {
138+
InitializeIfNot();
139+
if (Error_) {
140+
LOG_E("Error: " << *Error_);
141+
ActorSystem_->Send(SpillingActorId_, new TEvents::TEvPoison);
142+
}
143+
}
144+
145+
private:
146+
STATEFN(WorkState) {
147+
switch (ev->GetTypeRewrite()) {
148+
hFunc(TEvDqSpilling::TEvWriteResult, HandleWork);
149+
hFunc(TEvDqSpilling::TEvReadResult, HandleWork);
150+
hFunc(TEvDqSpilling::TEvError, HandleWork);
151+
cFunc(TEvents::TEvPoison::EventType, PassAway);
152+
default:
153+
Y_ABORT("TDqComputeStorageActor::WorkState unexpected event type: %" PRIx32 " event: %s",
154+
ev->GetTypeRewrite(),
155+
ev->ToString().data());
156+
}
157+
}
158+
159+
void HandleWork(TEvDqSpilling::TEvWriteResult::TPtr& ev) {
160+
auto& msg = *ev->Get();
161+
LOG_T("[TEvWriteResult] blobId: " << msg.BlobId);
162+
163+
// Use lock to prevent race when state is changed on event processing and on Put call
164+
std::lock_guard lock(Mutex_);
165+
166+
auto it = WritingBlobs_.find(msg.BlobId);
167+
if (it == WritingBlobs_.end()) {
168+
LOG_E("Got unexpected TEvWriteResult, blobId: " << msg.BlobId);
169+
170+
Error_ = "Internal error";
171+
172+
ActorSystem_->Send(SpillingActorId_, new TEvents::TEvPoison);
173+
return;
174+
}
175+
176+
auto& [size, promise] = it->second;
177+
178+
WritingBlobsSize_ -= size;
179+
180+
StoredBlobsCount_++;
181+
StoredBlobsSize_ += size;
182+
183+
StoredBlobs_.insert(msg.BlobId);
184+
185+
// complete future and wake up waiting compute node
186+
promise.SetValue(msg.BlobId);
187+
188+
WritingBlobs_.erase(it);
189+
WakeupCallback_();
190+
}
191+
192+
void HandleWork(TEvDqSpilling::TEvReadResult::TPtr& ev) {
193+
auto& msg = *ev->Get();
194+
LOG_T("[TEvReadResult] blobId: " << msg.BlobId << ", size: " << msg.Blob.size());
195+
196+
// Use lock to prevent race when state is changed on event processing and on Put call
197+
std::lock_guard lock(Mutex_);
198+
199+
// Deletion is read without fetching the results. So, after the deletion library sends TEvReadResult event
200+
// Check if the intention was to delete and complete correct future in this case.
201+
if (HandleDelete(msg.BlobId, msg.Blob.Size())) {
202+
WakeupCallback_();
203+
return;
204+
}
205+
206+
auto it = LoadingBlobs_.find(msg.BlobId);
207+
if (it == LoadingBlobs_.end()) {
208+
LOG_E("Got unexpected TEvReadResult, blobId: " << msg.BlobId);
209+
210+
Error_ = "Internal error";
211+
212+
ActorSystem_->Send(SpillingActorId_, new TEvents::TEvPoison);
213+
return;
214+
}
215+
216+
bool removedAfterRead = it->second.first;
217+
if (removedAfterRead) {
218+
UpdateStatsAfterBlobDeletion(msg.BlobId, msg.Blob.Size());
219+
}
220+
221+
TRope res(TString(reinterpret_cast<const char*>(msg.Blob.Data()), msg.Blob.Size()));
222+
223+
auto& promise = it->second.second;
224+
promise.SetValue(std::move(res));
225+
226+
LoadingBlobs_.erase(it);
227+
228+
WakeupCallback_();
229+
}
230+
231+
void HandleWork(TEvDqSpilling::TEvError::TPtr& ev) {
232+
auto& msg = *ev->Get();
233+
LOG_D("[TEvError] " << msg.Message);
234+
235+
Error_.ConstructInPlace(msg.Message);
236+
}
237+
238+
bool HandleDelete(IDqComputeStorageActor::TKey blobId, ui64 size) {
239+
auto it = DeletingBlobs_.find(blobId);
240+
if (it == DeletingBlobs_.end()) {
241+
return false;
242+
}
243+
244+
UpdateStatsAfterBlobDeletion(blobId, size);
245+
246+
auto& promise = it->second;
247+
promise.SetValue();
248+
DeletingBlobs_.erase(it);
249+
return true;
250+
}
251+
252+
void UpdateStatsAfterBlobDeletion(IDqComputeStorageActor::TKey blobId, ui64 size) {
253+
StoredBlobsCount_--;
254+
StoredBlobsSize_ -= size;
255+
StoredBlobs_.erase(blobId);
256+
}
257+
258+
// It's illegal to initialize an inner actor in the actor's ctor. Because in this case ctx will not be initialized because it's initialized afger Bootstrap event.
259+
// But also it's not possible to initialize inner actor in the bootstrap function because in this case Put/Get may be called before the Bootstrap -> inner worker will be uninitialized.
260+
// In current implementation it's still possible to leave inner actor uninitialized that is why it's planned to split this class into Actor part + non actor part
261+
void InitializeIfNot() {
262+
if (IsInitialized_) return;
263+
auto spillingActor = CreateDqLocalFileSpillingActor(TxId_, SpillerName_,
264+
SelfId(), false);
265+
SpillingActorId_ = Register(spillingActor);
266+
267+
IsInitialized_ = true;
268+
}
269+
270+
271+
protected:
272+
const TTxId TxId_;
273+
TActorId SpillingActorId_;
274+
TActorSystem* ActorSystem_;
275+
276+
TMap<IDqComputeStorageActor::TKey, TWritingBlobInfo> WritingBlobs_;
277+
TSet<ui64> StoredBlobs_;
278+
ui64 WritingBlobsSize_ = 0;
279+
280+
ui32 StoredBlobsCount_ = 0;
281+
ui64 StoredBlobsSize_ = 0;
282+
283+
TMap<IDqComputeStorageActor::TKey, TLoadingBlobInfo> LoadingBlobs_;
284+
285+
TMap<IDqComputeStorageActor::TKey, TDeletingBlobInfo> DeletingBlobs_;
286+
287+
TMaybe<TString> Error_;
288+
289+
IDqComputeStorageActor::TKey NextBlobId = 0;
290+
291+
TString SpillerName_;
292+
293+
bool IsInitialized_ = false;
294+
295+
std::function<void()> WakeupCallback_;
296+
297+
private:
298+
std::mutex Mutex_;
299+
300+
};
301+
302+
} // anonymous namespace
303+
304+
IDqComputeStorageActor* CreateDqComputeStorageActor(TTxId txId, const TString& spillerName, std::function<void()> wakeupCallback, TActorSystem* actorSystem) {
305+
return new TDqComputeStorageActor(txId, spillerName, wakeupCallback, actorSystem);
306+
}
307+
308+
} // namespace NYql::NDq

0 commit comments

Comments
 (0)