|
| 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