diff options
author | max42 <max42@yandex-team.com> | 2023-07-29 00:02:16 +0300 |
---|---|---|
committer | max42 <max42@yandex-team.com> | 2023-07-29 00:02:16 +0300 |
commit | 73b89de71748a21e102d27b9f3ed1bf658766cb5 (patch) | |
tree | 188bbd2d622fa91cdcbb1b6d6d77fbc84a0646f5 /yt/cpp/mapreduce/client | |
parent | 528e321bcc2a2b67b53aeba58c3bd88305a141ee (diff) | |
download | ydb-73b89de71748a21e102d27b9f3ed1bf658766cb5.tar.gz |
YT-19210: expose YQL shared library for YT.
After this, a new target libyqlplugin.so appears. in open-source cmake build.
Diff in open-source YDB repo looks like the following: https://paste.yandex-team.ru/f302bdb4-7ef2-4362-91c7-6ca45f329264
Diffstat (limited to 'yt/cpp/mapreduce/client')
58 files changed, 11776 insertions, 0 deletions
diff --git a/yt/cpp/mapreduce/client/CMakeLists.darwin-x86_64.txt b/yt/cpp/mapreduce/client/CMakeLists.darwin-x86_64.txt new file mode 100644 index 00000000000..9554a165855 --- /dev/null +++ b/yt/cpp/mapreduce/client/CMakeLists.darwin-x86_64.txt @@ -0,0 +1,70 @@ + +# This file was generated by the build system used internally in the Yandex monorepo. +# Only simple modifications are allowed (adding source-files to targets, adding simple properties +# like target_include_directories). These modifications will be ported to original +# ya.make files by maintainers. Any complex modifications which can't be ported back to the +# original buildsystem will not be accepted. + + +get_built_tool_path( + TOOL_enum_parser_bin + TOOL_enum_parser_dependency + tools/enum_parser/enum_parser + enum_parser +) + +add_library(cpp-mapreduce-client) +target_compile_options(cpp-mapreduce-client PRIVATE + -Wdeprecated-this-capture +) +target_link_libraries(cpp-mapreduce-client PUBLIC + contrib-libs-cxxsupp + yutil + cpp-digest-md5 + library-cpp-sighandler + cpp-threading-blocking_queue + cpp-threading-future + library-cpp-type_info + library-cpp-yson + cpp-mapreduce-common + cpp-mapreduce-http + cpp-mapreduce-interface + cpp-mapreduce-io + mapreduce-library-table_schema + cpp-mapreduce-raw_client + yt-yt-core + yt-core-http + tools-enum_parser-enum_serialization_runtime +) +target_sources(cpp-mapreduce-client PRIVATE + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/abortable_registry.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/batch_request_impl.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/format_hints.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/init.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/lock.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_preparer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_tracker.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/prepare_operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/py_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryful_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryless_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/skiff.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction_pinger.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/yt_poller.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/dummy_job_profiler.cpp +) +generate_enum_serilization(cpp-mapreduce-client + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.h + INCLUDE_HEADERS + yt/cpp/mapreduce/client/structured_table_formats.h +) diff --git a/yt/cpp/mapreduce/client/CMakeLists.linux-aarch64.txt b/yt/cpp/mapreduce/client/CMakeLists.linux-aarch64.txt new file mode 100644 index 00000000000..44ce5a6103f --- /dev/null +++ b/yt/cpp/mapreduce/client/CMakeLists.linux-aarch64.txt @@ -0,0 +1,70 @@ + +# This file was generated by the build system used internally in the Yandex monorepo. +# Only simple modifications are allowed (adding source-files to targets, adding simple properties +# like target_include_directories). These modifications will be ported to original +# ya.make files by maintainers. Any complex modifications which can't be ported back to the +# original buildsystem will not be accepted. + + +get_built_tool_path( + TOOL_enum_parser_bin + TOOL_enum_parser_dependency + tools/enum_parser/enum_parser + enum_parser +) + +add_library(cpp-mapreduce-client) +target_compile_options(cpp-mapreduce-client PRIVATE + -Wdeprecated-this-capture +) +target_link_libraries(cpp-mapreduce-client PUBLIC + contrib-libs-linux-headers + contrib-libs-cxxsupp + yutil + cpp-digest-md5 + library-cpp-sighandler + cpp-threading-blocking_queue + cpp-threading-future + library-cpp-type_info + library-cpp-yson + cpp-mapreduce-common + cpp-mapreduce-http + cpp-mapreduce-interface + cpp-mapreduce-io + mapreduce-library-table_schema + cpp-mapreduce-raw_client + yt_proto-yt-core + tools-enum_parser-enum_serialization_runtime +) +target_sources(cpp-mapreduce-client PRIVATE + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/abortable_registry.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/batch_request_impl.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/format_hints.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/init.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/lock.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_preparer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_tracker.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/prepare_operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/py_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryful_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryless_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/skiff.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction_pinger.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/yt_poller.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/dummy_job_profiler.cpp +) +generate_enum_serilization(cpp-mapreduce-client + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.h + INCLUDE_HEADERS + yt/cpp/mapreduce/client/structured_table_formats.h +) diff --git a/yt/cpp/mapreduce/client/CMakeLists.linux-x86_64.txt b/yt/cpp/mapreduce/client/CMakeLists.linux-x86_64.txt new file mode 100644 index 00000000000..969fd946972 --- /dev/null +++ b/yt/cpp/mapreduce/client/CMakeLists.linux-x86_64.txt @@ -0,0 +1,71 @@ + +# This file was generated by the build system used internally in the Yandex monorepo. +# Only simple modifications are allowed (adding source-files to targets, adding simple properties +# like target_include_directories). These modifications will be ported to original +# ya.make files by maintainers. Any complex modifications which can't be ported back to the +# original buildsystem will not be accepted. + + +get_built_tool_path( + TOOL_enum_parser_bin + TOOL_enum_parser_dependency + tools/enum_parser/enum_parser + enum_parser +) + +add_library(cpp-mapreduce-client) +target_compile_options(cpp-mapreduce-client PRIVATE + -Wdeprecated-this-capture +) +target_link_libraries(cpp-mapreduce-client PUBLIC + contrib-libs-linux-headers + contrib-libs-cxxsupp + yutil + cpp-digest-md5 + library-cpp-sighandler + cpp-threading-blocking_queue + cpp-threading-future + library-cpp-type_info + library-cpp-yson + cpp-mapreduce-common + cpp-mapreduce-http + cpp-mapreduce-interface + cpp-mapreduce-io + mapreduce-library-table_schema + cpp-mapreduce-raw_client + yt-yt-core + yt-core-http + tools-enum_parser-enum_serialization_runtime +) +target_sources(cpp-mapreduce-client PRIVATE + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/abortable_registry.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/batch_request_impl.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/format_hints.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/init.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/lock.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_preparer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_tracker.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/prepare_operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/py_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryful_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryless_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/skiff.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction_pinger.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/yt_poller.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/dummy_job_profiler.cpp +) +generate_enum_serilization(cpp-mapreduce-client + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.h + INCLUDE_HEADERS + yt/cpp/mapreduce/client/structured_table_formats.h +) diff --git a/yt/cpp/mapreduce/client/CMakeLists.txt b/yt/cpp/mapreduce/client/CMakeLists.txt new file mode 100644 index 00000000000..f8b31df0c11 --- /dev/null +++ b/yt/cpp/mapreduce/client/CMakeLists.txt @@ -0,0 +1,17 @@ + +# This file was generated by the build system used internally in the Yandex monorepo. +# Only simple modifications are allowed (adding source-files to targets, adding simple properties +# like target_include_directories). These modifications will be ported to original +# ya.make files by maintainers. Any complex modifications which can't be ported back to the +# original buildsystem will not be accepted. + + +if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA) + include(CMakeLists.linux-aarch64.txt) +elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + include(CMakeLists.darwin-x86_64.txt) +elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA) + include(CMakeLists.windows-x86_64.txt) +elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA) + include(CMakeLists.linux-x86_64.txt) +endif() diff --git a/yt/cpp/mapreduce/client/CMakeLists.windows-x86_64.txt b/yt/cpp/mapreduce/client/CMakeLists.windows-x86_64.txt new file mode 100644 index 00000000000..1a4f73e1376 --- /dev/null +++ b/yt/cpp/mapreduce/client/CMakeLists.windows-x86_64.txt @@ -0,0 +1,67 @@ + +# This file was generated by the build system used internally in the Yandex monorepo. +# Only simple modifications are allowed (adding source-files to targets, adding simple properties +# like target_include_directories). These modifications will be ported to original +# ya.make files by maintainers. Any complex modifications which can't be ported back to the +# original buildsystem will not be accepted. + + +get_built_tool_path( + TOOL_enum_parser_bin + TOOL_enum_parser_dependency + tools/enum_parser/enum_parser + enum_parser +) + +add_library(cpp-mapreduce-client) +target_link_libraries(cpp-mapreduce-client PUBLIC + contrib-libs-cxxsupp + yutil + cpp-digest-md5 + library-cpp-sighandler + cpp-threading-blocking_queue + cpp-threading-future + library-cpp-type_info + library-cpp-yson + cpp-mapreduce-common + cpp-mapreduce-http + cpp-mapreduce-interface + cpp-mapreduce-io + mapreduce-library-table_schema + cpp-mapreduce-raw_client + yt-yt-core + yt-core-http + tools-enum_parser-enum_serialization_runtime +) +target_sources(cpp-mapreduce-client PRIVATE + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/abortable_registry.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/batch_request_impl.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/client.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_reader.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/file_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/format_hints.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/init.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/lock.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_preparer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation_tracker.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/prepare_operation.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/py_helpers.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryful_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/retryless_writer.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/skiff.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/transaction_pinger.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/yt_poller.cpp + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/dummy_job_profiler.cpp +) +generate_enum_serilization(cpp-mapreduce-client + ${CMAKE_SOURCE_DIR}/yt/cpp/mapreduce/client/structured_table_formats.h + INCLUDE_HEADERS + yt/cpp/mapreduce/client/structured_table_formats.h +) diff --git a/yt/cpp/mapreduce/client/abortable_registry.cpp b/yt/cpp/mapreduce/client/abortable_registry.cpp new file mode 100644 index 00000000000..283d39e0495 --- /dev/null +++ b/yt/cpp/mapreduce/client/abortable_registry.cpp @@ -0,0 +1,125 @@ +#include "abortable_registry.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/interface/common.h> +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <util/generic/singleton.h> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +//////////////////////////////////////////////////////////////////////////////// + +TTransactionAbortable::TTransactionAbortable(const TClientContext& context, const TTransactionId& transactionId) + : Context_(context) + , TransactionId_(transactionId) +{ } + +void TTransactionAbortable::Abort() +{ + AbortTransaction(nullptr, Context_, TransactionId_); +} + +TString TTransactionAbortable::GetType() const +{ + return "transaction"; +} + +//////////////////////////////////////////////////////////////////////////////// + +TOperationAbortable::TOperationAbortable(IClientRetryPolicyPtr clientRetryPolicy, TClientContext context, const TOperationId& operationId) + : ClientRetryPolicy_(std::move(clientRetryPolicy)) + , Context_(std::move(context)) + , OperationId_(operationId) +{ } + + +void TOperationAbortable::Abort() +{ + AbortOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, OperationId_); +} + +TString TOperationAbortable::GetType() const +{ + return "operation"; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TAbortableRegistry::AbortAllAndBlockForever() +{ + auto guard = Guard(Lock_); + + for (const auto& entry : ActiveAbortables_) { + const auto& id = entry.first; + const auto& abortable = entry.second; + try { + abortable->Abort(); + } catch (std::exception& ex) { + YT_LOG_ERROR("Exception while aborting %v %v: %v", + abortable->GetType(), + id, + ex.what()); + } + } + + Running_ = false; +} + +void TAbortableRegistry::Add(const TGUID& id, IAbortablePtr abortable) +{ + auto guard = Guard(Lock_); + + if (!Running_) { + Sleep(TDuration::Max()); + } + + ActiveAbortables_[id] = abortable; +} + +void TAbortableRegistry::Remove(const TGUID& id) +{ + auto guard = Guard(Lock_); + + if (!Running_) { + Sleep(TDuration::Max()); + } + + ActiveAbortables_.erase(id); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +class TRegistryHolder +{ +public: + TRegistryHolder() + : Registry_(::MakeIntrusive<TAbortableRegistry>()) + { } + + ::TIntrusivePtr<TAbortableRegistry> Get() + { + return Registry_; + } + +private: + ::TIntrusivePtr<TAbortableRegistry> Registry_; +}; + +} // namespace + +::TIntrusivePtr<TAbortableRegistry> TAbortableRegistry::Get() +{ + return Singleton<TRegistryHolder>()->Get(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/abortable_registry.h b/yt/cpp/mapreduce/client/abortable_registry.h new file mode 100644 index 00000000000..119d685cad3 --- /dev/null +++ b/yt/cpp/mapreduce/client/abortable_registry.h @@ -0,0 +1,81 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/common.h> + +#include <yt/cpp/mapreduce/http/context.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <util/str_stl.h> +#include <util/system/mutex.h> +#include <util/generic/hash.h> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class IAbortable + : public TThrRefBase +{ +public: + virtual void Abort() = 0; + virtual TString GetType() const = 0; +}; + +using IAbortablePtr = ::TIntrusivePtr<IAbortable>; + +//////////////////////////////////////////////////////////////////////////////// + +class TTransactionAbortable + : public IAbortable +{ +public: + TTransactionAbortable(const TClientContext& context, const TTransactionId& transactionId); + void Abort() override; + TString GetType() const override; + +private: + TClientContext Context_; + TTransactionId TransactionId_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TOperationAbortable + : public IAbortable +{ +public: + TOperationAbortable(IClientRetryPolicyPtr clientRetryPolicy, TClientContext context, const TOperationId& operationId); + void Abort() override; + TString GetType() const override; + +private: + const IClientRetryPolicyPtr ClientRetryPolicy_; + const TClientContext Context_; + const TOperationId OperationId_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TAbortableRegistry + : public TThrRefBase +{ +public: + TAbortableRegistry() = default; + static ::TIntrusivePtr<TAbortableRegistry> Get(); + + void AbortAllAndBlockForever(); + void Add(const TGUID& id, IAbortablePtr abortable); + void Remove(const TGUID& id); + +private: + THashMap<TGUID, IAbortablePtr> ActiveAbortables_; + TMutex Lock_; + bool Running_ = true; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/batch_request_impl.cpp b/yt/cpp/mapreduce/client/batch_request_impl.cpp new file mode 100644 index 00000000000..6afa5665f12 --- /dev/null +++ b/yt/cpp/mapreduce/client/batch_request_impl.cpp @@ -0,0 +1,198 @@ +#include "batch_request_impl.h" + +#include "lock.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/interface/config.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <library/cpp/yson/node/node.h> +#include <library/cpp/yson/node/serialize.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> +#include <yt/cpp/mapreduce/raw_client/rpc_parameters_serialization.h> + +#include <util/generic/guid.h> +#include <util/string/builder.h> + +#include <exception> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +using ::NThreading::TFuture; +using ::NThreading::TPromise; +using ::NThreading::NewPromise; + +//////////////////////////////////////////////////////////////////// + +TBatchRequest::TBatchRequest(const TTransactionId& defaultTransaction, ::TIntrusivePtr<TClient> client) + : DefaultTransaction_(defaultTransaction) + , Impl_(MakeIntrusive<TRawBatchRequest>(client->GetContext().Config)) + , Client_(client) +{ } + +TBatchRequest::TBatchRequest(TRawBatchRequest* impl, ::TIntrusivePtr<TClient> client) + : Impl_(impl) + , Client_(std::move(client)) +{ } + +TBatchRequest::~TBatchRequest() = default; + +IBatchRequestBase& TBatchRequest::WithTransaction(const TTransactionId& transactionId) +{ + if (!TmpWithTransaction_) { + TmpWithTransaction_.Reset(new TBatchRequest(Impl_.Get(), Client_)); + } + TmpWithTransaction_->DefaultTransaction_ = transactionId; + return *TmpWithTransaction_; +} + +TFuture<TNode> TBatchRequest::Get( + const TYPath& path, + const TGetOptions& options) +{ + return Impl_->Get(DefaultTransaction_, path, options); +} + +TFuture<void> TBatchRequest::Set(const TYPath& path, const TNode& node, const TSetOptions& options) +{ + return Impl_->Set(DefaultTransaction_, path, node, options); +} + +TFuture<TNode::TListType> TBatchRequest::List(const TYPath& path, const TListOptions& options) +{ + return Impl_->List(DefaultTransaction_, path, options); +} + +TFuture<bool> TBatchRequest::Exists(const TYPath& path, const TExistsOptions& options) +{ + return Impl_->Exists(DefaultTransaction_, path, options); +} + +TFuture<ILockPtr> TBatchRequest::Lock( + const TYPath& path, + ELockMode mode, + const TLockOptions& options) +{ + auto convert = [waitable=options.Waitable_, client=Client_] (TFuture<TNodeId> nodeIdFuture) -> ILockPtr { + return ::MakeIntrusive<TLock>(nodeIdFuture.GetValue(), client, waitable); + }; + return Impl_->Lock(DefaultTransaction_, path, mode, options).Apply(convert); +} + +::NThreading::TFuture<void> TBatchRequest::Unlock( + const TYPath& path, + const TUnlockOptions& options = TUnlockOptions()) +{ + return Impl_->Unlock(DefaultTransaction_, path, options); +} + +TFuture<TLockId> TBatchRequest::Create( + const TYPath& path, + ENodeType type, + const TCreateOptions& options) +{ + return Impl_->Create(DefaultTransaction_, path, type, options); +} + +TFuture<void> TBatchRequest::Remove( + const TYPath& path, + const TRemoveOptions& options) +{ + return Impl_->Remove(DefaultTransaction_, path, options); +} + +TFuture<TNodeId> TBatchRequest::Move( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TMoveOptions& options) +{ + return Impl_->Move(DefaultTransaction_, sourcePath, destinationPath, options); +} + +TFuture<TNodeId> TBatchRequest::Copy( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TCopyOptions& options) +{ + return Impl_->Copy(DefaultTransaction_, sourcePath, destinationPath, options); +} + +TFuture<TNodeId> TBatchRequest::Link( + const TYPath& targetPath, + const TYPath& linkPath, + const TLinkOptions& options) +{ + return Impl_->Link(DefaultTransaction_, targetPath, linkPath, options); +} + +TFuture<void> TBatchRequest::AbortOperation(const NYT::TOperationId& operationId) +{ + return Impl_->AbortOperation(operationId); +} + +TFuture<void> TBatchRequest::CompleteOperation(const NYT::TOperationId& operationId) +{ + return Impl_->CompleteOperation(operationId); +} + +TFuture<void> TBatchRequest::SuspendOperation( + const TOperationId& operationId, + const TSuspendOperationOptions& options) +{ + return Impl_->SuspendOperation(operationId, options); +} + +TFuture<void> TBatchRequest::ResumeOperation( + const TOperationId& operationId, + const TResumeOperationOptions& options) +{ + return Impl_->ResumeOperation(operationId, options); +} + +TFuture<void> TBatchRequest::UpdateOperationParameters( + const NYT::TOperationId& operationId, + const NYT::TUpdateOperationParametersOptions& options) +{ + return Impl_->UpdateOperationParameters(operationId, options); +} + +TFuture<TRichYPath> TBatchRequest::CanonizeYPath(const TRichYPath& path) +{ + return Impl_->CanonizeYPath(path); +} + +TFuture<TVector<TTableColumnarStatistics>> TBatchRequest::GetTableColumnarStatistics( + const TVector<TRichYPath>& paths, + const NYT::TGetTableColumnarStatisticsOptions& options) +{ + return Impl_->GetTableColumnarStatistics(DefaultTransaction_, paths, options); +} + +TFuture<TCheckPermissionResponse> TBatchRequest::CheckPermission( + const TString& user, + EPermission permission, + const TYPath& path, + const TCheckPermissionOptions& options) +{ + return Impl_->CheckPermission(user, permission, path, options); +} + +void TBatchRequest::ExecuteBatch(const TExecuteBatchOptions& options) +{ + NYT::NDetail::ExecuteBatch(Client_->GetRetryPolicy()->CreatePolicyForGenericRequest(), Client_->GetContext(), *Impl_, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/batch_request_impl.h b/yt/cpp/mapreduce/client/batch_request_impl.h new file mode 100644 index 00000000000..0a176417b35 --- /dev/null +++ b/yt/cpp/mapreduce/client/batch_request_impl.h @@ -0,0 +1,137 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/batch_request.h> +#include <yt/cpp/mapreduce/interface/fwd.h> +#include <yt/cpp/mapreduce/interface/node.h> + +#include <yt/cpp/mapreduce/http/requests.h> + +#include <library/cpp/threading/future/future.h> + +#include <util/generic/ptr.h> +#include <util/generic/deque.h> + +#include <exception> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +struct TResponseInfo; +class TClient; +using TClientPtr = ::TIntrusivePtr<TClient>; + +namespace NRawClient { + class TRawBatchRequest; +} + +//////////////////////////////////////////////////////////////////////////////// + +class TBatchRequest + : public IBatchRequest +{ +public: + TBatchRequest(const TTransactionId& defaultTransaction, ::TIntrusivePtr<TClient> client); + + ~TBatchRequest(); + + virtual IBatchRequestBase& WithTransaction(const TTransactionId& transactionId) override; + + virtual ::NThreading::TFuture<TLockId> Create( + const TYPath& path, + ENodeType type, + const TCreateOptions& options = TCreateOptions()) override; + + virtual ::NThreading::TFuture<void> Remove( + const TYPath& path, + const TRemoveOptions& options = TRemoveOptions()) override; + + virtual ::NThreading::TFuture<bool> Exists( + const TYPath& path, + const TExistsOptions& options = TExistsOptions()) override; + + virtual ::NThreading::TFuture<TNode> Get( + const TYPath& path, + const TGetOptions& options = TGetOptions()) override; + + virtual ::NThreading::TFuture<void> Set( + const TYPath& path, + const TNode& node, + const TSetOptions& options = TSetOptions()) override; + + virtual ::NThreading::TFuture<TNode::TListType> List( + const TYPath& path, + const TListOptions& options = TListOptions()) override; + + virtual ::NThreading::TFuture<TNodeId> Copy( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TCopyOptions& options = TCopyOptions()) override; + + virtual ::NThreading::TFuture<TNodeId> Move( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TMoveOptions& options = TMoveOptions()) override; + + virtual ::NThreading::TFuture<TNodeId> Link( + const TYPath& targetPath, + const TYPath& linkPath, + const TLinkOptions& options = TLinkOptions()) override; + + virtual ::NThreading::TFuture<ILockPtr> Lock( + const TYPath& path, + ELockMode mode, + const TLockOptions& options) override; + + virtual ::NThreading::TFuture<void> Unlock( + const TYPath& path, + const TUnlockOptions& options) override; + + virtual ::NThreading::TFuture<void> AbortOperation(const TOperationId& operationId) override; + + virtual ::NThreading::TFuture<void> CompleteOperation(const TOperationId& operationId) override; + + ::NThreading::TFuture<void> SuspendOperation( + const TOperationId& operationId, + const TSuspendOperationOptions& options) override; + + ::NThreading::TFuture<void> ResumeOperation( + const TOperationId& operationId, + const TResumeOperationOptions& options) override; + + virtual ::NThreading::TFuture<void> UpdateOperationParameters( + const TOperationId& operationId, + const TUpdateOperationParametersOptions& options) override; + + virtual ::NThreading::TFuture<TRichYPath> CanonizeYPath(const TRichYPath& path) override; + + virtual ::NThreading::TFuture<TVector<TTableColumnarStatistics>> GetTableColumnarStatistics( + const TVector<TRichYPath>& paths, + const TGetTableColumnarStatisticsOptions& options) override; + + ::NThreading::TFuture<TCheckPermissionResponse> CheckPermission( + const TString& user, + EPermission permission, + const TYPath& path, + const TCheckPermissionOptions& options) override; + + virtual void ExecuteBatch(const TExecuteBatchOptions& executeBatch) override; + +private: + TBatchRequest(NDetail::NRawClient::TRawBatchRequest* impl, ::TIntrusivePtr<TClient> client); + +private: + TTransactionId DefaultTransaction_; + ::TIntrusivePtr<NDetail::NRawClient::TRawBatchRequest> Impl_; + THolder<TBatchRequest> TmpWithTransaction_; + ::TIntrusivePtr<TClient> Client_; + +private: + friend class NYT::NDetail::TClient; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client.cpp b/yt/cpp/mapreduce/client/client.cpp new file mode 100644 index 00000000000..ca979c55883 --- /dev/null +++ b/yt/cpp/mapreduce/client/client.cpp @@ -0,0 +1,1361 @@ +#include "client.h" + +#include "batch_request_impl.h" +#include "client_reader.h" +#include "client_writer.h" +#include "file_reader.h" +#include "file_writer.h" +#include "format_hints.h" +#include "lock.h" +#include "operation.h" +#include "retry_transaction.h" +#include "retryful_writer.h" +#include "transaction.h" +#include "transaction_pinger.h" +#include "yt_poller.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/http/helpers.h> +#include <yt/cpp/mapreduce/http/http.h> +#include <yt/cpp/mapreduce/http/http_client.h> +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/client.h> +#include <yt/cpp/mapreduce/interface/fluent.h> +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> +#include <yt/cpp/mapreduce/interface/skiff_row.h> + +#include <yt/cpp/mapreduce/io/yamr_table_reader.h> +#include <yt/cpp/mapreduce/io/yamr_table_writer.h> +#include <yt/cpp/mapreduce/io/node_table_reader.h> +#include <yt/cpp/mapreduce/io/node_table_writer.h> +#include <yt/cpp/mapreduce/io/proto_table_reader.h> +#include <yt/cpp/mapreduce/io/proto_table_writer.h> +#include <yt/cpp/mapreduce/io/skiff_row_table_reader.h> +#include <yt/cpp/mapreduce/io/proto_helpers.h> + +#include <yt/cpp/mapreduce/library/table_schema/protobuf.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> +#include <yt/cpp/mapreduce/raw_client/rpc_parameters_serialization.h> + +#include <library/cpp/json/json_reader.h> + +#include <util/generic/algorithm.h> +#include <util/string/type.h> +#include <util/system/env.h> + +#include <exception> + +using namespace NYT::NDetail::NRawClient; + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +TClientBase::TClientBase( + const TClientContext& context, + const TTransactionId& transactionId, + IClientRetryPolicyPtr retryPolicy) + : Context_(context) + , TransactionId_(transactionId) + , ClientRetryPolicy_(std::move(retryPolicy)) +{ } + +ITransactionPtr TClientBase::StartTransaction( + const TStartTransactionOptions& options) +{ + return MakeIntrusive<TTransaction>(GetParentClientImpl(), Context_, TransactionId_, options); +} + +TNodeId TClientBase::Create( + const TYPath& path, + ENodeType type, + const TCreateOptions& options) +{ + return NRawClient::Create(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, type, options); +} + +void TClientBase::Remove( + const TYPath& path, + const TRemoveOptions& options) +{ + return NRawClient::Remove(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +bool TClientBase::Exists( + const TYPath& path, + const TExistsOptions& options) +{ + return NRawClient::Exists(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +TNode TClientBase::Get( + const TYPath& path, + const TGetOptions& options) +{ + return NRawClient::Get(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +void TClientBase::Set( + const TYPath& path, + const TNode& value, + const TSetOptions& options) +{ + NRawClient::Set(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, value, options); +} + +void TClientBase::MultisetAttributes( + const TYPath& path, const TNode::TMapType& value, const TMultisetAttributesOptions& options) +{ + NRawClient::MultisetAttributes(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, value, options); +} + + +TNode::TListType TClientBase::List( + const TYPath& path, + const TListOptions& options) +{ + return NRawClient::List(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +TNodeId TClientBase::Copy( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TCopyOptions& options) +{ + return NRawClient::Copy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, sourcePath, destinationPath, options); +} + +TNodeId TClientBase::Move( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TMoveOptions& options) +{ + return NRawClient::Move(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, sourcePath, destinationPath, options); +} + +TNodeId TClientBase::Link( + const TYPath& targetPath, + const TYPath& linkPath, + const TLinkOptions& options) +{ + return NRawClient::Link(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, targetPath, linkPath, options); +} + +void TClientBase::Concatenate( + const TVector<TRichYPath>& sourcePaths, + const TRichYPath& destinationPath, + const TConcatenateOptions& options) +{ + std::function<void(ITransactionPtr)> lambda = [&sourcePaths, &destinationPath, &options, this](ITransactionPtr transaction) { + if (!options.Append_ && !sourcePaths.empty() && !transaction->Exists(destinationPath.Path_)) { + auto typeNode = transaction->Get(CanonizeYPath(sourcePaths.front()).Path_ + "/@type"); + auto type = FromString<ENodeType>(typeNode.AsString()); + transaction->Create(destinationPath.Path_, type, TCreateOptions().IgnoreExisting(true)); + } + NRawClient::Concatenate(this->Context_, transaction->GetId(), sourcePaths, destinationPath, options); + }; + RetryTransactionWithPolicy(this, lambda, ClientRetryPolicy_->CreatePolicyForGenericRequest()); +} + +TRichYPath TClientBase::CanonizeYPath(const TRichYPath& path) +{ + return NRawClient::CanonizeYPath(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, path); +} + +TVector<TTableColumnarStatistics> TClientBase::GetTableColumnarStatistics( + const TVector<TRichYPath>& paths, + const TGetTableColumnarStatisticsOptions& options) +{ + return NRawClient::GetTableColumnarStatistics( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + TransactionId_, + paths, + options); +} + +TMultiTablePartitions TClientBase::GetTablePartitions( + const TVector<TRichYPath>& paths, + const TGetTablePartitionsOptions& options) +{ + return NRawClient::GetTablePartitions( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + TransactionId_, + paths, + options); +} + +TMaybe<TYPath> TClientBase::GetFileFromCache( + const TString& md5Signature, + const TYPath& cachePath, + const TGetFileFromCacheOptions& options) +{ + return NRawClient::GetFileFromCache(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, md5Signature, cachePath, options); +} + +TYPath TClientBase::PutFileToCache( + const TYPath& filePath, + const TString& md5Signature, + const TYPath& cachePath, + const TPutFileToCacheOptions& options) +{ + return NRawClient::PutFileToCache(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, filePath, md5Signature, cachePath, options); +} + +IFileReaderPtr TClientBase::CreateBlobTableReader( + const TYPath& path, + const TKey& key, + const TBlobTableReaderOptions& options) +{ + return new TBlobTableReader( + path, + key, + ClientRetryPolicy_, + GetTransactionPinger(), + Context_, + TransactionId_, + options); +} + +IFileReaderPtr TClientBase::CreateFileReader( + const TRichYPath& path, + const TFileReaderOptions& options) +{ + return new TFileReader( + CanonizeYPath(path), + ClientRetryPolicy_, + GetTransactionPinger(), + Context_, + TransactionId_, + options); +} + +IFileWriterPtr TClientBase::CreateFileWriter( + const TRichYPath& path, + const TFileWriterOptions& options) +{ + auto realPath = CanonizeYPath(path); + if (!NRawClient::Exists(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, realPath.Path_)) { + NRawClient::Create(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, realPath.Path_, NT_FILE, + TCreateOptions().IgnoreExisting(true)); + } + return new TFileWriter(realPath, ClientRetryPolicy_, GetTransactionPinger(), Context_, TransactionId_, options); +} + +TTableWriterPtr<::google::protobuf::Message> TClientBase::CreateTableWriter( + const TRichYPath& path, const ::google::protobuf::Descriptor& descriptor, const TTableWriterOptions& options) +{ + const Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(&descriptor); + return new TTableWriter<::google::protobuf::Message>(CreateProtoWriter(path, options, prototype)); +} + +TRawTableReaderPtr TClientBase::CreateRawReader( + const TRichYPath& path, + const TFormat& format, + const TTableReaderOptions& options) +{ + return CreateClientReader(path, format, options).Get(); +} + +TRawTableWriterPtr TClientBase::CreateRawWriter( + const TRichYPath& path, + const TFormat& format, + const TTableWriterOptions& options) +{ + return ::MakeIntrusive<TRetryfulWriter>( + ClientRetryPolicy_, + GetTransactionPinger(), + Context_, + TransactionId_, + GetWriteTableCommand(Context_.Config->ApiVersion), + format, + CanonizeYPath(path), + options).Get(); +} + +IOperationPtr TClientBase::DoMap( + const TMapOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> mapper, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + mapper, + options + ] () { + ExecuteMap( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + mapper, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RawMap( + const TRawMapOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + mapper, + options + ] () { + ExecuteRawMap( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + mapper, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::DoReduce( + const TReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + reducer, + options + ] () { + ExecuteReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RawReduce( + const TRawReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + reducer, + options + ] () { + ExecuteRawReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::DoJoinReduce( + const TJoinReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + reducer, + options + ] () { + ExecuteJoinReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RawJoinReduce( + const TRawJoinReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + reducer, + options + ] () { + ExecuteRawJoinReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::DoMapReduce( + const TMapReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> mapper, + ::TIntrusivePtr<IStructuredJob> reduceCombiner, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + mapper, + reduceCombiner, + reducer, + options + ] () { + ExecuteMapReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + mapper, + reduceCombiner, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RawMapReduce( + const TRawMapReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + ::TIntrusivePtr<IRawJob> reduceCombiner, + ::TIntrusivePtr<IRawJob> reducer, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_=::TIntrusivePtr(this), + operation, + spec, + mapper, + reduceCombiner, + reducer, + options + ] () { + ExecuteRawMapReduce( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + mapper, + reduceCombiner, + reducer, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::Sort( + const TSortOperationSpec& spec, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + options + ] () { + ExecuteSort( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::Merge( + const TMergeOperationSpec& spec, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + options + ] () { + ExecuteMerge( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::Erase( + const TEraseOperationSpec& spec, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + options + ] () { + ExecuteErase( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RemoteCopy( + const TRemoteCopyOperationSpec& spec, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + options + ] () { + ExecuteRemoteCopy( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::RunVanilla( + const TVanillaOperationSpec& spec, + const TOperationOptions& options) +{ + auto operation = ::MakeIntrusive<TOperation>(GetParentClientImpl()); + auto prepareOperation = [ + this_ = ::TIntrusivePtr(this), + operation, + spec, + options + ] () { + ExecuteVanilla( + operation, + ::MakeIntrusive<TOperationPreparer>(this_->GetParentClientImpl(), this_->TransactionId_), + spec, + options); + }; + return ProcessOperation(GetParentClientImpl(), std::move(prepareOperation), std::move(operation), options); +} + +IOperationPtr TClientBase::AttachOperation(const TOperationId& operationId) +{ + auto operation = ::MakeIntrusive<TOperation>(operationId, GetParentClientImpl()); + operation->GetBriefState(); // check that operation exists + return operation; +} + +EOperationBriefState TClientBase::CheckOperation(const TOperationId& operationId) +{ + return NYT::NDetail::CheckOperation(ClientRetryPolicy_, Context_, operationId); +} + +void TClientBase::AbortOperation(const TOperationId& operationId) +{ + NRawClient::AbortOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId); +} + +void TClientBase::CompleteOperation(const TOperationId& operationId) +{ + NRawClient::CompleteOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId); +} + +void TClientBase::WaitForOperation(const TOperationId& operationId) +{ + NYT::NDetail::WaitForOperation(ClientRetryPolicy_, Context_, operationId); +} + +void TClientBase::AlterTable( + const TYPath& path, + const TAlterTableOptions& options) +{ + NRawClient::AlterTable(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +::TIntrusivePtr<TClientReader> TClientBase::CreateClientReader( + const TRichYPath& path, + const TFormat& format, + const TTableReaderOptions& options, + bool useFormatFromTableAttributes) +{ + return ::MakeIntrusive<TClientReader>( + CanonizeYPath(path), + ClientRetryPolicy_, + GetTransactionPinger(), + Context_, + TransactionId_, + format, + options, + useFormatFromTableAttributes); +} + +THolder<TClientWriter> TClientBase::CreateClientWriter( + const TRichYPath& path, + const TFormat& format, + const TTableWriterOptions& options) +{ + auto realPath = CanonizeYPath(path); + if (!NRawClient::Exists(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, realPath.Path_)) { + NRawClient::Create(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, realPath.Path_, NT_TABLE, + TCreateOptions().IgnoreExisting(true)); + } + return MakeHolder<TClientWriter>( + realPath, + ClientRetryPolicy_, + GetTransactionPinger(), + Context_, + TransactionId_, + format, + options + ); +} + +::TIntrusivePtr<INodeReaderImpl> TClientBase::CreateNodeReader( + const TRichYPath& path, const TTableReaderOptions& options) +{ + auto format = TFormat::YsonBinary(); + ApplyFormatHints<TNode>(&format, options.FormatHints_); + + // Skiff is disabled here because of large header problem (see https://st.yandex-team.ru/YT-6926). + // Revert this code to r3614168 when it is fixed. + return new TNodeTableReader( + CreateClientReader(path, format, options)); +} + +::TIntrusivePtr<IYaMRReaderImpl> TClientBase::CreateYaMRReader( + const TRichYPath& path, const TTableReaderOptions& options) +{ + return new TYaMRTableReader( + CreateClientReader(path, TFormat::YaMRLenval(), options, /* useFormatFromTableAttributes = */ true)); +} + +::TIntrusivePtr<IProtoReaderImpl> TClientBase::CreateProtoReader( + const TRichYPath& path, + const TTableReaderOptions& options, + const Message* prototype) +{ + TVector<const ::google::protobuf::Descriptor*> descriptors; + descriptors.push_back(prototype->GetDescriptor()); + + if (Context_.Config->UseClientProtobuf) { + return new TProtoTableReader( + CreateClientReader(path, TFormat::YsonBinary(), options), + std::move(descriptors)); + } else { + auto format = TFormat::Protobuf({prototype->GetDescriptor()}, Context_.Config->ProtobufFormatWithDescriptors); + return new TLenvalProtoTableReader( + CreateClientReader(path, format, options), + std::move(descriptors)); + } +} + +::TIntrusivePtr<ISkiffRowReaderImpl> TClientBase::CreateSkiffRowReader( + const TRichYPath& path, + const TTableReaderOptions& options, + const ISkiffRowSkipperPtr& skipper, + const NSkiff::TSkiffSchemaPtr& schema) +{ + auto skiffOptions = TCreateSkiffSchemaOptions().HasRangeIndex(true); + auto resultSchema = NYT::NDetail::CreateSkiffSchema(TVector{schema}, skiffOptions); + return new TSkiffRowTableReader( + CreateClientReader(path, NYT::NDetail::CreateSkiffFormat(resultSchema), options), + resultSchema, + {skipper}, + std::move(skiffOptions)); +} + +::TIntrusivePtr<INodeWriterImpl> TClientBase::CreateNodeWriter( + const TRichYPath& path, const TTableWriterOptions& options) +{ + auto format = TFormat::YsonBinary(); + ApplyFormatHints<TNode>(&format, options.FormatHints_); + + return new TNodeTableWriter( + CreateClientWriter(path, format, options)); +} + +::TIntrusivePtr<IYaMRWriterImpl> TClientBase::CreateYaMRWriter( + const TRichYPath& path, const TTableWriterOptions& options) +{ + auto format = TFormat::YaMRLenval(); + ApplyFormatHints<TYaMRRow>(&format, options.FormatHints_); + + return new TYaMRTableWriter( + CreateClientWriter(path, format, options)); +} + +::TIntrusivePtr<IProtoWriterImpl> TClientBase::CreateProtoWriter( + const TRichYPath& path, + const TTableWriterOptions& options, + const Message* prototype) +{ + TVector<const ::google::protobuf::Descriptor*> descriptors; + descriptors.push_back(prototype->GetDescriptor()); + + auto pathWithSchema = path; + if (options.InferSchema_.GetOrElse(Context_.Config->InferTableSchema) && !path.Schema_) { + pathWithSchema.Schema(CreateTableSchema(*prototype->GetDescriptor())); + } + + if (Context_.Config->UseClientProtobuf) { + auto format = TFormat::YsonBinary(); + ApplyFormatHints<TNode>(&format, options.FormatHints_); + return new TProtoTableWriter( + CreateClientWriter(pathWithSchema, format, options), + std::move(descriptors)); + } else { + auto format = TFormat::Protobuf({prototype->GetDescriptor()}, Context_.Config->ProtobufFormatWithDescriptors); + ApplyFormatHints<::google::protobuf::Message>(&format, options.FormatHints_); + return new TLenvalProtoTableWriter( + CreateClientWriter(pathWithSchema, format, options), + std::move(descriptors)); + } +} + +TBatchRequestPtr TClientBase::CreateBatchRequest() +{ + return MakeIntrusive<TBatchRequest>(TransactionId_, GetParentClientImpl()); +} + +IClientPtr TClientBase::GetParentClient() +{ + return GetParentClientImpl(); +} + +const TClientContext& TClientBase::GetContext() const +{ + return Context_; +} + +const IClientRetryPolicyPtr& TClientBase::GetRetryPolicy() const +{ + return ClientRetryPolicy_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TTransaction::TTransaction( + TClientPtr parentClient, + const TClientContext& context, + const TTransactionId& parentTransactionId, + const TStartTransactionOptions& options) + : TClientBase(context, parentTransactionId, parentClient->GetRetryPolicy()) + , TransactionPinger_(parentClient->GetTransactionPinger()) + , PingableTx_( + MakeHolder<TPingableTransaction>( + parentClient->GetRetryPolicy(), + context, + parentTransactionId, + TransactionPinger_->GetChildTxPinger(), + options)) + , ParentClient_(parentClient) +{ + TransactionId_ = PingableTx_->GetId(); +} + +TTransaction::TTransaction( + TClientPtr parentClient, + const TClientContext& context, + const TTransactionId& transactionId, + const TAttachTransactionOptions& options) + : TClientBase(context, transactionId, parentClient->GetRetryPolicy()) + , TransactionPinger_(parentClient->GetTransactionPinger()) + , PingableTx_( + new TPingableTransaction( + parentClient->GetRetryPolicy(), + context, + transactionId, + parentClient->GetTransactionPinger()->GetChildTxPinger(), + options)) + , ParentClient_(parentClient) +{ } + +const TTransactionId& TTransaction::GetId() const +{ + return TransactionId_; +} + +ILockPtr TTransaction::Lock( + const TYPath& path, + ELockMode mode, + const TLockOptions& options) +{ + auto lockId = NRawClient::Lock(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, mode, options); + return ::MakeIntrusive<TLock>(lockId, GetParentClientImpl(), options.Waitable_); +} + +void TTransaction::Unlock( + const TYPath& path, + const TUnlockOptions& options) +{ + NRawClient::Unlock(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_, path, options); +} + +void TTransaction::Commit() +{ + PingableTx_->Commit(); +} + +void TTransaction::Abort() +{ + PingableTx_->Abort(); +} + +void TTransaction::Ping() +{ + PingTx(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, TransactionId_); +} + +void TTransaction::Detach() +{ + PingableTx_->Detach(); +} + +ITransactionPingerPtr TTransaction::GetTransactionPinger() +{ + return TransactionPinger_; +} + +TClientPtr TTransaction::GetParentClientImpl() +{ + return ParentClient_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TClient::TClient( + const TClientContext& context, + const TTransactionId& globalId, + IClientRetryPolicyPtr retryPolicy) + : TClientBase(context, globalId, retryPolicy) + , TransactionPinger_(nullptr) +{ } + +TClient::~TClient() = default; + +ITransactionPtr TClient::AttachTransaction( + const TTransactionId& transactionId, + const TAttachTransactionOptions& options) +{ + CheckShutdown(); + + return MakeIntrusive<TTransaction>(this, Context_, transactionId, options); +} + +void TClient::MountTable( + const TYPath& path, + const TMountTableOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "mount_table"); + SetTabletParams(header, path, options); + if (options.CellId_) { + header.AddParameter("cell_id", GetGuidAsString(*options.CellId_)); + } + header.AddParameter("freeze", options.Freeze_); + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); +} + +void TClient::UnmountTable( + const TYPath& path, + const TUnmountTableOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "unmount_table"); + SetTabletParams(header, path, options); + header.AddParameter("force", options.Force_); + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); +} + +void TClient::RemountTable( + const TYPath& path, + const TRemountTableOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "remount_table"); + SetTabletParams(header, path, options); + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); +} + +void TClient::FreezeTable( + const TYPath& path, + const TFreezeTableOptions& options) +{ + CheckShutdown(); + NRawClient::FreezeTable(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, path, options); +} + +void TClient::UnfreezeTable( + const TYPath& path, + const TUnfreezeTableOptions& options) +{ + CheckShutdown(); + NRawClient::UnfreezeTable(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, path, options); +} + +void TClient::ReshardTable( + const TYPath& path, + const TVector<TKey>& keys, + const TReshardTableOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "reshard_table"); + SetTabletParams(header, path, options); + header.AddParameter("pivot_keys", BuildYsonNodeFluently().List(keys)); + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); +} + +void TClient::ReshardTable( + const TYPath& path, + i64 tabletCount, + const TReshardTableOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "reshard_table"); + SetTabletParams(header, path, options); + header.AddParameter("tablet_count", tabletCount); + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); +} + +void TClient::InsertRows( + const TYPath& path, + const TNode::TListType& rows, + const TInsertRowsOptions& options) +{ + CheckShutdown(); + + THttpHeader header("PUT", "insert_rows"); + header.SetInputFormat(TFormat::YsonBinary()); + // TODO: use corresponding raw request + header.MergeParameters(SerializeParametersForInsertRows(Context_.Config->Prefix, path, options)); + + auto body = NodeListToYsonString(rows); + TRequestConfig config; + config.IsHeavy = true; + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header, body, config); +} + +void TClient::DeleteRows( + const TYPath& path, + const TNode::TListType& keys, + const TDeleteRowsOptions& options) +{ + CheckShutdown(); + return NRawClient::DeleteRows(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, path, keys, options); +} + +void TClient::TrimRows( + const TYPath& path, + i64 tabletIndex, + i64 rowCount, + const TTrimRowsOptions& options) +{ + CheckShutdown(); + + THttpHeader header("POST", "trim_rows"); + header.AddParameter("trimmed_row_count", rowCount); + header.AddParameter("tablet_index", tabletIndex); + // TODO: use corresponding raw request + header.MergeParameters(NRawClient::SerializeParametersForTrimRows(Context_.Config->Prefix, path, options)); + + TRequestConfig config; + config.IsHeavy = true; + RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header, {}, config); +} + +TNode::TListType TClient::LookupRows( + const TYPath& path, + const TNode::TListType& keys, + const TLookupRowsOptions& options) +{ + CheckShutdown(); + + Y_UNUSED(options); + THttpHeader header("PUT", "lookup_rows"); + header.AddPath(AddPathPrefix(path, Context_.Config->ApiVersion)); + header.SetInputFormat(TFormat::YsonBinary()); + header.SetOutputFormat(TFormat::YsonBinary()); + + header.MergeParameters(BuildYsonNodeFluently().BeginMap() + .DoIf(options.Timeout_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("timeout").Value(static_cast<i64>(options.Timeout_->MilliSeconds())); + }) + .Item("keep_missing_rows").Value(options.KeepMissingRows_) + .DoIf(options.Versioned_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("versioned").Value(*options.Versioned_); + }) + .DoIf(options.Columns_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("column_names").Value(*options.Columns_); + }) + .EndMap()); + + auto body = NodeListToYsonString(keys); + TRequestConfig config; + config.IsHeavy = true; + auto result = RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header, body, config); + return NodeFromYsonString(result.Response, ::NYson::EYsonType::ListFragment).AsList(); +} + +TNode::TListType TClient::SelectRows( + const TString& query, + const TSelectRowsOptions& options) +{ + CheckShutdown(); + + THttpHeader header("GET", "select_rows"); + header.SetInputFormat(TFormat::YsonBinary()); + header.SetOutputFormat(TFormat::YsonBinary()); + + header.MergeParameters(BuildYsonNodeFluently().BeginMap() + .Item("query").Value(query) + .DoIf(options.Timeout_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("timeout").Value(static_cast<i64>(options.Timeout_->MilliSeconds())); + }) + .DoIf(options.InputRowLimit_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("input_row_limit").Value(*options.InputRowLimit_); + }) + .DoIf(options.OutputRowLimit_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("output_row_limit").Value(*options.OutputRowLimit_); + }) + .Item("range_expansion_limit").Value(options.RangeExpansionLimit_) + .Item("fail_on_incomplete_result").Value(options.FailOnIncompleteResult_) + .Item("verbose_logging").Value(options.VerboseLogging_) + .Item("enable_code_cache").Value(options.EnableCodeCache_) + .EndMap()); + + TRequestConfig config; + config.IsHeavy = true; + auto result = RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header, {}, config); + return NodeFromYsonString(result.Response, ::NYson::EYsonType::ListFragment).AsList(); +} + +void TClient::AlterTableReplica(const TReplicaId& replicaId, const TAlterTableReplicaOptions& options) +{ + CheckShutdown(); + NRawClient::AlterTableReplica(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, replicaId, options); +} + +ui64 TClient::GenerateTimestamp() +{ + CheckShutdown(); + THttpHeader header("GET", "generate_timestamp"); + TRequestConfig config; + config.IsHeavy = true; + auto requestResult = RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header, {}, config); + return NodeFromYsonString(requestResult.Response).AsUint64(); +} + +TAuthorizationInfo TClient::WhoAmI() +{ + CheckShutdown(); + + THttpHeader header("GET", "auth/whoami", /* isApi = */ false); + auto requestResult = RetryRequestWithPolicy(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, header); + TAuthorizationInfo result; + + NJson::TJsonValue jsonValue; + bool ok = NJson::ReadJsonTree(requestResult.Response, &jsonValue, /* throwOnError = */ true); + Y_VERIFY(ok); + result.Login = jsonValue["login"].GetString(); + result.Realm = jsonValue["realm"].GetString(); + return result; +} + +TOperationAttributes TClient::GetOperation( + const TOperationId& operationId, + const TGetOperationOptions& options) +{ + CheckShutdown(); + return NRawClient::GetOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, options); +} + +TListOperationsResult TClient::ListOperations( + const TListOperationsOptions& options) +{ + CheckShutdown(); + return NRawClient::ListOperations(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, options); +} + +void TClient::UpdateOperationParameters( + const TOperationId& operationId, + const TUpdateOperationParametersOptions& options) +{ + CheckShutdown(); + return NRawClient::UpdateOperationParameters(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, options); +} + +TJobAttributes TClient::GetJob( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobOptions& options) +{ + CheckShutdown(); + return NRawClient::GetJob(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, jobId, options); +} + +TListJobsResult TClient::ListJobs( + const TOperationId& operationId, + const TListJobsOptions& options) +{ + CheckShutdown(); + return NRawClient::ListJobs(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, options); +} + +IFileReaderPtr TClient::GetJobInput( + const TJobId& jobId, + const TGetJobInputOptions& options) +{ + CheckShutdown(); + return NRawClient::GetJobInput(Context_, jobId, options); +} + +IFileReaderPtr TClient::GetJobFailContext( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobFailContextOptions& options) +{ + CheckShutdown(); + return NRawClient::GetJobFailContext(Context_, operationId, jobId, options); +} + +IFileReaderPtr TClient::GetJobStderr( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobStderrOptions& options) +{ + CheckShutdown(); + return NRawClient::GetJobStderr(Context_, operationId, jobId, options); +} + +TNode::TListType TClient::SkyShareTable( + const std::vector<TYPath>& tablePaths, + const TSkyShareTableOptions& options) +{ + CheckShutdown(); + return NRawClient::SkyShareTable( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + tablePaths, + options); +} + +TCheckPermissionResponse TClient::CheckPermission( + const TString& user, + EPermission permission, + const TYPath& path, + const TCheckPermissionOptions& options) +{ + CheckShutdown(); + return NRawClient::CheckPermission(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, user, permission, path, options); +} + +TVector<TTabletInfo> TClient::GetTabletInfos( + const TYPath& path, + const TVector<int>& tabletIndexes, + const TGetTabletInfosOptions& options) +{ + CheckShutdown(); + return NRawClient::GetTabletInfos(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, path, tabletIndexes, options); +} + + +void TClient::SuspendOperation( + const TOperationId& operationId, + const TSuspendOperationOptions& options) +{ + CheckShutdown(); + NRawClient::SuspendOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, options); +} + +void TClient::ResumeOperation( + const TOperationId& operationId, + const TResumeOperationOptions& options) +{ + CheckShutdown(); + NRawClient::ResumeOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, operationId, options); +} + +TYtPoller& TClient::GetYtPoller() +{ + auto g = Guard(YtPollerLock_); + if (!YtPoller_) { + CheckShutdown(); + // We don't use current client and create new client because YtPoller_ might use + // this client during current client shutdown. + // That might lead to incrementing of current client refcount and double delete of current client object. + YtPoller_ = MakeHolder<TYtPoller>(Context_, ClientRetryPolicy_); + } + return *YtPoller_; +} + +void TClient::Shutdown() +{ + auto g = Guard(YtPollerLock_); + + if (!Shutdown_.exchange(true) && YtPoller_) { + YtPoller_->Stop(); + } +} + +ITransactionPingerPtr TClient::GetTransactionPinger() +{ + if (!TransactionPinger_) { + TransactionPinger_ = CreateTransactionPinger(Context_.Config); + } + return TransactionPinger_; +} + +TClientPtr TClient::GetParentClientImpl() +{ + return this; +} + +template <class TOptions> +void TClient::SetTabletParams( + THttpHeader& header, + const TYPath& path, + const TOptions& options) +{ + header.AddPath(AddPathPrefix(path, Context_.Config->Prefix)); + if (options.FirstTabletIndex_) { + header.AddParameter("first_tablet_index", *options.FirstTabletIndex_); + } + if (options.LastTabletIndex_) { + header.AddParameter("last_tablet_index", *options.LastTabletIndex_); + } +} + +void TClient::CheckShutdown() const +{ + if (Shutdown_) { + ythrow TApiUsageError() << "Call client's methods after shutdown"; + } +} + +TClientPtr CreateClientImpl( + const TString& serverName, + const TCreateClientOptions& options) +{ + TClientContext context; + context.Config = options.Config_ ? options.Config_ : TConfig::Get(); + context.TvmOnly = options.TvmOnly_; + context.UseTLS = options.UseTLS_; + + context.ServerName = serverName; + if (serverName.find('.') == TString::npos && + serverName.find(':') == TString::npos) + { + context.ServerName += ".yt.yandex.net"; + } + + if (serverName.find(':') == TString::npos) { + context.ServerName = CreateHostNameWithPort(context.ServerName, context); + } + if (options.TvmOnly_) { + context.ServerName = Format("tvm.%v", context.ServerName); + } + + if (options.UseTLS_ || options.UseCoreHttpClient_) { + context.HttpClient = NHttpClient::CreateCoreHttpClient(options.UseTLS_, context.Config); + } else { + context.HttpClient = NHttpClient::CreateDefaultHttpClient(); + } + + context.Token = context.Config->Token; + if (options.Token_) { + context.Token = options.Token_; + } else if (options.TokenPath_) { + context.Token = TConfig::LoadTokenFromFile(options.TokenPath_); + } else if (options.ServiceTicketAuth_) { + context.ServiceTicketAuth = options.ServiceTicketAuth_; + } + + context.ImpersonationUser = options.ImpersonationUser_; + + if (context.Token) { + TConfig::ValidateToken(context.Token); + } + + auto globalTxId = GetGuid(context.Config->GlobalTxId); + + auto retryConfigProvider = options.RetryConfigProvider_; + if (!retryConfigProvider) { + retryConfigProvider = CreateDefaultRetryConfigProvider(); + } + return new NDetail::TClient(context, globalTxId, CreateDefaultClientRetryPolicy(retryConfigProvider, context.Config)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +IClientPtr CreateClient( + const TString& serverName, + const TCreateClientOptions& options) +{ + return NDetail::CreateClientImpl(serverName, options); +} + +IClientPtr CreateClientFromEnv(const TCreateClientOptions& options) +{ + auto serverName = GetEnv("YT_PROXY"); + if (!serverName) { + ythrow yexception() << "YT_PROXY is not set"; + } + + return NDetail::CreateClientImpl(serverName, options); +} + + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client.h b/yt/cpp/mapreduce/client/client.h new file mode 100644 index 00000000000..0f4df09d0ba --- /dev/null +++ b/yt/cpp/mapreduce/client/client.h @@ -0,0 +1,506 @@ +#pragma once + +#include "client_reader.h" +#include "client_writer.h" +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/interface/client.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class TYtPoller; + +class TClientBase; +using TClientBasePtr = ::TIntrusivePtr<TClientBase>; + +class TClient; +using TClientPtr = ::TIntrusivePtr<TClient>; + +//////////////////////////////////////////////////////////////////////////////// + +class TClientBase + : virtual public IClientBase +{ +public: + TClientBase( + const TClientContext& context, + const TTransactionId& transactionId, + IClientRetryPolicyPtr retryPolicy); + + ITransactionPtr StartTransaction( + const TStartTransactionOptions& options) override; + + // cypress + + TNodeId Create( + const TYPath& path, + ENodeType type, + const TCreateOptions& options) override; + + void Remove( + const TYPath& path, + const TRemoveOptions& options) override; + + bool Exists( + const TYPath& path, + const TExistsOptions& options) override; + + TNode Get( + const TYPath& path, + const TGetOptions& options) override; + + void Set( + const TYPath& path, + const TNode& value, + const TSetOptions& options) override; + + void MultisetAttributes( + const TYPath& path, + const TNode::TMapType& value, + const TMultisetAttributesOptions& options) override; + + TNode::TListType List( + const TYPath& path, + const TListOptions& options) override; + + TNodeId Copy( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TCopyOptions& options) override; + + TNodeId Move( + const TYPath& sourcePath, + const TYPath& destinationPath, + const TMoveOptions& options) override; + + TNodeId Link( + const TYPath& targetPath, + const TYPath& linkPath, + const TLinkOptions& options) override; + + void Concatenate( + const TVector<TRichYPath>& sourcePaths, + const TRichYPath& destinationPath, + const TConcatenateOptions& options) override; + + TRichYPath CanonizeYPath(const TRichYPath& path) override; + + TVector<TTableColumnarStatistics> GetTableColumnarStatistics( + const TVector<TRichYPath>& paths, + const TGetTableColumnarStatisticsOptions& options) override; + + TMultiTablePartitions GetTablePartitions( + const TVector<TRichYPath>& paths, + const TGetTablePartitionsOptions& options) override; + + TMaybe<TYPath> GetFileFromCache( + const TString& md5Signature, + const TYPath& cachePath, + const TGetFileFromCacheOptions& options = TGetFileFromCacheOptions()) override; + + TYPath PutFileToCache( + const TYPath& filePath, + const TString& md5Signature, + const TYPath& cachePath, + const TPutFileToCacheOptions& options = TPutFileToCacheOptions()) override; + + IFileReaderPtr CreateFileReader( + const TRichYPath& path, + const TFileReaderOptions& options) override; + + IFileWriterPtr CreateFileWriter( + const TRichYPath& path, + const TFileWriterOptions& options) override; + + TTableWriterPtr<::google::protobuf::Message> CreateTableWriter( + const TRichYPath& path, + const ::google::protobuf::Descriptor& descriptor, + const TTableWriterOptions& options) override; + + TRawTableReaderPtr CreateRawReader( + const TRichYPath& path, + const TFormat& format, + const TTableReaderOptions& options) override; + + TRawTableWriterPtr CreateRawWriter( + const TRichYPath& path, + const TFormat& format, + const TTableWriterOptions& options) override; + + IFileReaderPtr CreateBlobTableReader( + const TYPath& path, + const TKey& key, + const TBlobTableReaderOptions& options) override; + + // operations + + IOperationPtr DoMap( + const TMapOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> mapper, + const TOperationOptions& options) override; + + IOperationPtr RawMap( + const TRawMapOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + const TOperationOptions& options) override; + + IOperationPtr DoReduce( + const TReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) override; + + IOperationPtr RawReduce( + const TRawReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + const TOperationOptions& options) override; + + IOperationPtr DoJoinReduce( + const TJoinReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) override; + + IOperationPtr RawJoinReduce( + const TRawJoinReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + const TOperationOptions& options) override; + + IOperationPtr DoMapReduce( + const TMapReduceOperationSpec& spec, + ::TIntrusivePtr<IStructuredJob> mapper, + ::TIntrusivePtr<IStructuredJob> reduceCombiner, + ::TIntrusivePtr<IStructuredJob> reducer, + const TOperationOptions& options) override; + + IOperationPtr RawMapReduce( + const TRawMapReduceOperationSpec& spec, + ::TIntrusivePtr<IRawJob> mapper, + ::TIntrusivePtr<IRawJob> reduceCombiner, + ::TIntrusivePtr<IRawJob> reducer, + const TOperationOptions& options) override; + + IOperationPtr Sort( + const TSortOperationSpec& spec, + const TOperationOptions& options) override; + + IOperationPtr Merge( + const TMergeOperationSpec& spec, + const TOperationOptions& options) override; + + IOperationPtr Erase( + const TEraseOperationSpec& spec, + const TOperationOptions& options) override; + + IOperationPtr RemoteCopy( + const TRemoteCopyOperationSpec& spec, + const TOperationOptions& options = TOperationOptions()) override; + + IOperationPtr RunVanilla( + const TVanillaOperationSpec& spec, + const TOperationOptions& options = TOperationOptions()) override; + + IOperationPtr AttachOperation(const TOperationId& operationId) override; + + EOperationBriefState CheckOperation(const TOperationId& operationId) override; + + void AbortOperation(const TOperationId& operationId) override; + + void CompleteOperation(const TOperationId& operationId) override; + + void WaitForOperation(const TOperationId& operationId) override; + + void AlterTable( + const TYPath& path, + const TAlterTableOptions& options) override; + + TBatchRequestPtr CreateBatchRequest() override; + + IClientPtr GetParentClient() override; + + const TClientContext& GetContext() const; + + const IClientRetryPolicyPtr& GetRetryPolicy() const; + + virtual ITransactionPingerPtr GetTransactionPinger() = 0; + +protected: + virtual TClientPtr GetParentClientImpl() = 0; + +protected: + const TClientContext Context_; + TTransactionId TransactionId_; + IClientRetryPolicyPtr ClientRetryPolicy_; + +private: + ::TIntrusivePtr<TClientReader> CreateClientReader( + const TRichYPath& path, + const TFormat& format, + const TTableReaderOptions& options, + bool useFormatFromTableAttributes = false); + + THolder<TClientWriter> CreateClientWriter( + const TRichYPath& path, + const TFormat& format, + const TTableWriterOptions& options); + + ::TIntrusivePtr<INodeReaderImpl> CreateNodeReader( + const TRichYPath& path, const TTableReaderOptions& options) override; + + ::TIntrusivePtr<IYaMRReaderImpl> CreateYaMRReader( + const TRichYPath& path, const TTableReaderOptions& options) override; + + ::TIntrusivePtr<IProtoReaderImpl> CreateProtoReader( + const TRichYPath& path, + const TTableReaderOptions& options, + const Message* prototype) override; + + ::TIntrusivePtr<ISkiffRowReaderImpl> CreateSkiffRowReader( + const TRichYPath& path, + const TTableReaderOptions& options, + const ISkiffRowSkipperPtr& skipper, + const NSkiff::TSkiffSchemaPtr& schema) override; + + ::TIntrusivePtr<INodeWriterImpl> CreateNodeWriter( + const TRichYPath& path, const TTableWriterOptions& options) override; + + ::TIntrusivePtr<IYaMRWriterImpl> CreateYaMRWriter( + const TRichYPath& path, const TTableWriterOptions& options) override; + + ::TIntrusivePtr<IProtoWriterImpl> CreateProtoWriter( + const TRichYPath& path, + const TTableWriterOptions& options, + const Message* prototype) override; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TTransaction + : public ITransaction + , public TClientBase +{ +public: + // + // Start a new transaction. + TTransaction( + TClientPtr parentClient, + const TClientContext& context, + const TTransactionId& parentTransactionId, + const TStartTransactionOptions& options); + + // + // Attach an existing transaction. + TTransaction( + TClientPtr parentClient, + const TClientContext& context, + const TTransactionId& transactionId, + const TAttachTransactionOptions& options); + + const TTransactionId& GetId() const override; + + ILockPtr Lock( + const TYPath& path, + ELockMode mode, + const TLockOptions& options) override; + + void Unlock( + const TYPath& path, + const TUnlockOptions& options) override; + + void Commit() override; + + void Abort() override; + + void Ping() override; + + void Detach() override; + + ITransactionPingerPtr GetTransactionPinger() override; + +protected: + TClientPtr GetParentClientImpl() override; + +private: + ITransactionPingerPtr TransactionPinger_; + THolder<TPingableTransaction> PingableTx_; + TClientPtr ParentClient_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TClient + : public IClient + , public TClientBase +{ +public: + TClient( + const TClientContext& context, + const TTransactionId& globalId, + IClientRetryPolicyPtr retryPolicy); + + ~TClient(); + + ITransactionPtr AttachTransaction( + const TTransactionId& transactionId, + const TAttachTransactionOptions& options) override; + + void MountTable( + const TYPath& path, + const TMountTableOptions& options) override; + + void UnmountTable( + const TYPath& path, + const TUnmountTableOptions& options) override; + + void RemountTable( + const TYPath& path, + const TRemountTableOptions& options) override; + + void FreezeTable( + const TYPath& path, + const TFreezeTableOptions& options) override; + + void UnfreezeTable( + const TYPath& path, + const TUnfreezeTableOptions& options) override; + + void ReshardTable( + const TYPath& path, + const TVector<TKey>& keys, + const TReshardTableOptions& options) override; + + void ReshardTable( + const TYPath& path, + i64 tabletCount, + const TReshardTableOptions& options) override; + + void InsertRows( + const TYPath& path, + const TNode::TListType& rows, + const TInsertRowsOptions& options) override; + + void DeleteRows( + const TYPath& path, + const TNode::TListType& keys, + const TDeleteRowsOptions& options) override; + + void TrimRows( + const TYPath& path, + i64 tabletIndex, + i64 rowCount, + const TTrimRowsOptions& options) override; + + TNode::TListType LookupRows( + const TYPath& path, + const TNode::TListType& keys, + const TLookupRowsOptions& options) override; + + TNode::TListType SelectRows( + const TString& query, + const TSelectRowsOptions& options) override; + + void AlterTableReplica( + const TReplicaId& replicaId, + const TAlterTableReplicaOptions& alterTableReplicaOptions) override; + + ui64 GenerateTimestamp() override; + + TAuthorizationInfo WhoAmI() override; + + TOperationAttributes GetOperation( + const TOperationId& operationId, + const TGetOperationOptions& options) override; + + TListOperationsResult ListOperations( + const TListOperationsOptions& options) override; + + void UpdateOperationParameters( + const TOperationId& operationId, + const TUpdateOperationParametersOptions& options) override; + + TJobAttributes GetJob( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobOptions& options) override; + + TListJobsResult ListJobs( + const TOperationId& operationId, + const TListJobsOptions& options = TListJobsOptions()) override; + + IFileReaderPtr GetJobInput( + const TJobId& jobId, + const TGetJobInputOptions& options = TGetJobInputOptions()) override; + + IFileReaderPtr GetJobFailContext( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobFailContextOptions& options = TGetJobFailContextOptions()) override; + + IFileReaderPtr GetJobStderr( + const TOperationId& operationId, + const TJobId& jobId, + const TGetJobStderrOptions& options = TGetJobStderrOptions()) override; + + TNode::TListType SkyShareTable( + const std::vector<TYPath>& tablePaths, + const TSkyShareTableOptions& options = TSkyShareTableOptions()) override; + + TCheckPermissionResponse CheckPermission( + const TString& user, + EPermission permission, + const TYPath& path, + const TCheckPermissionOptions& options) override; + + TVector<TTabletInfo> GetTabletInfos( + const TYPath& path, + const TVector<int>& tabletIndexes, + const TGetTabletInfosOptions& options) override; + + void SuspendOperation( + const TOperationId& operationId, + const TSuspendOperationOptions& options) override; + + void ResumeOperation( + const TOperationId& operationId, + const TResumeOperationOptions& options) override; + + void Shutdown() override; + + ITransactionPingerPtr GetTransactionPinger() override; + + // Helper methods + TYtPoller& GetYtPoller(); + +protected: + TClientPtr GetParentClientImpl() override; + +private: + template <class TOptions> + void SetTabletParams( + THttpHeader& header, + const TYPath& path, + const TOptions& options); + + void CheckShutdown() const; + + ITransactionPingerPtr TransactionPinger_; + + std::atomic<bool> Shutdown_ = false; + TMutex YtPollerLock_; + THolder<TYtPoller> YtPoller_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +TClientPtr CreateClientImpl( + const TString& serverName, + const TCreateClientOptions& options = TCreateClientOptions()); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client_reader.cpp b/yt/cpp/mapreduce/client/client_reader.cpp new file mode 100644 index 00000000000..80759b12dcc --- /dev/null +++ b/yt/cpp/mapreduce/client/client_reader.cpp @@ -0,0 +1,232 @@ +#include "client_reader.h" + +#include "structured_table_formats.h" +#include "transaction.h" +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/tvm.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/io/helpers.h> +#include <yt/cpp/mapreduce/io/yamr_table_reader.h> + +#include <yt/cpp/mapreduce/http/helpers.h> +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <library/cpp/yson/node/serialize.h> + +#include <util/random/random.h> +#include <util/stream/file.h> +#include <util/stream/str.h> +#include <util/string/builder.h> +#include <util/string/cast.h> + +namespace NYT { + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +TClientReader::TClientReader( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFormat& format, + const TTableReaderOptions& options, + bool useFormatFromTableAttributes) + : Path_(path) + , ClientRetryPolicy_(std::move(clientRetryPolicy)) + , Context_(context) + , ParentTransactionId_(transactionId) + , Format_(format) + , Options_(options) + , ReadTransaction_(nullptr) +{ + if (options.CreateTransaction_) { + Y_VERIFY(transactionPinger, "Internal error: transactionPinger is null"); + ReadTransaction_ = MakeHolder<TPingableTransaction>( + ClientRetryPolicy_, + Context_, + transactionId, + transactionPinger->GetChildTxPinger(), + TStartTransactionOptions()); + Path_.Path(Snapshot( + ClientRetryPolicy_, + Context_, + ReadTransaction_->GetId(), + path.Path_)); + } + + if (useFormatFromTableAttributes) { + auto transactionId2 = ReadTransaction_ ? ReadTransaction_->GetId() : ParentTransactionId_; + auto newFormat = GetTableFormat(ClientRetryPolicy_, Context_, transactionId2, Path_); + if (newFormat) { + Format_->Config = *newFormat; + } + } + + TransformYPath(); + CreateRequest(); +} + +bool TClientReader::Retry( + const TMaybe<ui32>& rangeIndex, + const TMaybe<ui64>& rowIndex) +{ + if (CurrentRequestRetryPolicy_) { + // TODO we should pass actual exception in Retry function + yexception genericError; + auto backoff = CurrentRequestRetryPolicy_->OnGenericError(genericError); + if (!backoff) { + return false; + } + } + + try { + CreateRequest(rangeIndex, rowIndex); + return true; + } catch (const std::exception& ex) { + YT_LOG_ERROR("Client reader retry failed: %v", + ex.what()); + + return false; + } +} + +void TClientReader::ResetRetries() +{ + CurrentRequestRetryPolicy_ = nullptr; +} + +size_t TClientReader::DoRead(void* buf, size_t len) +{ + return Input_->Read(buf, len); +} + +void TClientReader::TransformYPath() +{ + for (auto& range : Path_.MutableRangesView()) { + auto& exact = range.Exact_; + if (IsTrivial(exact)) { + continue; + } + + if (exact.RowIndex_) { + range.LowerLimit(TReadLimit().RowIndex(*exact.RowIndex_)); + range.UpperLimit(TReadLimit().RowIndex(*exact.RowIndex_ + 1)); + exact.RowIndex_.Clear(); + + } else if (exact.Key_) { + range.LowerLimit(TReadLimit().Key(*exact.Key_)); + + auto lastPart = TNode::CreateEntity(); + lastPart.Attributes() = TNode()("type", "max"); + exact.Key_->Parts_.push_back(lastPart); + + range.UpperLimit(TReadLimit().Key(*exact.Key_)); + exact.Key_.Clear(); + } + } +} + +void TClientReader::CreateRequest(const TMaybe<ui32>& rangeIndex, const TMaybe<ui64>& rowIndex) +{ + if (!CurrentRequestRetryPolicy_) { + CurrentRequestRetryPolicy_ = ClientRetryPolicy_->CreatePolicyForGenericRequest(); + } + while (true) { + CurrentRequestRetryPolicy_->NotifyNewAttempt(); + + THttpHeader header("GET", GetReadTableCommand(Context_.Config->ApiVersion)); + if (Context_.ServiceTicketAuth) { + header.SetServiceTicket(Context_.ServiceTicketAuth->Ptr->IssueServiceTicket()); + } else { + header.SetToken(Context_.Token); + } + auto transactionId = (ReadTransaction_ ? ReadTransaction_->GetId() : ParentTransactionId_); + header.AddTransactionId(transactionId); + + const auto& controlAttributes = Options_.ControlAttributes_; + header.AddParameter("control_attributes", TNode() + ("enable_row_index", controlAttributes.EnableRowIndex_) + ("enable_range_index", controlAttributes.EnableRangeIndex_)); + header.SetOutputFormat(Format_); + + header.SetResponseCompression(ToString(Context_.Config->AcceptEncoding)); + + if (rowIndex.Defined()) { + auto& ranges = Path_.MutableRanges(); + if (ranges.Empty()) { + ranges.ConstructInPlace(TVector{TReadRange()}); + } else { + if (rangeIndex.GetOrElse(0) >= ranges->size()) { + ythrow yexception() + << "range index " << rangeIndex.GetOrElse(0) + << " is out of range, input range count is " << ranges->size(); + } + ranges->erase(ranges->begin(), ranges->begin() + rangeIndex.GetOrElse(0)); + } + ranges->begin()->LowerLimit(TReadLimit().RowIndex(*rowIndex)); + } + + header.MergeParameters(FormIORequestParameters(Path_, Options_)); + + auto requestId = CreateGuidAsString(); + + try { + const auto proxyName = GetProxyForHeavyRequest(Context_); + Response_ = Context_.HttpClient->Request(GetFullUrl(proxyName, Context_, header), requestId, header); + + Input_ = Response_->GetResponseStream(); + + YT_LOG_DEBUG("RSP %v - table stream", requestId); + + return; + } catch (const TErrorResponse& e) { + LogRequestError( + requestId, + header, + e.what(), + CurrentRequestRetryPolicy_->GetAttemptDescription()); + + if (!IsRetriable(e)) { + throw; + } + auto backoff = CurrentRequestRetryPolicy_->OnRetriableError(e); + if (!backoff) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(*backoff); + } catch (const std::exception& e) { + LogRequestError( + requestId, + header, + e.what(), + CurrentRequestRetryPolicy_->GetAttemptDescription()); + + Response_.reset(); + Input_ = nullptr; + + auto backoff = CurrentRequestRetryPolicy_->OnGenericError(e); + if (!backoff) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(*backoff); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client_reader.h b/yt/cpp/mapreduce/client/client_reader.h new file mode 100644 index 00000000000..22f5a0ebb0b --- /dev/null +++ b/yt/cpp/mapreduce/client/client_reader.h @@ -0,0 +1,65 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/interface/io.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/http.h> +#include <yt/cpp/mapreduce/http/http_client.h> + +namespace NYT { + +class TPingableTransaction; + +//////////////////////////////////////////////////////////////////////////////// + +class TClientReader + : public TRawTableReader +{ +public: + TClientReader( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFormat& format, + const TTableReaderOptions& options, + bool useFormatFromTableAttributes); + + bool Retry( + const TMaybe<ui32>& rangeIndex, + const TMaybe<ui64>& rowIndex) override; + + void ResetRetries() override; + + bool HasRangeIndices() const override { return true; } + +protected: + size_t DoRead(void* buf, size_t len) override; + +private: + TRichYPath Path_; + const IClientRetryPolicyPtr ClientRetryPolicy_; + const TClientContext Context_; + TTransactionId ParentTransactionId_; + TMaybe<TFormat> Format_; + TTableReaderOptions Options_; + + THolder<TPingableTransaction> ReadTransaction_; + + NHttpClient::IHttpResponsePtr Response_; + IInputStream* Input_; + + IRequestRetryPolicyPtr CurrentRequestRetryPolicy_; + +private: + void TransformYPath(); + void CreateRequest(const TMaybe<ui32>& rangeIndex = Nothing(), const TMaybe<ui64>& rowIndex = Nothing()); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client_writer.cpp b/yt/cpp/mapreduce/client/client_writer.cpp new file mode 100644 index 00000000000..357abd32eb1 --- /dev/null +++ b/yt/cpp/mapreduce/client/client_writer.cpp @@ -0,0 +1,69 @@ +#include "client_writer.h" + +#include "retryful_writer.h" +#include "retryless_writer.h" + +#include <yt/cpp/mapreduce/interface/io.h> +#include <yt/cpp/mapreduce/common/fwd.h> +#include <yt/cpp/mapreduce/common/helpers.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TClientWriter::TClientWriter( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TMaybe<TFormat>& format, + const TTableWriterOptions& options) + : BUFFER_SIZE(options.BufferSize_) +{ + if (options.SingleHttpRequest_) { + RawWriter_.Reset(new TRetrylessWriter( + context, + transactionId, + GetWriteTableCommand(context.Config->ApiVersion), + format, + path, + BUFFER_SIZE, + options)); + } else { + RawWriter_.Reset(new TRetryfulWriter( + std::move(clientRetryPolicy), + std::move(transactionPinger), + context, + transactionId, + GetWriteTableCommand(context.Config->ApiVersion), + format, + path, + options)); + } +} + +size_t TClientWriter::GetStreamCount() const +{ + return 1; +} + +IOutputStream* TClientWriter::GetStream(size_t tableIndex) const +{ + Y_UNUSED(tableIndex); + return RawWriter_.Get(); +} + +void TClientWriter::OnRowFinished(size_t) +{ + RawWriter_->NotifyRowEnd(); +} + +void TClientWriter::Abort() +{ + RawWriter_->Abort(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/client_writer.h b/yt/cpp/mapreduce/client/client_writer.h new file mode 100644 index 00000000000..010a88a8ffb --- /dev/null +++ b/yt/cpp/mapreduce/client/client_writer.h @@ -0,0 +1,42 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/interface/io.h> + +namespace NYT { + +struct TTableWriterOptions; +class TRetryfulWriter; + +//////////////////////////////////////////////////////////////////////////////// + +class TClientWriter + : public IProxyOutput +{ +public: + TClientWriter( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TMaybe<TFormat>& format, + const TTableWriterOptions& options); + + size_t GetStreamCount() const override; + IOutputStream* GetStream(size_t tableIndex) const override; + void OnRowFinished(size_t tableIndex) override; + void Abort() override; + +private: + ::TIntrusivePtr<TRawTableWriter> RawWriter_; + + const size_t BUFFER_SIZE = 64 << 20; +}; + +//////////////////////////////////////////////////////////////////////////////// + + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/dummy_job_profiler.cpp b/yt/cpp/mapreduce/client/dummy_job_profiler.cpp new file mode 100644 index 00000000000..5a2f1e8d460 --- /dev/null +++ b/yt/cpp/mapreduce/client/dummy_job_profiler.cpp @@ -0,0 +1,26 @@ +#include "job_profiler.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TDummyJobProfiler + : public IJobProfiler +{ + void Start() override + { } + + void Stop() override + { } +}; + +//////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr<IJobProfiler> CreateJobProfiler() +{ + return std::make_unique<TDummyJobProfiler>(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/file_reader.cpp b/yt/cpp/mapreduce/client/file_reader.cpp new file mode 100644 index 00000000000..fc21e0bc02d --- /dev/null +++ b/yt/cpp/mapreduce/client/file_reader.cpp @@ -0,0 +1,243 @@ +#include "file_reader.h" + +#include "transaction.h" +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/tvm.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/io/helpers.h> + +#include <yt/cpp/mapreduce/http/helpers.h> +#include <yt/cpp/mapreduce/http/http.h> +#include <yt/cpp/mapreduce/http/http_client.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +namespace NYT { +namespace NDetail { + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +static TMaybe<ui64> GetEndOffset(const TFileReaderOptions& options) { + if (options.Length_) { + return options.Offset_.GetOrElse(0) + *options.Length_; + } else { + return Nothing(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TStreamReaderBase::TStreamReaderBase( + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId) + : Context_(context) + , ClientRetryPolicy_(std::move(clientRetryPolicy)) + , ReadTransaction_(MakeHolder<TPingableTransaction>( + ClientRetryPolicy_, + context, + transactionId, + transactionPinger->GetChildTxPinger(), + TStartTransactionOptions())) +{ } + +TStreamReaderBase::~TStreamReaderBase() = default; + +TYPath TStreamReaderBase::Snapshot(const TYPath& path) +{ + return NYT::Snapshot(ClientRetryPolicy_, Context_, ReadTransaction_->GetId(), path); +} + +TString TStreamReaderBase::GetActiveRequestId() const +{ + if (Response_) { + return Response_->GetRequestId();; + } else { + return "<no-active-request>"; + } +} + +size_t TStreamReaderBase::DoRead(void* buf, size_t len) +{ + const int retryCount = Context_.Config->ReadRetryCount; + for (int attempt = 1; attempt <= retryCount; ++attempt) { + try { + if (!Input_) { + Response_ = Request(Context_, ReadTransaction_->GetId(), CurrentOffset_); + Input_ = Response_->GetResponseStream(); + } + if (len == 0) { + return 0; + } + const size_t read = Input_->Read(buf, len); + CurrentOffset_ += read; + return read; + } catch (TErrorResponse& e) { + YT_LOG_ERROR("RSP %v - failed: %v (attempt %v of %v)", + GetActiveRequestId(), + e.what(), + attempt, + retryCount); + + if (!IsRetriable(e) || attempt == retryCount) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(GetBackoffDuration(e, Context_.Config)); + } catch (std::exception& e) { + YT_LOG_ERROR("RSP %v - failed: %v (attempt %v of %v)", + GetActiveRequestId(), + e.what(), + attempt, + retryCount); + + // Invalidate connection. + Response_.reset(); + + if (attempt == retryCount) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(GetBackoffDuration(e, Context_.Config)); + } + Input_ = nullptr; + } + Y_UNREACHABLE(); // we should either return or throw from loop above +} + +//////////////////////////////////////////////////////////////////////////////// + +TFileReader::TFileReader( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFileReaderOptions& options) + : TStreamReaderBase(std::move(clientRetryPolicy), std::move(transactionPinger), context, transactionId) + , FileReaderOptions_(options) + , Path_(path) + , StartOffset_(FileReaderOptions_.Offset_.GetOrElse(0)) + , EndOffset_(GetEndOffset(FileReaderOptions_)) +{ + Path_.Path_ = TStreamReaderBase::Snapshot(Path_.Path_); +} + +NHttpClient::IHttpResponsePtr TFileReader::Request(const TClientContext& context, const TTransactionId& transactionId, ui64 readBytes) +{ + const ui64 currentOffset = StartOffset_ + readBytes; + TString hostName = GetProxyForHeavyRequest(context); + + THttpHeader header("GET", GetReadFileCommand(context.Config->ApiVersion)); + if (context.ServiceTicketAuth) { + header.SetServiceTicket(context.ServiceTicketAuth->Ptr->IssueServiceTicket()); + } else { + header.SetToken(context.Token); + } + header.AddTransactionId(transactionId); + header.SetOutputFormat(TMaybe<TFormat>()); // Binary format + + if (EndOffset_) { + Y_VERIFY(*EndOffset_ >= currentOffset); + FileReaderOptions_.Length(*EndOffset_ - currentOffset); + } + FileReaderOptions_.Offset(currentOffset); + header.MergeParameters(FormIORequestParameters(Path_, FileReaderOptions_)); + + header.SetResponseCompression(ToString(context.Config->AcceptEncoding)); + + auto requestId = CreateGuidAsString(); + NHttpClient::IHttpResponsePtr response; + try { + response = context.HttpClient->Request(GetFullUrl(hostName, context, header), requestId, header); + } catch (const std::exception& ex) { + LogRequestError(requestId, header, ex.what(), ""); + throw; + } + + YT_LOG_DEBUG("RSP %v - file stream", + requestId); + + return response; +} + +//////////////////////////////////////////////////////////////////////////////// + +TBlobTableReader::TBlobTableReader( + const TYPath& path, + const TKey& key, + IClientRetryPolicyPtr retryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TBlobTableReaderOptions& options) + : TStreamReaderBase(std::move(retryPolicy), std::move(transactionPinger), context, transactionId) + , Key_(key) + , Options_(options) +{ + Path_ = TStreamReaderBase::Snapshot(path); +} + +NHttpClient::IHttpResponsePtr TBlobTableReader::Request(const TClientContext& context, const TTransactionId& transactionId, ui64 readBytes) +{ + TString hostName = GetProxyForHeavyRequest(context); + + THttpHeader header("GET", "read_blob_table"); + if (context.ServiceTicketAuth) { + header.SetServiceTicket(context.ServiceTicketAuth->Ptr->IssueServiceTicket()); + } else { + header.SetToken(context.Token); + } + header.AddTransactionId(transactionId); + header.SetOutputFormat(TMaybe<TFormat>()); // Binary format + + const ui64 currentOffset = Options_.Offset_ + readBytes; + const i64 startPartIndex = currentOffset / Options_.PartSize_; + const ui64 skipBytes = currentOffset - Options_.PartSize_ * startPartIndex; + auto lowerLimitKey = Key_; + lowerLimitKey.Parts_.push_back(startPartIndex); + auto upperLimitKey = Key_; + upperLimitKey.Parts_.push_back(std::numeric_limits<i64>::max()); + TNode params = PathToParamNode(TRichYPath(Path_).AddRange(TReadRange() + .LowerLimit(TReadLimit().Key(lowerLimitKey)) + .UpperLimit(TReadLimit().Key(upperLimitKey)))); + params["start_part_index"] = TNode(startPartIndex); + params["offset"] = skipBytes; + if (Options_.PartIndexColumnName_) { + params["part_index_column_name"] = *Options_.PartIndexColumnName_; + } + if (Options_.DataColumnName_) { + params["data_column_name"] = *Options_.DataColumnName_; + } + params["part_size"] = Options_.PartSize_; + header.MergeParameters(params); + header.SetResponseCompression(ToString(context.Config->AcceptEncoding)); + + auto requestId = CreateGuidAsString(); + NHttpClient::IHttpResponsePtr response; + try { + response = context.HttpClient->Request(GetFullUrl(hostName, context, header), requestId, header); + } catch (const std::exception& ex) { + LogRequestError(requestId, header, ex.what(), ""); + throw; + } + + YT_LOG_DEBUG("RSP %v - blob table stream", + requestId); + return response; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/file_reader.h b/yt/cpp/mapreduce/client/file_reader.h new file mode 100644 index 00000000000..d850008a312 --- /dev/null +++ b/yt/cpp/mapreduce/client/file_reader.h @@ -0,0 +1,105 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/interface/io.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> + +class IInputStream; + +namespace NYT { + +class THttpRequest; +class TPingableTransaction; + +namespace NDetail { +//////////////////////////////////////////////////////////////////////////////// + +class TStreamReaderBase + : public IFileReader +{ +public: + TStreamReaderBase( + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId); + + ~TStreamReaderBase(); + +protected: + TYPath Snapshot(const TYPath& path); + +protected: + const TClientContext Context_; + +private: + size_t DoRead(void* buf, size_t len) override; + virtual NHttpClient::IHttpResponsePtr Request(const TClientContext& context, const TTransactionId& transactionId, ui64 readBytes) = 0; + TString GetActiveRequestId() const; + +private: + const IClientRetryPolicyPtr ClientRetryPolicy_; + TFileReaderOptions FileReaderOptions_; + + NHttpClient::IHttpResponsePtr Response_; + IInputStream* Input_ = nullptr; + + THolder<TPingableTransaction> ReadTransaction_; + + ui64 CurrentOffset_ = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TFileReader + : public TStreamReaderBase +{ +public: + TFileReader( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFileReaderOptions& options = TFileReaderOptions()); + +private: + NHttpClient::IHttpResponsePtr Request(const TClientContext& context, const TTransactionId& transactionId, ui64 readBytes) override; + +private: + TFileReaderOptions FileReaderOptions_; + + TRichYPath Path_; + const ui64 StartOffset_; + const TMaybe<ui64> EndOffset_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TBlobTableReader + : public TStreamReaderBase +{ +public: + TBlobTableReader( + const TYPath& path, + const TKey& key, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TBlobTableReaderOptions& options); + +private: + NHttpClient::IHttpResponsePtr Request(const TClientContext& context, const TTransactionId& transactionId, ui64 readBytes) override; + +private: + const TKey Key_; + const TBlobTableReaderOptions Options_; + TYPath Path_; +}; + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/file_writer.cpp b/yt/cpp/mapreduce/client/file_writer.cpp new file mode 100644 index 00000000000..daf6461edd4 --- /dev/null +++ b/yt/cpp/mapreduce/client/file_writer.cpp @@ -0,0 +1,60 @@ +#include "file_writer.h" + +#include <yt/cpp/mapreduce/io/helpers.h> +#include <yt/cpp/mapreduce/interface/finish_or_die.h> + +#include <yt/cpp/mapreduce/common/helpers.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TFileWriter::TFileWriter( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFileWriterOptions& options) + : RetryfulWriter_( + std::move(clientRetryPolicy), + std::move(transactionPinger), + context, + transactionId, + GetWriteFileCommand(context.Config->ApiVersion), + TMaybe<TFormat>(), + path, + options) +{ } + +TFileWriter::~TFileWriter() +{ + NDetail::FinishOrDie(this, "TFileWriter"); +} + +void TFileWriter::DoWrite(const void* buf, size_t len) +{ + // If user tunes RetryBlockSize / DesiredChunkSize he expects + // us to send data exactly by RetryBlockSize. So behaviour of the writer is predictable. + // + // We want to avoid situation when size of sent data slightly exceeded DesiredChunkSize + // and server produced one chunk of desired size and one small chunk. + while (len > 0) { + const auto retryBlockRemainingSize = RetryfulWriter_.GetRetryBlockRemainingSize(); + Y_VERIFY(retryBlockRemainingSize > 0); + const auto firstWriteLen = Min(len, retryBlockRemainingSize); + RetryfulWriter_.Write(buf, firstWriteLen); + RetryfulWriter_.NotifyRowEnd(); + len -= firstWriteLen; + buf = static_cast<const char*>(buf) + firstWriteLen; + } +} + +void TFileWriter::DoFinish() +{ + RetryfulWriter_.Finish(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/file_writer.h b/yt/cpp/mapreduce/client/file_writer.h new file mode 100644 index 00000000000..f3b97b904ed --- /dev/null +++ b/yt/cpp/mapreduce/client/file_writer.h @@ -0,0 +1,38 @@ +#pragma once + +#include "retryful_writer.h" + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/interface/io.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TFileWriter + : public IFileWriter +{ +public: + TFileWriter( + const TRichYPath& path, + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& transactionId, + const TFileWriterOptions& options = TFileWriterOptions()); + + ~TFileWriter() override; + +protected: + void DoWrite(const void* buf, size_t len) override; + void DoFinish() override; + +private: + TRetryfulWriter RetryfulWriter_; + static const size_t BUFFER_SIZE = 64 << 20; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/format_hints.cpp b/yt/cpp/mapreduce/client/format_hints.cpp new file mode 100644 index 00000000000..1f6eb173adb --- /dev/null +++ b/yt/cpp/mapreduce/client/format_hints.cpp @@ -0,0 +1,84 @@ +#include "format_hints.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/operation.h> + +#include <util/string/builder.h> + +namespace NYT::NDetail { + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +static void ApplyEnableTypeConversion(TFormat* format, const TFormatHints& formatHints) +{ + if (formatHints.EnableAllToStringConversion_) { + format->Config.Attributes()["enable_all_to_string_conversion"] = *formatHints.EnableAllToStringConversion_; + } + if (formatHints.EnableStringToAllConversion_) { + format->Config.Attributes()["enable_string_to_all_conversion"] = *formatHints.EnableStringToAllConversion_; + } + if (formatHints.EnableIntegralTypeConversion_) { + format->Config.Attributes()["enable_integral_type_conversion"] = *formatHints.EnableIntegralTypeConversion_; + } + if (formatHints.EnableIntegralToDoubleConversion_) { + format->Config.Attributes()["enable_integral_to_double_conversion"] = *formatHints.EnableIntegralToDoubleConversion_; + } + if (formatHints.EnableTypeConversion_) { + format->Config.Attributes()["enable_type_conversion"] = *formatHints.EnableTypeConversion_; + } +} + +template <> +void ApplyFormatHints<TNode>(TFormat* format, const TMaybe<TFormatHints>& formatHints) +{ + Y_VERIFY(format); + if (!formatHints) { + return; + } + + ApplyEnableTypeConversion(format, *formatHints); + + if (formatHints->SkipNullValuesForTNode_) { + Y_ENSURE_EX( + format->Config.AsString() == "yson", + TApiUsageError() << "SkipNullForTNode option must be used with yson format, actual format: " << format->Config.AsString()); + format->Config.Attributes()["skip_null_values"] = formatHints->SkipNullValuesForTNode_; + } + + if (formatHints->ComplexTypeMode_) { + Y_ENSURE_EX( + format->Config.AsString() == "yson", + TApiUsageError() << "ComplexTypeMode option must be used with yson format, actual format: " + << format->Config.AsString()); + format->Config.Attributes()["complex_type_mode"] = ToString(*formatHints->ComplexTypeMode_); + } +} + +template <> +void ApplyFormatHints<TYaMRRow>(TFormat* format, const TMaybe<TFormatHints>& formatHints) +{ + Y_VERIFY(format); + if (!formatHints) { + return; + } + + ythrow TApiUsageError() << "Yamr format currently has no supported format hints"; +} + +template <> +void ApplyFormatHints<::google::protobuf::Message>(TFormat* format, const TMaybe<TFormatHints>& formatHints) +{ + Y_VERIFY(format); + if (!formatHints) { + return; + } + + ythrow TApiUsageError() << "Protobuf format currently has no supported format hints"; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/format_hints.h b/yt/cpp/mapreduce/client/format_hints.h new file mode 100644 index 00000000000..f6576b10451 --- /dev/null +++ b/yt/cpp/mapreduce/client/format_hints.h @@ -0,0 +1,27 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/fwd.h> + +#include <util/generic/maybe.h> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +template <typename TRow> +void ApplyFormatHints(TFormat* format, const TMaybe<TFormatHints>& formatHints); + +template <> +void ApplyFormatHints<TNode>(TFormat* format, const TMaybe<TFormatHints>& formatHints); + +template <> +void ApplyFormatHints<TYaMRRow>(TFormat* format, const TMaybe<TFormatHints>& formatHints); + +template <> +void ApplyFormatHints<::google::protobuf::Message>(TFormat* format, const TMaybe<TFormatHints>& formatHints); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/fwd.h b/yt/cpp/mapreduce/client/fwd.h new file mode 100644 index 00000000000..d4449d4ac10 --- /dev/null +++ b/yt/cpp/mapreduce/client/fwd.h @@ -0,0 +1,16 @@ +#pragma once + +#include <util/generic/ptr.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TPingableTransaction; + +class TClient; +using TClientPtr = ::TIntrusivePtr<TClient>; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/init.cpp b/yt/cpp/mapreduce/client/init.cpp new file mode 100644 index 00000000000..c74598ba14b --- /dev/null +++ b/yt/cpp/mapreduce/client/init.cpp @@ -0,0 +1,280 @@ +#include "init.h" + +#include "abortable_registry.h" +#include "job_profiler.h" + +#include <yt/cpp/mapreduce/http/requests.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/init.h> +#include <yt/cpp/mapreduce/interface/operation.h> + +#include <yt/cpp/mapreduce/interface/logging/logger.h> +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/io/job_reader.h> + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <library/cpp/sighandler/async_signals_handler.h> + +#include <util/folder/dirut.h> + +#include <util/generic/singleton.h> + +#include <util/string/builder.h> +#include <util/string/cast.h> +#include <util/string/type.h> + +#include <util/system/env.h> +#include <util/system/thread.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +void WriteVersionToLog() +{ + YT_LOG_INFO("Wrapper version: %v", + TProcessState::Get()->ClientVersion); +} + +static TNode SecureVaultContents; // safe + +void InitializeSecureVault() +{ + SecureVaultContents = NodeFromYsonString( + GetEnv("YT_SECURE_VAULT", "{}")); +} + +} + +//////////////////////////////////////////////////////////////////////////////// + +const TNode& GetJobSecureVault() +{ + return SecureVaultContents; +} + +//////////////////////////////////////////////////////////////////////////////// + +class TAbnormalTerminator +{ +public: + TAbnormalTerminator() = default; + + static void SetErrorTerminationHandler() + { + if (Instance().OldHandler_ != nullptr) { + return; + } + + Instance().OldHandler_ = std::set_terminate(&TerminateHandler); + + SetAsyncSignalFunction(SIGINT, SignalHandler); + SetAsyncSignalFunction(SIGTERM, SignalHandler); + } + +private: + static TAbnormalTerminator& Instance() + { + return *Singleton<TAbnormalTerminator>(); + } + + static void* Invoke(void* opaque) + { + (*reinterpret_cast<std::function<void()>*>(opaque))(); + return nullptr; + } + + static void TerminateWithTimeout( + const TDuration& timeout, + const std::function<void(void)>& exitFunction, + const TString& logMessage) + { + std::function<void()> threadFun = [=] { + YT_LOG_INFO("%v", + logMessage); + NDetail::TAbortableRegistry::Get()->AbortAllAndBlockForever(); + }; + TThread thread(TThread::TParams(Invoke, &threadFun).SetName("aborter")); + thread.Start(); + thread.Detach(); + + Sleep(timeout); + exitFunction(); + } + + static void SignalHandler(int signalNumber) + { + TerminateWithTimeout( + TDuration::Seconds(5), + std::bind(_exit, -signalNumber), + ::TStringBuilder() << "Signal " << signalNumber << " received, aborting transactions. Waiting 5 seconds..."); + } + + static void TerminateHandler() + { + TerminateWithTimeout( + TDuration::Seconds(5), + [&] { + if (Instance().OldHandler_) { + Instance().OldHandler_(); + } else { + abort(); + } + }, + ::TStringBuilder() << "Terminate called, aborting transactions. Waiting 5 seconds..."); + } + +private: + std::terminate_handler OldHandler_ = nullptr; +}; + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +EInitStatus& GetInitStatus() +{ + static EInitStatus initStatus = EInitStatus::NotInitialized; + return initStatus; +} + +static void ElevateInitStatus(const EInitStatus newStatus) { + NDetail::GetInitStatus() = Max(NDetail::GetInitStatus(), newStatus); +} + +void CommonInitialize(int argc, const char** argv) +{ + auto logLevelStr = to_lower(TConfig::Get()->LogLevel); + ILogger::ELevel logLevel; + + if (!TryFromString(logLevelStr, logLevel)) { + Cerr << "Invalid log level: " << TConfig::Get()->LogLevel << Endl; + exit(1); + } + + SetLogger(CreateStdErrLogger(logLevel)); + + TProcessState::Get()->SetCommandLine(argc, argv); +} + +void NonJobInitialize(const TInitializeOptions& options) +{ + if (FromString<bool>(GetEnv("YT_CLEANUP_ON_TERMINATION", "0")) || options.CleanupOnTermination_) { + TAbnormalTerminator::SetErrorTerminationHandler(); + } + if (options.WaitProxy_) { + NDetail::TWaitProxy::Get()->SetProxy(options.WaitProxy_); + } + WriteVersionToLog(); +} + +void ExecJob(int argc, const char** argv, const TInitializeOptions& options) +{ + // Now we are definitely in job. + // We take this setting from environment variable to be consistent with client code. + TConfig::Get()->UseClientProtobuf = IsTrue(GetEnv("YT_USE_CLIENT_PROTOBUF", "")); + + auto execJobImpl = [&options](TString jobName, i64 outputTableCount, bool hasState) { + auto jobProfiler = CreateJobProfiler(); + jobProfiler->Start(); + + InitializeSecureVault(); + + NDetail::OutputTableCount = static_cast<i64>(outputTableCount); + + THolder<IInputStream> jobStateStream; + if (hasState) { + jobStateStream = MakeHolder<TIFStream>("jobstate"); + } else { + jobStateStream = MakeHolder<TBufferStream>(0); + } + + int ret = 1; + try { + ret = TJobFactory::Get()->GetJobFunction(jobName.data())(outputTableCount, *jobStateStream); + } catch (const TSystemError& ex) { + if (ex.Status() == EPIPE) { + // 32 == EPIPE, write number here so it's easier to grep this exit code in source files + exit(32); + } + throw; + } + + jobProfiler->Stop(); + + if (options.JobOnExitFunction_) { + (*options.JobOnExitFunction_)(); + } + exit(ret); + }; + + auto jobArguments = NodeFromYsonString(GetEnv("YT_JOB_ARGUMENTS", "#")); + if (jobArguments.HasValue()) { + execJobImpl( + jobArguments["job_name"].AsString(), + jobArguments["output_table_count"].AsInt64(), + jobArguments["has_state"].AsBool()); + Y_UNREACHABLE(); + } + + TString jobType = argc >= 2 ? argv[1] : TString(); + if (argc != 5 || jobType != "--yt-map" && jobType != "--yt-reduce") { + // We are inside job but probably using old API + // (i.e. both NYT::Initialize and NMR::Initialize are called). + WriteVersionToLog(); + return; + } + + TString jobName(argv[2]); + i64 outputTableCount = FromString<i64>(argv[3]); + int hasState = FromString<int>(argv[4]); + execJobImpl(jobName, outputTableCount, hasState); + Y_UNREACHABLE(); +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +void JoblessInitialize(const TInitializeOptions& options) +{ + static const char* fakeArgv[] = {"unknown..."}; + NDetail::CommonInitialize(1, fakeArgv); + NDetail::NonJobInitialize(options); + NDetail::ElevateInitStatus(NDetail::EInitStatus::JoblessInitialization); +} + +void Initialize(int argc, const char* argv[], const TInitializeOptions& options) +{ + NDetail::CommonInitialize(argc, argv); + + NDetail::ElevateInitStatus(NDetail::EInitStatus::FullInitialization); + + const bool isInsideJob = !GetEnv("YT_JOB_ID").empty(); + if (isInsideJob) { + NDetail::ExecJob(argc, argv, options); + } else { + NDetail::NonJobInitialize(options); + } +} + +void Initialize(int argc, char* argv[], const TInitializeOptions& options) +{ + return Initialize(argc, const_cast<const char**>(argv), options); +} + +void Initialize(const TInitializeOptions& options) +{ + static const char* fakeArgv[] = {"unknown..."}; + Initialize(1, fakeArgv, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/init.h b/yt/cpp/mapreduce/client/init.h new file mode 100644 index 00000000000..af2fc80e552 --- /dev/null +++ b/yt/cpp/mapreduce/client/init.h @@ -0,0 +1,22 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/init.h> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +enum class EInitStatus : int +{ + NotInitialized, + JoblessInitialization, + FullInitialization, +}; + +EInitStatus& GetInitStatus(); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/job_profiler.h b/yt/cpp/mapreduce/client/job_profiler.h new file mode 100644 index 00000000000..65328713803 --- /dev/null +++ b/yt/cpp/mapreduce/client/job_profiler.h @@ -0,0 +1,27 @@ +#pragma once + +#include <memory> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +struct IJobProfiler +{ + virtual ~IJobProfiler() = default; + + //! Starts job profiling if corresponding options are set + //! in environment. + virtual void Start() = 0; + + //! Stops profiling and sends profile to job proxy. + virtual void Stop() = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr<IJobProfiler> CreateJobProfiler(); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/lock.cpp b/yt/cpp/mapreduce/client/lock.cpp new file mode 100644 index 00000000000..88110f9266e --- /dev/null +++ b/yt/cpp/mapreduce/client/lock.cpp @@ -0,0 +1,105 @@ +#include "lock.h" + +#include "yt_poller.h" + +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> + +#include <util/string/builder.h> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +//////////////////////////////////////////////////////////////////////////////// + +class TLockPollerItem + : public IYtPollerItem +{ +public: + TLockPollerItem(const TLockId& lockId, ::NThreading::TPromise<void> acquired) + : LockStateYPath_("#" + GetGuidAsString(lockId) + "/@state") + , Acquired_(acquired) + { } + + void PrepareRequest(TRawBatchRequest* batchRequest) override + { + LockState_ = batchRequest->Get(TTransactionId(), LockStateYPath_, TGetOptions()); + } + + EStatus OnRequestExecuted() override + { + try { + const auto& state = LockState_.GetValue().AsString(); + if (state == "acquired") { + Acquired_.SetValue(); + return PollBreak; + } + } catch (const TErrorResponse& e) { + if (!IsRetriable(e)) { + Acquired_.SetException(std::current_exception()); + return PollBreak; + } + } catch (const std::exception& e) { + if (!IsRetriable(e)) { + Acquired_.SetException(std::current_exception()); + return PollBreak; + } + } + return PollContinue; + } + + void OnItemDiscarded() override + { + Acquired_.SetException(std::make_exception_ptr(yexception() << "Operation cancelled")); + } + +private: + const TString LockStateYPath_; + ::NThreading::TPromise<void> Acquired_; + + ::NThreading::TFuture<TNode> LockState_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +TLock::TLock(const TLockId& lockId, TClientPtr client, bool waitable) + : LockId_(lockId) + , Client_(std::move(client)) +{ + if (!waitable) { + Acquired_ = ::NThreading::MakeFuture(); + } +} + +const TLockId& TLock::GetId() const +{ + return LockId_; +} + +TNodeId TLock::GetLockedNodeId() const +{ + auto nodeIdNode = Client_->Get( + ::TStringBuilder() << '#' << GetGuidAsString(LockId_) << "/@node_id", + TGetOptions()); + return GetGuid(nodeIdNode.AsString()); +} + +const ::NThreading::TFuture<void>& TLock::GetAcquiredFuture() const +{ + if (!Acquired_) { + auto promise = ::NThreading::NewPromise<void>(); + Client_->GetYtPoller().Watch(::MakeIntrusive<TLockPollerItem>(LockId_, promise)); + Acquired_ = promise.GetFuture(); + } + return *Acquired_; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/lock.h b/yt/cpp/mapreduce/client/lock.h new file mode 100644 index 00000000000..7e2c7a127d4 --- /dev/null +++ b/yt/cpp/mapreduce/client/lock.h @@ -0,0 +1,31 @@ +#pragma once + +#include "client.h" + +#include <yt/cpp/mapreduce/interface/client.h> + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class TLock + : public ILock +{ +public: + TLock(const TLockId& lockId, TClientPtr client, bool waitable); + + virtual const TLockId& GetId() const override; + virtual TNodeId GetLockedNodeId() const override; + virtual const ::NThreading::TFuture<void>& GetAcquiredFuture() const override; + +private: + const TLockId LockId_; + mutable TMaybe<::NThreading::TFuture<void>> Acquired_; + TClientPtr Client_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/operation.cpp b/yt/cpp/mapreduce/client/operation.cpp new file mode 100644 index 00000000000..fc1600c2402 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation.cpp @@ -0,0 +1,2981 @@ +#include "operation.h" + +#include "abortable_registry.h" +#include "client.h" +#include "operation_helpers.h" +#include "operation_tracker.h" +#include "transaction.h" +#include "prepare_operation.h" +#include "retry_heavy_write_request.h" +#include "skiff.h" +#include "structured_table_formats.h" +#include "yt_poller.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/errors.h> +#include <yt/cpp/mapreduce/interface/fluent.h> +#include <yt/cpp/mapreduce/interface/format.h> +#include <yt/cpp/mapreduce/interface/job_statistics.h> +#include <yt/cpp/mapreduce/interface/protobuf_format.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/io/job_reader.h> +#include <yt/cpp/mapreduce/io/job_writer.h> +#include <yt/cpp/mapreduce/io/yamr_table_reader.h> +#include <yt/cpp/mapreduce/io/yamr_table_writer.h> +#include <yt/cpp/mapreduce/io/node_table_reader.h> +#include <yt/cpp/mapreduce/io/node_table_writer.h> +#include <yt/cpp/mapreduce/io/proto_table_reader.h> +#include <yt/cpp/mapreduce/io/proto_table_writer.h> +#include <yt/cpp/mapreduce/io/proto_helpers.h> +#include <yt/cpp/mapreduce/io/skiff_table_reader.h> + +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <library/cpp/yson/node/serialize.h> + +#include <util/generic/hash_set.h> + +#include <util/string/builder.h> +#include <util/string/cast.h> + +#include <util/system/thread.h> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +static const ui64 DefaultExrtaTmpfsSize = 1024LL * 1024LL; + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +struct TMapReduceOperationIo +{ + TVector<TRichYPath> Inputs; + TVector<TRichYPath> MapOutputs; + TVector<TRichYPath> Outputs; + + TMaybe<TFormat> MapperInputFormat; + TMaybe<TFormat> MapperOutputFormat; + + TMaybe<TFormat> ReduceCombinerInputFormat; + TMaybe<TFormat> ReduceCombinerOutputFormat; + + TFormat ReducerInputFormat = TFormat::YsonBinary(); + TFormat ReducerOutputFormat = TFormat::YsonBinary(); + + TVector<TSmallJobFile> MapperJobFiles; + TVector<TSmallJobFile> ReduceCombinerJobFiles; + TVector<TSmallJobFile> ReducerJobFiles; +}; + +template <typename T> +void VerifyHasElements(const TVector<T>& paths, TStringBuf name) +{ + if (paths.empty()) { + ythrow TApiUsageError() << "no " << name << " table is specified"; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TVector<TSmallJobFile> CreateFormatConfig( + TMaybe<TSmallJobFile> inputConfig, + const TMaybe<TSmallJobFile>& outputConfig) +{ + TVector<TSmallJobFile> result; + if (inputConfig) { + result.push_back(std::move(*inputConfig)); + } + if (outputConfig) { + result.push_back(std::move(*outputConfig)); + } + return result; +} + +template <typename T> +ENodeReaderFormat NodeReaderFormatFromHintAndGlobalConfig(const TUserJobFormatHintsBase<T>& formatHints) +{ + auto result = TConfig::Get()->NodeReaderFormat; + if (formatHints.InputFormatHints_ && formatHints.InputFormatHints_->SkipNullValuesForTNode_) { + Y_ENSURE_EX( + result != ENodeReaderFormat::Skiff, + TApiUsageError() << "skiff format doesn't support SkipNullValuesForTNode format hint"); + result = ENodeReaderFormat::Yson; + } + return result; +} + +template <class TSpec> +const TVector<TStructuredTablePath>& GetStructuredInputs(const TSpec& spec) +{ + if constexpr (std::is_same_v<TSpec, TVanillaTask>) { + static const TVector<TStructuredTablePath> empty; + return empty; + } else { + return spec.GetStructuredInputs(); + } +} + +template <class TSpec> +const TVector<TStructuredTablePath>& GetStructuredOutputs(const TSpec& spec) +{ + return spec.GetStructuredOutputs(); +} + +template <class TSpec> +const TMaybe<TFormatHints>& GetInputFormatHints(const TSpec& spec) +{ + if constexpr (std::is_same_v<TSpec, TVanillaTask>) { + static const TMaybe<TFormatHints> empty = Nothing(); + return empty; + } else { + return spec.InputFormatHints_; + } +} + +template <class TSpec> +const TMaybe<TFormatHints>& GetOutputFormatHints(const TSpec& spec) +{ + return spec.OutputFormatHints_; +} + +template <class TSpec> +ENodeReaderFormat GetNodeReaderFormat(const TSpec& spec, bool allowSkiff) +{ + if constexpr (std::is_same<TSpec, TVanillaTask>::value) { + return ENodeReaderFormat::Yson; + } else { + return allowSkiff + ? NodeReaderFormatFromHintAndGlobalConfig(spec) + : ENodeReaderFormat::Yson; + } +} + +static void SortColumnsToNames(const TSortColumns& sortColumns, THashSet<TString>* result) +{ + auto names = sortColumns.GetNames(); + result->insert(names.begin(), names.end()); +} + +static THashSet<TString> SortColumnsToNames(const TSortColumns& sortColumns) +{ + THashSet<TString> columnNames; + SortColumnsToNames(sortColumns, &columnNames); + return columnNames; +} + +THashSet<TString> GetColumnsUsedInOperation(const TJoinReduceOperationSpec& spec) +{ + return SortColumnsToNames(spec.JoinBy_); +} + +THashSet<TString> GetColumnsUsedInOperation(const TReduceOperationSpec& spec) { + auto result = SortColumnsToNames(spec.SortBy_); + SortColumnsToNames(spec.ReduceBy_, &result); + if (spec.JoinBy_) { + SortColumnsToNames(*spec.JoinBy_, &result); + } + return result; +} + +THashSet<TString> GetColumnsUsedInOperation(const TMapReduceOperationSpec& spec) +{ + auto result = SortColumnsToNames(spec.SortBy_); + SortColumnsToNames(spec.ReduceBy_, &result); + return result; +} + +THashSet<TString> GetColumnsUsedInOperation(const TMapOperationSpec&) +{ + return THashSet<TString>(); +} + +THashSet<TString> GetColumnsUsedInOperation(const TVanillaTask&) +{ + return THashSet<TString>(); +} + +TStructuredJobTableList ApplyProtobufColumnFilters( + const TStructuredJobTableList& tableList, + const TOperationPreparer& preparer, + const THashSet<TString>& columnsUsedInOperations, + const TOperationOptions& options) +{ + bool hasInputQuery = options.Spec_.Defined() && options.Spec_->IsMap() && options.Spec_->HasKey("input_query"); + if (hasInputQuery) { + return tableList; + } + + auto isDynamic = BatchTransform( + CreateDefaultRequestRetryPolicy(preparer.GetContext().Config), + preparer.GetContext(), + tableList, + [&] (TRawBatchRequest& batch, const auto& table) { + return batch.Get(preparer.GetTransactionId(), table.RichYPath->Path_ + "/@dynamic", TGetOptions()); + }); + + auto newTableList = tableList; + for (size_t tableIndex = 0; tableIndex < tableList.size(); ++tableIndex) { + if (isDynamic[tableIndex].AsBool()) { + continue; + } + auto& table = newTableList[tableIndex]; + Y_VERIFY(table.RichYPath); + if (table.RichYPath->Columns_) { + continue; + } + if (!std::holds_alternative<TProtobufTableStructure>(table.Description)) { + continue; + } + const auto& descriptor = std::get<TProtobufTableStructure>(table.Description).Descriptor; + if (!descriptor) { + continue; + } + auto fromDescriptor = NDetail::InferColumnFilter(*descriptor); + if (!fromDescriptor) { + continue; + } + THashSet<TString> columns(fromDescriptor->begin(), fromDescriptor->end()); + columns.insert(columnsUsedInOperations.begin(), columnsUsedInOperations.end()); + table.RichYPath->Columns(TVector<TString>(columns.begin(), columns.end())); + } + return newTableList; +} + +template <class TSpec> +TSimpleOperationIo CreateSimpleOperationIo( + const IStructuredJob& structuredJob, + const TOperationPreparer& preparer, + const TSpec& spec, + const TOperationOptions& options, + bool allowSkiff) +{ + if (!std::holds_alternative<TVoidStructuredRowStream>(structuredJob.GetInputRowStreamDescription())) { + VerifyHasElements(GetStructuredInputs(spec), "input"); + } + + TUserJobFormatHints hints; + hints.InputFormatHints_ = GetInputFormatHints(spec); + hints.OutputFormatHints_ = GetOutputFormatHints(spec); + ENodeReaderFormat nodeReaderFormat = GetNodeReaderFormat(spec, allowSkiff); + + return CreateSimpleOperationIoHelper( + structuredJob, + preparer, + options, + CanonizeStructuredTableList(preparer.GetContext(), GetStructuredInputs(spec)), + CanonizeStructuredTableList(preparer.GetContext(), GetStructuredOutputs(spec)), + hints, + nodeReaderFormat, + GetColumnsUsedInOperation(spec)); +} + +template <class T> +TSimpleOperationIo CreateSimpleOperationIo( + const IJob& job, + const TOperationPreparer& preparer, + const TSimpleRawOperationIoSpec<T>& spec) +{ + auto getFormatOrDefault = [&] (const TMaybe<TFormat>& maybeFormat, const char* formatName) { + if (maybeFormat) { + return *maybeFormat; + } else if (spec.Format_) { + return *spec.Format_; + } else { + ythrow TApiUsageError() << "Neither " << formatName << "format nor default format is specified for raw operation"; + } + }; + + auto inputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer.GetContext(), spec.GetInputs()); + auto outputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer.GetContext(), spec.GetOutputs()); + + VerifyHasElements(inputs, "input"); + VerifyHasElements(outputs, "output"); + + TUserJobFormatHints hints; + + auto outputSchemas = PrepareOperation( + job, + TOperationPreparationContext( + inputs, + outputs, + preparer.GetContext(), + preparer.GetClientRetryPolicy(), + preparer.GetTransactionId()), + &inputs, + &outputs, + hints); + + Y_VERIFY(outputs.size() == outputSchemas.size()); + for (int i = 0; i < static_cast<int>(outputs.size()); ++i) { + if (!outputs[i].Schema_ && !outputSchemas[i].Columns().empty()) { + outputs[i].Schema_ = outputSchemas[i]; + } + } + + return TSimpleOperationIo { + inputs, + outputs, + + getFormatOrDefault(spec.InputFormat_, "input"), + getFormatOrDefault(spec.OutputFormat_, "output"), + + TVector<TSmallJobFile>{}, + }; +} + +//////////////////////////////////////////////////////////////////// + +TString GetJobStderrWithRetriesAndIgnoreErrors( + const IRequestRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TOperationId& operationId, + const TJobId& jobId, + const size_t stderrTailSize, + const TGetJobStderrOptions& options = TGetJobStderrOptions()) +{ + TString jobStderr; + try { + jobStderr = GetJobStderrWithRetries( + retryPolicy, + context, + operationId, + jobId, + options); + } catch (const TErrorResponse& e) { + YT_LOG_ERROR("Cannot get job stderr (OperationId: %v, JobId: %v, Error: %v)", + operationId, + jobId, + e.what()); + } + if (jobStderr.size() > stderrTailSize) { + jobStderr = jobStderr.substr(jobStderr.size() - stderrTailSize, stderrTailSize); + } + return jobStderr; +} + +TVector<TFailedJobInfo> GetFailedJobInfo( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId, + const TGetFailedJobInfoOptions& options) +{ + const auto listJobsResult = ListJobs( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + operationId, + TListJobsOptions() + .State(EJobState::Failed) + .Limit(options.MaxJobCount_)); + + const auto stderrTailSize = options.StderrTailSize_; + + TVector<TFailedJobInfo> result; + for (const auto& job : listJobsResult.Jobs) { + auto& info = result.emplace_back(); + Y_ENSURE(job.Id); + info.JobId = *job.Id; + info.Error = job.Error.GetOrElse(TYtError(TString("unknown error"))); + if (job.StderrSize.GetOrElse(0) != 0) { + // There are cases when due to bad luck we cannot read stderr even if + // list_jobs reports that stderr_size > 0. + // + // Such errors don't have special error code + // so we ignore all errors and try our luck on other jobs. + info.Stderr = GetJobStderrWithRetriesAndIgnoreErrors( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + operationId, + *job.Id, + stderrTailSize); + } + } + return result; +} + +struct TGetJobsStderrOptions +{ + using TSelf = TGetJobsStderrOptions; + + // How many jobs to download. Which jobs will be chosen is undefined. + FLUENT_FIELD_DEFAULT(ui64, MaxJobCount, 10); + + // How much of stderr should be downloaded. + FLUENT_FIELD_DEFAULT(ui64, StderrTailSize, 64 * 1024); +}; + +static TVector<TString> GetJobsStderr( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId, + const TGetJobsStderrOptions& options = TGetJobsStderrOptions()) +{ + const auto listJobsResult = ListJobs( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + operationId, + TListJobsOptions().Limit(options.MaxJobCount_).WithStderr(true)); + const auto stderrTailSize = options.StderrTailSize_; + TVector<TString> result; + for (const auto& job : listJobsResult.Jobs) { + result.push_back( + // There are cases when due to bad luck we cannot read stderr even if + // list_jobs reports that stderr_size > 0. + // + // Such errors don't have special error code + // so we ignore all errors and try our luck on other jobs. + GetJobStderrWithRetriesAndIgnoreErrors( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + operationId, + *job.Id, + stderrTailSize) + ); + } + return result; +} + +int CountIntermediateTables(const TStructuredJobTableList& tables) +{ + int result = 0; + for (const auto& table : tables) { + if (table.RichYPath) { + break; + } + ++result; + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +TSimpleOperationIo CreateSimpleOperationIoHelper( + const IStructuredJob& structuredJob, + const TOperationPreparer& preparer, + const TOperationOptions& options, + TStructuredJobTableList structuredInputs, + TStructuredJobTableList structuredOutputs, + TUserJobFormatHints hints, + ENodeReaderFormat nodeReaderFormat, + const THashSet<TString>& columnsUsedInOperations) +{ + auto intermediateInputTableCount = CountIntermediateTables(structuredInputs); + auto intermediateOutputTableCount = CountIntermediateTables(structuredOutputs); + + auto jobSchemaInferenceResult = PrepareOperation( + structuredJob, + TOperationPreparationContext( + structuredInputs, + structuredOutputs, + preparer.GetContext(), + preparer.GetClientRetryPolicy(), + preparer.GetTransactionId()), + &structuredInputs, + &structuredOutputs, + hints); + + TVector<TSmallJobFile> formatConfigList; + TFormatBuilder formatBuilder(preparer.GetClientRetryPolicy(), preparer.GetContext(), preparer.GetTransactionId(), options); + + auto [inputFormat, inputFormatConfig] = formatBuilder.CreateFormat( + structuredJob, + EIODirection::Input, + structuredInputs, + hints.InputFormatHints_, + nodeReaderFormat, + /* allowFormatFromTableAttribute = */ true); + + auto [outputFormat, outputFormatConfig] = formatBuilder.CreateFormat( + structuredJob, + EIODirection::Output, + structuredOutputs, + hints.OutputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute = */ false); + + const bool inferOutputSchema = options.InferOutputSchema_.GetOrElse(preparer.GetContext().Config->InferTableSchema); + + auto outputPaths = GetPathList( + TStructuredJobTableList(structuredOutputs.begin() + intermediateOutputTableCount, structuredOutputs.end()), + TVector<TTableSchema>(jobSchemaInferenceResult.begin() + intermediateOutputTableCount, jobSchemaInferenceResult.end()), + inferOutputSchema); + + auto inputPaths = GetPathList( + ApplyProtobufColumnFilters( + TStructuredJobTableList(structuredInputs.begin() + intermediateInputTableCount, structuredInputs.end()), + preparer, + columnsUsedInOperations, + options), + /*schemaInferenceResult*/ Nothing(), + /*inferSchema*/ false); + + return TSimpleOperationIo { + inputPaths, + outputPaths, + + inputFormat, + outputFormat, + + CreateFormatConfig(inputFormatConfig, outputFormatConfig) + }; +} + +EOperationBriefState CheckOperation( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId) +{ + auto attributes = GetOperation( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + operationId, + TGetOperationOptions().AttributeFilter(TOperationAttributeFilter() + .Add(EOperationAttribute::State) + .Add(EOperationAttribute::Result))); + Y_VERIFY(attributes.BriefState, + "get_operation for operation %s has not returned \"state\" field", + GetGuidAsString(operationId).Data()); + if (*attributes.BriefState == EOperationBriefState::Completed) { + return EOperationBriefState::Completed; + } else if (*attributes.BriefState == EOperationBriefState::Aborted || *attributes.BriefState == EOperationBriefState::Failed) { + YT_LOG_ERROR("Operation %v %v (%v)", + operationId, + ToString(*attributes.BriefState), + ToString(TOperationExecutionTimeTracker::Get()->Finish(operationId))); + + auto failedJobInfoList = GetFailedJobInfo( + clientRetryPolicy, + context, + operationId, + TGetFailedJobInfoOptions()); + + Y_VERIFY(attributes.Result && attributes.Result->Error); + ythrow TOperationFailedError( + *attributes.BriefState == EOperationBriefState::Aborted + ? TOperationFailedError::Aborted + : TOperationFailedError::Failed, + operationId, + *attributes.Result->Error, + failedJobInfoList); + } + return EOperationBriefState::InProgress; +} + +void WaitForOperation( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId) +{ + const TDuration checkOperationStateInterval = + UseLocalModeOptimization(context, clientRetryPolicy) + ? Min(TDuration::MilliSeconds(100), context.Config->OperationTrackerPollPeriod) + : context.Config->OperationTrackerPollPeriod; + + while (true) { + auto status = CheckOperation(clientRetryPolicy, context, operationId); + if (status == EOperationBriefState::Completed) { + YT_LOG_INFO("Operation %v completed (%v)", + operationId, + TOperationExecutionTimeTracker::Get()->Finish(operationId)); + break; + } + TWaitProxy::Get()->Sleep(checkOperationStateInterval); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +TNode BuildAutoMergeSpec(const TAutoMergeSpec& options) +{ + TNode result; + if (options.Mode_) { + result["mode"] = ToString(*options.Mode_); + } + if (options.MaxIntermediateChunkCount_) { + result["max_intermediate_chunk_count"] = *options.MaxIntermediateChunkCount_; + } + if (options.ChunkCountPerMergeJob_) { + result["chunk_count_per_merge_job"] = *options.ChunkCountPerMergeJob_; + } + if (options.ChunkSizeThreshold_) { + result["chunk_size_threshold"] = *options.ChunkSizeThreshold_; + } + return result; +} + +TNode BuildJobProfilerSpec(const TJobProfilerSpec& profilerSpec) +{ + TNode result; + if (profilerSpec.ProfilingBinary_) { + result["binary"] = ToString(*profilerSpec.ProfilingBinary_); + } + if (profilerSpec.ProfilerType_) { + result["type"] = ToString(*profilerSpec.ProfilerType_); + } + if (profilerSpec.ProfilingProbability_) { + result["profiling_probability"] = *profilerSpec.ProfilingProbability_; + } + if (profilerSpec.SamplingFrequency_) { + result["sampling_frequency"] = *profilerSpec.SamplingFrequency_; + } + + return result; +} + +// Returns undefined node if resources doesn't contain any meaningful field +TNode BuildSchedulerResourcesSpec(const TSchedulerResources& resources) +{ + TNode result; + if (resources.UserSlots().Defined()) { + result["user_slots"] = *resources.UserSlots(); + } + if (resources.Cpu().Defined()) { + result["cpu"] = *resources.Cpu(); + } + if (resources.Memory().Defined()) { + result["memory"] = *resources.Memory(); + } + return result; +} + +void BuildUserJobFluently( + const TJobPreparer& preparer, + const TMaybe<TFormat>& inputFormat, + const TMaybe<TFormat>& outputFormat, + TFluentMap fluent) +{ + const auto& userJobSpec = preparer.GetSpec(); + TMaybe<i64> memoryLimit = userJobSpec.MemoryLimit_; + TMaybe<double> cpuLimit = userJobSpec.CpuLimit_; + TMaybe<ui16> portCount = userJobSpec.PortCount_; + + // Use 1MB extra tmpfs size by default, it helps to detect job sandbox as tmp directory + // for standard python libraries. See YTADMINREQ-14505 for more details. + auto tmpfsSize = preparer.GetSpec().ExtraTmpfsSize_.GetOrElse(DefaultExrtaTmpfsSize); + if (preparer.ShouldMountSandbox()) { + tmpfsSize += preparer.GetTotalFileSize(); + if (tmpfsSize == 0) { + // This can be a case for example when it is local mode and we don't upload binary. + // NOTE: YT doesn't like zero tmpfs size. + tmpfsSize = RoundUpFileSize(1); + } + memoryLimit = memoryLimit.GetOrElse(512ll << 20) + tmpfsSize; + } + + fluent + .Item("file_paths").List(preparer.GetFiles()) + .Item("command").Value(preparer.GetCommand()) + .Item("class_name").Value(preparer.GetClassName()) + .DoIf(!userJobSpec.Environment_.empty(), [&] (TFluentMap fluentMap) { + TNode environment; + for (const auto& item : userJobSpec.Environment_) { + environment[item.first] = item.second; + } + fluentMap.Item("environment").Value(environment); + }) + .DoIf(userJobSpec.DiskSpaceLimit_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("disk_space_limit").Value(*userJobSpec.DiskSpaceLimit_); + }) + .DoIf(inputFormat.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("input_format").Value(inputFormat->Config); + }) + .DoIf(outputFormat.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("output_format").Value(outputFormat->Config); + }) + .DoIf(memoryLimit.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("memory_limit").Value(*memoryLimit); + }) + .DoIf(userJobSpec.MemoryReserveFactor_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("memory_reserve_factor").Value(*userJobSpec.MemoryReserveFactor_); + }) + .DoIf(cpuLimit.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("cpu_limit").Value(*cpuLimit); + }) + .DoIf(portCount.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("port_count").Value(*portCount); + }) + .DoIf(userJobSpec.JobTimeLimit_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("job_time_limit").Value(userJobSpec.JobTimeLimit_->MilliSeconds()); + }) + .DoIf(userJobSpec.NetworkProject_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("network_project").Value(*userJobSpec.NetworkProject_); + }) + .DoIf(preparer.ShouldMountSandbox(), [&] (TFluentMap fluentMap) { + fluentMap.Item("tmpfs_path").Value("."); + fluentMap.Item("tmpfs_size").Value(tmpfsSize); + fluentMap.Item("copy_files").Value(true); + }) + .Item("profilers") + .BeginList() + .DoFor(userJobSpec.JobProfilers_, [&] (TFluentList list, const auto& jobProfiler) { + list.Item().Value(BuildJobProfilerSpec(jobProfiler)); + }) + .EndList(); +} + +template <typename T> +void BuildCommonOperationPart(const TConfigPtr& config, const TOperationSpecBase<T>& baseSpec, const TOperationOptions& options, TFluentMap fluent) +{ + const TProcessState* properties = TProcessState::Get(); + TString pool = config->Pool; + + if (baseSpec.Pool_) { + pool = *baseSpec.Pool_; + } + + fluent + .Item("started_by") + .BeginMap() + .Item("hostname").Value(properties->FqdnHostName) + .Item("pid").Value(properties->Pid) + .Item("user").Value(properties->UserName) + .Item("command").List(properties->CensoredCommandLine) + .Item("wrapper_version").Value(properties->ClientVersion) + .EndMap() + .DoIf(!pool.empty(), [&] (TFluentMap fluentMap) { + fluentMap.Item("pool").Value(pool); + }) + .DoIf(baseSpec.Weight_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("weight").Value(*baseSpec.Weight_); + }) + .DoIf(baseSpec.TimeLimit_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("time_limit").Value(baseSpec.TimeLimit_->MilliSeconds()); + }) + .DoIf(baseSpec.PoolTrees().Defined(), [&] (TFluentMap fluentMap) { + TNode poolTreesSpec = TNode::CreateList(); + for (const auto& tree : *baseSpec.PoolTrees()) { + poolTreesSpec.Add(tree); + } + fluentMap.Item("pool_trees").Value(poolTreesSpec); + }) + .DoIf(baseSpec.ResourceLimits().Defined(), [&] (TFluentMap fluentMap) { + auto resourceLimitsSpec = BuildSchedulerResourcesSpec(*baseSpec.ResourceLimits()); + if (!resourceLimitsSpec.IsUndefined()) { + fluentMap.Item("resource_limits").Value(std::move(resourceLimitsSpec)); + } + }) + .DoIf(options.SecureVault_.Defined(), [&] (TFluentMap fluentMap) { + Y_ENSURE(options.SecureVault_->IsMap(), + "SecureVault must be a map node, got " << options.SecureVault_->GetType()); + fluentMap.Item("secure_vault").Value(*options.SecureVault_); + }) + .DoIf(baseSpec.Title_.Defined(), [&] (TFluentMap fluentMap) { + fluentMap.Item("title").Value(*baseSpec.Title_); + }); +} + +template <typename TSpec> +void BuildCommonUserOperationPart(const TSpec& baseSpec, TNode* spec) +{ + if (baseSpec.MaxFailedJobCount_.Defined()) { + (*spec)["max_failed_job_count"] = *baseSpec.MaxFailedJobCount_; + } + if (baseSpec.FailOnJobRestart_.Defined()) { + (*spec)["fail_on_job_restart"] = *baseSpec.FailOnJobRestart_; + } + if (baseSpec.StderrTablePath_.Defined()) { + (*spec)["stderr_table_path"] = *baseSpec.StderrTablePath_; + } + if (baseSpec.CoreTablePath_.Defined()) { + (*spec)["core_table_path"] = *baseSpec.CoreTablePath_; + } + if (baseSpec.WaitingJobTimeout_.Defined()) { + (*spec)["waiting_job_timeout"] = baseSpec.WaitingJobTimeout_->MilliSeconds(); + } +} + +template <typename TSpec> +void BuildJobCountOperationPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.JobCount_.Defined()) { + (*nodeSpec)["job_count"] = *spec.JobCount_; + } + if (spec.DataSizePerJob_.Defined()) { + (*nodeSpec)["data_size_per_job"] = *spec.DataSizePerJob_; + } +} + +template <typename TSpec> +void BuildPartitionCountOperationPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.PartitionCount_.Defined()) { + (*nodeSpec)["partition_count"] = *spec.PartitionCount_; + } + if (spec.PartitionDataSize_.Defined()) { + (*nodeSpec)["partition_data_size"] = *spec.PartitionDataSize_; + } +} + +template <typename TSpec> +void BuildDataSizePerSortJobPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.DataSizePerSortJob_.Defined()) { + (*nodeSpec)["data_size_per_sort_job"] = *spec.DataSizePerSortJob_; + } +} + +template <typename TSpec> +void BuildPartitionJobCountOperationPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.PartitionJobCount_.Defined()) { + (*nodeSpec)["partition_job_count"] = *spec.PartitionJobCount_; + } + if (spec.DataSizePerPartitionJob_.Defined()) { + (*nodeSpec)["data_size_per_partition_job"] = *spec.DataSizePerPartitionJob_; + } +} + +template <typename TSpec> +void BuildMapJobCountOperationPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.MapJobCount_.Defined()) { + (*nodeSpec)["map_job_count"] = *spec.MapJobCount_; + } + if (spec.DataSizePerMapJob_.Defined()) { + (*nodeSpec)["data_size_per_map_job"] = *spec.DataSizePerMapJob_; + } +} + +template <typename TSpec> +void BuildIntermediateDataPart(const TSpec& spec, TNode* nodeSpec) +{ + if (spec.IntermediateDataAccount_.Defined()) { + (*nodeSpec)["intermediate_data_account"] = *spec.IntermediateDataAccount_; + } + if (spec.IntermediateDataReplicationFactor_.Defined()) { + (*nodeSpec)["intermediate_data_replication_factor"] = *spec.IntermediateDataReplicationFactor_; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TNode MergeSpec(TNode dst, TNode spec, const TOperationOptions& options) +{ + MergeNodes(dst["spec"], spec); + if (options.Spec_) { + MergeNodes(dst["spec"], *options.Spec_); + } + return dst; +} + +template <typename TSpec> +void CreateDebugOutputTables(const TSpec& spec, const TOperationPreparer& preparer) +{ + if (spec.StderrTablePath_.Defined()) { + NYT::NDetail::Create( + preparer.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + preparer.GetContext(), + TTransactionId(), + *spec.StderrTablePath_, + NT_TABLE, + TCreateOptions() + .IgnoreExisting(true) + .Recursive(true)); + } + if (spec.CoreTablePath_.Defined()) { + NYT::NDetail::Create( + preparer.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + preparer.GetContext(), + TTransactionId(), + *spec.CoreTablePath_, + NT_TABLE, + TCreateOptions() + .IgnoreExisting(true) + .Recursive(true)); + } +} + +void CreateOutputTable( + const TOperationPreparer& preparer, + const TRichYPath& path) +{ + Y_ENSURE(path.Path_, "Output table is not set"); + Create( + preparer.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + preparer.GetContext(), preparer.GetTransactionId(), path.Path_, NT_TABLE, + TCreateOptions() + .IgnoreExisting(true) + .Recursive(true)); +} + +void CreateOutputTables( + const TOperationPreparer& preparer, + const TVector<TRichYPath>& paths) +{ + for (auto& path : paths) { + CreateOutputTable(preparer, path); + } +} + +void CheckInputTablesExist( + const TOperationPreparer& preparer, + const TVector<TRichYPath>& paths) +{ + Y_ENSURE(!paths.empty(), "Input tables are not set"); + for (auto& path : paths) { + auto curTransactionId = path.TransactionId_.GetOrElse(preparer.GetTransactionId()); + Y_ENSURE_EX( + Exists( + preparer.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + preparer.GetContext(), + curTransactionId, + path.Path_), + TApiUsageError() << "Input table '" << path.Path_ << "' doesn't exist"); + } +} + +void LogJob(const TOperationId& opId, const IJob* job, const char* type) +{ + if (job) { + YT_LOG_INFO("Operation %v; %v = %v", + opId, + type, + TJobFactory::Get()->GetJobName(job)); + } +} + +void LogYPaths(const TOperationId& opId, const TVector<TRichYPath>& paths, const char* type) +{ + for (size_t i = 0; i < paths.size(); ++i) { + YT_LOG_INFO("Operation %v; %v[%v] = %v", + opId, + type, + i, + paths[i].Path_); + } +} + +void LogYPath(const TOperationId& opId, const TRichYPath& path, const char* type) +{ + YT_LOG_INFO("Operation %v; %v = %v", + opId, + type, + path.Path_); +} + +TString AddModeToTitleIfDebug(const TString& title) { +#ifndef NDEBUG + return title + " (debug build)"; +#else + return title; +#endif +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +void DoExecuteMap( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TSimpleOperationIo& operationIo, + TMapOperationSpecBase<T> spec, + const IJobPtr& mapper, + const TOperationOptions& options) +{ + if (options.CreateDebugOutputTables_) { + CreateDebugOutputTables(spec, *preparer); + } + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, operationIo.Inputs); + CreateOutputTables(*preparer, operationIo.Outputs); + } + + TJobPreparer map( + *preparer, + spec.MapperSpec_, + *mapper, + operationIo.Outputs.size(), + operationIo.JobFiles, + options); + + spec.Title_ = spec.Title_.GetOrElse(AddModeToTitleIfDebug(map.GetClassName())); + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("mapper").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + map, + operationIo.InputFormat, + operationIo.OutputFormat, + fluent); + }) + .DoIf(spec.AutoMerge_.Defined(), [&] (TFluentMap fluent) { + auto autoMergeSpec = BuildAutoMergeSpec(*spec.AutoMerge_); + if (!autoMergeSpec.IsUndefined()) { + fluent.Item("auto_merge").Value(std::move(autoMergeSpec)); + } + }) + .Item("input_table_paths").List(operationIo.Inputs) + .Item("output_table_paths").List(operationIo.Outputs) + .DoIf(spec.Ordered_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("ordered").Value(spec.Ordered_.GetRef()); + }) + .Do(std::bind(BuildCommonOperationPart<T>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + specNode["spec"]["job_io"]["control_attributes"]["enable_row_index"] = TNode(true); + specNode["spec"]["job_io"]["control_attributes"]["enable_range_index"] = TNode(true); + if (!preparer->GetContext().Config->TableWriter.Empty()) { + specNode["spec"]["job_io"]["table_writer"] = preparer->GetContext().Config->TableWriter; + } + + BuildCommonUserOperationPart(spec, &specNode["spec"]); + BuildJobCountOperationPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + operationIo, + mapper + ] () { + auto operationId = preparer->StartOperation(operation, "map", spec); + + LogJob(operationId, mapper.Get(), "mapper"); + LogYPaths(operationId, operationIo.Inputs, "input"); + LogYPaths(operationId, operationIo.Outputs, "output"); + + return operationId; + }; + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteMap( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMapOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& mapper, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting map operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*mapper, *preparer, spec, options, /* allowSkiff = */ true); + DoExecuteMap( + operation, + preparer, + operationIo, + spec, + mapper, + options); +} + +void ExecuteRawMap( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawMapOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& mapper, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting raw map operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*mapper, *preparer, spec); + DoExecuteMap( + operation, + preparer, + operationIo, + spec, + mapper, + options); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +void DoExecuteReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TSimpleOperationIo& operationIo, + TReduceOperationSpecBase<T> spec, + const IJobPtr& reducer, + const TOperationOptions& options) +{ + if (options.CreateDebugOutputTables_) { + CreateDebugOutputTables(spec, *preparer); + } + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, operationIo.Inputs); + CreateOutputTables(*preparer, operationIo.Outputs); + } + + TJobPreparer reduce( + *preparer, + spec.ReducerSpec_, + *reducer, + operationIo.Outputs.size(), + operationIo.JobFiles, + options); + + spec.Title_ = spec.Title_.GetOrElse(AddModeToTitleIfDebug(reduce.GetClassName())); + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("reducer").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + reduce, + operationIo.InputFormat, + operationIo.OutputFormat, + fluent); + }) + .Item("sort_by").Value(spec.SortBy_) + .Item("reduce_by").Value(spec.ReduceBy_) + .DoIf(spec.JoinBy_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("join_by").Value(spec.JoinBy_.GetRef()); + }) + .DoIf(spec.EnableKeyGuarantee_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("enable_key_guarantee").Value(spec.EnableKeyGuarantee_.GetRef()); + }) + .Item("input_table_paths").List(operationIo.Inputs) + .Item("output_table_paths").List(operationIo.Outputs) + .Item("job_io").BeginMap() + .Item("control_attributes").BeginMap() + .Item("enable_key_switch").Value(true) + .Item("enable_row_index").Value(true) + .Item("enable_range_index").Value(true) + .EndMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&] (TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .EndMap() + .DoIf(spec.AutoMerge_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("auto_merge").Value(BuildAutoMergeSpec(*spec.AutoMerge_)); + }) + .Do(std::bind(BuildCommonOperationPart<T>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + BuildCommonUserOperationPart(spec, &specNode["spec"]); + BuildJobCountOperationPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + operationIo, + reducer + ] () { + auto operationId = preparer->StartOperation(operation, "reduce", spec); + + LogJob(operationId, reducer.Get(), "reducer"); + LogYPaths(operationId, operationIo.Inputs, "input"); + LogYPaths(operationId, operationIo.Outputs, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TReduceOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*reducer, *preparer, spec, options, /* allowSkiff = */ false); + DoExecuteReduce( + operation, + preparer, + operationIo, + spec, + reducer, + options); +} + +void ExecuteRawReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting raw reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*reducer, *preparer, spec); + DoExecuteReduce( + operation, + preparer, + operationIo, + spec, + reducer, + options); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +void DoExecuteJoinReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TSimpleOperationIo& operationIo, + TJoinReduceOperationSpecBase<T> spec, + const IJobPtr& reducer, + const TOperationOptions& options) +{ + if (options.CreateDebugOutputTables_) { + CreateDebugOutputTables(spec, *preparer); + } + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, operationIo.Inputs); + CreateOutputTables(*preparer, operationIo.Outputs); + } + + TJobPreparer reduce( + *preparer, + spec.ReducerSpec_, + *reducer, + operationIo.Outputs.size(), + operationIo.JobFiles, + options); + + spec.Title_ = spec.Title_.GetOrElse(AddModeToTitleIfDebug(reduce.GetClassName())); + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("reducer").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + reduce, + operationIo.InputFormat, + operationIo.OutputFormat, + fluent); + }) + .Item("join_by").Value(spec.JoinBy_) + .Item("input_table_paths").List(operationIo.Inputs) + .Item("output_table_paths").List(operationIo.Outputs) + .Item("job_io").BeginMap() + .Item("control_attributes").BeginMap() + .Item("enable_key_switch").Value(true) + .Item("enable_row_index").Value(true) + .Item("enable_range_index").Value(true) + .EndMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&] (TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .EndMap() + .Do(std::bind(BuildCommonOperationPart<T>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + BuildCommonUserOperationPart(spec, &specNode["spec"]); + BuildJobCountOperationPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + reducer, + operationIo + ] () { + auto operationId = preparer->StartOperation(operation, "join_reduce", spec); + + LogJob(operationId, reducer.Get(), "reducer"); + LogYPaths(operationId, operationIo.Inputs, "input"); + LogYPaths(operationId, operationIo.Outputs, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteJoinReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TJoinReduceOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting join reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*reducer, *preparer, spec, options, /* allowSkiff = */ false); + return DoExecuteJoinReduce( + operation, + preparer, + operationIo, + spec, + reducer, + options); +} + +void ExecuteRawJoinReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawJoinReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting raw join reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto operationIo = CreateSimpleOperationIo(*reducer, *preparer, spec); + return DoExecuteJoinReduce( + operation, + preparer, + operationIo, + spec, + reducer, + options); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +void DoExecuteMapReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMapReduceOperationIo& operationIo, + TMapReduceOperationSpecBase<T> spec, + const IJobPtr& mapper, + const IJobPtr& reduceCombiner, + const IJobPtr& reducer, + const TOperationOptions& options) +{ + TVector<TRichYPath> allOutputs; + allOutputs.insert(allOutputs.end(), operationIo.MapOutputs.begin(), operationIo.MapOutputs.end()); + allOutputs.insert(allOutputs.end(), operationIo.Outputs.begin(), operationIo.Outputs.end()); + + if (options.CreateDebugOutputTables_) { + CreateDebugOutputTables(spec, *preparer); + } + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, operationIo.Inputs); + CreateOutputTables(*preparer, allOutputs); + } + + TSortColumns sortBy = spec.SortBy_; + TSortColumns reduceBy = spec.ReduceBy_; + + if (sortBy.Parts_.empty()) { + sortBy = reduceBy; + } + + const bool hasMapper = mapper != nullptr; + const bool hasCombiner = reduceCombiner != nullptr; + + TVector<TRichYPath> files; + + TJobPreparer reduce( + *preparer, + spec.ReducerSpec_, + *reducer, + operationIo.Outputs.size(), + operationIo.ReducerJobFiles, + options); + + TString title; + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .DoIf(hasMapper, [&] (TFluentMap fluent) { + TJobPreparer map( + *preparer, + spec.MapperSpec_, + *mapper, + 1 + operationIo.MapOutputs.size(), + operationIo.MapperJobFiles, + options); + fluent.Item("mapper").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + std::cref(map), + *operationIo.MapperInputFormat, + *operationIo.MapperOutputFormat, + fluent); + }); + + title = "mapper:" + map.GetClassName() + " "; + }) + .DoIf(hasCombiner, [&] (TFluentMap fluent) { + TJobPreparer combine( + *preparer, + spec.ReduceCombinerSpec_, + *reduceCombiner, + size_t(1), + operationIo.ReduceCombinerJobFiles, + options); + fluent.Item("reduce_combiner").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + combine, + *operationIo.ReduceCombinerInputFormat, + *operationIo.ReduceCombinerOutputFormat, + fluent); + }); + title += "combiner:" + combine.GetClassName() + " "; + }) + .Item("reducer").DoMap([&] (TFluentMap fluent) { + BuildUserJobFluently( + reduce, + operationIo.ReducerInputFormat, + operationIo.ReducerOutputFormat, + fluent); + }) + .Item("sort_by").Value(sortBy) + .Item("reduce_by").Value(reduceBy) + .Item("input_table_paths").List(operationIo.Inputs) + .Item("output_table_paths").List(allOutputs) + .Item("mapper_output_table_count").Value(operationIo.MapOutputs.size()) + .DoIf(spec.ForceReduceCombiners_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("force_reduce_combiners").Value(*spec.ForceReduceCombiners_); + }) + .Item("map_job_io").BeginMap() + .Item("control_attributes").BeginMap() + .Item("enable_row_index").Value(true) + .Item("enable_range_index").Value(true) + .EndMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&] (TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .EndMap() + .Item("sort_job_io").BeginMap() + .Item("control_attributes").BeginMap() + .Item("enable_key_switch").Value(true) + .EndMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&] (TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .EndMap() + .Item("reduce_job_io").BeginMap() + .Item("control_attributes").BeginMap() + .Item("enable_key_switch").Value(true) + .EndMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&] (TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .EndMap() + .Do([&] (TFluentMap) { + spec.Title_ = spec.Title_.GetOrElse(AddModeToTitleIfDebug(title + "reducer:" + reduce.GetClassName())); + }) + .Do(std::bind(BuildCommonOperationPart<T>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + if (spec.Ordered_) { + specNode["spec"]["ordered"] = *spec.Ordered_; + } + + BuildCommonUserOperationPart(spec, &specNode["spec"]); + BuildMapJobCountOperationPart(spec, &specNode["spec"]); + BuildPartitionCountOperationPart(spec, &specNode["spec"]); + BuildIntermediateDataPart(spec, &specNode["spec"]); + BuildDataSizePerSortJobPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + mapper, + reduceCombiner, + reducer, + inputs=operationIo.Inputs, + allOutputs + ] () { + auto operationId = preparer->StartOperation(operation, "map_reduce", spec); + + LogJob(operationId, mapper.Get(), "mapper"); + LogJob(operationId, reduceCombiner.Get(), "reduce_combiner"); + LogJob(operationId, reducer.Get(), "reducer"); + LogYPaths(operationId, inputs, "input"); + LogYPaths(operationId, allOutputs, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteMapReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMapReduceOperationSpec& spec_, + const ::TIntrusivePtr<IStructuredJob>& mapper, + const ::TIntrusivePtr<IStructuredJob>& reduceCombiner, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting map-reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + TMapReduceOperationSpec spec = spec_; + + TMapReduceOperationIo operationIo; + auto structuredInputs = CanonizeStructuredTableList(preparer->GetContext(), spec.GetStructuredInputs()); + auto structuredMapOutputs = CanonizeStructuredTableList(preparer->GetContext(), spec.GetStructuredMapOutputs()); + auto structuredOutputs = CanonizeStructuredTableList(preparer->GetContext(), spec.GetStructuredOutputs()); + + const bool inferOutputSchema = options.InferOutputSchema_.GetOrElse(preparer->GetContext().Config->InferTableSchema); + + TVector<TTableSchema> currentInferenceResult; + + auto fixSpec = [&] (const TFormat& format) { + if (format.IsYamredDsv()) { + spec.SortBy_.Parts_.clear(); + spec.ReduceBy_.Parts_.clear(); + + const TYamredDsvAttributes attributes = format.GetYamredDsvAttributes(); + for (auto& column : attributes.KeyColumnNames) { + spec.SortBy_.Parts_.push_back(column); + spec.ReduceBy_.Parts_.push_back(column); + } + for (const auto& column : attributes.SubkeyColumnNames) { + spec.SortBy_.Parts_.push_back(column); + } + } + }; + + VerifyHasElements(structuredInputs, "inputs"); + + TFormatBuilder formatBuilder( + preparer->GetClientRetryPolicy(), + preparer->GetContext(), + preparer->GetTransactionId(), + options); + + if (mapper) { + auto mapperOutputDescription = + spec.GetIntermediateMapOutputDescription() + .GetOrElse(TUnspecifiedTableStructure()); + TStructuredJobTableList mapperOutput = { + TStructuredJobTable::Intermediate(mapperOutputDescription), + }; + + for (const auto& table : structuredMapOutputs) { + mapperOutput.push_back(TStructuredJobTable{table.Description, table.RichYPath}); + } + + auto hints = spec.MapperFormatHints_; + + auto mapperInferenceResult = PrepareOperation<TStructuredJobTableList>( + *mapper, + TOperationPreparationContext( + structuredInputs, + mapperOutput, + preparer->GetContext(), + preparer->GetClientRetryPolicy(), + preparer->GetTransactionId()), + &structuredInputs, + /* outputs */ nullptr, + hints); + + auto nodeReaderFormat = NodeReaderFormatFromHintAndGlobalConfig(spec.MapperFormatHints_); + + auto [inputFormat, inputFormatConfig] = formatBuilder.CreateFormat( + *mapper, + EIODirection::Input, + structuredInputs, + hints.InputFormatHints_, + nodeReaderFormat, + /* allowFormatFromTableAttribute */ true); + + auto [outputFormat, outputFormatConfig] = formatBuilder.CreateFormat( + *mapper, + EIODirection::Output, + mapperOutput, + hints.OutputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute */ false); + + operationIo.MapperJobFiles = CreateFormatConfig(inputFormatConfig, outputFormatConfig); + operationIo.MapperInputFormat = inputFormat; + operationIo.MapperOutputFormat = outputFormat; + + Y_VERIFY(mapperInferenceResult.size() >= 1); + currentInferenceResult = TVector<TTableSchema>{mapperInferenceResult[0]}; + // The first output as it corresponds to the intermediate data. + TVector<TTableSchema> additionalOutputsInferenceResult(mapperInferenceResult.begin() + 1, mapperInferenceResult.end()); + + operationIo.MapOutputs = GetPathList( + structuredMapOutputs, + additionalOutputsInferenceResult, + inferOutputSchema); + } + + if (reduceCombiner) { + const bool isFirstStep = !mapper; + TStructuredJobTableList inputs; + if (isFirstStep) { + inputs = structuredInputs; + } else { + auto reduceCombinerIntermediateInput = + spec.GetIntermediateReduceCombinerInputDescription() + .GetOrElse(TUnspecifiedTableStructure()); + inputs = { + TStructuredJobTable::Intermediate(reduceCombinerIntermediateInput), + }; + } + + auto reduceCombinerOutputDescription = spec.GetIntermediateReduceCombinerOutputDescription() + .GetOrElse(TUnspecifiedTableStructure()); + + TStructuredJobTableList outputs = { + TStructuredJobTable::Intermediate(reduceCombinerOutputDescription), + }; + + auto hints = spec.ReduceCombinerFormatHints_; + + if (isFirstStep) { + currentInferenceResult = PrepareOperation<TStructuredJobTableList>( + *reduceCombiner, + TOperationPreparationContext( + inputs, + outputs, + preparer->GetContext(), + preparer->GetClientRetryPolicy(), + preparer->GetTransactionId()), + &inputs, + /* outputs */ nullptr, + hints); + } else { + currentInferenceResult = PrepareOperation<TStructuredJobTableList>( + *reduceCombiner, + TSpeculativeOperationPreparationContext( + currentInferenceResult, + inputs, + outputs), + /* inputs */ nullptr, + /* outputs */ nullptr, + hints); + } + + auto [inputFormat, inputFormatConfig] = formatBuilder.CreateFormat( + *reduceCombiner, + EIODirection::Input, + inputs, + hints.InputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute = */ isFirstStep); + + auto [outputFormat, outputFormatConfig] = formatBuilder.CreateFormat( + *reduceCombiner, + EIODirection::Output, + outputs, + hints.OutputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute = */ false); + + operationIo.ReduceCombinerJobFiles = CreateFormatConfig(inputFormatConfig, outputFormatConfig); + operationIo.ReduceCombinerInputFormat = inputFormat; + operationIo.ReduceCombinerOutputFormat = outputFormat; + + if (isFirstStep) { + fixSpec(*operationIo.ReduceCombinerInputFormat); + } + } + + const bool isFirstStep = (!mapper && !reduceCombiner); + TStructuredJobTableList reducerInputs; + if (isFirstStep) { + reducerInputs = structuredInputs; + } else { + auto reducerInputDescription = + spec.GetIntermediateReducerInputDescription() + .GetOrElse(TUnspecifiedTableStructure()); + reducerInputs = { + TStructuredJobTable::Intermediate(reducerInputDescription), + }; + } + + auto hints = spec.ReducerFormatHints_; + + TVector<TTableSchema> reducerInferenceResult; + if (isFirstStep) { + reducerInferenceResult = PrepareOperation( + *reducer, + TOperationPreparationContext( + structuredInputs, + structuredOutputs, + preparer->GetContext(), + preparer->GetClientRetryPolicy(), + preparer->GetTransactionId()), + &structuredInputs, + &structuredOutputs, + hints); + } else { + reducerInferenceResult = PrepareOperation<TStructuredJobTableList>( + *reducer, + TSpeculativeOperationPreparationContext( + currentInferenceResult, + reducerInputs, + structuredOutputs), + /* inputs */ nullptr, + &structuredOutputs, + hints); + } + + auto [inputFormat, inputFormatConfig] = formatBuilder.CreateFormat( + *reducer, + EIODirection::Input, + reducerInputs, + hints.InputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute = */ isFirstStep); + + auto [outputFormat, outputFormatConfig] = formatBuilder.CreateFormat( + *reducer, + EIODirection::Output, + ToStructuredJobTableList(spec.GetStructuredOutputs()), + hints.OutputFormatHints_, + ENodeReaderFormat::Yson, + /* allowFormatFromTableAttribute = */ false); + operationIo.ReducerJobFiles = CreateFormatConfig(inputFormatConfig, outputFormatConfig); + operationIo.ReducerInputFormat = inputFormat; + operationIo.ReducerOutputFormat = outputFormat; + + if (isFirstStep) { + fixSpec(operationIo.ReducerInputFormat); + } + + operationIo.Inputs = GetPathList( + ApplyProtobufColumnFilters( + structuredInputs, + *preparer, + GetColumnsUsedInOperation(spec), + options), + /* jobSchemaInferenceResult */ Nothing(), + /* inferSchema */ false); + + operationIo.Outputs = GetPathList( + structuredOutputs, + reducerInferenceResult, + inferOutputSchema); + + VerifyHasElements(operationIo.Outputs, "outputs"); + + return DoExecuteMapReduce( + operation, + preparer, + operationIo, + spec, + mapper, + reduceCombiner, + reducer, + options); +} + +void ExecuteRawMapReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawMapReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& mapper, + const ::TIntrusivePtr<IRawJob>& reduceCombiner, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting raw map-reduce operation (PreparationId: %v)", + preparer->GetPreparationId()); + TMapReduceOperationIo operationIo; + operationIo.Inputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.GetInputs()); + operationIo.MapOutputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.GetMapOutputs()); + operationIo.Outputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.GetOutputs()); + + VerifyHasElements(operationIo.Inputs, "inputs"); + VerifyHasElements(operationIo.Outputs, "outputs"); + + auto getFormatOrDefault = [&] (const TMaybe<TFormat>& maybeFormat, const TMaybe<TFormat> stageDefaultFormat, const char* formatName) { + if (maybeFormat) { + return *maybeFormat; + } else if (stageDefaultFormat) { + return *stageDefaultFormat; + } else { + ythrow TApiUsageError() << "Cannot derive " << formatName; + } + }; + + if (mapper) { + operationIo.MapperInputFormat = getFormatOrDefault(spec.MapperInputFormat_, spec.MapperFormat_, "mapper input format"); + operationIo.MapperOutputFormat = getFormatOrDefault(spec.MapperOutputFormat_, spec.MapperFormat_, "mapper output format"); + } + + if (reduceCombiner) { + operationIo.ReduceCombinerInputFormat = getFormatOrDefault(spec.ReduceCombinerInputFormat_, spec.ReduceCombinerFormat_, "reduce combiner input format"); + operationIo.ReduceCombinerOutputFormat = getFormatOrDefault(spec.ReduceCombinerOutputFormat_, spec.ReduceCombinerFormat_, "reduce combiner output format"); + } + + operationIo.ReducerInputFormat = getFormatOrDefault(spec.ReducerInputFormat_, spec.ReducerFormat_, "reducer input format"); + operationIo.ReducerOutputFormat = getFormatOrDefault(spec.ReducerOutputFormat_, spec.ReducerFormat_, "reducer output format"); + + return DoExecuteMapReduce( + operation, + preparer, + operationIo, + spec, + mapper, + reduceCombiner, + reducer, + options); +} + +void ExecuteSort( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TSortOperationSpec& spec, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting sort operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto inputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.Inputs_); + auto output = CanonizeYPath(nullptr, preparer->GetContext(), spec.Output_); + + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, inputs); + CreateOutputTable(*preparer, output); + } + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("input_table_paths").List(inputs) + .Item("output_table_path").Value(output) + .Item("sort_by").Value(spec.SortBy_) + .DoIf(spec.SchemaInferenceMode_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("schema_inference_mode").Value(ToString(*spec.SchemaInferenceMode_)); + }) + .Do(std::bind(BuildCommonOperationPart<TSortOperationSpec>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + BuildPartitionCountOperationPart(spec, &specNode["spec"]); + BuildPartitionJobCountOperationPart(spec, &specNode["spec"]); + BuildIntermediateDataPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + inputs, + output + ] () { + auto operationId = preparer->StartOperation(operation, "sort", spec); + + LogYPaths(operationId, inputs, "input"); + LogYPath(operationId, output, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteMerge( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMergeOperationSpec& spec, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting merge operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto inputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.Inputs_); + auto output = CanonizeYPath(nullptr, preparer->GetContext(), spec.Output_); + + if (options.CreateOutputTables_) { + CheckInputTablesExist(*preparer, inputs); + CreateOutputTable(*preparer, output); + } + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("input_table_paths").List(inputs) + .Item("output_table_path").Value(output) + .Item("mode").Value(ToString(spec.Mode_)) + .Item("combine_chunks").Value(spec.CombineChunks_) + .Item("force_transform").Value(spec.ForceTransform_) + .Item("merge_by").Value(spec.MergeBy_) + .DoIf(spec.SchemaInferenceMode_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("schema_inference_mode").Value(ToString(*spec.SchemaInferenceMode_)); + }) + .Do(std::bind(BuildCommonOperationPart<TMergeOperationSpec>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + BuildJobCountOperationPart(spec, &specNode["spec"]); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + inputs, + output + ] () { + auto operationId = preparer->StartOperation(operation, "merge", spec); + + LogYPaths(operationId, inputs, "input"); + LogYPath(operationId, output, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteErase( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TEraseOperationSpec& spec, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting erase operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto tablePath = CanonizeYPath(nullptr, preparer->GetContext(), spec.TablePath_); + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("table_path").Value(tablePath) + .Item("combine_chunks").Value(spec.CombineChunks_) + .DoIf(spec.SchemaInferenceMode_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("schema_inference_mode").Value(ToString(*spec.SchemaInferenceMode_)); + }) + .Do(std::bind(BuildCommonOperationPart<TEraseOperationSpec>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), + preparer, + tablePath + ] () { + auto operationId = preparer->StartOperation(operation, "erase", spec); + + LogYPath(operationId, tablePath, "table_path"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteRemoteCopy( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRemoteCopyOperationSpec& spec, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting remote copy operation (PreparationId: %v)", + preparer->GetPreparationId()); + auto inputs = CanonizeYPaths(/* retryPolicy */ nullptr, preparer->GetContext(), spec.Inputs_); + auto output = CanonizeYPath(nullptr, preparer->GetContext(), spec.Output_); + + if (options.CreateOutputTables_) { + CreateOutputTable(*preparer, output); + } + + Y_ENSURE_EX(!spec.ClusterName_.empty(), TApiUsageError() << "ClusterName parameter is required"); + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("cluster_name").Value(spec.ClusterName_) + .Item("input_table_paths").List(inputs) + .Item("output_table_path").Value(output) + .DoIf(spec.NetworkName_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("network_name").Value(*spec.NetworkName_); + }) + .DoIf(spec.SchemaInferenceMode_.Defined(), [&] (TFluentMap fluent) { + fluent.Item("schema_inference_mode").Value(ToString(*spec.SchemaInferenceMode_)); + }) + .Item("copy_attributes").Value(spec.CopyAttributes_) + .DoIf(!spec.AttributeKeys_.empty(), [&] (TFluentMap fluent) { + Y_ENSURE_EX(spec.CopyAttributes_, TApiUsageError() << + "Specifying nonempty AttributeKeys in RemoteCopy " + "doesn't make sense without CopyAttributes == true"); + fluent.Item("attribute_keys").List(spec.AttributeKeys_); + }) + .Do(std::bind(BuildCommonOperationPart<TRemoteCopyOperationSpec>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + auto startOperation = [ + operation=operation.Get(), + spec=MergeSpec(specNode, preparer->GetContext().Config->Spec, options), + preparer, + inputs, + output + ] () { + auto operationId = preparer->StartOperation(operation, "remote_copy", spec); + + LogYPaths(operationId, inputs, "input"); + LogYPath(operationId, output, "output"); + + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +void ExecuteVanilla( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TVanillaOperationSpec& spec, + const TOperationOptions& options) +{ + YT_LOG_DEBUG("Starting vanilla operation (PreparationId: %v)", + preparer->GetPreparationId()); + + auto addTask = [&](TFluentMap fluent, const TVanillaTask& task) { + Y_VERIFY(task.Job_.Get()); + if (std::holds_alternative<TVoidStructuredRowStream>(task.Job_->GetOutputRowStreamDescription())) { + Y_ENSURE_EX(task.Outputs_.empty(), + TApiUsageError() << "Vanilla task with void IVanillaJob doesn't expect output tables"); + TJobPreparer jobPreparer( + *preparer, + task.Spec_, + *task.Job_, + /* outputTableCount */ 0, + /* smallFileList */ {}, + options); + fluent + .Item(task.Name_).BeginMap() + .Item("job_count").Value(task.JobCount_) + .DoIf(task.NetworkProject_.Defined(), [&](TFluentMap fluent) { + fluent.Item("network_project").Value(*task.NetworkProject_); + }) + .Do([&] (TFluentMap fluent) { + BuildUserJobFluently( + std::cref(jobPreparer), + /* inputFormat */ Nothing(), + /* outputFormat */ Nothing(), + fluent); + }) + .EndMap(); + } else { + auto operationIo = CreateSimpleOperationIo( + *task.Job_, + *preparer, + task, + options, + false); + Y_ENSURE_EX(operationIo.Outputs.size() > 0, + TApiUsageError() << "Vanilla task with IVanillaJob that has table writer expects output tables"); + if (options.CreateOutputTables_) { + CreateOutputTables(*preparer, operationIo.Outputs); + } + TJobPreparer jobPreparer( + *preparer, + task.Spec_, + *task.Job_, + operationIo.Outputs.size(), + operationIo.JobFiles, + options); + fluent + .Item(task.Name_).BeginMap() + .Item("job_count").Value(task.JobCount_) + .DoIf(task.NetworkProject_.Defined(), [&](TFluentMap fluent) { + fluent.Item("network_project").Value(*task.NetworkProject_); + }) + .Do([&] (TFluentMap fluent) { + BuildUserJobFluently( + std::cref(jobPreparer), + /* inputFormat */ Nothing(), + operationIo.OutputFormat, + fluent); + }) + .Item("output_table_paths").List(operationIo.Outputs) + .Item("job_io").BeginMap() + .DoIf(!preparer->GetContext().Config->TableWriter.Empty(), [&](TFluentMap fluent) { + fluent.Item("table_writer").Value(preparer->GetContext().Config->TableWriter); + }) + .Item("control_attributes").BeginMap() + .Item("enable_row_index").Value(TNode(true)) + .Item("enable_range_index").Value(TNode(true)) + .EndMap() + .EndMap() + .EndMap(); + } + }; + + if (options.CreateDebugOutputTables_) { + CreateDebugOutputTables(spec, *preparer); + } + + TNode specNode = BuildYsonNodeFluently() + .BeginMap().Item("spec").BeginMap() + .Item("tasks").DoMapFor(spec.Tasks_, addTask) + .Do(std::bind(BuildCommonOperationPart<TVanillaOperationSpec>, preparer->GetContext().Config, spec, options, std::placeholders::_1)) + .EndMap().EndMap(); + + BuildCommonUserOperationPart(spec, &specNode["spec"]); + + auto startOperation = [operation=operation.Get(), spec=MergeSpec(std::move(specNode), preparer->GetContext().Config->Spec, options), preparer] () { + auto operationId = preparer->StartOperation(operation, "vanilla", spec, /* useStartOperationRequest */ true); + return operationId; + }; + + operation->SetDelayedStartFunction(std::move(startOperation)); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TOperation::TOperationImpl + : public TThrRefBase +{ +public: + TOperationImpl( + IClientRetryPolicyPtr clientRetryPolicy, + TClientContext context, + const TMaybe<TOperationId>& operationId = {}) + : ClientRetryPolicy_(clientRetryPolicy) + , Context_(std::move(context)) + , Id_(operationId) + , PreparedPromise_(::NThreading::NewPromise<void>()) + , StartedPromise_(::NThreading::NewPromise<void>()) + { + if (Id_) { + PreparedPromise_.SetValue(); + StartedPromise_.SetValue(); + } else { + PreparedPromise_.GetFuture().Subscribe([this_=::TIntrusivePtr(this)] (const ::NThreading::TFuture<void>& preparedResult) { + try { + preparedResult.GetValue(); + } catch (...) { + this_->StartedPromise_.SetException(std::current_exception()); + return; + } + }); + } + } + + const TOperationId& GetId() const; + TString GetWebInterfaceUrl() const; + + void OnPrepared(); + void SetDelayedStartFunction(std::function<TOperationId()> start); + void Start(); + bool IsStarted() const; + void OnPreparationException(std::exception_ptr e); + + TString GetStatus(); + void OnStatusUpdated(const TString& newStatus); + + ::NThreading::TFuture<void> GetPreparedFuture(); + ::NThreading::TFuture<void> GetStartedFuture(); + ::NThreading::TFuture<void> Watch(TClientPtr client); + + EOperationBriefState GetBriefState(); + TMaybe<TYtError> GetError(); + TJobStatistics GetJobStatistics(); + TMaybe<TOperationBriefProgress> GetBriefProgress(); + void AbortOperation(); + void CompleteOperation(); + void SuspendOperation(const TSuspendOperationOptions& options); + void ResumeOperation(const TResumeOperationOptions& options); + TOperationAttributes GetAttributes(const TGetOperationOptions& options); + void UpdateParameters(const TUpdateOperationParametersOptions& options); + TJobAttributes GetJob(const TJobId& jobId, const TGetJobOptions& options); + TListJobsResult ListJobs(const TListJobsOptions& options); + + void AsyncFinishOperation(TOperationAttributes operationAttributes); + void FinishWithException(std::exception_ptr exception); + void UpdateBriefProgress(TMaybe<TOperationBriefProgress> briefProgress); + void AnalyzeUnrecognizedSpec(TNode unrecognizedSpec); + + const TClientContext& GetContext() const; + +private: + void OnStarted(const TOperationId& operationId); + + void UpdateAttributesAndCall(bool needJobStatistics, std::function<void(const TOperationAttributes&)> func); + + void SyncFinishOperationImpl(const TOperationAttributes&); + static void* SyncFinishOperationProc(void* ); + + void ValidateOperationStarted() const; + +private: + IClientRetryPolicyPtr ClientRetryPolicy_; + const TClientContext Context_; + TMaybe<TOperationId> Id_; + TMutex Lock_; + + ::NThreading::TPromise<void> PreparedPromise_; + ::NThreading::TPromise<void> StartedPromise_; + TMaybe<::NThreading::TPromise<void>> CompletePromise_; + + std::function<TOperationId()> DelayedStartFunction_; + TString Status_; + TOperationAttributes Attributes_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TOperationPollerItem + : public IYtPollerItem +{ +public: + TOperationPollerItem(::TIntrusivePtr<TOperation::TOperationImpl> operationImpl) + : OperationImpl_(std::move(operationImpl)) + { } + + void PrepareRequest(TRawBatchRequest* batchRequest) override + { + auto filter = TOperationAttributeFilter() + .Add(EOperationAttribute::State) + .Add(EOperationAttribute::BriefProgress) + .Add(EOperationAttribute::Result); + + if (!UnrecognizedSpecAnalyzed_) { + filter.Add(EOperationAttribute::UnrecognizedSpec); + } + + OperationState_ = batchRequest->GetOperation( + OperationImpl_->GetId(), + TGetOperationOptions().AttributeFilter(filter)); + } + + EStatus OnRequestExecuted() override + { + try { + const auto& attributes = OperationState_.GetValue(); + if (!UnrecognizedSpecAnalyzed_ && !attributes.UnrecognizedSpec.Empty()) { + OperationImpl_->AnalyzeUnrecognizedSpec(*attributes.UnrecognizedSpec); + UnrecognizedSpecAnalyzed_ = true; + } + Y_VERIFY(attributes.BriefState, + "get_operation for operation %s has not returned \"state\" field", + GetGuidAsString(OperationImpl_->GetId()).Data()); + if (*attributes.BriefState != EOperationBriefState::InProgress) { + OperationImpl_->AsyncFinishOperation(attributes); + return PollBreak; + } else { + OperationImpl_->UpdateBriefProgress(attributes.BriefProgress); + } + } catch (const TErrorResponse& e) { + if (!IsRetriable(e)) { + OperationImpl_->FinishWithException(std::current_exception()); + return PollBreak; + } + } catch (const std::exception& e) { + OperationImpl_->FinishWithException(std::current_exception()); + return PollBreak; + } + return PollContinue; + } + + void OnItemDiscarded() override { + OperationImpl_->FinishWithException(std::make_exception_ptr(yexception() << "Operation cancelled")); + } + +private: + ::TIntrusivePtr<TOperation::TOperationImpl> OperationImpl_; + ::NThreading::TFuture<TOperationAttributes> OperationState_; + bool UnrecognizedSpecAnalyzed_ = false; +}; + +//////////////////////////////////////////////////////////////////////////////// + +const TOperationId& TOperation::TOperationImpl::GetId() const +{ + ValidateOperationStarted(); + return *Id_; +} + +TString TOperation::TOperationImpl::GetWebInterfaceUrl() const +{ + ValidateOperationStarted(); + return GetOperationWebInterfaceUrl(Context_.ServerName, *Id_); +} + +void TOperation::TOperationImpl::OnPrepared() +{ + Y_VERIFY(!PreparedPromise_.HasException() && !PreparedPromise_.HasValue()); + PreparedPromise_.SetValue(); +} + +void TOperation::TOperationImpl::SetDelayedStartFunction(std::function<TOperationId()> start) +{ + DelayedStartFunction_ = std::move(start); +} + +void TOperation::TOperationImpl::Start() +{ + { + auto guard = Guard(Lock_); + if (Id_) { + ythrow TApiUsageError() << "Start() should not be called on running operations"; + } + } + GetPreparedFuture().GetValueSync(); + + std::function<TOperationId()> startStuff; + { + auto guard = Guard(Lock_); + startStuff.swap(DelayedStartFunction_); + } + if (!startStuff) { + ythrow TApiUsageError() << "Seems that Start() was called multiple times. If not, contact yt@"; + } + + TOperationId operationId; + try { + operationId = startStuff(); + } catch (...) { + auto exception = std::current_exception(); + StartedPromise_.SetException(exception); + std::rethrow_exception(exception); + } + OnStarted(operationId); +} + +bool TOperation::TOperationImpl::IsStarted() const { + auto guard = Guard(Lock_); + return bool(Id_); +} + +void TOperation::TOperationImpl::OnPreparationException(std::exception_ptr e) +{ + Y_VERIFY(!PreparedPromise_.HasValue() && !PreparedPromise_.HasException()); + PreparedPromise_.SetException(e); +} + +TString TOperation::TOperationImpl::GetStatus() +{ + { + auto guard = Guard(Lock_); + if (!Id_) { + return Status_; + } + } + TMaybe<TString> state; + UpdateAttributesAndCall(false, [&] (const TOperationAttributes& attributes) { + state = attributes.State; + }); + + return "On YT cluster: " + state.GetOrElse("undefined state"); +} + +void TOperation::TOperationImpl::OnStatusUpdated(const TString& newStatus) +{ + auto guard = Guard(Lock_); + Status_ = newStatus; +} + +::NThreading::TFuture<void> TOperation::TOperationImpl::GetPreparedFuture() +{ + return PreparedPromise_.GetFuture(); +} + +::NThreading::TFuture<void> TOperation::TOperationImpl::GetStartedFuture() +{ + return StartedPromise_.GetFuture(); +} + +::NThreading::TFuture<void> TOperation::TOperationImpl::Watch(TClientPtr client) +{ + { + auto guard = Guard(Lock_); + if (CompletePromise_) { + return *CompletePromise_; + } + CompletePromise_ = ::NThreading::NewPromise<void>(); + } + GetStartedFuture().Subscribe([ + this_=::TIntrusivePtr(this), + client=std::move(client) + ] (const ::NThreading::TFuture<void>& startedResult) { + try { + startedResult.GetValue(); + } catch (...) { + this_->CompletePromise_->SetException(std::current_exception()); + return; + } + client->GetYtPoller().Watch(::MakeIntrusive<TOperationPollerItem>(this_)); + auto operationId = this_->GetId(); + auto registry = TAbortableRegistry::Get(); + registry->Add( + operationId, + ::MakeIntrusive<TOperationAbortable>(this_->ClientRetryPolicy_, this_->Context_, operationId)); + // We have to own an IntrusivePtr to registry to prevent use-after-free + auto removeOperation = [registry, operationId] (const ::NThreading::TFuture<void>&) { + registry->Remove(operationId); + }; + this_->CompletePromise_->GetFuture().Subscribe(removeOperation); + }); + + return *CompletePromise_; +} + +EOperationBriefState TOperation::TOperationImpl::GetBriefState() +{ + ValidateOperationStarted(); + EOperationBriefState result = EOperationBriefState::InProgress; + UpdateAttributesAndCall(false, [&] (const TOperationAttributes& attributes) { + Y_VERIFY(attributes.BriefState, + "get_operation for operation %s has not returned \"state\" field", + GetGuidAsString(*Id_).Data()); + result = *attributes.BriefState; + }); + return result; +} + +TMaybe<TYtError> TOperation::TOperationImpl::GetError() +{ + ValidateOperationStarted(); + TMaybe<TYtError> result; + UpdateAttributesAndCall(false, [&] (const TOperationAttributes& attributes) { + Y_VERIFY(attributes.Result); + result = attributes.Result->Error; + }); + return result; +} + +TJobStatistics TOperation::TOperationImpl::GetJobStatistics() +{ + ValidateOperationStarted(); + TJobStatistics result; + UpdateAttributesAndCall(true, [&] (const TOperationAttributes& attributes) { + if (attributes.Progress) { + result = attributes.Progress->JobStatistics; + } + }); + return result; +} + +TMaybe<TOperationBriefProgress> TOperation::TOperationImpl::GetBriefProgress() +{ + ValidateOperationStarted(); + { + auto g = Guard(Lock_); + if (CompletePromise_.Defined()) { + // Poller do this job for us + return Attributes_.BriefProgress; + } + } + TMaybe<TOperationBriefProgress> result; + UpdateAttributesAndCall(false, [&] (const TOperationAttributes& attributes) { + result = attributes.BriefProgress; + }); + return result; +} + +void TOperation::TOperationImpl::UpdateBriefProgress(TMaybe<TOperationBriefProgress> briefProgress) +{ + auto g = Guard(Lock_); + Attributes_.BriefProgress = std::move(briefProgress); +} + +void TOperation::TOperationImpl::AnalyzeUnrecognizedSpec(TNode unrecognizedSpec) +{ + static const TVector<TVector<TString>> knownUnrecognizedSpecFieldPaths = { + {"mapper", "class_name"}, + {"reducer", "class_name"}, + {"reduce_combiner", "class_name"}, + }; + + auto removeByPath = [] (TNode& node, auto pathBegin, auto pathEnd, auto& removeByPath) { + if (pathBegin == pathEnd) { + return; + } + if (!node.IsMap()) { + return; + } + auto* child = node.AsMap().FindPtr(*pathBegin); + if (!child) { + return; + } + removeByPath(*child, std::next(pathBegin), pathEnd, removeByPath); + if (std::next(pathBegin) == pathEnd || (child->IsMap() && child->Empty())) { + node.AsMap().erase(*pathBegin); + } + }; + + Y_VERIFY(unrecognizedSpec.IsMap()); + for (const auto& knownFieldPath : knownUnrecognizedSpecFieldPaths) { + Y_VERIFY(!knownFieldPath.empty()); + removeByPath(unrecognizedSpec, knownFieldPath.cbegin(), knownFieldPath.cend(), removeByPath); + } + + if (!unrecognizedSpec.Empty()) { + YT_LOG_INFO( + "WARNING! Unrecognized spec for operation %s is not empty " + "(fields added by the YT API library are excluded): %s", + GetGuidAsString(*Id_).Data(), + NodeToYsonString(unrecognizedSpec).Data()); + } +} + +void TOperation::TOperationImpl::OnStarted(const TOperationId& operationId) +{ + auto guard = Guard(Lock_); + Y_VERIFY(!Id_, + "OnStarted() called with operationId = %s for operation with id %s", + GetGuidAsString(operationId).Data(), + GetGuidAsString(*Id_).Data()); + Id_ = operationId; + + Y_VERIFY(!StartedPromise_.HasValue() && !StartedPromise_.HasException()); + StartedPromise_.SetValue(); +} + +void TOperation::TOperationImpl::UpdateAttributesAndCall(bool needJobStatistics, std::function<void(const TOperationAttributes&)> func) +{ + { + auto g = Guard(Lock_); + if (Attributes_.BriefState + && *Attributes_.BriefState != EOperationBriefState::InProgress + && (!needJobStatistics || Attributes_.Progress)) + { + func(Attributes_); + return; + } + } + + TOperationAttributes attributes = NDetail::GetOperation( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + *Id_, + TGetOperationOptions().AttributeFilter(TOperationAttributeFilter() + .Add(EOperationAttribute::Result) + .Add(EOperationAttribute::Progress) + .Add(EOperationAttribute::State) + .Add(EOperationAttribute::BriefProgress))); + + func(attributes); + + Y_ENSURE(attributes.BriefState); + if (*attributes.BriefState != EOperationBriefState::InProgress) { + auto g = Guard(Lock_); + Attributes_ = std::move(attributes); + } +} + +void TOperation::TOperationImpl::FinishWithException(std::exception_ptr e) +{ + CompletePromise_->SetException(std::move(e)); +} + +void TOperation::TOperationImpl::AbortOperation() +{ + ValidateOperationStarted(); + NYT::NDetail::AbortOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_); +} + +void TOperation::TOperationImpl::CompleteOperation() +{ + ValidateOperationStarted(); + NYT::NDetail::CompleteOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_); +} + +void TOperation::TOperationImpl::SuspendOperation(const TSuspendOperationOptions& options) +{ + ValidateOperationStarted(); + NYT::NDetail::SuspendOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, options); +} + +void TOperation::TOperationImpl::ResumeOperation(const TResumeOperationOptions& options) +{ + ValidateOperationStarted(); + NYT::NDetail::ResumeOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, options); +} + +TOperationAttributes TOperation::TOperationImpl::GetAttributes(const TGetOperationOptions& options) +{ + ValidateOperationStarted(); + return NYT::NDetail::GetOperation(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, options); +} + +void TOperation::TOperationImpl::UpdateParameters(const TUpdateOperationParametersOptions& options) +{ + ValidateOperationStarted(); + return NYT::NDetail::UpdateOperationParameters(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, options); +} + +TJobAttributes TOperation::TOperationImpl::GetJob(const TJobId& jobId, const TGetJobOptions& options) +{ + ValidateOperationStarted(); + return NYT::NDetail::GetJob(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, jobId, options); +} + +TListJobsResult TOperation::TOperationImpl::ListJobs(const TListJobsOptions& options) +{ + ValidateOperationStarted(); + return NYT::NDetail::ListJobs(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, *Id_, options); +} + +struct TAsyncFinishOperationsArgs +{ + ::TIntrusivePtr<TOperation::TOperationImpl> OperationImpl; + TOperationAttributes OperationAttributes; +}; + +void TOperation::TOperationImpl::AsyncFinishOperation(TOperationAttributes operationAttributes) +{ + auto args = new TAsyncFinishOperationsArgs; + args->OperationImpl = this; + args->OperationAttributes = std::move(operationAttributes); + + TThread thread(TThread::TParams(&TOperation::TOperationImpl::SyncFinishOperationProc, args).SetName("finish operation")); + thread.Start(); + thread.Detach(); +} + +void* TOperation::TOperationImpl::SyncFinishOperationProc(void* pArgs) +{ + THolder<TAsyncFinishOperationsArgs> args(static_cast<TAsyncFinishOperationsArgs*>(pArgs)); + args->OperationImpl->SyncFinishOperationImpl(args->OperationAttributes); + return nullptr; +} + +void TOperation::TOperationImpl::SyncFinishOperationImpl(const TOperationAttributes& attributes) +{ + { + auto guard = Guard(Lock_); + Y_VERIFY(Id_); + } + Y_VERIFY(attributes.BriefState, + "get_operation for operation %s has not returned \"state\" field", + GetGuidAsString(*Id_).Data()); + Y_VERIFY(*attributes.BriefState != EOperationBriefState::InProgress); + + { + try { + // `attributes' that came from poller don't have JobStatistics + // so we call `GetJobStatistics' in order to get it from server + // and cache inside object. + GetJobStatistics(); + } catch (const TErrorResponse& ) { + // But if for any reason we failed to get attributes + // we complete operation using what we have. + auto g = Guard(Lock_); + Attributes_ = attributes; + } + } + + if (*attributes.BriefState == EOperationBriefState::Completed) { + CompletePromise_->SetValue(); + } else if (*attributes.BriefState == EOperationBriefState::Aborted || *attributes.BriefState == EOperationBriefState::Failed) { + Y_VERIFY(attributes.Result && attributes.Result->Error); + const auto& error = *attributes.Result->Error; + YT_LOG_ERROR("Operation %v is `%v' with error: %v", + *Id_, + ToString(*attributes.BriefState), + error.FullDescription()); + + TString additionalExceptionText; + TVector<TFailedJobInfo> failedJobStderrInfo; + if (*attributes.BriefState == EOperationBriefState::Failed) { + try { + failedJobStderrInfo = NYT::NDetail::GetFailedJobInfo(ClientRetryPolicy_, Context_, *Id_, TGetFailedJobInfoOptions()); + } catch (const std::exception& e) { + additionalExceptionText = "Cannot get job stderrs: "; + additionalExceptionText += e.what(); + } + } + CompletePromise_->SetException( + std::make_exception_ptr( + TOperationFailedError( + *attributes.BriefState == EOperationBriefState::Failed + ? TOperationFailedError::Failed + : TOperationFailedError::Aborted, + *Id_, + error, + failedJobStderrInfo) << additionalExceptionText)); + } +} + +void TOperation::TOperationImpl::ValidateOperationStarted() const +{ + auto guard = Guard(Lock_); + if (!Id_) { + ythrow TApiUsageError() << "Operation is not started"; + } +} + +const TClientContext& TOperation::TOperationImpl::GetContext() const +{ + return Context_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TOperation::TOperation(TClientPtr client) + : Client_(std::move(client)) + , Impl_(::MakeIntrusive<TOperationImpl>(Client_->GetRetryPolicy(), Client_->GetContext())) +{ +} + +TOperation::TOperation(TOperationId id, TClientPtr client) + : Client_(std::move(client)) + , Impl_(::MakeIntrusive<TOperationImpl>(Client_->GetRetryPolicy(), Client_->GetContext(), id)) +{ +} + +const TOperationId& TOperation::GetId() const +{ + return Impl_->GetId(); +} + +TString TOperation::GetWebInterfaceUrl() const +{ + return Impl_->GetWebInterfaceUrl(); +} + +void TOperation::OnPrepared() +{ + Impl_->OnPrepared(); +} + +void TOperation::SetDelayedStartFunction(std::function<TOperationId()> start) +{ + Impl_->SetDelayedStartFunction(std::move(start)); +} + +void TOperation::Start() +{ + Impl_->Start(); +} + +bool TOperation::IsStarted() const +{ + return Impl_->IsStarted(); +} + +void TOperation::OnPreparationException(std::exception_ptr e) +{ + Impl_->OnPreparationException(std::move(e)); +} + +TString TOperation::GetStatus() const +{ + return Impl_->GetStatus(); +} + +void TOperation::OnStatusUpdated(const TString& newStatus) +{ + Impl_->OnStatusUpdated(newStatus); +} + +::NThreading::TFuture<void> TOperation::GetPreparedFuture() +{ + return Impl_->GetPreparedFuture(); +} + +::NThreading::TFuture<void> TOperation::GetStartedFuture() +{ + return Impl_->GetStartedFuture(); +} + +::NThreading::TFuture<void> TOperation::Watch() +{ + return Impl_->Watch(Client_); +} + +TVector<TFailedJobInfo> TOperation::GetFailedJobInfo(const TGetFailedJobInfoOptions& options) +{ + return NYT::NDetail::GetFailedJobInfo(Client_->GetRetryPolicy(), Client_->GetContext(), GetId(), options); +} + +EOperationBriefState TOperation::GetBriefState() +{ + return Impl_->GetBriefState(); +} + +TMaybe<TYtError> TOperation::GetError() +{ + return Impl_->GetError(); +} + +TJobStatistics TOperation::GetJobStatistics() +{ + return Impl_->GetJobStatistics(); +} + +TMaybe<TOperationBriefProgress> TOperation::GetBriefProgress() +{ + return Impl_->GetBriefProgress(); +} + +void TOperation::AbortOperation() +{ + Impl_->AbortOperation(); +} + +void TOperation::CompleteOperation() +{ + Impl_->CompleteOperation(); +} + +void TOperation::SuspendOperation(const TSuspendOperationOptions& options) +{ + Impl_->SuspendOperation(options); +} + +void TOperation::ResumeOperation(const TResumeOperationOptions& options) +{ + Impl_->ResumeOperation(options); +} + +TOperationAttributes TOperation::GetAttributes(const TGetOperationOptions& options) +{ + return Impl_->GetAttributes(options); +} + +void TOperation::UpdateParameters(const TUpdateOperationParametersOptions& options) +{ + Impl_->UpdateParameters(options); +} + +TJobAttributes TOperation::GetJob(const TJobId& jobId, const TGetJobOptions& options) +{ + return Impl_->GetJob(jobId, options); +} + +TListJobsResult TOperation::ListJobs(const TListJobsOptions& options) +{ + return Impl_->ListJobs(options); +} + +//////////////////////////////////////////////////////////////////////////////// + +struct TAsyncPrepareAndStartOperationArgs +{ + std::function<void()> PrepareAndStart; +}; + +void* SyncPrepareAndStartOperation(void* pArgs) +{ + THolder<TAsyncPrepareAndStartOperationArgs> args(static_cast<TAsyncPrepareAndStartOperationArgs*>(pArgs)); + args->PrepareAndStart(); + return nullptr; +} + +::TIntrusivePtr<TOperation> ProcessOperation( + NYT::NDetail::TClientPtr client, + std::function<void()> prepare, + ::TIntrusivePtr<TOperation> operation, + const TOperationOptions& options) +{ + auto prepareAndStart = [prepare = std::move(prepare), operation, mode = options.StartOperationMode_] () { + try { + prepare(); + operation->OnPrepared(); + } catch (...) { + operation->OnPreparationException(std::current_exception()); + } + if (mode >= TOperationOptions::EStartOperationMode::AsyncStart) { + try { + operation->Start(); + } catch (...) { } + } + }; + if (options.StartOperationMode_ >= TOperationOptions::EStartOperationMode::SyncStart) { + prepareAndStart(); + WaitIfRequired(operation, client, options); + } else { + auto args = new TAsyncPrepareAndStartOperationArgs; + args->PrepareAndStart = std::move(prepareAndStart); + + TThread thread(TThread::TParams(SyncPrepareAndStartOperation, args).SetName("prepare and start operation")); + thread.Start(); + thread.Detach(); + } + return operation; +} + +void WaitIfRequired(const TOperationPtr& operation, const TClientPtr& client, const TOperationOptions& options) +{ + auto retryPolicy = client->GetRetryPolicy(); + auto context = client->GetContext(); + if (options.StartOperationMode_ >= TOperationOptions::EStartOperationMode::SyncStart) { + operation->GetStartedFuture().GetValueSync(); + } + if (options.StartOperationMode_ == TOperationOptions::EStartOperationMode::SyncWait) { + auto finishedFuture = operation->Watch(); + TWaitProxy::Get()->WaitFuture(finishedFuture); + finishedFuture.GetValue(); + if (context.Config->WriteStderrSuccessfulJobs) { + auto stderrs = GetJobsStderr(retryPolicy, context, operation->GetId()); + for (const auto& jobStderr : stderrs) { + if (!jobStderr.empty()) { + Cerr << jobStderr << '\n'; + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void ResetUseClientProtobuf(const char* methodName) +{ + Cerr << "WARNING! OPTION `TConfig::UseClientProtobuf' IS RESET TO `true'; " + << "IT CAN DETERIORATE YOUR CODE PERFORMANCE!!! DON'T USE DEPRECATED METHOD `" + << "TOperationIOSpec::" << methodName << "' TO AVOID THIS RESET" << Endl; + // Give users some time to contemplate about usage of deprecated functions. + Cerr << "Sleeping for 5 seconds..." << Endl; + Sleep(TDuration::Seconds(5)); + TConfig::Get()->UseClientProtobuf = true; +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +::TIntrusivePtr<INodeReaderImpl> CreateJobNodeReader(TRawTableReaderPtr rawTableReader) +{ + if (auto schema = NDetail::GetJobInputSkiffSchema()) { + return new NDetail::TSkiffTableReader(rawTableReader, schema); + } else { + return new TNodeTableReader(rawTableReader); + } +} + +::TIntrusivePtr<IYaMRReaderImpl> CreateJobYaMRReader(TRawTableReaderPtr rawTableReader) +{ + return new TYaMRTableReader(rawTableReader); +} + +::TIntrusivePtr<IProtoReaderImpl> CreateJobProtoReader(TRawTableReaderPtr rawTableReader) +{ + if (TConfig::Get()->UseClientProtobuf) { + return new TProtoTableReader( + rawTableReader, + GetJobInputDescriptors()); + } else { + return new TLenvalProtoTableReader( + rawTableReader, + GetJobInputDescriptors()); + } +} + +::TIntrusivePtr<INodeWriterImpl> CreateJobNodeWriter(THolder<IProxyOutput> rawJobWriter) +{ + return new TNodeTableWriter(std::move(rawJobWriter)); +} + +::TIntrusivePtr<IYaMRWriterImpl> CreateJobYaMRWriter(THolder<IProxyOutput> rawJobWriter) +{ + return new TYaMRTableWriter(std::move(rawJobWriter)); +} + +::TIntrusivePtr<IProtoWriterImpl> CreateJobProtoWriter(THolder<IProxyOutput> rawJobWriter) +{ + if (TConfig::Get()->UseClientProtobuf) { + return new TProtoTableWriter( + std::move(rawJobWriter), + GetJobOutputDescriptors()); + } else { + return new TLenvalProtoTableWriter( + std::move(rawJobWriter), + GetJobOutputDescriptors()); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/operation.h b/yt/cpp/mapreduce/client/operation.h new file mode 100644 index 00000000000..141161b0b72 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation.h @@ -0,0 +1,203 @@ +#pragma once + +#include "fwd.h" +#include "structured_table_formats.h" +#include "operation_preparer.h" + +#include <yt/cpp/mapreduce/http/fwd.h> + +#include <yt/cpp/mapreduce/interface/client.h> +#include <yt/cpp/mapreduce/interface/operation.h> +#include <yt/cpp/mapreduce/interface/retry_policy.h> + +#include <util/generic/ptr.h> +#include <util/generic/vector.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class TOperation + : public IOperation +{ +public: + class TOperationImpl; + +public: + explicit TOperation(TClientPtr client); + TOperation(TOperationId id, TClientPtr client); + virtual const TOperationId& GetId() const override; + virtual TString GetWebInterfaceUrl() const override; + + void OnPrepared(); + void SetDelayedStartFunction(std::function<TOperationId()> start); + virtual void Start() override; + void OnPreparationException(std::exception_ptr e); + virtual bool IsStarted() const override; + + virtual TString GetStatus() const override; + void OnStatusUpdated(const TString& newStatus); + + virtual ::NThreading::TFuture<void> GetPreparedFuture() override; + virtual ::NThreading::TFuture<void> GetStartedFuture() override; + virtual ::NThreading::TFuture<void> Watch() override; + + virtual TVector<TFailedJobInfo> GetFailedJobInfo(const TGetFailedJobInfoOptions& options = TGetFailedJobInfoOptions()) override; + virtual EOperationBriefState GetBriefState() override; + virtual TMaybe<TYtError> GetError() override; + virtual TJobStatistics GetJobStatistics() override; + virtual TMaybe<TOperationBriefProgress> GetBriefProgress() override; + virtual void AbortOperation() override; + virtual void CompleteOperation() override; + virtual void SuspendOperation(const TSuspendOperationOptions& options) override; + virtual void ResumeOperation(const TResumeOperationOptions& options) override; + virtual TOperationAttributes GetAttributes(const TGetOperationOptions& options) override; + virtual void UpdateParameters(const TUpdateOperationParametersOptions& options) override; + virtual TJobAttributes GetJob(const TJobId& jobId, const TGetJobOptions& options) override; + virtual TListJobsResult ListJobs(const TListJobsOptions& options) override; + +private: + TClientPtr Client_; + ::TIntrusivePtr<TOperationImpl> Impl_; +}; + +using TOperationPtr = ::TIntrusivePtr<TOperation>; + +//////////////////////////////////////////////////////////////////////////////// + +struct TSimpleOperationIo +{ + TVector<TRichYPath> Inputs; + TVector<TRichYPath> Outputs; + + TFormat InputFormat; + TFormat OutputFormat; + + TVector<TSmallJobFile> JobFiles; +}; + +TSimpleOperationIo CreateSimpleOperationIoHelper( + const IStructuredJob& structuredJob, + const TOperationPreparer& preparer, + const TOperationOptions& options, + TStructuredJobTableList structuredInputs, + TStructuredJobTableList structuredOutputs, + TUserJobFormatHints hints, + ENodeReaderFormat nodeReaderFormat, + const THashSet<TString>& columnsUsedInOperations); + +//////////////////////////////////////////////////////////////////////////////// + +void ExecuteMap( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMapOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& mapper, + const TOperationOptions& options); + +void ExecuteRawMap( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawMapOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& mapper, + const TOperationOptions& options); + +void ExecuteReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TReduceOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options); + +void ExecuteRawReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options); + +void ExecuteJoinReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TJoinReduceOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options); + +void ExecuteRawJoinReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawJoinReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options); + +void ExecuteMapReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMapReduceOperationSpec& spec, + const ::TIntrusivePtr<IStructuredJob>& mapper, + const ::TIntrusivePtr<IStructuredJob>& reduceCombiner, + const ::TIntrusivePtr<IStructuredJob>& reducer, + const TOperationOptions& options); + +void ExecuteRawMapReduce( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRawMapReduceOperationSpec& spec, + const ::TIntrusivePtr<IRawJob>& mapper, + const ::TIntrusivePtr<IRawJob>& reduceCombiner, + const ::TIntrusivePtr<IRawJob>& reducer, + const TOperationOptions& options); + +void ExecuteSort( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TSortOperationSpec& spec, + const TOperationOptions& options); + +void ExecuteMerge( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TMergeOperationSpec& spec, + const TOperationOptions& options); + +void ExecuteErase( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TEraseOperationSpec& spec, + const TOperationOptions& options); + +void ExecuteRemoteCopy( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TRemoteCopyOperationSpec& spec, + const TOperationOptions& options); + +void ExecuteVanilla( + const TOperationPtr& operation, + const TOperationPreparerPtr& preparer, + const TVanillaOperationSpec& spec, + const TOperationOptions& options); + +EOperationBriefState CheckOperation( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId); + +void WaitForOperation( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TOperationId& operationId); + +//////////////////////////////////////////////////////////////////////////////// + +::TIntrusivePtr<TOperation> ProcessOperation( + NYT::NDetail::TClientPtr client, + std::function<void()> prepare, + ::TIntrusivePtr<TOperation> operation, + const TOperationOptions& options); + +void WaitIfRequired(const TOperationPtr& operation, const TClientPtr& client, const TOperationOptions& options); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/operation_helpers.cpp b/yt/cpp/mapreduce/client/operation_helpers.cpp new file mode 100644 index 00000000000..abb21856622 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_helpers.cpp @@ -0,0 +1,91 @@ +#include "operation_helpers.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/interface/config.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> + +#include <util/string/builder.h> + +#include <util/system/mutex.h> +#include <util/system/rwlock.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +ui64 RoundUpFileSize(ui64 size) +{ + constexpr ui64 roundUpTo = 4ull << 10; + return (size + roundUpTo - 1) & ~(roundUpTo - 1); +} + +bool UseLocalModeOptimization(const TClientContext& context, const IClientRetryPolicyPtr& clientRetryPolicy) +{ + if (!context.Config->EnableLocalModeOptimization) { + return false; + } + + static THashMap<TString, bool> localModeMap; + static TRWMutex mutex; + + { + TReadGuard guard(mutex); + auto it = localModeMap.find(context.ServerName); + if (it != localModeMap.end()) { + return it->second; + } + } + + bool isLocalMode = false; + TString localModeAttr("//sys/@local_mode_fqdn"); + // We don't want to pollute logs with errors about failed request, + // so we check if path exists before getting it. + if (NRawClient::Exists(clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + TTransactionId(), + localModeAttr, + TExistsOptions().ReadFrom(EMasterReadKind::Cache))) + { + auto fqdnNode = NRawClient::TryGet( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + TTransactionId(), + localModeAttr, + TGetOptions().ReadFrom(EMasterReadKind::Cache)); + if (!fqdnNode.IsUndefined()) { + auto fqdn = fqdnNode.AsString(); + isLocalMode = (fqdn == TProcessState::Get()->FqdnHostName); + YT_LOG_DEBUG("Checking local mode; LocalModeFqdn: %v FqdnHostName: %v IsLocalMode: %v", + fqdn, + TProcessState::Get()->FqdnHostName, + isLocalMode ? "true" : "false"); + } + } + + { + TWriteGuard guard(mutex); + localModeMap[context.ServerName] = isLocalMode; + } + + return isLocalMode; +} + +TString GetOperationWebInterfaceUrl(TStringBuf serverName, TOperationId operationId) +{ + serverName.ChopSuffix(":80"); + serverName.ChopSuffix(".yt.yandex-team.ru"); + serverName.ChopSuffix(".yt.yandex.net"); + return ::TStringBuilder() << "https://yt.yandex-team.ru/" << serverName << + "/operations/" << GetGuidAsString(operationId); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/operation_helpers.h b/yt/cpp/mapreduce/client/operation_helpers.h new file mode 100644 index 00000000000..7fd2ffb0de7 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_helpers.h @@ -0,0 +1,20 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> +#include <yt/cpp/mapreduce/interface/fwd.h> + +#include <yt/cpp/mapreduce/http/fwd.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +ui64 RoundUpFileSize(ui64 size); + +bool UseLocalModeOptimization(const TClientContext& context, const IClientRetryPolicyPtr& clientRetryPolicy); + +TString GetOperationWebInterfaceUrl(TStringBuf serverName, TOperationId operationId); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/operation_preparer.cpp b/yt/cpp/mapreduce/client/operation_preparer.cpp new file mode 100644 index 00000000000..e06fac40614 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_preparer.cpp @@ -0,0 +1,881 @@ +#include "operation_preparer.h" + +#include "init.h" +#include "file_writer.h" +#include "operation.h" +#include "operation_helpers.h" +#include "operation_tracker.h" +#include "transaction.h" +#include "transaction_pinger.h" +#include "yt_poller.h" + +#include <yt/cpp/mapreduce/common/helpers.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> + +#include <yt/cpp/mapreduce/interface/error_codes.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <library/cpp/digest/md5/md5.h> + +#include <util/folder/path.h> + +#include <util/string/builder.h> + +#include <util/system/execpath.h> + +namespace NYT::NDetail { + +using namespace NRawClient; + +//////////////////////////////////////////////////////////////////////////////// + +class TWaitOperationStartPollerItem + : public IYtPollerItem +{ +public: + TWaitOperationStartPollerItem(TOperationId operationId, THolder<TPingableTransaction> transaction) + : OperationId_(operationId) + , Transaction_(std::move(transaction)) + { } + + void PrepareRequest(TRawBatchRequest* batchRequest) override + { + Future_ = batchRequest->GetOperation( + OperationId_, + TGetOperationOptions().AttributeFilter( + TOperationAttributeFilter().Add(EOperationAttribute::State))); + } + + EStatus OnRequestExecuted() override + { + try { + auto attributes = Future_.GetValue(); + Y_ENSURE(attributes.State.Defined()); + bool operationHasLockedFiles = + *attributes.State != "starting" && + *attributes.State != "pending" && + *attributes.State != "orphaned" && + *attributes.State != "waiting_for_agent" && + *attributes.State != "initializing"; + return operationHasLockedFiles ? EStatus::PollBreak : EStatus::PollContinue; + } catch (const TErrorResponse& e) { + YT_LOG_ERROR("get_operation request failed: %v (RequestId: %v)", + e.GetError().GetMessage(), + e.GetRequestId()); + return IsRetriable(e) ? PollContinue : PollBreak; + } catch (const std::exception& e) { + YT_LOG_ERROR("%v", e.what()); + return PollBreak; + } + } + + void OnItemDiscarded() override { + } + +private: + TOperationId OperationId_; + THolder<TPingableTransaction> Transaction_; + ::NThreading::TFuture<TOperationAttributes> Future_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TOperationForwardingRequestRetryPolicy + : public IRequestRetryPolicy +{ +public: + TOperationForwardingRequestRetryPolicy(const IRequestRetryPolicyPtr& underlying, const TOperationPtr& operation) + : Underlying_(underlying) + , Operation_(operation) + { } + + void NotifyNewAttempt() override + { + Underlying_->NotifyNewAttempt(); + } + + TMaybe<TDuration> OnGenericError(const std::exception& e) override + { + UpdateOperationStatus(e.what()); + return Underlying_->OnGenericError(e); + } + + TMaybe<TDuration> OnRetriableError(const TErrorResponse& e) override + { + auto msg = e.GetError().ShortDescription(); + UpdateOperationStatus(msg); + return Underlying_->OnRetriableError(e); + } + + void OnIgnoredError(const TErrorResponse& e) override + { + Underlying_->OnIgnoredError(e); + } + + TString GetAttemptDescription() const override + { + return Underlying_->GetAttemptDescription(); + } + +private: + void UpdateOperationStatus(TStringBuf err) + { + Y_VERIFY(Operation_); + Operation_->OnStatusUpdated( + ::TStringBuilder() << "Retriable error during operation start: " << err); + } + +private: + IRequestRetryPolicyPtr Underlying_; + TOperationPtr Operation_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +TOperationPreparer::TOperationPreparer(TClientPtr client, TTransactionId transactionId) + : Client_(std::move(client)) + , TransactionId_(transactionId) + , FileTransaction_(MakeHolder<TPingableTransaction>( + Client_->GetRetryPolicy(), + Client_->GetContext(), + TransactionId_, + Client_->GetTransactionPinger()->GetChildTxPinger(), + TStartTransactionOptions())) + , ClientRetryPolicy_(Client_->GetRetryPolicy()) + , PreparationId_(CreateGuidAsString()) +{ } + +const TClientContext& TOperationPreparer::GetContext() const +{ + return Client_->GetContext(); +} + +TTransactionId TOperationPreparer::GetTransactionId() const +{ + return TransactionId_; +} + +TClientPtr TOperationPreparer::GetClient() const +{ + return Client_; +} + +const TString& TOperationPreparer::GetPreparationId() const +{ + return PreparationId_; +} + +const IClientRetryPolicyPtr& TOperationPreparer::GetClientRetryPolicy() const +{ + return ClientRetryPolicy_; +} + +TOperationId TOperationPreparer::StartOperation( + TOperation* operation, + const TString& operationType, + const TNode& spec, + bool useStartOperationRequest) +{ + CheckValidity(); + + THttpHeader header("POST", (useStartOperationRequest ? "start_op" : operationType)); + if (useStartOperationRequest) { + header.AddParameter("operation_type", operationType); + } + header.AddTransactionId(TransactionId_); + header.AddMutationId(); + + auto ysonSpec = NodeToYsonString(spec); + auto responseInfo = RetryRequestWithPolicy( + ::MakeIntrusive<TOperationForwardingRequestRetryPolicy>( + ClientRetryPolicy_->CreatePolicyForStartOperationRequest(), + TOperationPtr(operation)), + GetContext(), + header, + ysonSpec); + TOperationId operationId = ParseGuidFromResponse(responseInfo.Response); + YT_LOG_DEBUG("Operation started (OperationId: %v; PreparationId: %v)", + operationId, + GetPreparationId()); + + YT_LOG_INFO("Operation %v started (%v): %v", + operationId, + operationType, + GetOperationWebInterfaceUrl(GetContext().ServerName, operationId)); + + TOperationExecutionTimeTracker::Get()->Start(operationId); + + Client_->GetYtPoller().Watch( + new TWaitOperationStartPollerItem(operationId, std::move(FileTransaction_))); + + return operationId; +} + +void TOperationPreparer::LockFiles(TVector<TRichYPath>* paths) +{ + CheckValidity(); + + TVector<::NThreading::TFuture<TLockId>> lockIdFutures; + lockIdFutures.reserve(paths->size()); + TRawBatchRequest lockRequest(GetContext().Config); + for (const auto& path : *paths) { + lockIdFutures.push_back(lockRequest.Lock( + FileTransaction_->GetId(), + path.Path_, + ELockMode::LM_SNAPSHOT, + TLockOptions().Waitable(true))); + } + ExecuteBatch(ClientRetryPolicy_->CreatePolicyForGenericRequest(), GetContext(), lockRequest); + + TVector<::NThreading::TFuture<TNode>> nodeIdFutures; + nodeIdFutures.reserve(paths->size()); + TRawBatchRequest getNodeIdRequest(GetContext().Config); + for (const auto& lockIdFuture : lockIdFutures) { + nodeIdFutures.push_back(getNodeIdRequest.Get( + FileTransaction_->GetId(), + ::TStringBuilder() << '#' << GetGuidAsString(lockIdFuture.GetValue()) << "/@node_id", + TGetOptions())); + } + ExecuteBatch(ClientRetryPolicy_->CreatePolicyForGenericRequest(), GetContext(), getNodeIdRequest); + + for (size_t i = 0; i != paths->size(); ++i) { + auto& richPath = (*paths)[i]; + richPath.OriginalPath(richPath.Path_); + richPath.Path("#" + nodeIdFutures[i].GetValue().AsString()); + YT_LOG_DEBUG("Locked file %v, new path is %v", + *richPath.OriginalPath_, + richPath.Path_); + } +} + +void TOperationPreparer::CheckValidity() const +{ + Y_ENSURE( + FileTransaction_, + "File transaction is already moved, are you trying to use preparer for more than one operation?"); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TRetryPolicyIgnoringLockConflicts + : public TAttemptLimitedRetryPolicy +{ +public: + using TAttemptLimitedRetryPolicy::TAttemptLimitedRetryPolicy; + using TAttemptLimitedRetryPolicy::OnGenericError; + + TMaybe<TDuration> OnRetriableError(const TErrorResponse& e) override + { + if (IsAttemptLimitExceeded()) { + return Nothing(); + } + if (e.IsConcurrentTransactionLockConflict()) { + return GetBackoffDuration(Config_); + } + return TAttemptLimitedRetryPolicy::OnRetriableError(e); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TFileToUpload + : public IItemToUpload +{ +public: + TFileToUpload(TString fileName, TMaybe<TString> md5) + : FileName_(std::move(fileName)) + , MD5_(std::move(md5)) + { } + + TString CalculateMD5() const override + { + if (MD5_) { + return *MD5_; + } + constexpr size_t md5Size = 32; + TString result; + result.ReserveAndResize(md5Size); + MD5::File(FileName_.data(), result.Detach()); + MD5_ = result; + return result; + } + + THolder<IInputStream> CreateInputStream() const override + { + return MakeHolder<TFileInput>(FileName_); + } + + TString GetDescription() const override + { + return FileName_; + } + + ui64 GetDataSize() const override + { + return GetFileLength(FileName_); + } + +private: + TString FileName_; + mutable TMaybe<TString> MD5_; +}; + +class TDataToUpload + : public IItemToUpload +{ +public: + TDataToUpload(TString data, TString description) + : Data_(std::move(data)) + , Description_(std::move(description)) + { } + + TString CalculateMD5() const override + { + constexpr size_t md5Size = 32; + TString result; + result.ReserveAndResize(md5Size); + MD5::Data(reinterpret_cast<const unsigned char*>(Data_.data()), Data_.size(), result.Detach()); + return result; + } + + THolder<IInputStream> CreateInputStream() const override + { + return MakeHolder<TMemoryInput>(Data_.data(), Data_.size()); + } + + TString GetDescription() const override + { + return Description_; + } + + ui64 GetDataSize() const override + { + return Data_.size(); + } + +private: + TString Data_; + TString Description_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +static const TString& GetPersistentExecPathMd5() +{ + static TString md5 = MD5::File(GetPersistentExecPath()); + return md5; +} + +static TMaybe<TSmallJobFile> GetJobState(const IJob& job) +{ + TString result; + { + TStringOutput output(result); + job.Save(output); + output.Finish(); + } + if (result.empty()) { + return Nothing(); + } else { + return TSmallJobFile{"jobstate", result}; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TJobPreparer::TJobPreparer( + TOperationPreparer& operationPreparer, + const TUserJobSpec& spec, + const IJob& job, + size_t outputTableCount, + const TVector<TSmallJobFile>& smallFileList, + const TOperationOptions& options) + : OperationPreparer_(operationPreparer) + , Spec_(spec) + , Options_(options) +{ + + CreateStorage(); + auto cypressFileList = CanonizeYPaths(/* retryPolicy */ nullptr, OperationPreparer_.GetContext(), spec.Files_); + + for (const auto& file : cypressFileList) { + UseFileInCypress(file); + } + for (const auto& localFile : spec.GetLocalFiles()) { + UploadLocalFile(std::get<0>(localFile), std::get<1>(localFile)); + } + auto jobStateSmallFile = GetJobState(job); + if (jobStateSmallFile) { + UploadSmallFile(*jobStateSmallFile); + } + for (const auto& smallFile : smallFileList) { + UploadSmallFile(smallFile); + } + + if (auto commandJob = dynamic_cast<const ICommandJob*>(&job)) { + ClassName_ = TJobFactory::Get()->GetJobName(&job); + Command_ = commandJob->GetCommand(); + } else { + PrepareJobBinary(job, outputTableCount, jobStateSmallFile.Defined()); + } + + operationPreparer.LockFiles(&CachedFiles_); +} + +TVector<TRichYPath> TJobPreparer::GetFiles() const +{ + TVector<TRichYPath> allFiles = CypressFiles_; + allFiles.insert(allFiles.end(), CachedFiles_.begin(), CachedFiles_.end()); + return allFiles; +} + +const TString& TJobPreparer::GetClassName() const +{ + return ClassName_; +} + +const TString& TJobPreparer::GetCommand() const +{ + return Command_; +} + +const TUserJobSpec& TJobPreparer::GetSpec() const +{ + return Spec_; +} + +bool TJobPreparer::ShouldMountSandbox() const +{ + return OperationPreparer_.GetContext().Config->MountSandboxInTmpfs || Options_.MountSandboxInTmpfs_; +} + +ui64 TJobPreparer::GetTotalFileSize() const +{ + return TotalFileSize_; +} + +TString TJobPreparer::GetFileStorage() const +{ + return Options_.FileStorage_ ? + *Options_.FileStorage_ : + OperationPreparer_.GetContext().Config->RemoteTempFilesDirectory; +} + +TYPath TJobPreparer::GetCachePath() const +{ + return AddPathPrefix( + ::TStringBuilder() << GetFileStorage() << "/new_cache", + OperationPreparer_.GetContext().Config->Prefix); +} + +void TJobPreparer::CreateStorage() const +{ + Create( + OperationPreparer_.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + OperationPreparer_.GetContext(), + Options_.FileStorageTransactionId_, + GetCachePath(), + NT_MAP, + TCreateOptions() + .IgnoreExisting(true) + .Recursive(true)); +} + +int TJobPreparer::GetFileCacheReplicationFactor() const +{ + if (IsLocalMode()) { + return 1; + } else { + return OperationPreparer_.GetContext().Config->FileCacheReplicationFactor; + } +} + +void TJobPreparer::CreateFileInCypress(const TString& path) const +{ + auto attributes = TNode()("replication_factor", GetFileCacheReplicationFactor()); + if (Options_.FileExpirationTimeout_) { + attributes["expiration_timeout"] = Options_.FileExpirationTimeout_->MilliSeconds(); + } + + Create( + OperationPreparer_.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + OperationPreparer_.GetContext(), + Options_.FileStorageTransactionId_, + path, + NT_FILE, + TCreateOptions() + .IgnoreExisting(true) + .Recursive(true) + .Attributes(attributes) + ); +} + +TString TJobPreparer::PutFileToCypressCache( + const TString& path, + const TString& md5Signature, + TTransactionId transactionId) const +{ + constexpr ui32 LockConflictRetryCount = 30; + auto retryPolicy = MakeIntrusive<TRetryPolicyIgnoringLockConflicts>( + LockConflictRetryCount, + OperationPreparer_.GetContext().Config); + + auto putFileToCacheOptions = TPutFileToCacheOptions(); + if (Options_.FileExpirationTimeout_) { + putFileToCacheOptions.PreserveExpirationTimeout(true); + } + + auto cachePath = PutFileToCache( + retryPolicy, + OperationPreparer_.GetContext(), + transactionId, + path, + md5Signature, + GetCachePath(), + putFileToCacheOptions); + + Remove( + OperationPreparer_.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + OperationPreparer_.GetContext(), + transactionId, + path, + TRemoveOptions().Force(true)); + + return cachePath; +} + +TMaybe<TString> TJobPreparer::GetItemFromCypressCache(const TString& md5Signature, const TString& fileName) const +{ + constexpr ui32 LockConflictRetryCount = 30; + auto retryPolicy = MakeIntrusive<TRetryPolicyIgnoringLockConflicts>( + LockConflictRetryCount, + OperationPreparer_.GetContext().Config); + auto maybePath = GetFileFromCache( + retryPolicy, + OperationPreparer_.GetContext(), + TTransactionId(), + md5Signature, + GetCachePath(), + TGetFileFromCacheOptions()); + if (maybePath) { + YT_LOG_DEBUG("File is already in cache (FileName: %v)", + fileName, + *maybePath); + } + return maybePath; +} + +TDuration TJobPreparer::GetWaitForUploadTimeout(const IItemToUpload& itemToUpload) const +{ + const TDuration extraTime = OperationPreparer_.GetContext().Config->WaitLockPollInterval + + TDuration::MilliSeconds(100); + const double dataSizeGb = static_cast<double>(itemToUpload.GetDataSize()) / 1_GB; + return extraTime + dataSizeGb * OperationPreparer_.GetContext().Config->CacheLockTimeoutPerGb; +} + +TString TJobPreparer::UploadToRandomPath(const IItemToUpload& itemToUpload) const +{ + TString uniquePath = AddPathPrefix( + ::TStringBuilder() << GetFileStorage() << "/cpp_" << CreateGuidAsString(), + OperationPreparer_.GetContext().Config->Prefix); + YT_LOG_INFO("Uploading file to random cypress path (FileName: %v; CypressPath: %v; PreparationId: %v)", + itemToUpload.GetDescription(), + uniquePath, + OperationPreparer_.GetPreparationId()); + + CreateFileInCypress(uniquePath); + + { + TFileWriter writer( + uniquePath, + OperationPreparer_.GetClientRetryPolicy(), + OperationPreparer_.GetClient()->GetTransactionPinger(), + OperationPreparer_.GetContext(), + Options_.FileStorageTransactionId_, + TFileWriterOptions().ComputeMD5(true)); + itemToUpload.CreateInputStream()->ReadAll(writer); + writer.Finish(); + } + return uniquePath; +} + +TMaybe<TString> TJobPreparer::TryUploadWithDeduplication(const IItemToUpload& itemToUpload) const +{ + const auto md5Signature = itemToUpload.CalculateMD5(); + + auto fileName = ::TStringBuilder() << GetFileStorage() << "/cpp_md5_" << md5Signature; + if (OperationPreparer_.GetContext().Config->CacheUploadDeduplicationMode == EUploadDeduplicationMode::Host) { + fileName << "_" << MD5::Data(TProcessState::Get()->FqdnHostName); + } + TString cypressPath = AddPathPrefix(fileName, OperationPreparer_.GetContext().Config->Prefix); + + CreateFileInCypress(cypressPath); + + auto uploadTx = MakeIntrusive<TTransaction>( + OperationPreparer_.GetClient(), + OperationPreparer_.GetContext(), + TTransactionId(), + TStartTransactionOptions()); + + ILockPtr lock; + try { + lock = uploadTx->Lock(cypressPath, ELockMode::LM_EXCLUSIVE, TLockOptions().Waitable(true)); + } catch (const TErrorResponse& e) { + if (e.IsResolveError()) { + // If the node doesn't exist, it must be removed by concurrent uploading process. + // Let's try to find it in the cache. + return GetItemFromCypressCache(md5Signature, itemToUpload.GetDescription()); + } + throw; + } + + auto waitTimeout = GetWaitForUploadTimeout(itemToUpload); + YT_LOG_DEBUG("Waiting for the lock on file (FileName: %v; CypressPath: %v; LockTimeout: %v)", + itemToUpload.GetDescription(), + cypressPath, + waitTimeout); + + if (!TWaitProxy::Get()->WaitFuture(lock->GetAcquiredFuture(), waitTimeout)) { + YT_LOG_DEBUG("Waiting for the lock timed out. Fallback to random path uploading (FileName: %v; CypressPath: %v)", + itemToUpload.GetDescription(), + cypressPath); + return Nothing(); + } + + YT_LOG_DEBUG("Exclusive lock successfully acquired (FileName: %v; CypressPath: %v)", + itemToUpload.GetDescription(), + cypressPath); + + // Ensure that this process is the first to take a lock. + if (auto cachedItemPath = GetItemFromCypressCache(md5Signature, itemToUpload.GetDescription())) { + return *cachedItemPath; + } + + YT_LOG_INFO("Uploading file to cypress (FileName: %v; CypressPath: %v; PreparationId: %v)", + itemToUpload.GetDescription(), + cypressPath, + OperationPreparer_.GetPreparationId()); + + { + auto writer = uploadTx->CreateFileWriter(cypressPath, TFileWriterOptions().ComputeMD5(true)); + YT_VERIFY(writer); + itemToUpload.CreateInputStream()->ReadAll(*writer); + writer->Finish(); + } + + auto path = PutFileToCypressCache(cypressPath, md5Signature, uploadTx->GetId()); + + uploadTx->Commit(); + return path; +} + +TString TJobPreparer::UploadToCacheUsingApi(const IItemToUpload& itemToUpload) const +{ + auto md5Signature = itemToUpload.CalculateMD5(); + Y_VERIFY(md5Signature.size() == 32); + + if (auto cachedItemPath = GetItemFromCypressCache(md5Signature, itemToUpload.GetDescription())) { + return *cachedItemPath; + } + + YT_LOG_INFO("File not found in cache; uploading to cypress (FileName: %v; PreparationId: %v)", + itemToUpload.GetDescription(), + OperationPreparer_.GetPreparationId()); + + if (OperationPreparer_.GetContext().Config->CacheUploadDeduplicationMode != EUploadDeduplicationMode::Disabled) { + if (auto path = TryUploadWithDeduplication(itemToUpload)) { + return *path; + } + } + + auto path = UploadToRandomPath(itemToUpload); + return PutFileToCypressCache(path, md5Signature, Options_.FileStorageTransactionId_); +} + +TString TJobPreparer::UploadToCache(const IItemToUpload& itemToUpload) const +{ + YT_LOG_INFO("Uploading file (FileName: %v; PreparationId: %v)", + itemToUpload.GetDescription(), + OperationPreparer_.GetPreparationId()); + + TString result; + switch (Options_.FileCacheMode_) { + case TOperationOptions::EFileCacheMode::ApiCommandBased: + Y_ENSURE_EX(Options_.FileStorageTransactionId_.IsEmpty(), TApiUsageError() << + "Default cache mode (API command-based) doesn't allow non-default 'FileStorageTransactionId_'"); + result = UploadToCacheUsingApi(itemToUpload); + break; + case TOperationOptions::EFileCacheMode::CachelessRandomPathUpload: + result = UploadToRandomPath(itemToUpload); + break; + default: + Y_FAIL("Unknown file cache mode: %d", static_cast<int>(Options_.FileCacheMode_)); + } + + YT_LOG_INFO("Complete uploading file (FileName: %v; PreparationId: %v)", + itemToUpload.GetDescription(), + OperationPreparer_.GetPreparationId()); + + return result; +} + +void TJobPreparer::UseFileInCypress(const TRichYPath& file) +{ + if (!Exists( + OperationPreparer_.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + OperationPreparer_.GetContext(), + file.TransactionId_.GetOrElse(OperationPreparer_.GetTransactionId()), + file.Path_)) + { + ythrow yexception() << "File " << file.Path_ << " does not exist"; + } + + if (ShouldMountSandbox()) { + auto size = Get( + OperationPreparer_.GetClientRetryPolicy()->CreatePolicyForGenericRequest(), + OperationPreparer_.GetContext(), + file.TransactionId_.GetOrElse(OperationPreparer_.GetTransactionId()), + file.Path_ + "/@uncompressed_data_size") + .AsInt64(); + + TotalFileSize_ += RoundUpFileSize(static_cast<ui64>(size)); + } + CypressFiles_.push_back(file); +} + +void TJobPreparer::UploadLocalFile( + const TLocalFilePath& localPath, + const TAddLocalFileOptions& options, + bool isApiFile) +{ + TFsPath fsPath(localPath); + fsPath.CheckExists(); + + TFileStat stat; + fsPath.Stat(stat); + + bool isExecutable = stat.Mode & (S_IXUSR | S_IXGRP | S_IXOTH); + auto cachePath = UploadToCache(TFileToUpload(localPath, options.MD5CheckSum_)); + + TRichYPath cypressPath; + if (isApiFile) { + cypressPath = OperationPreparer_.GetContext().Config->ApiFilePathOptions; + } + cypressPath.Path(cachePath).FileName(options.PathInJob_.GetOrElse(fsPath.Basename())); + if (isExecutable) { + cypressPath.Executable(true); + } + if (options.BypassArtifactCache_) { + cypressPath.BypassArtifactCache(*options.BypassArtifactCache_); + } + + if (ShouldMountSandbox()) { + TotalFileSize_ += RoundUpFileSize(stat.Size); + } + + CachedFiles_.push_back(cypressPath); +} + +void TJobPreparer::UploadBinary(const TJobBinaryConfig& jobBinary) +{ + if (std::holds_alternative<TJobBinaryLocalPath>(jobBinary)) { + auto binaryLocalPath = std::get<TJobBinaryLocalPath>(jobBinary); + auto opts = TAddLocalFileOptions().PathInJob("cppbinary"); + if (binaryLocalPath.MD5CheckSum) { + opts.MD5CheckSum(*binaryLocalPath.MD5CheckSum); + } + UploadLocalFile(binaryLocalPath.Path, opts, /* isApiFile */ true); + } else if (std::holds_alternative<TJobBinaryCypressPath>(jobBinary)) { + auto binaryCypressPath = std::get<TJobBinaryCypressPath>(jobBinary); + TRichYPath ytPath = OperationPreparer_.GetContext().Config->ApiFilePathOptions; + ytPath.Path(binaryCypressPath.Path); + if (binaryCypressPath.TransactionId) { + ytPath.TransactionId(*binaryCypressPath.TransactionId); + } + UseFileInCypress(ytPath.FileName("cppbinary").Executable(true)); + } else { + Y_FAIL("%s", (::TStringBuilder() << "Unexpected jobBinary tag: " << jobBinary.index()).data()); + } +} + +void TJobPreparer::UploadSmallFile(const TSmallJobFile& smallFile) +{ + auto cachePath = UploadToCache(TDataToUpload(smallFile.Data, smallFile.FileName + " [generated-file]")); + auto path = OperationPreparer_.GetContext().Config->ApiFilePathOptions; + CachedFiles_.push_back(path.Path(cachePath).FileName(smallFile.FileName)); + if (ShouldMountSandbox()) { + TotalFileSize_ += RoundUpFileSize(smallFile.Data.size()); + } +} + +bool TJobPreparer::IsLocalMode() const +{ + return UseLocalModeOptimization(OperationPreparer_.GetContext(), OperationPreparer_.GetClientRetryPolicy()); +} + +void TJobPreparer::PrepareJobBinary(const IJob& job, int outputTableCount, bool hasState) +{ + auto jobBinary = TJobBinaryConfig(); + if (!std::holds_alternative<TJobBinaryDefault>(Spec_.GetJobBinary())) { + jobBinary = Spec_.GetJobBinary(); + } + TString binaryPathInsideJob; + if (std::holds_alternative<TJobBinaryDefault>(jobBinary)) { + if (GetInitStatus() != EInitStatus::FullInitialization) { + ythrow yexception() << "NYT::Initialize() must be called prior to any operation"; + } + + const bool isLocalMode = IsLocalMode(); + const TMaybe<TString> md5 = !isLocalMode ? MakeMaybe(GetPersistentExecPathMd5()) : Nothing(); + jobBinary = TJobBinaryLocalPath{GetPersistentExecPath(), md5}; + + if (isLocalMode) { + binaryPathInsideJob = GetExecPath(); + } + } else if (std::holds_alternative<TJobBinaryLocalPath>(jobBinary)) { + const bool isLocalMode = IsLocalMode(); + if (isLocalMode) { + binaryPathInsideJob = TFsPath(std::get<TJobBinaryLocalPath>(jobBinary).Path).RealPath(); + } + } + Y_ASSERT(!std::holds_alternative<TJobBinaryDefault>(jobBinary)); + + // binaryPathInsideJob is only set when LocalModeOptimization option is on, so upload is not needed + if (!binaryPathInsideJob) { + binaryPathInsideJob = "./cppbinary"; + UploadBinary(jobBinary); + } + + TString jobCommandPrefix = Options_.JobCommandPrefix_; + if (!Spec_.JobCommandPrefix_.empty()) { + jobCommandPrefix = Spec_.JobCommandPrefix_; + } + + TString jobCommandSuffix = Options_.JobCommandSuffix_; + if (!Spec_.JobCommandSuffix_.empty()) { + jobCommandSuffix = Spec_.JobCommandSuffix_; + } + + ClassName_ = TJobFactory::Get()->GetJobName(&job); + + auto jobArguments = TNode::CreateMap(); + jobArguments["job_name"] = ClassName_; + jobArguments["output_table_count"] = static_cast<i64>(outputTableCount); + jobArguments["has_state"] = hasState; + Spec_.AddEnvironment("YT_JOB_ARGUMENTS", NodeToYsonString(jobArguments)); + + Command_ = ::TStringBuilder() << + jobCommandPrefix << + (OperationPreparer_.GetContext().Config->UseClientProtobuf ? "YT_USE_CLIENT_PROTOBUF=1" : "YT_USE_CLIENT_PROTOBUF=0") << " " << + binaryPathInsideJob << + jobCommandSuffix; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/operation_preparer.h b/yt/cpp/mapreduce/client/operation_preparer.h new file mode 100644 index 00000000000..7ced54e3b54 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_preparer.h @@ -0,0 +1,129 @@ +#pragma once + +#include "client.h" +#include "structured_table_formats.h" + +#include <yt/cpp/mapreduce/interface/operation.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class TOperation; + +class TOperationPreparer + : public TThrRefBase +{ +public: + TOperationPreparer(TClientPtr client, TTransactionId transactionId); + + const TClientContext& GetContext() const; + TTransactionId GetTransactionId() const; + ITransactionPingerPtr GetTransactionPinger() const; + TClientPtr GetClient() const; + + const TString& GetPreparationId() const; + + void LockFiles(TVector<TRichYPath>* paths); + + TOperationId StartOperation( + TOperation* operation, + const TString& operationType, + const TNode& spec, + bool useStartOperationRequest = false); + + const IClientRetryPolicyPtr& GetClientRetryPolicy() const; + +private: + TClientPtr Client_; + TTransactionId TransactionId_; + THolder<TPingableTransaction> FileTransaction_; + IClientRetryPolicyPtr ClientRetryPolicy_; + const TString PreparationId_; + +private: + void CheckValidity() const; +}; + +using TOperationPreparerPtr = ::TIntrusivePtr<TOperationPreparer>; + +//////////////////////////////////////////////////////////////////////////////// + +struct IItemToUpload +{ + virtual ~IItemToUpload() = default; + + virtual TString CalculateMD5() const = 0; + virtual THolder<IInputStream> CreateInputStream() const = 0; + virtual TString GetDescription() const = 0; + virtual ui64 GetDataSize() const = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TJobPreparer + : private TNonCopyable +{ +public: + TJobPreparer( + TOperationPreparer& operationPreparer, + const TUserJobSpec& spec, + const IJob& job, + size_t outputTableCount, + const TVector<TSmallJobFile>& smallFileList, + const TOperationOptions& options); + + TVector<TRichYPath> GetFiles() const; + const TString& GetClassName() const; + const TString& GetCommand() const; + const TUserJobSpec& GetSpec() const; + bool ShouldMountSandbox() const; + ui64 GetTotalFileSize() const; + +private: + TOperationPreparer& OperationPreparer_; + TUserJobSpec Spec_; + TOperationOptions Options_; + + TVector<TRichYPath> CypressFiles_; + TVector<TRichYPath> CachedFiles_; + + TString ClassName_; + TString Command_; + ui64 TotalFileSize_ = 0; + +private: + TString GetFileStorage() const; + TYPath GetCachePath() const; + + bool IsLocalMode() const; + int GetFileCacheReplicationFactor() const; + + void CreateStorage() const; + + void CreateFileInCypress(const TString& path) const; + TString PutFileToCypressCache(const TString& path, const TString& md5Signature, TTransactionId transactionId) const; + TMaybe<TString> GetItemFromCypressCache(const TString& md5Signature, const TString& fileName) const; + + TDuration GetWaitForUploadTimeout(const IItemToUpload& itemToUpload) const; + TString UploadToRandomPath(const IItemToUpload& itemToUpload) const; + TString UploadToCacheUsingApi(const IItemToUpload& itemToUpload) const; + TMaybe<TString> TryUploadWithDeduplication(const IItemToUpload& itemToUpload) const; + TString UploadToCache(const IItemToUpload& itemToUpload) const; + + void UseFileInCypress(const TRichYPath& file); + + void UploadLocalFile( + const TLocalFilePath& localPath, + const TAddLocalFileOptions& options, + bool isApiFile = false); + + void UploadBinary(const TJobBinaryConfig& jobBinary); + void UploadSmallFile(const TSmallJobFile& smallFile); + + void PrepareJobBinary(const IJob& job, int outputTableCount, bool hasState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/operation_tracker.cpp b/yt/cpp/mapreduce/client/operation_tracker.cpp new file mode 100644 index 00000000000..56623e99277 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_tracker.cpp @@ -0,0 +1,34 @@ +#include "operation_tracker.h" + +#include <yt/cpp/mapreduce/interface/config.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +void TOperationExecutionTimeTracker::Start(const TOperationId& operationId) { + with_lock(Lock_) { + StartTimes_[operationId] = TInstant::Now(); + } +} + +TMaybe<TDuration> TOperationExecutionTimeTracker::Finish(const TOperationId& operationId) { + TDuration duration; + with_lock(Lock_) { + auto i = StartTimes_.find(operationId); + if (i == StartTimes_.end()) { + return Nothing(); + } + duration = TInstant::Now() - i->second; + StartTimes_.erase(i); + } + return duration; +} + +TOperationExecutionTimeTracker* TOperationExecutionTimeTracker::Get() { + return Singleton<TOperationExecutionTimeTracker>(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/operation_tracker.h b/yt/cpp/mapreduce/client/operation_tracker.h new file mode 100644 index 00000000000..9f1504ea910 --- /dev/null +++ b/yt/cpp/mapreduce/client/operation_tracker.h @@ -0,0 +1,27 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/operation.h> + +#include <util/datetime/base.h> +#include <util/generic/hash.h> +#include <util/generic/maybe.h> +#include <util/system/mutex.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TOperationExecutionTimeTracker { +public: + void Start(const TOperationId& operationId); + TMaybe<TDuration> Finish(const TOperationId& operationId); + static TOperationExecutionTimeTracker* Get(); + +private: + THashMap<TOperationId, TInstant> StartTimes_; + TMutex Lock_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/prepare_operation.cpp b/yt/cpp/mapreduce/client/prepare_operation.cpp new file mode 100644 index 00000000000..7f772dc99ad --- /dev/null +++ b/yt/cpp/mapreduce/client/prepare_operation.cpp @@ -0,0 +1,286 @@ +#include "prepare_operation.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/interface/serialize.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> + +#include <library/cpp/iterator/functools.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +TOperationPreparationContext::TOperationPreparationContext( + const TStructuredJobTableList& structuredInputs, + const TStructuredJobTableList& structuredOutputs, + const TClientContext& context, + const IClientRetryPolicyPtr& retryPolicy, + TTransactionId transactionId) + : Context_(context) + , RetryPolicy_(retryPolicy) + , TransactionId_(transactionId) + , InputSchemas_(structuredInputs.size()) + , InputSchemasLoaded_(structuredInputs.size(), false) +{ + Inputs_.reserve(structuredInputs.size()); + for (const auto& input : structuredInputs) { + Inputs_.push_back(input.RichYPath); + } + Outputs_.reserve(structuredOutputs.size()); + for (const auto& output : structuredOutputs) { + Outputs_.push_back(output.RichYPath); + } +} + +TOperationPreparationContext::TOperationPreparationContext( + TVector<TRichYPath> inputs, + TVector<TRichYPath> outputs, + const TClientContext& context, + const IClientRetryPolicyPtr& retryPolicy, + TTransactionId transactionId) + : Context_(context) + , RetryPolicy_(retryPolicy) + , TransactionId_(transactionId) + , InputSchemas_(inputs.size()) + , InputSchemasLoaded_(inputs.size(), false) +{ + Inputs_.reserve(inputs.size()); + for (auto& input : inputs) { + Inputs_.push_back(std::move(input)); + } + Outputs_.reserve(outputs.size()); + for (const auto& output : outputs) { + Outputs_.push_back(std::move(output)); + } +} + +int TOperationPreparationContext::GetInputCount() const +{ + return static_cast<int>(Inputs_.size()); +} + +int TOperationPreparationContext::GetOutputCount() const +{ + return static_cast<int>(Outputs_.size()); +} + +const TVector<TTableSchema>& TOperationPreparationContext::GetInputSchemas() const +{ + TVector<::NThreading::TFuture<TNode>> schemaFutures; + NRawClient::TRawBatchRequest batch(Context_.Config); + for (int tableIndex = 0; tableIndex < static_cast<int>(InputSchemas_.size()); ++tableIndex) { + if (InputSchemasLoaded_[tableIndex]) { + schemaFutures.emplace_back(); + continue; + } + Y_VERIFY(Inputs_[tableIndex]); + schemaFutures.push_back(batch.Get(TransactionId_, Inputs_[tableIndex]->Path_ + "/@schema", TGetOptions{})); + } + + NRawClient::ExecuteBatch( + RetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + batch); + + for (int tableIndex = 0; tableIndex < static_cast<int>(InputSchemas_.size()); ++tableIndex) { + if (schemaFutures[tableIndex].Initialized()) { + Deserialize(InputSchemas_[tableIndex], schemaFutures[tableIndex].ExtractValueSync()); + } + } + + return InputSchemas_; +} + +const TTableSchema& TOperationPreparationContext::GetInputSchema(int index) const +{ + auto& schema = InputSchemas_[index]; + if (!InputSchemasLoaded_[index]) { + Y_VERIFY(Inputs_[index]); + auto schemaNode = NRawClient::Get( + RetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + TransactionId_, + Inputs_[index]->Path_ + "/@schema"); + Deserialize(schema, schemaNode); + } + return schema; +} + +TMaybe<TYPath> TOperationPreparationContext::GetInputPath(int index) const +{ + Y_VERIFY(index < static_cast<int>(Inputs_.size())); + if (Inputs_[index]) { + return Inputs_[index]->Path_; + } + return Nothing(); +} + +TMaybe<TYPath> TOperationPreparationContext::GetOutputPath(int index) const +{ + Y_VERIFY(index < static_cast<int>(Outputs_.size())); + if (Outputs_[index]) { + return Outputs_[index]->Path_; + } + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// + +TSpeculativeOperationPreparationContext::TSpeculativeOperationPreparationContext( + const TVector<TTableSchema>& previousResult, + TStructuredJobTableList inputs, + TStructuredJobTableList outputs) + : InputSchemas_(previousResult) + , Inputs_(std::move(inputs)) + , Outputs_(std::move(outputs)) +{ + Y_VERIFY(Inputs_.size() == previousResult.size()); +} + +int TSpeculativeOperationPreparationContext::GetInputCount() const +{ + return static_cast<int>(Inputs_.size()); +} + +int TSpeculativeOperationPreparationContext::GetOutputCount() const +{ + return static_cast<int>(Outputs_.size()); +} + +const TVector<TTableSchema>& TSpeculativeOperationPreparationContext::GetInputSchemas() const +{ + return InputSchemas_; +} + +const TTableSchema& TSpeculativeOperationPreparationContext::GetInputSchema(int index) const +{ + Y_VERIFY(index < static_cast<int>(InputSchemas_.size())); + return InputSchemas_[index]; +} + +TMaybe<TYPath> TSpeculativeOperationPreparationContext::GetInputPath(int index) const +{ + Y_VERIFY(index < static_cast<int>(Inputs_.size())); + if (Inputs_[index].RichYPath) { + return Inputs_[index].RichYPath->Path_; + } + return Nothing(); +} + +TMaybe<TYPath> TSpeculativeOperationPreparationContext::GetOutputPath(int index) const +{ + Y_VERIFY(index < static_cast<int>(Outputs_.size())); + if (Outputs_[index].RichYPath) { + return Outputs_[index].RichYPath->Path_; + } + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// + +static void FixInputTable(TRichYPath& table, int index, const TJobOperationPreparer& preparer) +{ + const auto& columnRenamings = preparer.GetInputColumnRenamings(); + const auto& columnFilters = preparer.GetInputColumnFilters(); + + if (!columnRenamings[index].empty()) { + table.RenameColumns(columnRenamings[index]); + } + if (columnFilters[index]) { + table.Columns(*columnFilters[index]); + } +} + +static void FixInputTable(TStructuredJobTable& table, int index, const TJobOperationPreparer& preparer) +{ + const auto& inputDescriptions = preparer.GetInputDescriptions(); + + if (inputDescriptions[index] && std::holds_alternative<TUnspecifiedTableStructure>(table.Description)) { + table.Description = *inputDescriptions[index]; + } + if (table.RichYPath) { + FixInputTable(*table.RichYPath, index, preparer); + } +} + +static void FixOutputTable(TRichYPath& /* table */, int /* index */, const TJobOperationPreparer& /* preparer */) +{ } + +static void FixOutputTable(TStructuredJobTable& table, int index, const TJobOperationPreparer& preparer) +{ + const auto& outputDescriptions = preparer.GetOutputDescriptions(); + + if (outputDescriptions[index] && std::holds_alternative<TUnspecifiedTableStructure>(table.Description)) { + table.Description = *outputDescriptions[index]; + } + if (table.RichYPath) { + FixOutputTable(*table.RichYPath, index, preparer); + } +} + +template <typename TTables> +TVector<TTableSchema> PrepareOperation( + const IJob& job, + const IOperationPreparationContext& context, + TTables* inputsPtr, + TTables* outputsPtr, + TUserJobFormatHints& hints) +{ + TJobOperationPreparer preparer(context); + job.PrepareOperation(context, preparer); + preparer.Finish(); + + if (inputsPtr) { + auto& inputs = *inputsPtr; + for (int i = 0; i < static_cast<int>(inputs.size()); ++i) { + FixInputTable(inputs[i], i, preparer); + } + } + + if (outputsPtr) { + auto& outputs = *outputsPtr; + for (int i = 0; i < static_cast<int>(outputs.size()); ++i) { + FixOutputTable(outputs[i], i, preparer); + } + } + + auto applyPatch = [](TMaybe<TFormatHints>& origin, const TMaybe<TFormatHints>& patch) { + if (origin) { + if (patch) { + origin->Merge(*patch); + } + } else { + origin = patch; + } + }; + + auto preparerHints = preparer.GetFormatHints(); + applyPatch(preparerHints.InputFormatHints_, hints.InputFormatHints_); + applyPatch(preparerHints.OutputFormatHints_, hints.OutputFormatHints_); + hints = std::move(preparerHints); + + return preparer.GetOutputSchemas(); +} + +template +TVector<TTableSchema> PrepareOperation<TStructuredJobTableList>( + const IJob& job, + const IOperationPreparationContext& context, + TStructuredJobTableList* inputsPtr, + TStructuredJobTableList* outputsPtr, + TUserJobFormatHints& hints); + +template +TVector<TTableSchema> PrepareOperation<TVector<TRichYPath>>( + const IJob& job, + const IOperationPreparationContext& context, + TVector<TRichYPath>* inputsPtr, + TVector<TRichYPath>* outputsPtr, + TUserJobFormatHints& hints); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/prepare_operation.h b/yt/cpp/mapreduce/client/prepare_operation.h new file mode 100644 index 00000000000..3b64aa28565 --- /dev/null +++ b/yt/cpp/mapreduce/client/prepare_operation.h @@ -0,0 +1,93 @@ +#pragma once + +#include "structured_table_formats.h" + +#include <yt/cpp/mapreduce/interface/operation.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +class TOperationPreparationContext + : public IOperationPreparationContext +{ +public: + TOperationPreparationContext( + const TStructuredJobTableList& structuredInputs, + const TStructuredJobTableList& structuredOutputs, + const TClientContext& context, + const IClientRetryPolicyPtr& retryPolicy, + TTransactionId transactionId); + + TOperationPreparationContext( + TVector<TRichYPath> inputs, + TVector<TRichYPath> outputs, + const TClientContext& context, + const IClientRetryPolicyPtr& retryPolicy, + TTransactionId transactionId); + + int GetInputCount() const override; + int GetOutputCount() const override; + + const TVector<TTableSchema>& GetInputSchemas() const override; + const TTableSchema& GetInputSchema(int index) const override; + + TMaybe<TYPath> GetInputPath(int index) const override; + TMaybe<TYPath> GetOutputPath(int index) const override; + +private: + TVector<TMaybe<TRichYPath>> Inputs_; + TVector<TMaybe<TRichYPath>> Outputs_; + const TClientContext& Context_; + const IClientRetryPolicyPtr RetryPolicy_; + TTransactionId TransactionId_; + + mutable TVector<TTableSchema> InputSchemas_; + mutable TVector<bool> InputSchemasLoaded_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TSpeculativeOperationPreparationContext + : public IOperationPreparationContext +{ +public: + TSpeculativeOperationPreparationContext( + const TVector<TTableSchema>& previousResult, + TStructuredJobTableList inputs, + TStructuredJobTableList outputs); + + int GetInputCount() const override; + int GetOutputCount() const override; + + const TVector<TTableSchema>& GetInputSchemas() const override; + const TTableSchema& GetInputSchema(int index) const override; + + TMaybe<TYPath> GetInputPath(int index) const override; + TMaybe<TYPath> GetOutputPath(int index) const override; + +private: + TVector<TTableSchema> InputSchemas_; + TStructuredJobTableList Inputs_; + TStructuredJobTableList Outputs_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <typename TTables> +TVector<TTableSchema> PrepareOperation( + const IJob& job, + const IOperationPreparationContext& context, + TTables* inputsPtr, + TTables* outputsPtr, + TUserJobFormatHints& hints); + +//////////////////////////////////////////////////////////////////////////////// + +TJobOperationPreparer GetOperationPreparer( + const IJob& job, + const IOperationPreparationContext& context); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/py_helpers.cpp b/yt/cpp/mapreduce/client/py_helpers.cpp new file mode 100644 index 00000000000..3072449866e --- /dev/null +++ b/yt/cpp/mapreduce/client/py_helpers.cpp @@ -0,0 +1,112 @@ +#include "py_helpers.h" + +#include "client.h" +#include "operation.h" +#include "transaction.h" + +#include <yt/cpp/mapreduce/interface/client.h> +#include <yt/cpp/mapreduce/interface/fluent.h> + +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/helpers.h> + +#include <library/cpp/yson/node/node_io.h> + +#include <util/generic/hash_set.h> + +namespace NYT { + +using namespace NDetail; + +//////////////////////////////////////////////////////////////////////////////// + +IStructuredJobPtr ConstructJob(const TString& jobName, const TString& state) +{ + auto node = TNode(); + if (!state.empty()) { + node = NodeFromYsonString(state); + } + return TJobFactory::Get()->GetConstructingFunction(jobName.data())(node); +} + +TString GetJobStateString(const IStructuredJob& job) +{ + TString result; + { + TStringOutput output(result); + job.Save(output); + output.Finish(); + } + return result; +} + +TStructuredJobTableList NodeToStructuredTablePaths(const TNode& node, const TOperationPreparer& preparer) +{ + int intermediateTableCount = 0; + TVector<TRichYPath> paths; + for (const auto& inputNode : node.AsList()) { + if (inputNode.IsNull()) { + ++intermediateTableCount; + } else { + paths.emplace_back(inputNode.AsString()); + } + } + paths = NRawClient::CanonizeYPaths(/* retryPolicy */ nullptr, preparer.GetContext(), paths); + TStructuredJobTableList result(intermediateTableCount, TStructuredJobTable::Intermediate(TUnspecifiedTableStructure())); + for (const auto& path : paths) { + result.emplace_back(TStructuredJobTable{TUnspecifiedTableStructure(), path}); + } + return result; +} + +TString GetIOInfo( + const IStructuredJob& job, + const TCreateClientOptions& options, + const TString& cluster, + const TString& transactionId, + const TString& inputPaths, + const TString& outputPaths, + const TString& neededColumns) +{ + auto client = NDetail::CreateClientImpl(cluster, options); + TOperationPreparer preparer(client, GetGuid(transactionId)); + + auto structuredInputs = NodeToStructuredTablePaths(NodeFromYsonString(inputPaths), preparer); + auto structuredOutputs = NodeToStructuredTablePaths(NodeFromYsonString(outputPaths), preparer); + + auto neededColumnsNode = NodeFromYsonString(neededColumns); + THashSet<TString> columnsUsedInOperations; + for (const auto& columnNode : neededColumnsNode.AsList()) { + columnsUsedInOperations.insert(columnNode.AsString()); + } + + auto operationIo = CreateSimpleOperationIoHelper( + job, + preparer, + TOperationOptions(), + std::move(structuredInputs), + std::move(structuredOutputs), + TUserJobFormatHints(), + ENodeReaderFormat::Yson, + columnsUsedInOperations); + + return BuildYsonStringFluently().BeginMap() + .Item("input_format").Value(operationIo.InputFormat.Config) + .Item("output_format").Value(operationIo.OutputFormat.Config) + .Item("input_table_paths").List(operationIo.Inputs) + .Item("output_table_paths").List(operationIo.Outputs) + .Item("small_files").DoListFor( + operationIo.JobFiles.begin(), + operationIo.JobFiles.end(), + [] (TFluentList fluent, auto fileIt) { + fluent.Item().BeginMap() + .Item("file_name").Value(fileIt->FileName) + .Item("data").Value(fileIt->Data) + .EndMap(); + }) + .EndMap(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/py_helpers.h b/yt/cpp/mapreduce/client/py_helpers.h new file mode 100644 index 00000000000..85aa0a93f34 --- /dev/null +++ b/yt/cpp/mapreduce/client/py_helpers.h @@ -0,0 +1,25 @@ +#include <yt/cpp/mapreduce/interface/client_method_options.h> +#include <yt/cpp/mapreduce/interface/operation.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +using IStructuredJobPtr = TIntrusiveConstPtr<IStructuredJob>; + +IStructuredJobPtr ConstructJob(const TString& jobName, const TString& state); + +TString GetJobStateString(const IStructuredJob& job); + +TString GetIOInfo( + const IStructuredJob& job, + const TCreateClientOptions& options, + const TString& cluster, + const TString& transactionId, + const TString& inputPaths, + const TString& outputPaths, + const TString& neededColumns); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp b/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp new file mode 100644 index 00000000000..b4e4975d7f3 --- /dev/null +++ b/yt/cpp/mapreduce/client/retry_heavy_write_request.cpp @@ -0,0 +1,87 @@ +#include "retry_heavy_write_request.h" + +#include "transaction.h" +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/tvm.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/http/helpers.h> +#include <yt/cpp/mapreduce/http/http_client.h> +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +namespace NYT { + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +void RetryHeavyWriteRequest( + const IClientRetryPolicyPtr& clientRetryPolicy, + const ITransactionPingerPtr& transactionPinger, + const TClientContext& context, + const TTransactionId& parentId, + THttpHeader& header, + std::function<THolder<IInputStream>()> streamMaker) +{ + int retryCount = context.Config->RetryCount; + if (context.ServiceTicketAuth) { + header.SetServiceTicket(context.ServiceTicketAuth->Ptr->IssueServiceTicket()); + } else { + header.SetToken(context.Token); + } + + for (int attempt = 0; attempt < retryCount; ++attempt) { + TPingableTransaction attemptTx(clientRetryPolicy, context, parentId, transactionPinger->GetChildTxPinger(), TStartTransactionOptions()); + + auto input = streamMaker(); + TString requestId; + + try { + auto hostName = GetProxyForHeavyRequest(context); + requestId = CreateGuidAsString(); + + header.AddTransactionId(attemptTx.GetId(), /* overwrite = */ true); + header.SetRequestCompression(ToString(context.Config->ContentEncoding)); + + auto request = context.HttpClient->StartRequest(GetFullUrl(hostName, context, header), requestId, header); + TransferData(input.Get(), request->GetStream()); + request->Finish()->GetResponse(); + } catch (TErrorResponse& e) { + YT_LOG_ERROR("RSP %v - attempt %v failed", + requestId, + attempt); + + if (!IsRetriable(e) || attempt + 1 == retryCount) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(GetBackoffDuration(e, context.Config)); + continue; + + } catch (std::exception& e) { + YT_LOG_ERROR("RSP %v - %v - attempt %v failed", + requestId, + e.what(), + attempt); + + if (attempt + 1 == retryCount) { + throw; + } + NDetail::TWaitProxy::Get()->Sleep(GetBackoffDuration(e, context.Config)); + continue; + } + + attemptTx.Commit(); + return; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/retry_heavy_write_request.h b/yt/cpp/mapreduce/client/retry_heavy_write_request.h new file mode 100644 index 00000000000..647cad302ca --- /dev/null +++ b/yt/cpp/mapreduce/client/retry_heavy_write_request.h @@ -0,0 +1,21 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/http/requests.h> + +namespace NYT { + +/////////////////////////////////////////////////////////////////////////////// + +void RetryHeavyWriteRequest( + const IClientRetryPolicyPtr& clientRetryPolicy, + const ITransactionPingerPtr& transactionPinger, + const TClientContext& context, + const TTransactionId& parentId, + THttpHeader& header, + std::function<THolder<IInputStream>()> streamMaker); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/retry_transaction.h b/yt/cpp/mapreduce/client/retry_transaction.h new file mode 100644 index 00000000000..5220c222b84 --- /dev/null +++ b/yt/cpp/mapreduce/client/retry_transaction.h @@ -0,0 +1,71 @@ +#pragma once + +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/client/client.h> + +#include <yt/cpp/mapreduce/common/wait_proxy.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +namespace NYT::NDetail { + +template <typename TResult> +TResult RetryTransactionWithPolicy( + const TClientBasePtr& client, + std::function<TResult(ITransactionPtr)> func, + IRequestRetryPolicyPtr retryPolicy) +{ + if (!retryPolicy) { + retryPolicy = CreateDefaultRequestRetryPolicy(client->GetContext().Config); + } + + while (true) { + try { + retryPolicy->NotifyNewAttempt(); + auto transaction = client->StartTransaction(TStartTransactionOptions()); + if constexpr (std::is_same<TResult, void>::value) { + func(transaction); + transaction->Commit(); + return; + } else { + auto result = func(transaction); + transaction->Commit(); + return result; + } + } catch (const TErrorResponse& e) { + YT_LOG_ERROR("Retry failed %v - %v", + e.GetError().GetMessage(), + retryPolicy->GetAttemptDescription()); + + if (!IsRetriable(e)) { + throw; + } + + auto maybeRetryTimeout = retryPolicy->OnRetriableError(e); + if (maybeRetryTimeout) { + TWaitProxy::Get()->Sleep(*maybeRetryTimeout); + } else { + throw; + } + } catch (const std::exception& e) { + YT_LOG_ERROR("Retry failed %v - %v", + e.what(), + retryPolicy->GetAttemptDescription()); + + if (!IsRetriable(e)) { + throw; + } + + auto maybeRetryTimeout = retryPolicy->OnGenericError(e); + if (maybeRetryTimeout) { + TWaitProxy::Get()->Sleep(*maybeRetryTimeout); + } else { + throw; + } + } + } +} + +} // namespace NYT::NDetail diff --git a/yt/cpp/mapreduce/client/retryful_writer.cpp b/yt/cpp/mapreduce/client/retryful_writer.cpp new file mode 100644 index 00000000000..12b2939ffad --- /dev/null +++ b/yt/cpp/mapreduce/client/retryful_writer.cpp @@ -0,0 +1,163 @@ +#include "retryful_writer.h" + +#include "retry_heavy_write_request.h" + +#include <yt/cpp/mapreduce/http/requests.h> + +#include <yt/cpp/mapreduce/interface/errors.h> +#include <yt/cpp/mapreduce/interface/finish_or_die.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <util/generic/size_literals.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TRetryfulWriter::~TRetryfulWriter() +{ + NDetail::FinishOrDie(this, "TRetryfulWriter"); +} + +void TRetryfulWriter::CheckWriterState() +{ + switch (WriterState_) { + case Ok: + break; + case Completed: + ythrow TApiUsageError() << "Cannot use table writer that is finished"; + case Error: + ythrow TApiUsageError() << "Cannot use table writer that finished with error"; + } +} + +void TRetryfulWriter::NotifyRowEnd() +{ + CheckWriterState(); + if (Buffer_.Size() >= BufferSize_) { + FlushBuffer(false); + } +} + +void TRetryfulWriter::DoWrite(const void* buf, size_t len) +{ + CheckWriterState(); + while (Buffer_.Size() + len > Buffer_.Capacity()) { + Buffer_.Reserve(Buffer_.Capacity() * 2); + } + Buffer_.Append(static_cast<const char*>(buf), len); +} + +void TRetryfulWriter::DoFinish() +{ + if (WriterState_ != Ok) { + return; + } + FlushBuffer(true); + if (Started_) { + FilledBuffers_.Stop(); + Thread_.Join(); + } + if (Exception_) { + WriterState_ = Error; + std::rethrow_exception(Exception_); + } + if (WriteTransaction_) { + WriteTransaction_->Commit(); + } + WriterState_ = Completed; +} + +void TRetryfulWriter::FlushBuffer(bool lastBlock) +{ + if (!Started_) { + if (lastBlock) { + try { + Send(Buffer_); + } catch (...) { + WriterState_ = Error; + throw; + } + return; + } else { + Started_ = true; + Thread_.Start(); + } + } + + auto emptyBuffer = EmptyBuffers_.Pop(); + if (!emptyBuffer) { + WriterState_ = Error; + std::rethrow_exception(Exception_); + } + FilledBuffers_.Push(std::move(Buffer_)); + Buffer_ = std::move(emptyBuffer.GetRef()); +} + +void TRetryfulWriter::Send(const TBuffer& buffer) +{ + THttpHeader header("PUT", Command_); + header.SetInputFormat(Format_); + header.MergeParameters(Parameters_); + + auto streamMaker = [&buffer] () { + return MakeHolder<TBufferInput>(buffer); + }; + + auto transactionId = (WriteTransaction_ ? WriteTransaction_->GetId() : ParentTransactionId_); + RetryHeavyWriteRequest(ClientRetryPolicy_, TransactionPinger_, Context_, transactionId, header, streamMaker); + + Parameters_ = SecondaryParameters_; // all blocks except the first one are appended +} + +void TRetryfulWriter::SendThread() +{ + while (auto maybeBuffer = FilledBuffers_.Pop()) { + auto& buffer = maybeBuffer.GetRef(); + try { + Send(buffer); + } catch (const std::exception&) { + Exception_ = std::current_exception(); + EmptyBuffers_.Stop(); + break; + } + buffer.Clear(); + EmptyBuffers_.Push(std::move(buffer)); + } +} + +void* TRetryfulWriter::SendThread(void* opaque) +{ + static_cast<TRetryfulWriter*>(opaque)->SendThread(); + return nullptr; +} + +void TRetryfulWriter::Abort() +{ + if (Started_) { + FilledBuffers_.Stop(); + Thread_.Join(); + } + if (WriteTransaction_) { + WriteTransaction_->Abort(); + } + WriterState_ = Completed; +} + +size_t TRetryfulWriter::GetBufferSize(const TMaybe<TWriterOptions>& writerOptions) +{ + auto retryBlockSize = TMaybe<size_t>(); + if (writerOptions) { + if (writerOptions->RetryBlockSize_) { + retryBlockSize = *writerOptions->RetryBlockSize_; + } else if (writerOptions->DesiredChunkSize_) { + retryBlockSize = *writerOptions->DesiredChunkSize_; + } + } + return retryBlockSize.GetOrElse(64_MB); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/retryful_writer.h b/yt/cpp/mapreduce/client/retryful_writer.h new file mode 100644 index 00000000000..38e351977d6 --- /dev/null +++ b/yt/cpp/mapreduce/client/retryful_writer.h @@ -0,0 +1,130 @@ +#pragma once + +#include "transaction.h" +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/http/http.h> +#include <yt/cpp/mapreduce/interface/common.h> +#include <yt/cpp/mapreduce/interface/io.h> +#include <yt/cpp/mapreduce/io/helpers.h> +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <library/cpp/threading/blocking_queue/blocking_queue.h> + +#include <util/stream/output.h> +#include <util/generic/buffer.h> +#include <util/stream/buffer.h> +#include <util/system/thread.h> +#include <util/system/event.h> + +#include <atomic> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TRetryfulWriter + : public TRawTableWriter +{ +public: + template <class TWriterOptions> + TRetryfulWriter( + IClientRetryPolicyPtr clientRetryPolicy, + ITransactionPingerPtr transactionPinger, + const TClientContext& context, + const TTransactionId& parentId, + const TString& command, + const TMaybe<TFormat>& format, + const TRichYPath& path, + const TWriterOptions& options) + : ClientRetryPolicy_(std::move(clientRetryPolicy)) + , TransactionPinger_(std::move(transactionPinger)) + , Context_(context) + , Command_(command) + , Format_(format) + , BufferSize_(GetBufferSize(options.WriterOptions_)) + , ParentTransactionId_(parentId) + , WriteTransaction_() + , FilledBuffers_(2) + , EmptyBuffers_(2) + , Buffer_(BufferSize_ * 2) + , Thread_(TThread::TParams{SendThread, this}.SetName("retryful_writer")) + { + Parameters_ = FormIORequestParameters(path, options); + + auto secondaryPath = path; + secondaryPath.Append_ = true; + secondaryPath.Schema_.Clear(); + secondaryPath.CompressionCodec_.Clear(); + secondaryPath.ErasureCodec_.Clear(); + secondaryPath.OptimizeFor_.Clear(); + SecondaryParameters_ = FormIORequestParameters(secondaryPath, options); + + if (options.CreateTransaction_) { + WriteTransaction_.ConstructInPlace(ClientRetryPolicy_, context, parentId, TransactionPinger_->GetChildTxPinger(), TStartTransactionOptions()); + auto append = path.Append_.GetOrElse(false); + auto lockMode = (append ? LM_SHARED : LM_EXCLUSIVE); + NDetail::NRawClient::Lock(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, WriteTransaction_->GetId(), path.Path_, lockMode); + } + + EmptyBuffers_.Push(TBuffer(BufferSize_ * 2)); + } + + ~TRetryfulWriter() override; + void NotifyRowEnd() override; + void Abort() override; + + size_t GetRetryBlockRemainingSize() const + { + return (BufferSize_ > Buffer_.size()) ? (BufferSize_ - Buffer_.size()) : 0; + } + +protected: + void DoWrite(const void* buf, size_t len) override; + void DoFinish() override; + +private: + static size_t GetBufferSize(const TMaybe<TWriterOptions>& writerOptions); + +private: + const IClientRetryPolicyPtr ClientRetryPolicy_; + const ITransactionPingerPtr TransactionPinger_; + const TClientContext Context_; + TString Command_; + TMaybe<TFormat> Format_; + const size_t BufferSize_; + + TNode Parameters_; + TNode SecondaryParameters_; + + TTransactionId ParentTransactionId_; + TMaybe<TPingableTransaction> WriteTransaction_; + + ::NThreading::TBlockingQueue<TBuffer> FilledBuffers_; + ::NThreading::TBlockingQueue<TBuffer> EmptyBuffers_; + + TBuffer Buffer_; + + TThread Thread_; + bool Started_ = false; + std::exception_ptr Exception_ = nullptr; + + enum EWriterState { + Ok, + Completed, + Error, + } WriterState_ = Ok; + +private: + void FlushBuffer(bool lastBlock); + void Send(const TBuffer& buffer); + void CheckWriterState(); + + void SendThread(); + static void* SendThread(void* opaque); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} diff --git a/yt/cpp/mapreduce/client/retryless_writer.cpp b/yt/cpp/mapreduce/client/retryless_writer.cpp new file mode 100644 index 00000000000..4c25c1a1dde --- /dev/null +++ b/yt/cpp/mapreduce/client/retryless_writer.cpp @@ -0,0 +1,45 @@ +#include "retryless_writer.h" + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TRetrylessWriter::~TRetrylessWriter() +{ + NDetail::FinishOrDie(this, "TRetrylessWriter"); +} + +void TRetrylessWriter::DoFinish() +{ + if (!Running_) { + return; + } + Running_ = false; + + BufferedOutput_->Finish(); + Request_->Finish()->GetResponse(); +} + +void TRetrylessWriter::DoWrite(const void* buf, size_t len) +{ + try { + BufferedOutput_->Write(buf, len); + } catch (...) { + Running_ = false; + throw; + } +} + +void TRetrylessWriter::NotifyRowEnd() +{ } + +void TRetrylessWriter::Abort() +{ + Running_ = false; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/retryless_writer.h b/yt/cpp/mapreduce/client/retryless_writer.h new file mode 100644 index 00000000000..baf49a258f6 --- /dev/null +++ b/yt/cpp/mapreduce/client/retryless_writer.h @@ -0,0 +1,73 @@ +#pragma once + +#include "transaction.h" + +#include <yt/cpp/mapreduce/http/helpers.h> +#include <yt/cpp/mapreduce/http/http.h> +#include <yt/cpp/mapreduce/http/http_client.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/common.h> +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/io.h> +#include <yt/cpp/mapreduce/interface/tvm.h> + +#include <yt/cpp/mapreduce/io/helpers.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <util/stream/buffered.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TRetrylessWriter + : public TRawTableWriter +{ +public: + template <class TWriterOptions> + TRetrylessWriter( + const TClientContext& context, + const TTransactionId& parentId, + const TString& command, + const TMaybe<TFormat>& format, + const TRichYPath& path, + size_t bufferSize, + const TWriterOptions& options) + { + THttpHeader header("PUT", command); + header.SetInputFormat(format); + header.MergeParameters(FormIORequestParameters(path, options)); + header.AddTransactionId(parentId); + header.SetRequestCompression(ToString(context.Config->ContentEncoding)); + if (context.ServiceTicketAuth) { + header.SetServiceTicket(context.ServiceTicketAuth->Ptr->IssueServiceTicket()); + } else { + header.SetToken(context.Token); + } + + TString requestId = CreateGuidAsString(); + + auto hostName = GetProxyForHeavyRequest(context); + Request_ = context.HttpClient->StartRequest(GetFullUrl(hostName, context, header), requestId, header); + BufferedOutput_.Reset(new TBufferedOutput(Request_->GetStream(), bufferSize)); + } + + ~TRetrylessWriter() override; + void NotifyRowEnd() override; + void Abort() override; + +protected: + void DoWrite(const void* buf, size_t len) override; + void DoFinish() override; + +private: + bool Running_ = true; + NHttpClient::IHttpRequestPtr Request_; + THolder<TBufferedOutput> BufferedOutput_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/skiff.cpp b/yt/cpp/mapreduce/client/skiff.cpp new file mode 100644 index 00000000000..67a0f960ae2 --- /dev/null +++ b/yt/cpp/mapreduce/client/skiff.cpp @@ -0,0 +1,396 @@ +#include "skiff.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/http/retry_request.h> +#include <yt/cpp/mapreduce/http/requests.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/common.h> +#include <yt/cpp/mapreduce/interface/serialize.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <library/cpp/yson/node/node_builder.h> +#include <library/cpp/yson/node/node_io.h> + +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <yt/cpp/mapreduce/skiff/skiff_schema.h> + +#include <library/cpp/yson/consumer.h> +#include <library/cpp/yson/writer.h> + +#include <util/string/cast.h> +#include <util/stream/str.h> +#include <util/stream/file.h> +#include <util/folder/path.h> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +using ::ToString; + +//////////////////////////////////////////////////////////////////////////////// + +static NSkiff::TSkiffSchemaPtr ReadSkiffSchema(const TString& fileName) +{ + if (!TFsPath(fileName).Exists()) { + return nullptr; + } + TIFStream input(fileName); + NSkiff::TSkiffSchemaPtr schema; + Deserialize(schema, NodeFromYsonStream(&input)); + return schema; +} + +NSkiff::TSkiffSchemaPtr GetJobInputSkiffSchema() +{ + return ReadSkiffSchema("skiff_input"); +} + +NSkiff::EWireType ValueTypeToSkiffType(EValueType valueType) +{ + using NSkiff::EWireType; + switch (valueType) { + case VT_INT64: + case VT_INT32: + case VT_INT16: + case VT_INT8: + return EWireType::Int64; + + case VT_UINT64: + case VT_UINT32: + case VT_UINT16: + case VT_UINT8: + return EWireType::Uint64; + + case VT_DOUBLE: + case VT_FLOAT: + return EWireType::Double; + + case VT_BOOLEAN: + return EWireType::Boolean; + + case VT_STRING: + case VT_UTF8: + case VT_JSON: + return EWireType::String32; + + case VT_ANY: + return EWireType::Yson32; + + case VT_NULL: + case VT_VOID: + return EWireType::Nothing; + + case VT_DATE: + case VT_DATETIME: + case VT_TIMESTAMP: + return EWireType::Uint64; + + case VT_INTERVAL: + return EWireType::Int64; + }; + ythrow yexception() << "Cannot convert EValueType '" << valueType << "' to NSkiff::EWireType"; +} + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TTableSchema& schema, + const TCreateSkiffSchemaOptions& options) +{ + using namespace NSkiff; + + Y_ENSURE(schema.Strict(), "Cannot create Skiff schema for non-strict table schema"); + TVector<TSkiffSchemaPtr> skiffColumns; + for (const auto& column: schema.Columns()) { + TSkiffSchemaPtr skiffColumn; + if (column.Type() == VT_ANY && *column.TypeV3() != *NTi::Optional(NTi::Yson())) { + // We ignore all complex types until YT-12717 is done. + return nullptr; + } + if (column.Required() || NTi::IsSingular(column.TypeV3()->GetTypeName())) { + skiffColumn = CreateSimpleTypeSchema(ValueTypeToSkiffType(column.Type())); + } else { + skiffColumn = CreateVariant8Schema({ + CreateSimpleTypeSchema(EWireType::Nothing), + CreateSimpleTypeSchema(ValueTypeToSkiffType(column.Type()))}); + } + if (options.RenameColumns_) { + auto maybeName = options.RenameColumns_->find(column.Name()); + skiffColumn->SetName(maybeName == options.RenameColumns_->end() ? column.Name() : maybeName->second); + } else { + skiffColumn->SetName(column.Name()); + } + skiffColumns.push_back(skiffColumn); + } + + if (options.HasKeySwitch_) { + skiffColumns.push_back( + CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$key_switch")); + } + if (options.HasRangeIndex_) { + skiffColumns.push_back( + CreateVariant8Schema({ + CreateSimpleTypeSchema(EWireType::Nothing), + CreateSimpleTypeSchema(EWireType::Int64)}) + ->SetName("$range_index")); + } + + skiffColumns.push_back( + CreateVariant8Schema({ + CreateSimpleTypeSchema(EWireType::Nothing), + CreateSimpleTypeSchema(EWireType::Int64)}) + ->SetName("$row_index")); + + return CreateTupleSchema(std::move(skiffColumns)); +} + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TNode& schemaNode, + const TCreateSkiffSchemaOptions& options) +{ + TTableSchema schema; + Deserialize(schema, schemaNode); + return CreateSkiffSchema(schema, options); +} + +void Serialize(const NSkiff::TSkiffSchemaPtr& schema, NYson::IYsonConsumer* consumer) +{ + consumer->OnBeginMap(); + if (schema->GetName().size() > 0) { + consumer->OnKeyedItem("name"); + consumer->OnStringScalar(schema->GetName()); + } + consumer->OnKeyedItem("wire_type"); + consumer->OnStringScalar(ToString(schema->GetWireType())); + if (schema->GetChildren().size() > 0) { + consumer->OnKeyedItem("children"); + consumer->OnBeginList(); + for (const auto& child : schema->GetChildren()) { + consumer->OnListItem(); + Serialize(child, consumer); + } + consumer->OnEndList(); + } + consumer->OnEndMap(); +} + +void Deserialize(NSkiff::TSkiffSchemaPtr& schema, const TNode& node) +{ + using namespace NSkiff; + + static auto createSchema = [](EWireType wireType, TVector<TSkiffSchemaPtr>&& children) -> TSkiffSchemaPtr { + switch (wireType) { + case EWireType::Tuple: + return CreateTupleSchema(std::move(children)); + case EWireType::Variant8: + return CreateVariant8Schema(std::move(children)); + case EWireType::Variant16: + return CreateVariant16Schema(std::move(children)); + case EWireType::RepeatedVariant8: + return CreateRepeatedVariant8Schema(std::move(children)); + case EWireType::RepeatedVariant16: + return CreateRepeatedVariant16Schema(std::move(children)); + default: + return CreateSimpleTypeSchema(wireType); + } + }; + + const auto& map = node.AsMap(); + const auto* wireTypePtr = map.FindPtr("wire_type"); + Y_ENSURE(wireTypePtr, "'wire_type' is a required key"); + auto wireType = FromString<NSkiff::EWireType>(wireTypePtr->AsString()); + + const auto* childrenPtr = map.FindPtr("children"); + Y_ENSURE(NSkiff::IsSimpleType(wireType) || childrenPtr, + "'children' key is required for complex node '" << wireType << "'"); + TVector<TSkiffSchemaPtr> children; + if (childrenPtr) { + for (const auto& childNode : childrenPtr->AsList()) { + TSkiffSchemaPtr childSchema; + Deserialize(childSchema, childNode); + children.push_back(std::move(childSchema)); + } + } + + schema = createSchema(wireType, std::move(children)); + + const auto* namePtr = map.FindPtr("name"); + if (namePtr) { + schema->SetName(namePtr->AsString()); + } +} + +TFormat CreateSkiffFormat(const NSkiff::TSkiffSchemaPtr& schema) { + Y_ENSURE(schema->GetWireType() == NSkiff::EWireType::Variant16, + "Bad wire type for schema; expected 'variant16', got " << schema->GetWireType()); + + THashMap< + NSkiff::TSkiffSchemaPtr, + size_t, + NSkiff::TSkiffSchemaPtrHasher, + NSkiff::TSkiffSchemaPtrEqual> schemasMap; + size_t tableIndex = 0; + auto config = TNode("skiff"); + config.Attributes()["table_skiff_schemas"] = TNode::CreateList(); + + for (const auto& schemaChild : schema->GetChildren()) { + auto [iter, inserted] = schemasMap.emplace(schemaChild, tableIndex); + size_t currentIndex; + if (inserted) { + currentIndex = tableIndex; + ++tableIndex; + } else { + currentIndex = iter->second; + } + config.Attributes()["table_skiff_schemas"].Add("$" + ToString(currentIndex)); + } + + config.Attributes()["skiff_schema_registry"] = TNode::CreateMap(); + + for (const auto& [tableSchema, index] : schemasMap) { + TNode node; + TNodeBuilder nodeBuilder(&node); + Serialize(tableSchema, &nodeBuilder); + config.Attributes()["skiff_schema_registry"][ToString(index)] = std::move(node); + } + + return TFormat(config); +} + +NSkiff::TSkiffSchemaPtr CreateSkiffSchemaIfNecessary( + const TClientContext& context, + const IClientRetryPolicyPtr& clientRetryPolicy, + const TTransactionId& transactionId, + ENodeReaderFormat nodeReaderFormat, + const TVector<TRichYPath>& tablePaths, + const TCreateSkiffSchemaOptions& options) +{ + if (nodeReaderFormat == ENodeReaderFormat::Yson) { + return nullptr; + } + + for (const auto& path : tablePaths) { + if (path.Columns_) { + switch (nodeReaderFormat) { + case ENodeReaderFormat::Skiff: + ythrow TApiUsageError() << "Cannot use Skiff format with column selectors"; + case ENodeReaderFormat::Auto: + return nullptr; + default: + Y_FAIL("Unexpected node reader format: %d", static_cast<int>(nodeReaderFormat)); + } + } + } + + auto nodes = NRawClient::BatchTransform( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + NRawClient::CanonizeYPaths(clientRetryPolicy->CreatePolicyForGenericRequest(), context, tablePaths), + [&] (TRawBatchRequest& batch, const TRichYPath& path) { + auto getOptions = TGetOptions() + .AttributeFilter( + TAttributeFilter() + .AddAttribute("schema") + .AddAttribute("dynamic") + .AddAttribute("type") + ); + return batch.Get(transactionId, path.Path_, getOptions); + }); + + TVector<NSkiff::TSkiffSchemaPtr> schemas; + for (size_t tableIndex = 0; tableIndex < nodes.size(); ++tableIndex) { + const auto& tablePath = tablePaths[tableIndex].Path_; + const auto& attributes = nodes[tableIndex].GetAttributes(); + Y_ENSURE_EX(attributes["type"] == TNode("table"), + TApiUsageError() << "Operation input path " << tablePath << " is not a table"); + bool dynamic = attributes["dynamic"].AsBool(); + bool strict = attributes["schema"].GetAttributes()["strict"].AsBool(); + switch (nodeReaderFormat) { + case ENodeReaderFormat::Skiff: + Y_ENSURE_EX(strict, + TApiUsageError() << "Cannot use skiff format for table with non-strict schema '" << tablePath << "'"); + Y_ENSURE_EX(!dynamic, + TApiUsageError() << "Cannot use skiff format for dynamic table '" << tablePath << "'"); + break; + case ENodeReaderFormat::Auto: + if (dynamic || !strict) { + YT_LOG_DEBUG("Cannot use skiff format for table '%v' as it is dynamic or has non-strict schema", + tablePath); + return nullptr; + } + break; + default: + Y_FAIL("Unexpected node reader format: %d", static_cast<int>(nodeReaderFormat)); + } + + NSkiff::TSkiffSchemaPtr curSkiffSchema; + if (tablePaths[tableIndex].RenameColumns_) { + auto customOptions = options; + customOptions.RenameColumns(*tablePaths[tableIndex].RenameColumns_); + curSkiffSchema = CreateSkiffSchema(attributes["schema"], customOptions); + } else { + curSkiffSchema = CreateSkiffSchema(attributes["schema"], options); + } + + if (!curSkiffSchema) { + return nullptr; + } + schemas.push_back(curSkiffSchema); + } + return NSkiff::CreateVariant16Schema(std::move(schemas)); +} + +//////////////////////////////////////////////////////////////////////////////// + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TVector<NSkiff::TSkiffSchemaPtr>& tableSchemas, + const TCreateSkiffSchemaOptions& options +) { + constexpr auto KEY_SWITCH_COLUMN = "$key_switch"; + constexpr auto ROW_INDEX_COLUMN = "$row_index"; + constexpr auto RANGE_INDEX_COLUMN = "$range_index"; + + TVector<NSkiff::TSkiffSchemaPtr> schemas; + schemas.reserve(tableSchemas.size()); + + for (const auto& tableSchema : tableSchemas) { + Y_ENSURE(tableSchema->GetWireType() == NSkiff::EWireType::Tuple, + "Expected 'tuple' wire type for table schema, got '" << tableSchema->GetWireType() << "'"); + + const auto& children = tableSchema->GetChildren(); + NSkiff::TSkiffSchemaList columns; + + columns.reserve(children.size() + 3); + if (options.HasKeySwitch_) { + columns.push_back( + CreateSimpleTypeSchema(NSkiff::EWireType::Boolean)->SetName(KEY_SWITCH_COLUMN)); + } + columns.push_back( + NSkiff::CreateVariant8Schema({ + CreateSimpleTypeSchema(NSkiff::EWireType::Nothing), + CreateSimpleTypeSchema(NSkiff::EWireType::Int64)}) + ->SetName(ROW_INDEX_COLUMN)); + if (options.HasRangeIndex_) { + columns.push_back( + NSkiff::CreateVariant8Schema({ + CreateSimpleTypeSchema(NSkiff::EWireType::Nothing), + CreateSimpleTypeSchema(NSkiff::EWireType::Int64)}) + ->SetName(RANGE_INDEX_COLUMN)); + } + columns.insert(columns.end(), children.begin(), children.end()); + + schemas.push_back(NSkiff::CreateTupleSchema(columns)); + } + + return NSkiff::CreateVariant16Schema(schemas); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/skiff.h b/yt/cpp/mapreduce/client/skiff.h new file mode 100644 index 00000000000..82d80a4967a --- /dev/null +++ b/yt/cpp/mapreduce/client/skiff.h @@ -0,0 +1,72 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/interface/fwd.h> +#include <yt/cpp/mapreduce/interface/common.h> + +#include <yt/cpp/mapreduce/skiff/wire_type.h> +#include <yt/cpp/mapreduce/skiff/skiff_schema.h> + +#include <util/generic/vector.h> + +namespace NYT::NYson { +struct IYsonConsumer; +} // namespace NYT::NYson + +namespace NYT { + +struct TClientContext; +enum class ENodeReaderFormat : int; + +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +struct TCreateSkiffSchemaOptions +{ + using TSelf = TCreateSkiffSchemaOptions; + + FLUENT_FIELD_DEFAULT(bool, HasKeySwitch, false); + FLUENT_FIELD_DEFAULT(bool, HasRangeIndex, false); + + using TRenameColumnsDescriptor = THashMap<TString, TString>; + FLUENT_FIELD_OPTION(TRenameColumnsDescriptor, RenameColumns); +}; + +//////////////////////////////////////////////////////////////////////////////// + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TVector<NSkiff::TSkiffSchemaPtr>& tableSchemas, + const TCreateSkiffSchemaOptions& options); + +NSkiff::TSkiffSchemaPtr GetJobInputSkiffSchema(); + +NSkiff::EWireType ValueTypeToSkiffType(EValueType valueType); + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TTableSchema& schema, + const TCreateSkiffSchemaOptions& options = TCreateSkiffSchemaOptions()); + +NSkiff::TSkiffSchemaPtr CreateSkiffSchema( + const TNode& schemaNode, + const TCreateSkiffSchemaOptions& options = TCreateSkiffSchemaOptions()); + +void Serialize(const NSkiff::TSkiffSchemaPtr& schema, NYson::IYsonConsumer* consumer); + +void Deserialize(NSkiff::TSkiffSchemaPtr& schema, const TNode& node); + +TFormat CreateSkiffFormat(const NSkiff::TSkiffSchemaPtr& schema); + +NSkiff::TSkiffSchemaPtr CreateSkiffSchemaIfNecessary( + const TClientContext& context, + const IClientRetryPolicyPtr& clientRetryPolicy, + const TTransactionId& transactionId, + ENodeReaderFormat nodeReaderFormat, + const TVector<TRichYPath>& tablePaths, + const TCreateSkiffSchemaOptions& options = TCreateSkiffSchemaOptions()); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/structured_table_formats.cpp b/yt/cpp/mapreduce/client/structured_table_formats.cpp new file mode 100644 index 00000000000..b6e82c6c156 --- /dev/null +++ b/yt/cpp/mapreduce/client/structured_table_formats.cpp @@ -0,0 +1,572 @@ +#include "structured_table_formats.h" + +#include "format_hints.h" +#include "skiff.h" + +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/io/yamr_table_reader.h> + +#include <yt/cpp/mapreduce/library/table_schema/protobuf.h> + +#include <yt/cpp/mapreduce/interface/common.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <library/cpp/type_info/type_info.h> +#include <library/cpp/yson/writer.h> + +#include <memory> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TMaybe<TNode> GetCommonTableFormat( + const TVector<TMaybe<TNode>>& formats) +{ + TMaybe<TNode> result; + bool start = true; + for (auto& format : formats) { + if (start) { + result = format; + start = false; + continue; + } + + if (result.Defined() != format.Defined()) { + ythrow yexception() << "Different formats of input tables"; + } + + if (!result.Defined()) { + continue; + } + + auto& resultAttrs = result.Get()->GetAttributes(); + auto& formatAttrs = format.Get()->GetAttributes(); + + if (resultAttrs["key_column_names"] != formatAttrs["key_column_names"]) { + ythrow yexception() << "Different formats of input tables"; + } + + bool hasSubkeyColumns = resultAttrs.HasKey("subkey_column_names"); + if (hasSubkeyColumns != formatAttrs.HasKey("subkey_column_names")) { + ythrow yexception() << "Different formats of input tables"; + } + + if (hasSubkeyColumns && + resultAttrs["subkey_column_names"] != formatAttrs["subkey_column_names"]) + { + ythrow yexception() << "Different formats of input tables"; + } + } + + return result; +} + +TMaybe<TNode> GetTableFormat( + const IClientRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TRichYPath& path) +{ + auto formatPath = path.Path_ + "/@_format"; + if (!NDetail::NRawClient::Exists(retryPolicy->CreatePolicyForGenericRequest(), context, transactionId, formatPath)) { + return TMaybe<TNode>(); + } + TMaybe<TNode> format = NDetail::NRawClient::Get(retryPolicy->CreatePolicyForGenericRequest(), context, transactionId, formatPath); + if (format.Get()->AsString() != "yamred_dsv") { + return TMaybe<TNode>(); + } + auto& formatAttrs = format.Get()->Attributes(); + if (!formatAttrs.HasKey("key_column_names")) { + ythrow yexception() << + "Table '" << path.Path_ << "': attribute 'key_column_names' is missing"; + } + formatAttrs["has_subkey"] = "true"; + formatAttrs["lenval"] = "true"; + return format; +} + +TMaybe<TNode> GetTableFormats( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TVector<TRichYPath>& inputs) +{ + TVector<TMaybe<TNode>> formats; + for (auto& table : inputs) { + formats.push_back(GetTableFormat(clientRetryPolicy, context, transactionId, table)); + } + + return GetCommonTableFormat(formats); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +NSkiff::TSkiffSchemaPtr TryCreateSkiffSchema( + const TClientContext& context, + const IClientRetryPolicyPtr& clientRetryPolicy, + const TTransactionId& transactionId, + const TVector<TRichYPath>& tables, + const TOperationOptions& options, + ENodeReaderFormat nodeReaderFormat) +{ + bool hasInputQuery = options.Spec_.Defined() && options.Spec_->IsMap() && options.Spec_->HasKey("input_query"); + if (hasInputQuery) { + Y_ENSURE_EX(nodeReaderFormat != ENodeReaderFormat::Skiff, + TApiUsageError() << "Cannot use Skiff format for operations with 'input_query' in spec"); + return nullptr; + } + return CreateSkiffSchemaIfNecessary( + context, + clientRetryPolicy, + transactionId, + nodeReaderFormat, + tables, + TCreateSkiffSchemaOptions() + .HasKeySwitch(true) + .HasRangeIndex(true)); +} + +TString CreateSkiffConfig(const NSkiff::TSkiffSchemaPtr& schema) +{ + TString result; + TStringOutput stream(result); + ::NYson::TYsonWriter writer(&stream); + Serialize(schema, &writer); + return result; +} + +TString CreateProtoConfig(const TVector<const ::google::protobuf::Descriptor*>& descriptorList) +{ + TString result; + TStringOutput messageTypeList(result); + for (const auto& descriptor : descriptorList) { + messageTypeList << descriptor->full_name() << Endl; + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct TGetTableStructureDescriptionStringImpl { + template<typename T> + TString operator()(const T& description) { + if constexpr (std::is_same_v<T, TUnspecifiedTableStructure>) { + return "Unspecified"; + } else if constexpr (std::is_same_v<T, TProtobufTableStructure>) { + TString res; + TStringStream out(res); + if (description.Descriptor) { + out << description.Descriptor->full_name(); + } else { + out << "<unknown>"; + } + out << " protobuf message"; + return res; + } else { + static_assert(TDependentFalse<T>, "Unknown type"); + } + } +}; + +TString GetTableStructureDescriptionString(const TTableStructure& tableStructure) +{ + return std::visit(TGetTableStructureDescriptionStringImpl(), tableStructure); +} + +//////////////////////////////////////////////////////////////////////////////// + +TString JobTablePathString(const TStructuredJobTable& jobTable) +{ + if (jobTable.RichYPath) { + return jobTable.RichYPath->Path_; + } else { + return "<intermediate-table>"; + } +} + +TStructuredJobTableList ToStructuredJobTableList(const TVector<TStructuredTablePath>& tableList) +{ + TStructuredJobTableList result; + for (const auto& table : tableList) { + result.push_back(TStructuredJobTable{table.Description, table.RichYPath}); + } + return result; +} + +TStructuredJobTableList CanonizeStructuredTableList(const TClientContext& context, const TVector<TStructuredTablePath>& tableList) +{ + TVector<TRichYPath> toCanonize; + toCanonize.reserve(tableList.size()); + for (const auto& table : tableList) { + toCanonize.emplace_back(table.RichYPath); + } + const auto canonized = NRawClient::CanonizeYPaths(/* retryPolicy */ nullptr, context, toCanonize); + Y_VERIFY(canonized.size() == tableList.size()); + + TStructuredJobTableList result; + result.reserve(tableList.size()); + for (size_t i = 0; i != canonized.size(); ++i) { + result.emplace_back(TStructuredJobTable{tableList[i].Description, canonized[i]}); + } + return result; +} + +TVector<TRichYPath> GetPathList( + const TStructuredJobTableList& tableList, + const TMaybe<TVector<TTableSchema>>& jobSchemaInferenceResult, + bool inferSchemaFromDescriptions) +{ + Y_VERIFY(!jobSchemaInferenceResult || tableList.size() == jobSchemaInferenceResult->size()); + + auto maybeInferSchema = [&] (const TStructuredJobTable& table, ui32 tableIndex) -> TMaybe<TTableSchema> { + if (jobSchemaInferenceResult && !jobSchemaInferenceResult->at(tableIndex).Empty()) { + return jobSchemaInferenceResult->at(tableIndex); + } + if (inferSchemaFromDescriptions) { + return GetTableSchema(table.Description); + } + return Nothing(); + }; + + TVector<TRichYPath> result; + result.reserve(tableList.size()); + for (size_t tableIndex = 0; tableIndex != tableList.size(); ++tableIndex) { + const auto& table = tableList[tableIndex]; + Y_VERIFY(table.RichYPath, "Cannot get path for intermediate table"); + auto richYPath = *table.RichYPath; + if (!richYPath.Schema_) { + if (auto schema = maybeInferSchema(table, tableIndex)) { + richYPath.Schema(std::move(*schema)); + } + } + + result.emplace_back(std::move(richYPath)); + } + return result; +} + + +TStructuredRowStreamDescription GetJobStreamDescription( + const IStructuredJob& job, + EIODirection direction) +{ + switch (direction) { + case EIODirection::Input: + return job.GetInputRowStreamDescription(); + case EIODirection::Output: + return job.GetOutputRowStreamDescription(); + default: + Y_FAIL("unreachable"); + } +} + +TString GetSuffix(EIODirection direction) +{ + switch (direction) { + case EIODirection::Input: + return "_input"; + case EIODirection::Output: + return "_output"; + } + Y_FAIL("unreachable"); +} + +TString GetAddIOMethodName(EIODirection direction) +{ + switch (direction) { + case EIODirection::Input: + return "AddInput<>"; + case EIODirection::Output: + return "AddOutput<>"; + } + Y_FAIL("unreachable"); +} + +//////////////////////////////////////////////////////////////////////////////// + +struct TFormatBuilder::TFormatSwitcher +{ + template <typename T> + auto operator() (const T& /*t*/) { + if constexpr (std::is_same_v<T, TTNodeStructuredRowStream>) { + return &TFormatBuilder::CreateNodeFormat; + } else if constexpr (std::is_same_v<T, TTYaMRRowStructuredRowStream>) { + return &TFormatBuilder::CreateYamrFormat; + } else if constexpr (std::is_same_v<T, TProtobufStructuredRowStream>) { + return &TFormatBuilder::CreateProtobufFormat; + } else if constexpr (std::is_same_v<T, TVoidStructuredRowStream>) { + return &TFormatBuilder::CreateVoidFormat; + } else { + static_assert(TDependentFalse<T>, "unknown stream description"); + } + } +}; + +TFormatBuilder::TFormatBuilder( + IClientRetryPolicyPtr clientRetryPolicy, + TClientContext context, + TTransactionId transactionId, + TOperationOptions operationOptions) + : ClientRetryPolicy_(std::move(clientRetryPolicy)) + , Context_(std::move(context)) + , TransactionId_(transactionId) + , OperationOptions_(std::move(operationOptions)) +{ } + +std::pair <TFormat, TMaybe<TSmallJobFile>> TFormatBuilder::CreateFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe <TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute) +{ + auto jobStreamDescription = GetJobStreamDescription(job, direction); + auto method = std::visit(TFormatSwitcher(), jobStreamDescription); + return (this->*method)( + job, + direction, + structuredTableList, + formatHints, + nodeReaderFormat, + allowFormatFromTableAttribute); +} + +std::pair<TFormat, TMaybe<TSmallJobFile>> TFormatBuilder::CreateVoidFormat( + const IStructuredJob& /*job*/, + const EIODirection& /*direction*/, + const TStructuredJobTableList& /*structuredTableList*/, + const TMaybe<TFormatHints>& /*formatHints*/, + ENodeReaderFormat /*nodeReaderFormat*/, + bool /*allowFormatFromTableAttribute*/) +{ + return { + TFormat(), + Nothing() + }; +} + +std::pair<TFormat, TMaybe<TSmallJobFile>> TFormatBuilder::CreateYamrFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& /*formatHints*/, + ENodeReaderFormat /*nodeReaderFormat*/, + bool allowFormatFromTableAttribute) +{ + for (const auto& table: structuredTableList) { + if (!std::holds_alternative<TUnspecifiedTableStructure>(table.Description)) { + ythrow TApiUsageError() + << "cannot use " << direction << " table '" << JobTablePathString(table) + << "' with job " << TJobFactory::Get()->GetJobName(&job) << "; " + << "table has unsupported structure description; check " << GetAddIOMethodName(direction) << " for this table"; + } + } + TMaybe<TNode> formatFromTableAttributes; + if (allowFormatFromTableAttribute && OperationOptions_.UseTableFormats_) { + TVector<TRichYPath> tableList; + for (const auto& table: structuredTableList) { + Y_VERIFY(table.RichYPath, "Cannot use format from table for intermediate table"); + tableList.push_back(*table.RichYPath); + } + formatFromTableAttributes = GetTableFormats(ClientRetryPolicy_, Context_, TransactionId_, tableList); + } + if (formatFromTableAttributes) { + return { + TFormat(*formatFromTableAttributes), + Nothing() + }; + } else { + auto formatNode = TNode("yamr"); + formatNode.Attributes() = TNode() + ("lenval", true) + ("has_subkey", true) + ("enable_table_index", true); + return { + TFormat(formatNode), + Nothing() + }; + } +} + +std::pair<TFormat, TMaybe<TSmallJobFile>> TFormatBuilder::CreateNodeFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool /*allowFormatFromTableAttribute*/) +{ + for (const auto& table: structuredTableList) { + if (!std::holds_alternative<TUnspecifiedTableStructure>(table.Description)) { + ythrow TApiUsageError() + << "cannot use " << direction << " table '" << JobTablePathString(table) + << "' with job " << TJobFactory::Get()->GetJobName(&job) << "; " + << "table has unsupported structure description; check AddInput<> / AddOutput<> for this table"; + } + } + NSkiff::TSkiffSchemaPtr skiffSchema = nullptr; + if (nodeReaderFormat != ENodeReaderFormat::Yson) { + TVector<TRichYPath> tableList; + for (const auto& table: structuredTableList) { + Y_VERIFY(table.RichYPath, "Cannot use skiff with temporary tables"); + tableList.emplace_back(*table.RichYPath); + } + skiffSchema = TryCreateSkiffSchema( + Context_, + ClientRetryPolicy_, + TransactionId_, + tableList, + OperationOptions_, + nodeReaderFormat); + } + if (skiffSchema) { + auto format = CreateSkiffFormat(skiffSchema); + NYT::NDetail::ApplyFormatHints<TNode>(&format, formatHints); + return { + CreateSkiffFormat(skiffSchema), + TSmallJobFile{ + TString("skiff") + GetSuffix(direction), + CreateSkiffConfig(skiffSchema) + } + }; + } else { + auto format = TFormat::YsonBinary(); + NYT::NDetail::ApplyFormatHints<TNode>(&format, formatHints); + return { + format, + Nothing() + }; + } +} + +[[noreturn]] static void ThrowUnsupportedStructureDescription( + const EIODirection& direction, + const TStructuredJobTable& table, + const IStructuredJob& job) +{ + ythrow TApiUsageError() + << "cannot use " << direction << " table '" << JobTablePathString(table) + << "' with job " << TJobFactory::Get()->GetJobName(&job) << "; " + << "table has unsupported structure description; check " << GetAddIOMethodName(direction) << " for this table"; +} + +[[noreturn]] static void ThrowTypeDeriveFail( + const EIODirection& direction, + const IStructuredJob& job, + const TString& type) +{ + ythrow TApiUsageError() + << "Cannot derive exact " << type << " type for intermediate " << direction << " table for job " + << TJobFactory::Get()->GetJobName(&job) + << "; use one of TMapReduceOperationSpec::Hint* methods to specifiy intermediate table structure"; +} + +[[noreturn]] static void ThrowUnexpectedDifferentDescriptors( + const EIODirection& direction, + const TStructuredJobTable& table, + const IStructuredJob& job, + const TMaybe<TStringBuf> jobDescriptorName, + const TMaybe<TStringBuf> descriptorName) +{ + ythrow TApiUsageError() + << "Job " << TJobFactory::Get()->GetJobName(&job) << " expects " + << jobDescriptorName << " as " << direction << ", but table " << JobTablePathString(table) + << " is tagged with " << descriptorName; +} + +std::pair<TFormat, TMaybe<TSmallJobFile>> TFormatBuilder::CreateProtobufFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& /*formatHints*/, + ENodeReaderFormat /*nodeReaderFormat*/, + bool /*allowFormatFromTableAttribute*/) +{ + if (Context_.Config->UseClientProtobuf) { + return { + TFormat::YsonBinary(), + TSmallJobFile{ + TString("proto") + GetSuffix(direction), + CreateProtoConfig({}), + }, + }; + } + const ::google::protobuf::Descriptor* const jobDescriptor = + std::get<TProtobufStructuredRowStream>(GetJobStreamDescription(job, direction)).Descriptor; + Y_ENSURE(!structuredTableList.empty(), + "empty " << direction << " tables for job " << TJobFactory::Get()->GetJobName(&job)); + + TVector<const ::google::protobuf::Descriptor*> descriptorList; + for (const auto& table : structuredTableList) { + const ::google::protobuf::Descriptor* descriptor = nullptr; + if (std::holds_alternative<TProtobufTableStructure>(table.Description)) { + descriptor = std::get<TProtobufTableStructure>(table.Description).Descriptor; + } else if (table.RichYPath) { + ThrowUnsupportedStructureDescription(direction, table, job); + } + if (!descriptor) { + // It must be intermediate table, because there is no proper way to add such table to spec + // (AddInput requires to specify proper message). + Y_VERIFY(!table.RichYPath, "Descriptors for all tables except intermediate must be known"); + if (jobDescriptor) { + descriptor = jobDescriptor; + } else { + ThrowTypeDeriveFail(direction, job, "protobuf"); + } + } + if (jobDescriptor && descriptor != jobDescriptor) { + ThrowUnexpectedDifferentDescriptors( + direction, + table, + job, + jobDescriptor->full_name(), + descriptor->full_name()); + } + descriptorList.push_back(descriptor); + } + Y_VERIFY(!descriptorList.empty(), "Messages for proto format are unknown (empty ProtoDescriptors)"); + return { + TFormat::Protobuf(descriptorList, Context_.Config->ProtobufFormatWithDescriptors), + TSmallJobFile{ + TString("proto") + GetSuffix(direction), + CreateProtoConfig(descriptorList) + }, + }; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct TGetTableSchemaImpl +{ + template <typename T> + TMaybe<TTableSchema> operator() (const T& description) { + if constexpr (std::is_same_v<T, TUnspecifiedTableStructure>) { + return Nothing(); + } else if constexpr (std::is_same_v<T, TProtobufTableStructure>) { + if (!description.Descriptor) { + return Nothing(); + } + return CreateTableSchema(*description.Descriptor); + } else { + static_assert(TDependentFalse<T>, "unknown type"); + } + } +}; + +TMaybe<TTableSchema> GetTableSchema(const TTableStructure& tableStructure) +{ + return std::visit(TGetTableSchemaImpl(), tableStructure); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/structured_table_formats.h b/yt/cpp/mapreduce/client/structured_table_formats.h new file mode 100644 index 00000000000..27d980c587a --- /dev/null +++ b/yt/cpp/mapreduce/client/structured_table_formats.h @@ -0,0 +1,146 @@ +#pragma once + +#include <yt/cpp/mapreduce/interface/fwd.h> + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/operation.h> + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> + +#include <utility> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TMaybe<TNode> GetCommonTableFormat( + const TVector<TMaybe<TNode>>& formats); + +TMaybe<TNode> GetTableFormat( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TRichYPath& path); + +TMaybe<TNode> GetTableFormats( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TVector<TRichYPath>& paths); + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +enum class EIODirection +{ + Input, + Output, +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct TSmallJobFile +{ + TString FileName; + TString Data; +}; + +//////////////////////////////////////////////////////////////////////////////// + +// Table that is used while preparing operation formats. Can be real table or intermediate +struct TStructuredJobTable +{ + TTableStructure Description; + // Might be null for intermediate tables in MapReduce operation + TMaybe<TRichYPath> RichYPath; + + static TStructuredJobTable Intermediate(TTableStructure description) + { + return TStructuredJobTable{std::move(description), Nothing()}; + } +}; +using TStructuredJobTableList = TVector<TStructuredJobTable>; +TString JobTablePathString(const TStructuredJobTable& jobTable); +TStructuredJobTableList ToStructuredJobTableList(const TVector<TStructuredTablePath>& tableList); + +TStructuredJobTableList CanonizeStructuredTableList(const TClientContext& context, const TVector<TStructuredTablePath>& tableList); +TVector<TRichYPath> GetPathList( + const TStructuredJobTableList& tableList, + const TMaybe<TVector<TTableSchema>>& schemaInferenceResult, + bool inferSchema); + +//////////////////////////////////////////////////////////////////////////////// + +class TFormatBuilder +{ +private: + struct TFormatSwitcher; + +public: + TFormatBuilder( + IClientRetryPolicyPtr clientRetryPolicy, + TClientContext context, + TTransactionId transactionId, + TOperationOptions operationOptions); + + std::pair<TFormat, TMaybe<TSmallJobFile>> CreateFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute); + + std::pair<TFormat, TMaybe<TSmallJobFile>> CreateVoidFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute); + + std::pair<TFormat, TMaybe<TSmallJobFile>> CreateYamrFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute); + + std::pair<TFormat, TMaybe<TSmallJobFile>> CreateNodeFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute); + + std::pair<TFormat, TMaybe<TSmallJobFile>> CreateProtobufFormat( + const IStructuredJob& job, + const EIODirection& direction, + const TStructuredJobTableList& structuredTableList, + const TMaybe<TFormatHints>& formatHints, + ENodeReaderFormat nodeReaderFormat, + bool allowFormatFromTableAttribute); + +private: + const IClientRetryPolicyPtr ClientRetryPolicy_; + const TClientContext Context_; + const TTransactionId TransactionId_; + const TOperationOptions OperationOptions_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +TMaybe<TTableSchema> GetTableSchema(const TTableStructure& tableStructure); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/transaction.cpp b/yt/cpp/mapreduce/client/transaction.cpp new file mode 100644 index 00000000000..0aa1a7a1c39 --- /dev/null +++ b/yt/cpp/mapreduce/client/transaction.cpp @@ -0,0 +1,195 @@ +#include "transaction.h" + +#include "transaction_pinger.h" + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/error_codes.h> + +#include <yt/cpp/mapreduce/common/wait_proxy.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <util/datetime/base.h> + +#include <util/generic/scope.h> + +#include <util/random/random.h> + +#include <util/string/builder.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TPingableTransaction::TPingableTransaction( + const IClientRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TTransactionId& parentId, + ITransactionPingerPtr transactionPinger, + const TStartTransactionOptions& options) + : ClientRetryPolicy_(retryPolicy) + , Context_(context) + , AbortableRegistry_(NDetail::TAbortableRegistry::Get()) + , AbortOnTermination_(true) + , AutoPingable_(options.AutoPingable_) + , Pinger_(std::move(transactionPinger)) +{ + auto transactionId = NDetail::NRawClient::StartTransaction( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + context, + parentId, + options); + + auto actualTimeout = options.Timeout_.GetOrElse(Context_.Config->TxTimeout); + Init(context, transactionId, actualTimeout); +} + +TPingableTransaction::TPingableTransaction( + const IClientRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + ITransactionPingerPtr transactionPinger, + const TAttachTransactionOptions& options) + : ClientRetryPolicy_(retryPolicy) + , Context_(context) + , AbortableRegistry_(NDetail::TAbortableRegistry::Get()) + , AbortOnTermination_(options.AbortOnTermination_) + , AutoPingable_(options.AutoPingable_) + , Pinger_(std::move(transactionPinger)) +{ + auto timeoutNode = NDetail::NRawClient::TryGet( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + context, + TTransactionId(), + "#" + GetGuidAsString(transactionId) + "/@timeout", + TGetOptions()); + if (timeoutNode.IsUndefined()) { + throw yexception() << "Transaction " << GetGuidAsString(transactionId) << " does not exist"; + } + auto timeout = TDuration::MilliSeconds(timeoutNode.AsInt64()); + Init(context, transactionId, timeout); +} + +void TPingableTransaction::Init( + const TClientContext& context, + const TTransactionId& transactionId, + TDuration timeout) +{ + TransactionId_ = transactionId; + + if (AbortOnTermination_) { + AbortableRegistry_->Add( + TransactionId_, + ::MakeIntrusive<NDetail::TTransactionAbortable>(context, TransactionId_)); + } + + if (AutoPingable_) { + // Compute 'MaxPingInterval_' and 'MinPingInterval_' such that 'pingInterval == (max + min) / 2'. + auto pingInterval = Context_.Config->PingInterval; + auto safeTimeout = timeout - TDuration::Seconds(5); + MaxPingInterval_ = Max(pingInterval, Min(safeTimeout, pingInterval * 1.5)); + MinPingInterval_ = pingInterval - (MaxPingInterval_ - pingInterval); + + Pinger_->RegisterTransaction(*this); + } +} + +TPingableTransaction::~TPingableTransaction() +{ + try { + Stop(AbortOnTermination_ ? EStopAction::Abort : EStopAction::Detach); + } catch (...) { + } +} + +const TTransactionId TPingableTransaction::GetId() const +{ + return TransactionId_; +} + +const std::pair<TDuration, TDuration> TPingableTransaction::GetPingInterval() const { + return {MinPingInterval_, MaxPingInterval_}; +} + +const TClientContext TPingableTransaction::GetContext() const { + return Context_; +} + +void TPingableTransaction::Commit() +{ + Stop(EStopAction::Commit); +} + +void TPingableTransaction::Abort() +{ + Stop(EStopAction::Abort); +} + +void TPingableTransaction::Detach() +{ + Stop(EStopAction::Detach); +} + +void TPingableTransaction::Stop(EStopAction action) +{ + if (Finalized_) { + return; + } + + Y_DEFER { + Finalized_ = true; + if (AutoPingable_ && Pinger_->HasTransaction(*this)) { + Pinger_->RemoveTransaction(*this); + } + }; + + switch (action) { + case EStopAction::Commit: + NDetail::NRawClient::CommitTransaction( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + TransactionId_); + break; + case EStopAction::Abort: + NDetail::NRawClient::AbortTransaction( + ClientRetryPolicy_->CreatePolicyForGenericRequest(), + Context_, + TransactionId_); + break; + case EStopAction::Detach: + // Do nothing. + break; + } + + AbortableRegistry_->Remove(TransactionId_); +} + +//////////////////////////////////////////////////////////////////////////////// + +TYPath Snapshot( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TYPath& path) +{ + auto lockId = NDetail::NRawClient::Lock( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + transactionId, + path, + ELockMode::LM_SNAPSHOT); + auto lockedNodeId = NDetail::NRawClient::Get( + clientRetryPolicy->CreatePolicyForGenericRequest(), + context, + transactionId, + ::TStringBuilder() << '#' << GetGuidAsString(lockId) << "/@node_id"); + return "#" + lockedNodeId.AsString(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/transaction.h b/yt/cpp/mapreduce/client/transaction.h new file mode 100644 index 00000000000..559fca619e9 --- /dev/null +++ b/yt/cpp/mapreduce/client/transaction.h @@ -0,0 +1,95 @@ +#pragma once + +#include "abortable_registry.h" + +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <util/datetime/base.h> +#include <util/generic/maybe.h> +#include <util/generic/ptr.h> +#include <util/system/thread.h> + +#include <atomic> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TPingableTransaction +{ +public: + // + // Start a new transaction. + TPingableTransaction( + const IClientRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TTransactionId& parentId, + ITransactionPingerPtr transactionPinger, + const TStartTransactionOptions& options); + + // + // Attach to an existing transaction. + TPingableTransaction( + const IClientRetryPolicyPtr& retryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + ITransactionPingerPtr transactionPinger, + const TAttachTransactionOptions& options); + + ~TPingableTransaction(); + + const TTransactionId GetId() const; + + const std::pair<TDuration, TDuration> GetPingInterval() const; + const TClientContext GetContext() const; + + void Commit(); + void Abort(); + void Detach(); + + +private: + enum class EStopAction + { + Detach, + Abort, + Commit, + }; + +private: + IClientRetryPolicyPtr ClientRetryPolicy_; + TClientContext Context_; + TTransactionId TransactionId_; + TDuration MinPingInterval_; + TDuration MaxPingInterval_; + + // We have to own an IntrusivePtr to registry to prevent use-after-free. + ::TIntrusivePtr<NDetail::TAbortableRegistry> AbortableRegistry_; + + bool AbortOnTermination_; + + bool AutoPingable_; + bool Finalized_ = false; + ITransactionPingerPtr Pinger_; + +private: + void Init( + const TClientContext& context, + const TTransactionId& transactionId, + TDuration timeout); + + void Stop(EStopAction action); +}; + +//////////////////////////////////////////////////////////////////////////////// + +TYPath Snapshot( + const IClientRetryPolicyPtr& clientRetryPolicy, + const TClientContext& context, + const TTransactionId& transactionId, + const TYPath& path); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/transaction_pinger.cpp b/yt/cpp/mapreduce/client/transaction_pinger.cpp new file mode 100644 index 00000000000..2b51e47f9f0 --- /dev/null +++ b/yt/cpp/mapreduce/client/transaction_pinger.cpp @@ -0,0 +1,321 @@ +#include "transaction_pinger.h" + +#include "transaction.h" + +#include <yt/cpp/mapreduce/interface/config.h> +#include <yt/cpp/mapreduce/interface/error_codes.h> +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +#include <yt/cpp/mapreduce/common/wait_proxy.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> + +#include <yt/cpp/mapreduce/http/requests.h> +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#if defined(__x86_64__) || defined(__arm64__) + #include <yt/yt/core/concurrency/periodic_executor.h> + #include <yt/yt/core/concurrency/poller.h> + #include <yt/yt/core/concurrency/scheduler_api.h> + #include <yt/yt/core/concurrency/thread_pool_poller.h> + #include <yt/yt/core/concurrency/thread_pool.h> + + #include <yt/yt/core/http/client.h> + #include <yt/yt/core/http/http.h> +#endif // defined(__x86_64__) || defined(__arm64__) + +#include <library/cpp/yson/node/node_io.h> + +#include <library/cpp/yt/threading/spin_lock.h> +#include <library/cpp/yt/assert/assert.h> + +#include <util/datetime/base.h> +#include <util/random/random.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__x86_64__) || defined(__arm64__) + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +void CheckError(const TString& requestId, NHttp::IResponsePtr response) +{ + TErrorResponse errorResponse(static_cast<int>(response->GetStatusCode()), requestId); + + if (const auto* ytError = response->GetHeaders()->Find("X-YT-Error")) { + errorResponse.ParseFromJsonError(*ytError); + } + if (errorResponse.IsOk()) { + return; + } + + YT_LOG_ERROR("RSP %v - HTTP %v - %v", + requestId, + response->GetStatusCode(), + errorResponse.AsStrBuf()); + + ythrow errorResponse; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace + +void PingTx(NHttp::IClientPtr httpClient, const TPingableTransaction& tx) +{ + auto url = TString::Join("http://", tx.GetContext().ServerName, "/api/", tx.GetContext().Config->ApiVersion, "/ping_tx"); + auto headers = New<NHttp::THeaders>(); + auto requestId = CreateGuidAsString(); + + headers->Add("Host", url); + headers->Add("User-Agent", TProcessState::Get()->ClientVersion); + + const auto& token = tx.GetContext().Token; + if (!token.empty()) { + headers->Add("Authorization", "OAuth " + token); + } + + headers->Add("Transfer-Encoding", "chunked"); + headers->Add("X-YT-Correlation-Id", requestId); + headers->Add("X-YT-Header-Format", "<format=text>yson"); + headers->Add("Content-Encoding", "identity"); + headers->Add("Accept-Encoding", "identity"); + + TNode node; + node["transaction_id"] = GetGuidAsString(tx.GetId()); + auto strParams = NodeToYsonString(node); + + YT_LOG_DEBUG("REQ %v - sending request (HostName: %v; Method POST %v; X-YT-Parameters (sent in body): %v)", + requestId, + tx.GetContext().ServerName, + url, + strParams + ); + + auto response = NConcurrency::WaitFor(httpClient->Post(url, TSharedRef::FromString(strParams), headers)).ValueOrThrow(); + CheckError(requestId, response); + + YT_LOG_DEBUG("RSP %v - received response %v bytes. (%v)", + requestId, + response->ReadAll().size(), + strParams); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +class TSharedTransactionPinger + : public ITransactionPinger +{ +public: + TSharedTransactionPinger(NHttp::IClientPtr httpClient, int poolThreadCount) + : PingerPool_(NConcurrency::CreateThreadPool( + poolThreadCount, "tx_pinger_pool")) + , HttpClient_(std::move(httpClient)) + { } + + ~TSharedTransactionPinger() override + { + PingerPool_->Shutdown(); + } + + ITransactionPingerPtr GetChildTxPinger() override + { + return this; + } + + void RegisterTransaction(const TPingableTransaction& pingableTx) override + { + auto [minPingInterval, maxPingInterval] = pingableTx.GetPingInterval(); + auto pingInterval = (minPingInterval + maxPingInterval) / 2; + double jitter = (maxPingInterval - pingInterval) / pingInterval; + + auto opts = NConcurrency::TPeriodicExecutorOptions{pingInterval, pingInterval, jitter}; + auto periodic = std::make_shared<NConcurrency::TPeriodicExecutorPtr>(nullptr); + // Have to use weak_ptr in order to break reference cycle + // This weak_ptr holds pointer to periodic, which will contain this lambda + // Also we consider that lifetime of this lambda is no longer than lifetime of pingableTx + // because every pingableTx have to call RemoveTransaction before it is destroyed + auto pingRoutine = BIND([this, &pingableTx, periodic = std::weak_ptr{periodic}] { + auto strong_ptr = periodic.lock(); + YT_VERIFY(strong_ptr); + DoPingTransaction(pingableTx, *strong_ptr); + }); + *periodic = New<NConcurrency::TPeriodicExecutor>(PingerPool_->GetInvoker(), pingRoutine, opts); + (*periodic)->Start(); + + auto guard = Guard(SpinLock_); + YT_VERIFY(!Transactions_.contains(pingableTx.GetId())); + Transactions_[pingableTx.GetId()] = std::move(periodic); + } + + bool HasTransaction(const TPingableTransaction& pingableTx) override + { + auto guard = Guard(SpinLock_); + return Transactions_.contains(pingableTx.GetId()); + } + + + void RemoveTransaction(const TPingableTransaction& pingableTx) override + { + std::shared_ptr<NConcurrency::TPeriodicExecutorPtr> periodic; + { + auto guard = Guard(SpinLock_); + + auto it = Transactions_.find(pingableTx.GetId()); + + YT_VERIFY(it != Transactions_.end()); + + periodic = std::move(it->second); + Transactions_.erase(it); + } + NConcurrency::WaitUntilSet((*periodic)->Stop()); + } + +private: + void DoPingTransaction(const TPingableTransaction& pingableTx, + NConcurrency::TPeriodicExecutorPtr periodic) + { + try { + PingTx(HttpClient_, pingableTx); + } catch (const std::exception& e) { + if (auto* errorResponse = dynamic_cast<const TErrorResponse*>(&e)) { + if (errorResponse->GetError().ContainsErrorCode(NYT::NClusterErrorCodes::NTransactionClient::NoSuchTransaction)) { + YT_UNUSED_FUTURE(periodic->Stop()); + } else if (errorResponse->GetError().ContainsErrorCode(NYT::NClusterErrorCodes::Timeout)) { + periodic->ScheduleOutOfBand(); + } + } + } + } + + +private: + YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_); + THashMap<TTransactionId, std::shared_ptr<NConcurrency::TPeriodicExecutorPtr>> Transactions_; + + NConcurrency::IThreadPoolPtr PingerPool_; + NHttp::IClientPtr HttpClient_; +}; + +#endif // defined(__x86_64__) || defined(__arm64__) + +//////////////////////////////////////////////////////////////////////////////// + +class TThreadPerTransactionPinger + : public ITransactionPinger +{ +public: + ~TThreadPerTransactionPinger() override + { + if (Running_) { + RemoveTransaction(*PingableTx_); + } + } + + ITransactionPingerPtr GetChildTxPinger() override + { + return MakeIntrusive<TThreadPerTransactionPinger>(); + } + + void RegisterTransaction(const TPingableTransaction& pingableTx) override + { + YT_VERIFY(!Running_); + YT_VERIFY(PingableTx_ == nullptr); + + PingableTx_ = &pingableTx; + Running_ = true; + + PingerThread_ = MakeHolder<TThread>( + TThread::TParams{Pinger, this}.SetName("pingable_tx")); + PingerThread_->Start(); + } + + bool HasTransaction(const TPingableTransaction& pingableTx) override + { + return PingableTx_ == &pingableTx && Running_; + } + + void RemoveTransaction(const TPingableTransaction& pingableTx) override + { + YT_VERIFY(HasTransaction(pingableTx)); + + Running_ = false; + if (PingerThread_) { + PingerThread_->Join(); + } + } + +private: + static void* Pinger(void* opaque) + { + static_cast<TThreadPerTransactionPinger*>(opaque)->Pinger(); + return nullptr; + } + + void Pinger() + { + auto [minPingInterval, maxPingInterval] = PingableTx_->GetPingInterval(); + while (Running_) { + TDuration waitTime = minPingInterval + (maxPingInterval - minPingInterval) * RandomNumber<float>(); + try { + auto noRetryPolicy = MakeIntrusive<TAttemptLimitedRetryPolicy>(1u, PingableTx_->GetContext().Config); + NDetail::NRawClient::PingTx(noRetryPolicy, PingableTx_->GetContext(), PingableTx_->GetId()); + } catch (const std::exception& e) { + if (auto* errorResponse = dynamic_cast<const TErrorResponse*>(&e)) { + if (errorResponse->GetError().ContainsErrorCode(NYT::NClusterErrorCodes::NTransactionClient::NoSuchTransaction)) { + break; + } else if (errorResponse->GetError().ContainsErrorCode(NYT::NClusterErrorCodes::Timeout)) { + waitTime = TDuration::MilliSeconds(0); + } + } + // Else do nothing, going to retry this error. + } + + TInstant t = Now(); + while (Running_ && Now() - t < waitTime) { + NDetail::TWaitProxy::Get()->Sleep(TDuration::MilliSeconds(100)); + } + } + } + +private: + const TPingableTransaction* PingableTx_ = nullptr; + + std::atomic<bool> Running_ = false; + THolder<TThread> PingerThread_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +ITransactionPingerPtr CreateTransactionPinger(const TConfigPtr& config) +{ + if (config->UseAsyncTxPinger) { +// TODO(aleexfi): Remove it after YT-17689 +#if defined(__x86_64__) || defined(__arm64__) + YT_LOG_DEBUG("Using async transaction pinger"); + auto httpClientConfig = NYT::New<NHttp::TClientConfig>(); + httpClientConfig->MaxIdleConnections = 16; + auto httpPoller = NConcurrency::CreateThreadPoolPoller( + config->AsyncHttpClientThreads, + "tx_http_client_poller"); + auto httpClient = NHttp::CreateClient(std::move(httpClientConfig), std::move(httpPoller)); + + return MakeIntrusive<TSharedTransactionPinger>( + std::move(httpClient), + config->AsyncTxPingerPoolThreads); +#else + YT_LOG_WARNING("Async transaction pinger is not supported on your platform. Fallback to TThreadPerTransactionPinger..."); +#endif // defined(__x86_64__) || defined(__arm64__) + } + return MakeIntrusive<TThreadPerTransactionPinger>(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/transaction_pinger.h b/yt/cpp/mapreduce/client/transaction_pinger.h new file mode 100644 index 00000000000..98e8b5cb2f8 --- /dev/null +++ b/yt/cpp/mapreduce/client/transaction_pinger.h @@ -0,0 +1,39 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/http/requests.h> + +#include <util/generic/ptr.h> +#include <util/system/thread.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TPingableTransaction; + +//////////////////////////////////////////////////////////////////////////////// + +// Each registered transaction must be removed from pinger +// (using RemoveTransaction) before it is destroyed +class ITransactionPinger + : public TThrRefBase +{ +public: + virtual ~ITransactionPinger() = default; + + virtual ITransactionPingerPtr GetChildTxPinger() = 0; + + virtual void RegisterTransaction(const TPingableTransaction& pingableTx) = 0; + + virtual bool HasTransaction(const TPingableTransaction& pingableTx) = 0; + + virtual void RemoveTransaction(const TPingableTransaction& pingableTx) = 0; +}; + +ITransactionPingerPtr CreateTransactionPinger(const TConfigPtr& config); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/ya.make b/yt/cpp/mapreduce/client/ya.make new file mode 100644 index 00000000000..a1b3b4da694 --- /dev/null +++ b/yt/cpp/mapreduce/client/ya.make @@ -0,0 +1,75 @@ +LIBRARY() + +INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc) + +SRCS( + abortable_registry.cpp + batch_request_impl.cpp + client_reader.cpp + client_writer.cpp + client.cpp + file_reader.cpp + file_writer.cpp + format_hints.cpp + init.cpp + lock.cpp + operation_helpers.cpp + operation_preparer.cpp + operation_tracker.cpp + operation.cpp + prepare_operation.cpp + py_helpers.cpp + retry_heavy_write_request.cpp + retryful_writer.cpp + retryless_writer.cpp + skiff.cpp + structured_table_formats.cpp + transaction.cpp + transaction_pinger.cpp + yt_poller.cpp +) + +PEERDIR( + library/cpp/digest/md5 + library/cpp/sighandler + library/cpp/threading/blocking_queue + library/cpp/threading/future + library/cpp/type_info + library/cpp/yson + yt/cpp/mapreduce/common + yt/cpp/mapreduce/http + yt/cpp/mapreduce/interface + yt/cpp/mapreduce/io + yt/cpp/mapreduce/library/table_schema + yt/cpp/mapreduce/raw_client +) + +IF (ARCH_X86_64 OR OS_DARWIN) + PEERDIR( + yt/yt/core + yt/yt/core/http + ) +ELSE() + # Suppress yamaker's WBadIncl error on exotic platforms + PEERDIR( + yt/yt_proto/yt/core + ) +ENDIF() + +IF (BUILD_TYPE == "PROFILE") + PEERDIR( + yt/yt/library/ytprof + ) + + SRCS( + job_profiler.cpp + ) +ELSE() + SRCS( + dummy_job_profiler.cpp + ) +ENDIF() + +GENERATE_ENUM_SERIALIZATION(structured_table_formats.h) + +END() diff --git a/yt/cpp/mapreduce/client/yt_poller.cpp b/yt/cpp/mapreduce/client/yt_poller.cpp new file mode 100644 index 00000000000..e0bea1690ed --- /dev/null +++ b/yt/cpp/mapreduce/client/yt_poller.cpp @@ -0,0 +1,132 @@ +#include "yt_poller.h" + +#include <yt/cpp/mapreduce/raw_client/raw_batch_request.h> +#include <yt/cpp/mapreduce/raw_client/raw_requests.h> + +#include <yt/cpp/mapreduce/common/debug_metrics.h> +#include <yt/cpp/mapreduce/common/retry_lib.h> +#include <yt/cpp/mapreduce/common/wait_proxy.h> + +#include <yt/cpp/mapreduce/http/retry_request.h> + +#include <yt/cpp/mapreduce/interface/config.h> + +#include <yt/cpp/mapreduce/interface/logging/yt_log.h> + +namespace NYT { +namespace NDetail { + +using namespace NRawClient; + +//////////////////////////////////////////////////////////////////////////////// + +TYtPoller::TYtPoller( + TClientContext context, + const IClientRetryPolicyPtr& retryPolicy) + : Context_(std::move(context)) + , ClientRetryPolicy_(retryPolicy) + , WaiterThread_(&TYtPoller::WatchLoopProc, this) +{ + WaiterThread_.Start(); +} + +TYtPoller::~TYtPoller() +{ + Stop(); +} + +void TYtPoller::Watch(IYtPollerItemPtr item) +{ + auto g = Guard(Lock_); + Pending_.emplace_back(std::move(item)); + HasData_.Signal(); +} + + +void TYtPoller::Stop() +{ + { + auto g = Guard(Lock_); + if (!IsRunning_) { + return; + } + IsRunning_ = false; + HasData_.Signal(); + } + WaiterThread_.Join(); +} + +void TYtPoller::DiscardQueuedItems() +{ + for (auto& item : Pending_) { + item->OnItemDiscarded(); + } + for (auto& item : InProgress_) { + item->OnItemDiscarded(); + } +} + +void TYtPoller::WatchLoop() +{ + TInstant nextRequest = TInstant::Zero(); + while (true) { + { + auto g = Guard(Lock_); + if (IsRunning_ && Pending_.empty() && InProgress_.empty()) { + TWaitProxy::Get()->WaitCondVar(HasData_, Lock_); + } + + if (!IsRunning_) { + DiscardQueuedItems(); + return; + } + + { + auto ug = Unguard(Lock_); // allow adding new items into Pending_ + TWaitProxy::Get()->SleepUntil(nextRequest); + nextRequest = TInstant::Now() + Context_.Config->WaitLockPollInterval; + } + if (!Pending_.empty()) { + InProgress_.splice(InProgress_.end(), Pending_); + } + Y_VERIFY(!InProgress_.empty()); + } + + TRawBatchRequest rawBatchRequest(Context_.Config); + + for (auto& item : InProgress_) { + item->PrepareRequest(&rawBatchRequest); + } + + try { + ExecuteBatch(ClientRetryPolicy_->CreatePolicyForGenericRequest(), Context_, rawBatchRequest); + } catch (const std::exception& ex) { + YT_LOG_ERROR("Exception while executing batch request: %v", ex.what()); + } + + for (auto it = InProgress_.begin(); it != InProgress_.end();) { + auto& item = *it; + + IYtPollerItem::EStatus status = item->OnRequestExecuted(); + + if (status == IYtPollerItem::PollBreak) { + it = InProgress_.erase(it); + } else { + ++it; + } + } + + IncDebugMetric(TStringBuf("yt_poller_top_loop_repeat_count")); + } +} + +void* TYtPoller::WatchLoopProc(void* data) +{ + static_cast<TYtPoller*>(data)->WatchLoop(); + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT diff --git a/yt/cpp/mapreduce/client/yt_poller.h b/yt/cpp/mapreduce/client/yt_poller.h new file mode 100644 index 00000000000..4f4e9eb7abe --- /dev/null +++ b/yt/cpp/mapreduce/client/yt_poller.h @@ -0,0 +1,86 @@ +#pragma once + +#include <yt/cpp/mapreduce/common/fwd.h> + +#include <yt/cpp/mapreduce/http/context.h> +#include <yt/cpp/mapreduce/http/requests.h> + +#include <yt/cpp/mapreduce/interface/client.h> + +#include <util/generic/list.h> +#include <util/system/mutex.h> +#include <util/system/thread.h> +#include <util/system/condvar.h> + +namespace NYT { +namespace NDetail { + +namespace NRawClient { + class TRawBatchRequest; +} + +//////////////////////////////////////////////////////////////////////////////// + +class IYtPollerItem + : public TThrRefBase +{ +public: + enum EStatus { + PollContinue, + PollBreak, + }; + +public: + virtual ~IYtPollerItem() = default; + + virtual void PrepareRequest(NRawClient::TRawBatchRequest* batchRequest) = 0; + + // Should return PollContinue if poller should continue polling this item. + // Should return PollBreak if poller should stop polling this item. + virtual EStatus OnRequestExecuted() = 0; + + virtual void OnItemDiscarded() = 0; + +}; +using IYtPollerItemPtr = ::TIntrusivePtr<IYtPollerItem>; + +//////////////////////////////////////////////////////////////////////////////// + +class TYtPoller + : public TThrRefBase +{ +public: + TYtPoller(TClientContext context, const IClientRetryPolicyPtr& retryPolicy); + ~TYtPoller(); + + void Watch(IYtPollerItemPtr item); + + void Stop(); + +private: + void DiscardQueuedItems(); + + void WatchLoop(); + static void* WatchLoopProc(void*); + +private: + struct TItem; + + const TClientContext Context_; + const IClientRetryPolicyPtr ClientRetryPolicy_; + + + TList<IYtPollerItemPtr> InProgress_; + TList<IYtPollerItemPtr> Pending_; + + TThread WaiterThread_; + TMutex Lock_; + TCondVar HasData_; + + bool IsRunning_ = true; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail +} // namespace NYT |