From 7ddc267dd6485f1622c9f3c7d697f1e2c7738c54 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 7 Dec 2015 19:25:42 +0300 Subject: [PATCH 01/32] zkutil: add ZooKeeperHolder to hold one instance of zookeeper and reinit session if there is no reference on it [#METR-18346] --- .../include/zkutil/ZooKeeperHolder.h | 71 +++++++++++++++++++ libs/libzkutil/src/ZooKeeperHolder.cpp | 42 +++++++++++ .../src/tests/zkutil_zookeeper_holder.cpp | 23 ++++++ 3 files changed, 136 insertions(+) create mode 100644 libs/libzkutil/include/zkutil/ZooKeeperHolder.h create mode 100644 libs/libzkutil/src/ZooKeeperHolder.cpp create mode 100644 libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h new file mode 100644 index 00000000000..c988aec6881 --- /dev/null +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +namespace zkutil +{ + +class ZooKeeperHolder +{ +protected: + class UnstorableZookeeperHandler; + +public: + /// вызывать из одного потока - не thread safe + template + static void create(Args&&... args); + + static ZooKeeperHolder & getInstance(); + + static UnstorableZookeeperHandler getZooKeeper(); + static bool replaceZooKeeperSessionToNewOne(); + + static bool isSessionExpired(); + +protected: + /** Хендлер для подсчета количества используемых ссылок на ZooKeeper + * + * Специально поддерживает только хранение на стеке. + * Что вынуждает перед каждым использованием явно запросить хэндлер у ZooKeeperHolder + */ + class UnstorableZookeeperHandler + { + public: + UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_); + + ZooKeeper * operator->() { return zk_ptr.get(); } + const ZooKeeper * operator->() const { return zk_ptr.get(); } + ZooKeeper & operator*() { return *zk_ptr; } + const ZooKeeper & operator*() const { return *zk_ptr; } + + private: + ZooKeeper::Ptr zk_ptr; + }; + +protected: + template + ZooKeeperHolder(Args&&... args); + +private: + static std::unique_ptr instance; + + mutable std::mutex mutex; + ZooKeeper::Ptr ptr; +}; + +template +ZooKeeperHolder::ZooKeeperHolder(Args&&... args) + : ptr(std::make_shared(std::forward(args)...)) +{ +} + +template +void ZooKeeperHolder::create(Args&&... args) +{ + if (instance) + throw DB::Exception("Initialization is called twice"); + instance.reset(new ZooKeeperHolder(std::forward(args)...)); +} + +}; /*namespace zkutil*/ \ No newline at end of file diff --git a/libs/libzkutil/src/ZooKeeperHolder.cpp b/libs/libzkutil/src/ZooKeeperHolder.cpp new file mode 100644 index 00000000000..66851efc85c --- /dev/null +++ b/libs/libzkutil/src/ZooKeeperHolder.cpp @@ -0,0 +1,42 @@ +#include + +using namespace zkutil; + +std::unique_ptr ZooKeeperHolder::instance; + +ZooKeeperHolder & ZooKeeperHolder::getInstance() +{ + if (!instance) + throw DB::Exception("ZooKeeperHolder should be initialized before getInstance is called."); + return *instance; +} + +ZooKeeperHolder::UnstorableZookeeperHandler ZooKeeperHolder::getZooKeeper() +{ + auto & inst = getInstance(); + std::unique_lock lock(inst.mutex); + return UnstorableZookeeperHandler(inst.ptr); +} + +bool ZooKeeperHolder::replaceZooKeeperSessionToNewOne() +{ + auto & inst = getInstance(); + std::unique_lock lock(inst.mutex); + + if (inst.ptr.unique()) + { + inst.ptr = inst.ptr->startNewSession(); + return true; + } + return false; +} + +bool ZooKeeperHolder::isSessionExpired() +{ + return getZooKeeper()->expired(); +} + +ZooKeeperHolder::UnstorableZookeeperHandler::UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_) +: zk_ptr(zk_ptr_) +{ +} \ No newline at end of file diff --git a/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp b/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp new file mode 100644 index 00000000000..fac96231d60 --- /dev/null +++ b/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp @@ -0,0 +1,23 @@ +#include +#include + +int main() +{ + zkutil::ZooKeeperHolder::create("localhost:2181"); + + { + auto zk_handler = zkutil::ZooKeeperHolder::getInstance().getZooKeeper(); + bool started_new_session = zkutil::ZooKeeperHolder::getInstance().replaceZooKeeperSessionToNewOne(); + std::cerr << "Started new session: " << started_new_session << "\n"; + std::cerr << "get / " << zk_handler->get("/") << "\n"; + } + + { + bool started_new_session = zkutil::ZooKeeperHolder::getInstance().replaceZooKeeperSessionToNewOne(); + std::cerr << "Started new session: " << started_new_session << "\n"; + auto zk_handler = zkutil::ZooKeeperHolder::getInstance().getZooKeeper(); + std::cerr << "get / " << zk_handler->get("/") << "\n"; + } + + return 0; +} \ No newline at end of file From d3be463a7d59187c59c1bac1dbb0903ded2384fe Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Tue, 8 Dec 2015 13:48:40 +0300 Subject: [PATCH 02/32] ZooKeeperHolder: add comparison to nullptr [#METR-18346] --- .../include/zkutil/ZooKeeperHolder.h | 34 +++++++------------ libs/libzkutil/src/ZooKeeperHolder.cpp | 25 ++++---------- .../src/tests/zkutil_zookeeper_holder.cpp | 28 ++++++++++----- 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h index c988aec6881..a692f80ee22 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -12,16 +12,16 @@ protected: class UnstorableZookeeperHandler; public: + ZooKeeperHolder() = default; + /// вызывать из одного потока - не thread safe template - static void create(Args&&... args); + void init(Args&&... args); - static ZooKeeperHolder & getInstance(); + UnstorableZookeeperHandler getZooKeeper(); + bool replaceZooKeeperSessionToNewOne(); - static UnstorableZookeeperHandler getZooKeeper(); - static bool replaceZooKeeperSessionToNewOne(); - - static bool isSessionExpired(); + bool isSessionExpired() const; protected: /** Хендлер для подсчета количества используемых ссылок на ZooKeeper @@ -34,6 +34,10 @@ protected: public: UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_); + explicit operator bool() const { return bool(zk_ptr); } + bool operator==(nullptr_t) const { return zk_ptr == nullptr; } + bool operator!=(nullptr_t) const { return !(*this == nullptr); } + ZooKeeper * operator->() { return zk_ptr.get(); } const ZooKeeper * operator->() const { return zk_ptr.get(); } ZooKeeper & operator*() { return *zk_ptr; } @@ -43,29 +47,15 @@ protected: ZooKeeper::Ptr zk_ptr; }; -protected: - template - ZooKeeperHolder(Args&&... args); - private: - static std::unique_ptr instance; - mutable std::mutex mutex; ZooKeeper::Ptr ptr; }; template -ZooKeeperHolder::ZooKeeperHolder(Args&&... args) - : ptr(std::make_shared(std::forward(args)...)) +void ZooKeeperHolder::init(Args&&... args) { -} - -template -void ZooKeeperHolder::create(Args&&... args) -{ - if (instance) - throw DB::Exception("Initialization is called twice"); - instance.reset(new ZooKeeperHolder(std::forward(args)...)); + ptr = std::make_shared(std::forward(args)...); } }; /*namespace zkutil*/ \ No newline at end of file diff --git a/libs/libzkutil/src/ZooKeeperHolder.cpp b/libs/libzkutil/src/ZooKeeperHolder.cpp index 66851efc85c..582cc14d307 100644 --- a/libs/libzkutil/src/ZooKeeperHolder.cpp +++ b/libs/libzkutil/src/ZooKeeperHolder.cpp @@ -2,38 +2,27 @@ using namespace zkutil; -std::unique_ptr ZooKeeperHolder::instance; - -ZooKeeperHolder & ZooKeeperHolder::getInstance() -{ - if (!instance) - throw DB::Exception("ZooKeeperHolder should be initialized before getInstance is called."); - return *instance; -} - ZooKeeperHolder::UnstorableZookeeperHandler ZooKeeperHolder::getZooKeeper() { - auto & inst = getInstance(); - std::unique_lock lock(inst.mutex); - return UnstorableZookeeperHandler(inst.ptr); + std::unique_lock lock(mutex); + return UnstorableZookeeperHandler(ptr); } bool ZooKeeperHolder::replaceZooKeeperSessionToNewOne() { - auto & inst = getInstance(); - std::unique_lock lock(inst.mutex); + std::unique_lock lock(mutex); - if (inst.ptr.unique()) + if (ptr.unique()) { - inst.ptr = inst.ptr->startNewSession(); + ptr = ptr->startNewSession(); return true; } return false; } -bool ZooKeeperHolder::isSessionExpired() +bool ZooKeeperHolder::isSessionExpired() const { - return getZooKeeper()->expired(); + return ptr ? ptr->expired() : false; } ZooKeeperHolder::UnstorableZookeeperHandler::UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_) diff --git a/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp b/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp index fac96231d60..a6a8fda93a1 100644 --- a/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp +++ b/libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp @@ -1,23 +1,33 @@ #include #include +#include + +#include int main() { - zkutil::ZooKeeperHolder::create("localhost:2181"); + Test::initLogger(); + + zkutil::ZooKeeperHolder zk_holder; + zk_holder.init("localhost:2181"); { - auto zk_handler = zkutil::ZooKeeperHolder::getInstance().getZooKeeper(); - bool started_new_session = zkutil::ZooKeeperHolder::getInstance().replaceZooKeeperSessionToNewOne(); - std::cerr << "Started new session: " << started_new_session << "\n"; - std::cerr << "get / " << zk_handler->get("/") << "\n"; + auto zk_handler = zk_holder.getZooKeeper(); + if (zk_handler) + { + bool started_new_session = zk_holder.replaceZooKeeperSessionToNewOne(); + std::cerr << "Started new session: " << started_new_session << "\n"; + std::cerr << "get / " << zk_handler->get("/") << "\n"; + } } { - bool started_new_session = zkutil::ZooKeeperHolder::getInstance().replaceZooKeeperSessionToNewOne(); + bool started_new_session = zk_holder.replaceZooKeeperSessionToNewOne(); std::cerr << "Started new session: " << started_new_session << "\n"; - auto zk_handler = zkutil::ZooKeeperHolder::getInstance().getZooKeeper(); - std::cerr << "get / " << zk_handler->get("/") << "\n"; + auto zk_handler = zk_holder.getZooKeeper(); + if (zk_handler != nullptr) + std::cerr << "get / " << zk_handler->get("/") << "\n"; } return 0; -} \ No newline at end of file +} From ad29c8c42c13a5d3a212aca6577ce3a8ffecf95c Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Thu, 10 Dec 2015 16:35:41 +0300 Subject: [PATCH 03/32] ZooKeeperHolder: changed interface [#METR-18346] --- .../include/zkutil/ZooKeeperHolder.h | 19 ++++++++--- libs/libzkutil/src/ZooKeeperHolder.cpp | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h index a692f80ee22..9778f6ce4e5 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -2,11 +2,12 @@ #include #include +#include namespace zkutil { -class ZooKeeperHolder +class ZooKeeperHolder : public boost::noncopyable { protected: class UnstorableZookeeperHandler; @@ -17,6 +18,8 @@ public: /// вызывать из одного потока - не thread safe template void init(Args&&... args); + /// был ли класс инициализирован + bool isInitialized() const { return ptr != nullptr; } UnstorableZookeeperHandler getZooKeeper(); bool replaceZooKeeperSessionToNewOne(); @@ -38,10 +41,12 @@ protected: bool operator==(nullptr_t) const { return zk_ptr == nullptr; } bool operator!=(nullptr_t) const { return !(*this == nullptr); } - ZooKeeper * operator->() { return zk_ptr.get(); } - const ZooKeeper * operator->() const { return zk_ptr.get(); } - ZooKeeper & operator*() { return *zk_ptr; } - const ZooKeeper & operator*() const { return *zk_ptr; } + /// в случае nullptr методы разыменования кидают исключение, + /// с более подробным текстом, чем просто nullptr + ZooKeeper * operator->(); + const ZooKeeper * operator->() const; + ZooKeeper & operator*(); + const ZooKeeper & operator*() const; private: ZooKeeper::Ptr zk_ptr; @@ -50,6 +55,8 @@ protected: private: mutable std::mutex mutex; ZooKeeper::Ptr ptr; + + static std::string nullptr_exception_message; }; template @@ -58,4 +65,6 @@ void ZooKeeperHolder::init(Args&&... args) ptr = std::make_shared(std::forward(args)...); } +using ZooKeeperHolderPtr = std::shared_ptr; + }; /*namespace zkutil*/ \ No newline at end of file diff --git a/libs/libzkutil/src/ZooKeeperHolder.cpp b/libs/libzkutil/src/ZooKeeperHolder.cpp index 582cc14d307..80639fe589e 100644 --- a/libs/libzkutil/src/ZooKeeperHolder.cpp +++ b/libs/libzkutil/src/ZooKeeperHolder.cpp @@ -25,7 +25,41 @@ bool ZooKeeperHolder::isSessionExpired() const return ptr ? ptr->expired() : false; } + +std::string ZooKeeperHolder::nullptr_exception_message = + "UnstorableZookeeperHandler::zk_ptr is nullptr. " + "ZooKeeperHolder should be initialized before sending any request to ZooKeeper"; + ZooKeeperHolder::UnstorableZookeeperHandler::UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_) : zk_ptr(zk_ptr_) { +} + +ZooKeeper * ZooKeeperHolder::UnstorableZookeeperHandler::operator->() +{ + if (zk_ptr == nullptr) + throw DB::Exception(nullptr_exception_message); + + return zk_ptr.get(); +} + +const ZooKeeper * ZooKeeperHolder::UnstorableZookeeperHandler::operator->() const +{ + if (zk_ptr == nullptr) + throw DB::Exception(nullptr_exception_message); + return zk_ptr.get(); +} + +ZooKeeper & ZooKeeperHolder::UnstorableZookeeperHandler::operator*() +{ + if (zk_ptr == nullptr) + throw DB::Exception(nullptr_exception_message); + return *zk_ptr; +} + +const ZooKeeper & ZooKeeperHolder::UnstorableZookeeperHandler::operator*() const +{ + if (zk_ptr == nullptr) + throw DB::Exception(nullptr_exception_message); + return *zk_ptr; } \ No newline at end of file From 4afceba26c7cc22cb34fb5a973f547f5c2454640 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Thu, 10 Dec 2015 17:58:13 +0300 Subject: [PATCH 04/32] zkutil::Lock: use ZooKeeperHolder [#METR-18346] --- libs/libzkutil/include/zkutil/Lock.h | 26 +++++++++++++++++++------- libs/libzkutil/src/Lock.cpp | 7 +++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/libs/libzkutil/include/zkutil/Lock.h b/libs/libzkutil/include/zkutil/Lock.h index aa8e579b686..e9cbfcd5c26 100644 --- a/libs/libzkutil/include/zkutil/Lock.h +++ b/libs/libzkutil/include/zkutil/Lock.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -11,13 +11,22 @@ namespace zkutil public: /// lock_prefix - относительный путь до блокировки в ZK. Начинается со слеша /// lock_name - имя ноды блокировки в ZK - Lock(zkutil::ZooKeeperPtr zk, const std::string & lock_prefix_, const std::string & lock_name_, const std::string & lock_message_ = "", - bool create_parent_path = false) : - zookeeper(zk), lock_path(lock_prefix_ + "/" + lock_name_), lock_message(lock_message_), log(&Logger::get("zkutil::Lock")) + Lock( + zkutil::ZooKeeperHolderPtr zookeeper_holder_, + const std::string & lock_prefix_, + const std::string & lock_name_, + const std::string & lock_message_ = "", + bool create_parent_path_ = false) + : + zookeeper_holder(zookeeper_holder_), + lock_path(lock_prefix_ + "/" + lock_name_), + lock_message(lock_message_), + log(&Logger::get("zkutil::Lock")) { - if (create_parent_path) + auto zookeeper = zookeeper_holder->getZooKeeper(); + if (create_parent_path_) zookeeper->createAncestors(lock_prefix_); - + zookeeper->createIfNotExists(lock_prefix_, ""); } @@ -59,7 +68,10 @@ namespace zkutil private: Status checkImpl(); - zkutil::ZooKeeperPtr zookeeper; + + private: + zkutil::ZooKeeperHolderPtr zookeeper_holder; + std::string lock_path; std::string lock_message; Logger * log; diff --git a/libs/libzkutil/src/Lock.cpp b/libs/libzkutil/src/Lock.cpp index 2eda4a97dc3..73c391796af 100644 --- a/libs/libzkutil/src/Lock.cpp +++ b/libs/libzkutil/src/Lock.cpp @@ -4,6 +4,8 @@ using namespace zkutil; bool Lock::tryLock() { + auto zookeeper = zookeeper_holder->getZooKeeper(); + if (locked) { /// проверим, что нода создана и я ее владелец @@ -43,6 +45,8 @@ bool Lock::tryLock() void Lock::unlock() { + auto zookeeper = zookeeper_holder->getZooKeeper(); + if (locked) { /// проверим, что до сих пор мы владельцы ноды @@ -76,6 +80,8 @@ Lock::Status Lock::check() Lock::Status Lock::checkImpl() { + auto zookeeper = zookeeper_holder->getZooKeeper(); + Stat stat; std::string dummy; bool result = zookeeper->tryGet(lock_path, dummy, &stat); @@ -101,3 +107,4 @@ std::string Lock::status2String(Status status) static const char * names[] = {"Unlocked", "Locked by me", "Locked by other"}; return names[status]; } + From 9c064a1440b1bae22033845ea533219518a2ec79 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 14 Dec 2015 14:49:50 +0300 Subject: [PATCH 05/32] zookeeper: use ZookeeperHolder in statdaemons [#METR-18346] --- libs/libzkutil/include/zkutil/Increment.h | 17 +++++++++-------- libs/libzkutil/src/tests/zkutil_test_lock.cpp | 5 ++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/libs/libzkutil/include/zkutil/Increment.h b/libs/libzkutil/include/zkutil/Increment.h index 37b698d0290..decb71c287f 100644 --- a/libs/libzkutil/include/zkutil/Increment.h +++ b/libs/libzkutil/include/zkutil/Increment.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace zkutil { @@ -8,10 +8,10 @@ namespace zkutil class Increment { public: - Increment(ZooKeeperPtr zk_, const std::string & path_) - : zk(zk_), path(path_) + Increment(ZooKeeperHolderPtr zk_holder_, const std::string & path_) + : zookeeper_holder(zk_holder_), path(path_) { - zk->createAncestors(path); + zookeeper_holder->getZooKeeper()->createAncestors(path); } size_t get() @@ -23,16 +23,17 @@ public: zkutil::Stat stat; bool success = false; + auto zookeeper = zookeeper_holder->getZooKeeper(); do { - if (zk->tryGet(path, result_str, &stat)) + if (zookeeper->tryGet(path, result_str, &stat)) { result = std::stol(result_str) + 1; - success = zk->trySet(path, std::to_string(result), stat.version) == ZOK; + success = zookeeper->trySet(path, std::to_string(result), stat.version) == ZOK; } else { - success = zk->tryCreate(path, std::to_string(result), zkutil::CreateMode::Persistent) == ZOK; + success = zookeeper->tryCreate(path, std::to_string(result), zkutil::CreateMode::Persistent) == ZOK; } } while (!success); @@ -40,7 +41,7 @@ public: return result; } private: - zkutil::ZooKeeperPtr zk; + zkutil::ZooKeeperHolderPtr zookeeper_holder; std::string path; Logger * log = &Logger::get("zkutil::Increment"); }; diff --git a/libs/libzkutil/src/tests/zkutil_test_lock.cpp b/libs/libzkutil/src/tests/zkutil_test_lock.cpp index 6acaeaac4a7..6da1a827829 100644 --- a/libs/libzkutil/src/tests/zkutil_test_lock.cpp +++ b/libs/libzkutil/src/tests/zkutil_test_lock.cpp @@ -6,7 +6,10 @@ int main() try { - zkutil::Lock l(std::make_shared("localhost:2181"), "/test", "test_lock"); + auto zookeeper_holder = std::make_shared(); + zookeeper_holder->init("localhost:2181"); + + zkutil::Lock l(zookeeper_holder, "/test", "test_lock"); std::cout << "check " << l.check() << std::endl; std::cout << "lock tryLock() " << l.tryLock() << std::endl; std::cout << "check " << l.check() << std::endl; From 3dd3eca4098ebeecc5952ba131926a780045024b Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 14 Dec 2015 18:29:12 +0300 Subject: [PATCH 06/32] zkutil::Lock: add tryCheck method which down't throw if lock was stolen [#METR-18346] --- libs/libzkutil/include/zkutil/Lock.h | 7 +--- libs/libzkutil/src/Lock.cpp | 59 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/libs/libzkutil/include/zkutil/Lock.h b/libs/libzkutil/include/zkutil/Lock.h index e9cbfcd5c26..c78797120e1 100644 --- a/libs/libzkutil/include/zkutil/Lock.h +++ b/libs/libzkutil/include/zkutil/Lock.h @@ -55,9 +55,7 @@ namespace zkutil std::string status2String(Status status); /// проверяет создана ли эфемерная нода и кто ее владелец. - /// если мы сами создавали эфемерную ноду, то надо вызывать этот метод, чтобы убедится, - /// что сессия с зукипером не порвалось - Status check(); + Status tryCheck() const; void unlock(); @@ -66,9 +64,6 @@ namespace zkutil /// путь к ноде блокировки в zookeeper const std::string & getPath() { return lock_path; } - private: - Status checkImpl(); - private: zkutil::ZooKeeperHolderPtr zookeeper_holder; diff --git a/libs/libzkutil/src/Lock.cpp b/libs/libzkutil/src/Lock.cpp index 73c391796af..89fbda5db73 100644 --- a/libs/libzkutil/src/Lock.cpp +++ b/libs/libzkutil/src/Lock.cpp @@ -5,13 +5,14 @@ using namespace zkutil; bool Lock::tryLock() { auto zookeeper = zookeeper_holder->getZooKeeper(); - if (locked) { /// проверим, что нода создана и я ее владелец - check(); + if (tryCheck() != Status::LOCKED_BY_ME) + locked = false; } - else + + if (!locked) { size_t attempt; std::string dummy; @@ -49,55 +50,53 @@ void Lock::unlock() if (locked) { - /// проверим, что до сих пор мы владельцы ноды - check(); - - size_t attempt; - int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt); - if (attempt) + if (tryCheck() == Status::LOCKED_BY_ME) { - if (code != ZOK) - throw zkutil::KeeperException(code); - } - else - { - if (code == ZNONODE) - LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error."); - else if (code != ZOK) - throw zkutil::KeeperException(code); + size_t attempt; + int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt); + if (attempt) + { + if (code != ZOK) + throw zkutil::KeeperException(code); + } + else + { + if (code == ZNONODE) + LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error."); + else if (code != ZOK) + throw zkutil::KeeperException(code); + } } locked = false; } } -Lock::Status Lock::check() -{ - Status status = checkImpl(); - if ((locked && status != LOCKED_BY_ME) || (!locked && (status != UNLOCKED && status != LOCKED_BY_OTHER))) - throw zkutil::KeeperException(std::string("Incompability of local state and state in zookeeper. Local is ") + (locked ? "locked" : "unlocked") + ". Zookeeper state is " + status2String(status)); - return status; -} - -Lock::Status Lock::checkImpl() +Lock::Status Lock::tryCheck() const { auto zookeeper = zookeeper_holder->getZooKeeper(); + Status lock_status; Stat stat; std::string dummy; bool result = zookeeper->tryGet(lock_path, dummy, &stat); if (!result) - return UNLOCKED; + lock_status = UNLOCKED; else { if (stat.ephemeralOwner == zookeeper->getClientID()) { - return LOCKED_BY_ME; + lock_status = LOCKED_BY_ME; } else { - return LOCKED_BY_OTHER; + lock_status = LOCKED_BY_OTHER; } } + + if (locked && lock_status != LOCKED_BY_ME) + LOG_WARNING(log, "Lock is lost. It is normal if session was reinitialized. Path: " << lock_path << "/" << lock_message); + + return lock_status; } std::string Lock::status2String(Status status) From 75142c5d4103c280a998e405d736c660476b1db5 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 14 Dec 2015 18:30:38 +0300 Subject: [PATCH 07/32] daemons: use zkutil::Lock::tryCheck [#METR-18346] --- libs/libzkutil/src/tests/zkutil_test_lock.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/libzkutil/src/tests/zkutil_test_lock.cpp b/libs/libzkutil/src/tests/zkutil_test_lock.cpp index 6da1a827829..52f8943a2e1 100644 --- a/libs/libzkutil/src/tests/zkutil_test_lock.cpp +++ b/libs/libzkutil/src/tests/zkutil_test_lock.cpp @@ -10,9 +10,9 @@ int main() zookeeper_holder->init("localhost:2181"); zkutil::Lock l(zookeeper_holder, "/test", "test_lock"); - std::cout << "check " << l.check() << std::endl; + std::cout << "check " << l.tryCheck() << std::endl; std::cout << "lock tryLock() " << l.tryLock() << std::endl; - std::cout << "check " << l.check() << std::endl; + std::cout << "check " << l.tryCheck() << std::endl; } catch (const Poco::Exception & e) { From c8bc21d0324963a4a5bf6f31b078a1c3d5494b67 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Tue, 15 Dec 2015 13:09:06 +0300 Subject: [PATCH 08/32] ZooKeeperHolder: add debug message [#METR-18346] --- libs/libzkutil/include/zkutil/ZooKeeperHolder.h | 2 ++ libs/libzkutil/src/ZooKeeperHolder.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h index 9778f6ce4e5..a18214eb315 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -56,6 +56,8 @@ private: mutable std::mutex mutex; ZooKeeper::Ptr ptr; + Logger * log = &Logger::get("ZooKeeperHolder"); + static std::string nullptr_exception_message; }; diff --git a/libs/libzkutil/src/ZooKeeperHolder.cpp b/libs/libzkutil/src/ZooKeeperHolder.cpp index 80639fe589e..e55446c41b3 100644 --- a/libs/libzkutil/src/ZooKeeperHolder.cpp +++ b/libs/libzkutil/src/ZooKeeperHolder.cpp @@ -17,7 +17,11 @@ bool ZooKeeperHolder::replaceZooKeeperSessionToNewOne() ptr = ptr->startNewSession(); return true; } + else + { + LOG_ERROR(log, "replaceZooKeeperSessionToNewOne(): Fail to replace zookeeper session to new one because handlers for old zookeeper session still exists."); return false; + } } bool ZooKeeperHolder::isSessionExpired() const From 915fbd16a652a72f9a3e22126665738b06e09de4 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Fri, 18 Dec 2015 14:22:03 +0300 Subject: [PATCH 09/32] zkutil::Lock: store ZooKeeperHandler if lock is locked to prevent reinitialization of zookeeper session [#METR-18346] --- libs/libzkutil/include/zkutil/Lock.h | 5 ++++- libs/libzkutil/include/zkutil/ZooKeeperHolder.h | 14 ++++++++++++-- libs/libzkutil/src/Lock.cpp | 12 ++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/libs/libzkutil/include/zkutil/Lock.h b/libs/libzkutil/include/zkutil/Lock.h index c78797120e1..31486b86c3d 100644 --- a/libs/libzkutil/include/zkutil/Lock.h +++ b/libs/libzkutil/include/zkutil/Lock.h @@ -66,10 +66,13 @@ namespace zkutil private: zkutil::ZooKeeperHolderPtr zookeeper_holder; + /// пока храним указатель на хендлер, никто не может переиницализировать сессию с zookeeper + using ZooKeeperHandler = zkutil::ZooKeeperHolder::UnstorableZookeeperHandler; + std::unique_ptr locked; std::string lock_path; std::string lock_message; Logger * log; - bool locked = false; + }; } diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h index a18214eb315..c2e009f0094 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -9,6 +9,8 @@ namespace zkutil class ZooKeeperHolder : public boost::noncopyable { + friend class zkutil::Lock; + protected: class UnstorableZookeeperHandler; @@ -29,8 +31,16 @@ public: protected: /** Хендлер для подсчета количества используемых ссылок на ZooKeeper * - * Специально поддерживает только хранение на стеке. - * Что вынуждает перед каждым использованием явно запросить хэндлер у ZooKeeperHolder + * Запрещается переинициализировать ZooKeeper пока, хранится хотя бы один хендлер на него. + * Большинство классов должны хранить хендлер на стеке и не хранить как член класса. + * Т.е. хранить holder и запрашивать хендлер перед каждым использованием. + * Поэтому класс специально объявлен, как protected. + * + * Исключение - классы, работающие с эфимерными нодами. Пример: zkutil::Lock + * + * Как использовать: + * auto zookeeper = zookeeper_holder->getZooKeeper(); + * zookeeper->get(path); */ class UnstorableZookeeperHandler { diff --git a/libs/libzkutil/src/Lock.cpp b/libs/libzkutil/src/Lock.cpp index 89fbda5db73..aea62ca00af 100644 --- a/libs/libzkutil/src/Lock.cpp +++ b/libs/libzkutil/src/Lock.cpp @@ -9,7 +9,7 @@ bool Lock::tryLock() { /// проверим, что нода создана и я ее владелец if (tryCheck() != Status::LOCKED_BY_ME) - locked = false; + locked.reset(nullptr); } if (!locked) @@ -21,20 +21,20 @@ bool Lock::tryLock() if (code == ZNODEEXISTS) { if (attempt == 0) - locked = false; + locked.reset(nullptr); else { zkutil::Stat stat; zookeeper->get(lock_path, &stat); if (stat.ephemeralOwner == zookeeper->getClientID()) - locked = true; + locked.reset(new ZooKeeperHandler(zookeeper)); else - locked = false; + locked.reset(nullptr); } } else if (code == ZOK) { - locked = true; + locked.reset(new ZooKeeperHandler(zookeeper)); } else { @@ -67,7 +67,7 @@ void Lock::unlock() throw zkutil::KeeperException(code); } } - locked = false; + locked.reset(nullptr); } } From a910893edc1667fb3f2c453c43c30089e0583bc1 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 21 Dec 2015 12:54:08 +0300 Subject: [PATCH 10/32] zkutil::Lock: unlock doesn't throw if zookeeper in unrecoverable state [#METR-18346] --- libs/libzkutil/src/Lock.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/libs/libzkutil/src/Lock.cpp b/libs/libzkutil/src/Lock.cpp index aea62ca00af..6dc397155bf 100644 --- a/libs/libzkutil/src/Lock.cpp +++ b/libs/libzkutil/src/Lock.cpp @@ -50,23 +50,33 @@ void Lock::unlock() if (locked) { - if (tryCheck() == Status::LOCKED_BY_ME) + try { - size_t attempt; - int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt); - if (attempt) + if (tryCheck() == Status::LOCKED_BY_ME) { - if (code != ZOK) - throw zkutil::KeeperException(code); - } - else - { - if (code == ZNONODE) - LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error."); - else if (code != ZOK) - throw zkutil::KeeperException(code); + size_t attempt; + int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt); + if (attempt) + { + if (code != ZOK) + throw zkutil::KeeperException(code); + } + else + { + if (code == ZNONODE) + LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error."); + else if (code != ZOK) + throw zkutil::KeeperException(code); + } } } + catch (const zkutil::KeeperException & e) + { + /// если сессия находится в невостанавливаемом состоянии, то эфимерные ноды нам больше не принадлежат + /// и лок через таймаут будет отпущен + if (!e.isUnrecoverable()) + throw; + } locked.reset(nullptr); } } From db6e6a28425a94bbb3d0b2724f18c726dc71b7ed Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Mon, 21 Dec 2015 12:55:56 +0300 Subject: [PATCH 11/32] zkutil::KeeperException: add isHardwareError [#METR-18346] --- libs/libzkutil/include/zkutil/KeeperException.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/libzkutil/include/zkutil/KeeperException.h b/libs/libzkutil/include/zkutil/KeeperException.h index 96fae823c36..4044fe6ed91 100644 --- a/libs/libzkutil/include/zkutil/KeeperException.h +++ b/libs/libzkutil/include/zkutil/KeeperException.h @@ -34,6 +34,13 @@ public: return code == ZINVALIDSTATE || code == ZSESSIONEXPIRED || code == ZSESSIONMOVED; } + /// любая ошибка связанная с работой сети, перевыбором мастера + /// при этих ошибках надо либо повторить запрос повторно, либо переинициализировать сессию (см. isUnrecoverable()) + bool isHardwareError() const + { + return isUnrecoverable() || code == ZCONNECTIONLOSS || code == ZOPERATIONTIMEOUT; + } + const int32_t code; private: From 74bf0583791d2225f9dcb318cd86d2aeaa635625 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Fri, 25 Dec 2015 12:10:17 +0300 Subject: [PATCH 12/32] zkutil: build fix [#METR-18346] --- libs/libzkutil/include/zkutil/ZooKeeperHolder.h | 1 + libs/libzkutil/src/Lock.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h index c2e009f0094..d82b74ea68e 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeperHolder.h +++ b/libs/libzkutil/include/zkutil/ZooKeeperHolder.h @@ -6,6 +6,7 @@ namespace zkutil { +class Lock; class ZooKeeperHolder : public boost::noncopyable { diff --git a/libs/libzkutil/src/Lock.cpp b/libs/libzkutil/src/Lock.cpp index 6dc397155bf..d10a3a10f28 100644 --- a/libs/libzkutil/src/Lock.cpp +++ b/libs/libzkutil/src/Lock.cpp @@ -41,7 +41,7 @@ bool Lock::tryLock() throw zkutil::KeeperException(code); } } - return locked; + return bool(locked); } void Lock::unlock() From 9c54ecb78b903affab00caa026a6689ce561d4ce Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 04:36:30 +0300 Subject: [PATCH 13/32] Cutting dependencies [#METR-2944]. --- dbms/src/Server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 5c623b4fe05..baad718b1dd 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -345,7 +345,7 @@ UsersConfigReloader::UsersConfigReloader(const std::string & path_, Context * co /// Сначала поищем его рядом с основным конфигом, потом - в текущей директории. if (path.empty() || path[0] != '/') { - std::string main_config_path = Application::instance().config().getString("config-file", "config.xml"); + std::string main_config_path = Poco::Util::Application::instance().config().getString("config-file", "config.xml"); std::string config_dir = Poco::Path(main_config_path).parent().toString(); if (Poco::File(config_dir + path).exists()) path = config_dir + path; From b22ce41d596f6afcbfcc3624f1d33631fe8e7d6f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 05:32:55 +0300 Subject: [PATCH 14/32] Removing dependency of daemon to statdaemons [#METR-17973]. --- dbms/src/Interpreters/Compiler.cpp | 2 -- dbms/src/Server/OLAPAttributesMetadata.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dbms/src/Interpreters/Compiler.cpp b/dbms/src/Interpreters/Compiler.cpp index a6f0c69fb77..8f9ce629c8c 100644 --- a/dbms/src/Interpreters/Compiler.cpp +++ b/dbms/src/Interpreters/Compiler.cpp @@ -192,8 +192,6 @@ void Compiler::compile( " -I /usr/share/clickhouse/headers/contrib/libcpuid/include/" " -I /usr/share/clickhouse/headers/libs/libcommon/include/" " -I /usr/share/clickhouse/headers/libs/libmysqlxx/include/" - " -I /usr/share/clickhouse/headers/libs/libstatdaemons/include/" - " -I /usr/share/clickhouse/headers/libs/libstats/include/" " " << additional_compiler_flags << " -o " << so_file_path << " " << cpp_file_path << " 2>&1 || echo Exit code: $?"; diff --git a/dbms/src/Server/OLAPAttributesMetadata.h b/dbms/src/Server/OLAPAttributesMetadata.h index d5ac92a7a15..53f16635050 100644 --- a/dbms/src/Server/OLAPAttributesMetadata.h +++ b/dbms/src/Server/OLAPAttributesMetadata.h @@ -12,9 +12,9 @@ #include #include + #include #include -#include /// Код в основном взят из из OLAP-server. Здесь нужен только для парсинга значений атрибутов. From 81d5b9aaaba0743bcb1d71b6dc7942a368961aa0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 05:47:19 +0300 Subject: [PATCH 15/32] Moved embedded dictionaries to dbms [#METR-17973]. --- .../Embedded/RegionsHierarchies.h | 80 +++++ .../Dictionaries/Embedded/RegionsHierarchy.h | 301 ++++++++++++++++++ .../DB/Dictionaries/Embedded/RegionsNames.h | 208 ++++++++++++ .../Dictionaries/Embedded/TechDataHierarchy.h | 117 +++++++ dbms/include/DB/Interpreters/Dictionaries.h | 6 +- dbms/src/Server/OLAPAttributesMetadata.h | 4 +- 6 files changed, 711 insertions(+), 5 deletions(-) create mode 100644 dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h create mode 100644 dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h create mode 100644 dbms/include/DB/Dictionaries/Embedded/RegionsNames.h create mode 100644 dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h diff --git a/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h b/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h new file mode 100644 index 00000000000..42721c07cb7 --- /dev/null +++ b/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + + +/** Содержит несколько иерархий регионов, загружаемых из нескольких разных файлов. + * Используется, чтобы поддержать несколько разных точек зрения о принадлежности регионов странам. + * В первую очередь, для Крыма (Российская и Украинская точки зрения). + */ +class RegionsHierarchies +{ +private: + typedef std::unordered_map Container; + Container data; + Logger * log = &Logger::get("RegionsHierarchies"); + +public: + static constexpr auto required_key = "path_to_regions_hierarchy_file"; + + /** path должен указывать на файл с иерархией регионов "по-умолчанию". Она будет доступна по пустому ключу. + * Кроме того, рядом ищутся файлы, к имени которых (до расширения, если есть) добавлен произвольный _suffix. + * Такие файлы загружаются, и иерархия регионов кладётся по ключу suffix. + * + * Например, если указано /opt/geo/regions_hierarchy.txt, + * то будет также загружен файл /opt/geo/regions_hierarchy_ua.txt, если такой есть - он будет доступен по ключу ua. + */ + RegionsHierarchies(const std::string & default_path = Poco::Util::Application::instance().config().getString(required_key)) + { + LOG_DEBUG(log, "Adding default regions hierarchy from " << default_path); + + data.emplace(std::piecewise_construct, + std::forward_as_tuple(""), + std::forward_as_tuple(default_path)); + + std::string basename = Poco::Path(default_path).getBaseName(); + + Poco::Path dir_path = Poco::Path(default_path).absolute().parent(); + + Poco::DirectoryIterator dir_end; + for (Poco::DirectoryIterator dir_it(dir_path); dir_it != dir_end; ++dir_it) + { + std::string other_basename = dir_it.path().getBaseName(); + + if (0 == other_basename.compare(0, basename.size(), basename) && other_basename.size() > basename.size() + 1) + { + if (other_basename[basename.size()] != '_') + continue; + + std::string suffix = other_basename.substr(basename.size() + 1); + + LOG_DEBUG(log, "Adding regions hierarchy from " << dir_it->path() << ", key: " << suffix); + + data.emplace(std::piecewise_construct, + std::forward_as_tuple(suffix), + std::forward_as_tuple(dir_it->path())); + } + } + } + + + /** Перезагружает, при необходимости, все иерархии регионов. + */ + void reload() + { + for (auto & elem : data) + elem.second.reload(); + } + + + const RegionsHierarchy & get(const std::string & key) const + { + auto it = data.find(key); + + if (data.end() == it) + throw Poco::Exception("There is no regions hierarchy for key " + key); + + return it->second; + } +}; diff --git a/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h b/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h new file mode 100644 index 00000000000..72aa66ac6e5 --- /dev/null +++ b/dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h @@ -0,0 +1,301 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#define REGION_TYPE_CITY 6 +#define REGION_TYPE_AREA 5 +#define REGION_TYPE_DISTRICT 4 +#define REGION_TYPE_COUNTRY 3 +#define REGION_TYPE_CONTINENT 1 + + +/** Класс, позволяющий узнавать, принадлежит ли регион с одним RegionID региону с другим RegionID. + * Информацию об иерархии регионов загружает из текстового файла. + * Умеет, по запросу, обновлять данные. + */ +class RegionsHierarchy : private boost::noncopyable +{ +private: + std::string path; + time_t file_modification_time; + Logger * log; + + typedef Int32 RegionID; + typedef Int8 RegionType; + typedef Int8 RegionDepth; + typedef UInt32 RegionPopulation; + + /// отношение parent; 0, если родителей нет - обычная lookup таблица. + typedef std::vector RegionParents; + /// тип региона + typedef std::vector RegionTypes; + /// глубина в дереве, начиная от страны (страна: 1, корень: 0) + typedef std::vector RegionDepths; + /// население региона. Если больше 2^32 - 1, то приравнивается к этому максимуму. + typedef std::vector RegionPopulations; + + /// регион -> родительский регион + RegionParents parents; + /// регион -> город, включающий его или 0, если такого нет + RegionParents city; + /// регион -> страна, включающая его или 0, если такого нет + RegionParents country; + /// регион -> область, включающая его или 0, если такой нет + RegionParents area; + /// регион -> округ, включающий его или 0, если такого нет + RegionParents district; + /// регион -> континет, включающий его или 0, если такого нет + RegionParents continent; + + /// регион -> население или 0, если неизвестно. + RegionPopulations populations; + + /// регион - глубина в дереве + RegionDepths depths; + +public: + RegionsHierarchy(const std::string & path_ = Poco::Util::Application::instance().config().getString("path_to_regions_hierarchy_file")) + : path(path_), file_modification_time(0), log(&Logger::get("RegionsHierarchy")) + { + } + + + /// Перезагружает, при необходимости, иерархию регионов. Непотокобезопасно. + void reload() + { + time_t new_modification_time = Poco::File(path).getLastModified().epochTime(); + if (new_modification_time <= file_modification_time) + return; + file_modification_time = new_modification_time; + + LOG_DEBUG(log, "Reloading regions hierarchy"); + + const size_t initial_size = 10000; + + RegionParents new_parents(initial_size); + RegionParents new_city(initial_size); + RegionParents new_country(initial_size); + RegionParents new_area(initial_size); + RegionParents new_district(initial_size); + RegionParents new_continent(initial_size); + RegionPopulations new_populations(initial_size); + RegionDepths new_depths(initial_size); + RegionTypes types(initial_size); + + DB::ReadBufferFromFile in(path); + + RegionID max_region_id = 0; + while (!in.eof()) + { + RegionID region_id = 0; + RegionID parent_id = 0; + RegionType type = 0; + RegionPopulation population = 0; + + DB::readIntText(region_id, in); + DB::assertString("\t", in); + DB::readIntText(parent_id, in); + DB::assertString("\t", in); + DB::readIntText(type, in); + + /** Далее может быть перевод строки (старый вариант) + * или таб, население региона, перевод строки (новый вариант). + */ + if (!in.eof() && *in.position() == '\t') + { + ++in.position(); + UInt64 population_big = 0; + DB::readIntText(population_big, in); + population = population_big > std::numeric_limits::max() + ? std::numeric_limits::max() + : population_big; + } + DB::assertString("\n", in); + + if (region_id <= 0) + continue; + + if (parent_id < 0) + parent_id = 0; + + if (region_id > max_region_id) + { + max_region_id = region_id; + + while (region_id >= static_cast(new_parents.size())) + { + new_parents.resize(new_parents.size() * 2); + new_populations.resize(new_parents.size()); + types.resize(new_parents.size()); + } + } + + new_parents[region_id] = parent_id; + new_populations[region_id] = population; + types[region_id] = type; + } + + new_parents .resize(max_region_id + 1); + new_city .resize(max_region_id + 1); + new_country .resize(max_region_id + 1); + new_area .resize(max_region_id + 1); + new_district .resize(max_region_id + 1); + new_continent .resize(max_region_id + 1); + new_populations .resize(max_region_id + 1); + new_depths .resize(max_region_id + 1); + types .resize(max_region_id + 1); + + /// пропишем города и страны для регионов + for (RegionID i = 0; i <= max_region_id; ++i) + { + if (types[i] == REGION_TYPE_CITY) + new_city[i] = i; + + if (types[i] == REGION_TYPE_AREA) + new_area[i] = i; + + if (types[i] == REGION_TYPE_DISTRICT) + new_district[i] = i; + + if (types[i] == REGION_TYPE_COUNTRY) + new_country[i] = i; + + if (types[i] == REGION_TYPE_CONTINENT) + { + new_continent[i] = i; + continue; + } + + RegionDepth depth = 0; + RegionID current = i; + while (true) + { + ++depth; + + current = new_parents[current]; + if (current == 0) + break; + + if (current > max_region_id) + throw Poco::Exception("Logical error in regions hierarchy: region " + DB::toString(current) + " (specified as parent) doesn't exist"); + + if (types[current] == REGION_TYPE_CITY) + new_city[i] = current; + + if (types[current] == REGION_TYPE_AREA) + new_area[i] = current; + + if (types[current] == REGION_TYPE_DISTRICT) + new_district[i] = current; + + if (types[current] == REGION_TYPE_COUNTRY) + new_country[i] = current; + + if (types[current] == REGION_TYPE_CONTINENT) + { + new_continent[i] = current; + break; + } + } + + new_depths[i] = depth; + } + + parents.swap(new_parents); + country.swap(new_country); + city.swap(new_city); + area.swap(new_area); + district.swap(new_district); + continent.swap(new_continent); + populations.swap(new_populations); + depths.swap(new_depths); + } + + + bool in(RegionID lhs, RegionID rhs) const + { + if (static_cast(lhs) >= parents.size()) + return false; + + while (lhs != 0 && lhs != rhs) + lhs = parents[lhs]; + + return lhs != 0; + } + + RegionID toCity(RegionID region) const + { + if (static_cast(region) >= city.size()) + return 0; + return city[region]; + } + + RegionID toCountry(RegionID region) const + { + if (static_cast(region) >= country.size()) + return 0; + return country[region]; + } + + RegionID toArea(RegionID region) const + { + if (static_cast(region) >= area.size()) + return 0; + return area[region]; + } + + RegionID toDistrict(RegionID region) const + { + if (static_cast(region) >= district.size()) + return 0; + return district[region]; + } + + RegionID toContinent(RegionID region) const + { + if (static_cast(region) >= continent.size()) + return 0; + return continent[region]; + } + + RegionID toParent(RegionID region) const + { + if (static_cast(region) >= parents.size()) + return 0; + return parents[region]; + } + + RegionDepth getDepth(RegionID region) const + { + if (static_cast(region) >= depths.size()) + return 0; + return depths[region]; + } + + RegionPopulation getPopulation(RegionID region) const + { + if (static_cast(region) >= populations.size()) + return 0; + return populations[region]; + } +}; + + +class RegionsHierarchySingleton : public Singleton, public RegionsHierarchy +{ +friend class Singleton; +protected: + RegionsHierarchySingleton() + { + } +}; diff --git a/dbms/include/DB/Dictionaries/Embedded/RegionsNames.h b/dbms/include/DB/Dictionaries/Embedded/RegionsNames.h new file mode 100644 index 00000000000..27187e2db79 --- /dev/null +++ b/dbms/include/DB/Dictionaries/Embedded/RegionsNames.h @@ -0,0 +1,208 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +/** @brief Класс, позволяющий узнавать по id региона его текстовое название на одном из поддерживаемых языков: ru, en, ua, by, kz, tr. + * + * Информацию об именах регионов загружает из текстовых файлов с названиями следующего формата: + * regions_names_xx.txt, + * где xx - одно из двух буквенных обозначений следующих поддерживаемых языков: + * ru, en, ua, by, kz, tr. + * + * Умеет, по запросу, обновлять данные. + */ +class RegionsNames +{ +public: + enum class Language + { + RU = 0, + EN, + UA, + BY, + KZ, + TR, + }; + +private: + static const size_t ROOT_LANGUAGE = 0; + static const size_t SUPPORTED_LANGUAGES_COUNT = 6; + static const size_t LANGUAGE_ALIASES_COUNT = 7; + + static const char ** getSupportedLanguages() + { + static const char * res[] { "ru", "en", "ua", "by", "kz", "tr" }; + return res; + } + + struct language_alias { const char * const name; const Language lang; }; + static const language_alias * getLanguageAliases() + { + static constexpr const language_alias language_aliases[] { + { "ru", Language::RU }, + { "en", Language::EN }, + { "ua", Language::UA }, + { "uk", Language::UA }, + { "by", Language::BY }, + { "kz", Language::KZ }, + { "tr", Language::TR } + }; + + return language_aliases; + } + + typedef int RegionID_t; + + typedef std::vector Chars_t; + typedef std::vector CharsForLanguageID_t; + typedef std::vector ModificationTimes_t; + typedef std::vector StringRefs_t; /// Lookup table RegionID -> StringRef + typedef std::vector StringRefsForLanguageID_t; + +public: + static constexpr auto required_key = "path_to_regions_names_files"; + + RegionsNames(const std::string & directory_ = Poco::Util::Application::instance().config().getString(required_key)) + : directory(directory_) + { + } + + + /** @brief Перезагружает, при необходимости, имена регионов. + */ + void reload() + { + LOG_DEBUG(log, "Reloading regions names"); + + RegionID_t max_region_id = 0; + for (size_t language_id = 0; language_id < SUPPORTED_LANGUAGES_COUNT; ++language_id) + { + const std::string & language = getSupportedLanguages()[language_id]; + std::string path = directory + "/regions_names_" + language + ".txt"; + + Poco::File file(path); + time_t new_modification_time = file.getLastModified().epochTime(); + if (new_modification_time <= file_modification_times[language_id]) + continue; + file_modification_times[language_id] = new_modification_time; + + LOG_DEBUG(log, "Reloading regions names for language: " << language); + + DB::ReadBufferFromFile in(path); + + const size_t initial_size = 10000; + + Chars_t new_chars; + StringRefs_t new_names_refs(initial_size, StringRef("", 0)); + + /// Выделим непрерывный кусок памяти, которого хватит для хранения всех имён. + new_chars.reserve(Poco::File(path).getSize()); + + while (!in.eof()) + { + RegionID_t region_id; + std::string region_name; + + DB::readIntText(region_id, in); + DB::assertString("\t", in); + DB::readString(region_name, in); + DB::assertString("\n", in); + + if (region_id <= 0) + continue; + + size_t old_size = new_chars.size(); + + if (new_chars.capacity() < old_size + region_name.length() + 1) + throw Poco::Exception("Logical error. Maybe size of file " + path + " is wrong."); + + new_chars.resize(old_size + region_name.length() + 1); + memcpy(&new_chars[old_size], region_name.c_str(), region_name.length() + 1); + + if (region_id > max_region_id) + max_region_id = region_id; + + while (region_id >= static_cast(new_names_refs.size())) + new_names_refs.resize(new_names_refs.size() * 2, StringRef("", 0)); + + new_names_refs[region_id] = StringRef(&new_chars[old_size], region_name.length()); + } + + chars[language_id].swap(new_chars); + names_refs[language_id].swap(new_names_refs); + } + + for (size_t language_id = 0; language_id < SUPPORTED_LANGUAGES_COUNT; ++language_id) + names_refs[language_id].resize(max_region_id + 1, StringRef("", 0)); + } + + StringRef getRegionName(RegionID_t region_id, Language language = Language::RU) const + { + size_t language_id = static_cast(language); + + if (static_cast(region_id) > names_refs[language_id].size()) + return StringRef("", 0); + + StringRef ref = names_refs[language_id][region_id]; + + while (ref.size == 0 && language_id != ROOT_LANGUAGE) + { + static const size_t FALLBACK[] = { 0, 0, 0, 0, 0, 1 }; + language_id = FALLBACK[language_id]; + ref = names_refs[language_id][region_id]; + } + + return ref; + } + + static Language getLanguageEnum(const std::string & language) + { + if (language.size() == 2) + { + for (size_t i = 0; i < LANGUAGE_ALIASES_COUNT; ++i) + { + const auto & alias = getLanguageAliases()[i]; + if (language[0] == alias.name[0] && language[1] == alias.name[1]) + return alias.lang; + } + } + throw Poco::Exception("Unsupported language for region name. Supported languages are: " + dumpSupportedLanguagesNames() + "."); + } + + static std::string dumpSupportedLanguagesNames() + { + std::string res = ""; + for (size_t i = 0; i < LANGUAGE_ALIASES_COUNT; ++i) + { + if (i > 0) + res += ", "; + res += '\''; + res += getLanguageAliases()[i].name; + res += '\''; + } + return res; + } + +private: + const std::string directory; + ModificationTimes_t file_modification_times = ModificationTimes_t(SUPPORTED_LANGUAGES_COUNT); + Logger * log = &Logger::get("RegionsNames"); + + /// Байты имен для каждого языка, уложенные подряд, разделенные нулями + CharsForLanguageID_t chars = CharsForLanguageID_t(SUPPORTED_LANGUAGES_COUNT); + + /// Отображение для каждого языка из id региона в указатель на диапазон байт имени + StringRefsForLanguageID_t names_refs = StringRefsForLanguageID_t(SUPPORTED_LANGUAGES_COUNT); +}; diff --git a/dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h b/dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h new file mode 100644 index 00000000000..a44749c7090 --- /dev/null +++ b/dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h @@ -0,0 +1,117 @@ +#pragma once + +#include + +#include +#include + +#include + + +/** @brief Класс, позволяющий узнавать, принадлежит ли поисковая система или операционная система + * другой поисковой или операционной системе, соответственно. + * Информацию об иерархии регионов загружает из БД. + */ +class TechDataHierarchy +{ +private: + Logger * log; + + UInt8 os_parent[256]; + UInt8 se_parent[256]; + +public: + static constexpr auto required_key = "mysql_metrica"; + + TechDataHierarchy() + : log(&Logger::get("TechDataHierarchy")) + { + LOG_DEBUG(log, "Loading tech data hierarchy."); + + memset(os_parent, 0, sizeof(os_parent)); + memset(se_parent, 0, sizeof(se_parent)); + + mysqlxx::PoolWithFailover pool(required_key); + mysqlxx::Pool::Entry conn = pool.Get(); + + { + mysqlxx::Query q = conn->query("SELECT Id, COALESCE(Parent_Id, 0) FROM OS2"); + LOG_TRACE(log, q.str()); + mysqlxx::UseQueryResult res = q.use(); + while (mysqlxx::Row row = res.fetch()) + { + UInt64 child = row[0].getUInt(); + UInt64 parent = row[1].getUInt(); + + if (child > 255 || parent > 255) + throw Poco::Exception("Too large OS id (> 255)."); + + os_parent[child] = parent; + } + } + + { + mysqlxx::Query q = conn->query("SELECT Id, COALESCE(ParentId, 0) FROM SearchEngines"); + LOG_TRACE(log, q.str()); + mysqlxx::UseQueryResult res = q.use(); + while (mysqlxx::Row row = res.fetch()) + { + UInt64 child = row[0].getUInt(); + UInt64 parent = row[1].getUInt(); + + if (child > 255 || parent > 255) + throw Poco::Exception("Too large search engine id (> 255)."); + + se_parent[child] = parent; + } + } + } + + + /// Отношение "принадлежит". + bool isOSIn(UInt8 lhs, UInt8 rhs) const + { + while (lhs != rhs && os_parent[lhs]) + lhs = os_parent[lhs]; + + return lhs == rhs; + } + + bool isSEIn(UInt8 lhs, UInt8 rhs) const + { + while (lhs != rhs && se_parent[lhs]) + lhs = se_parent[lhs]; + + return lhs == rhs; + } + + + UInt8 OSToParent(UInt8 x) const + { + return os_parent[x]; + } + + UInt8 SEToParent(UInt8 x) const + { + return se_parent[x]; + } + + + /// К самому верхнему предку. + UInt8 OSToMostAncestor(UInt8 x) const + { + while (os_parent[x]) + x = os_parent[x]; + return x; + } + + UInt8 SEToMostAncestor(UInt8 x) const + { + while (se_parent[x]) + x = se_parent[x]; + return x; + } +}; + + +class TechDataHierarchySingleton : public Singleton, public TechDataHierarchy {}; diff --git a/dbms/include/DB/Interpreters/Dictionaries.h b/dbms/include/DB/Interpreters/Dictionaries.h index c37f6a6dd70..2045f6f9b00 100644 --- a/dbms/include/DB/Interpreters/Dictionaries.h +++ b/dbms/include/DB/Interpreters/Dictionaries.h @@ -3,9 +3,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include diff --git a/dbms/src/Server/OLAPAttributesMetadata.h b/dbms/src/Server/OLAPAttributesMetadata.h index 53f16635050..dd67bfc559e 100644 --- a/dbms/src/Server/OLAPAttributesMetadata.h +++ b/dbms/src/Server/OLAPAttributesMetadata.h @@ -13,8 +13,8 @@ #include -#include -#include +#include +#include /// Код в основном взят из из OLAP-server. Здесь нужен только для парсинга значений атрибутов. From 30b4b1e4a52b7dbcc827dc14ef75906669bdcf67 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 06:55:07 +0300 Subject: [PATCH 16/32] dbms: removed dependency of clickhouse-server to statdaemons [#METR-17973]. --- dbms/src/Server/Server.h | 2 +- libs/libcommon/include/common/Daemon.h | 174 ++++ .../libcommon/include/common/GraphiteWriter.h | 82 ++ libs/libcommon/src/Daemon.cpp | 850 ++++++++++++++++++ libs/libcommon/src/GraphiteWriter.cpp | 86 ++ 5 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 libs/libcommon/include/common/Daemon.h create mode 100644 libs/libcommon/include/common/GraphiteWriter.h create mode 100644 libs/libcommon/src/Daemon.cpp create mode 100644 libs/libcommon/src/GraphiteWriter.cpp diff --git a/dbms/src/Server/Server.h b/dbms/src/Server/Server.h index 45e8b99a88e..0996b9d2a7e 100644 --- a/dbms/src/Server/Server.h +++ b/dbms/src/Server/Server.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include diff --git a/libs/libcommon/include/common/Daemon.h b/libs/libcommon/include/common/Daemon.h new file mode 100644 index 00000000000..ec22c7e6cca --- /dev/null +++ b/libs/libcommon/include/common/Daemon.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + + +namespace Poco { class TaskManager; } + + +/// \brief Базовый класс для демонов +/// +/// \code +/// # Список возможных опций командной строки обрабатываемых демоном: +/// # --config-file или --config - имя файла конфигурации. По умолчанию - config.xml +/// # --pid-file - имя PID файла. По умолчанию - pid +/// # --log-file - имя лог файла +/// # --error-file - имя лог файла, в который будут помещаться только ошибки +/// # --daemon - запустить в режиме демона; если не указан - логгирование будет вестись на консоль +/// --daemon --config-file=localfile.xml --pid-file=pid.pid --log-file=log.log --errorlog-file=error.log +/// \endcode +/// +/// Если неперехваченное исключение выкинуто в других потоках (не Task-и), то по-умолчанию +/// используется KillingErrorHandler, который вызывает std::terminate. +/// +/// Кроме того, класс позволяет достаточно гибко управлять журналированием. В методе initialize() вызывается метод +/// buildLoggers() который и строит нужные логгеры. Эта функция ожидает увидеть в конфигурации определённые теги +/// заключённые в секции "logger". +/// Если нужно журналирование на консоль, нужно просто не использовать тег "log" или использовать --console. +/// Теги уровней вывода использовать можно в любом случае + + +class Daemon : public Poco::Util::ServerApplication +{ +public: + Daemon(); + ~Daemon(); + + /// Загружает конфигурацию и "строит" логгеры на запись в файлы + void initialize(Poco::Util::Application &); + + /// Читает конфигурацию + void reloadConfiguration(); + + /// Строит необходимые логгеры + void buildLoggers(); + + /// Определяет параметр командной строки + void defineOptions(Poco::Util::OptionSet& _options); + + /// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно + void exitOnTaskError(); + + /// Возвращает TaskManager приложения + Poco::TaskManager & getTaskManager() { return *task_manager; } + + /// Завершение демона ("мягкое") + void terminate(); + + /// Завершение демона ("жёсткое") + void kill(); + + /// Получен ли сигнал на завершение? + bool isCancelled() + { + return is_cancelled; + } + + /// Получение ссылки на экземпляр демона + static Daemon & instance() + { + return dynamic_cast(Poco::Util::Application::instance()); + } + + /// Спит заданное количество секунд или до события wakeup + void sleep(double seconds); + + /// Разбудить + void wakeup(); + + /// Закрыть файлы с логами. При следующей записи, будут созданы новые файлы. + void closeLogs(); + + /// В Graphite компоненты пути(папки) разделяются точкой. + /// У нас принят путь формата root_path.hostname_yandex_ru.key + /// root_path по умолчанию one_min + /// key - лучше группировать по смыслу. Например "meminfo.cached" или "meminfo.free", "meminfo.total" + template + void writeToGraphite(const std::string & key, const T & value, time_t timestamp = 0, const std::string & custom_root_path = "") + { + graphite_writer->write(key, value, timestamp, custom_root_path); + } + + template + void writeToGraphite(const GraphiteWriter::KeyValueVector & key_vals, time_t timestamp = 0, const std::string & custom_root_path = "") + { + graphite_writer->write(key_vals, timestamp, custom_root_path); + } + + boost::optional getLayer() const + { + return layer; /// layer выставляется в классе-наследнике BaseDaemonApplication. + } + +protected: + + /// Используется при exitOnTaskError() + void handleNotification(Poco::TaskFailedNotification *); + + std::unique_ptr task_manager; + + /// Создание и автоматическое удаление pid файла. + struct PID + { + std::string file; + + /// Создать объект, не создавая PID файл + PID() {} + + /// Создать объект, создать PID файл + PID(const std::string & file_) { seed(file_); } + + /// Создать PID файл + void seed(const std::string & file_); + + /// Удалить PID файл + void clear(); + + ~PID() { clear(); } + }; + + PID pid; + + /// Получен ли сигнал на завершение? Этот флаг устанавливается в BaseDaemonApplication. + bool is_cancelled = false; + + bool log_to_console = false; + + /// Событие, чтобы проснуться во время ожидания + Poco::Event wakeup_event; + + /// Поток, в котором принимается сигнал HUP/USR1 для закрытия логов. + Poco::Thread close_logs_thread; + std::unique_ptr close_logs_listener; + + /// Файлы с логами. + Poco::AutoPtr log_file; + Poco::AutoPtr error_log_file; + Poco::AutoPtr syslog_channel; + + std::unique_ptr graphite_writer; + + boost::optional layer; +}; diff --git a/libs/libcommon/include/common/GraphiteWriter.h b/libs/libcommon/include/common/GraphiteWriter.h new file mode 100644 index 00000000000..8d01131716b --- /dev/null +++ b/libs/libcommon/include/common/GraphiteWriter.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +/// пишет в Graphite данные в формате +/// path value timestamp\n +/// path может иметь любую вложенность. Директории разделяются с помощью "." +/// у нас принят следующий формат path - root_path.server_name.sub_path.key +class GraphiteWriter +{ +public: + GraphiteWriter(const std::string & config_name, const std::string & sub_path = ""); + + template using KeyValuePair = std::pair; + template using KeyValueVector = std::vector>; + + template void write(const std::string & key, const T & value, + time_t timestamp = 0, const std::string & custom_root_path = "") + { + writeImpl(KeyValuePair{ key, value }, timestamp, custom_root_path); + } + + template void write(const KeyValueVector & key_val_vec, + time_t timestamp = 0, const std::string & custom_root_path = "") + { + writeImpl(key_val_vec, timestamp, custom_root_path); + } + + /// Для облачных демонов удобней использовать + /// путь вида prefix.environment.layer.daemon_name.metrica + static const std::string & getPerLayerPath(const std::string & prefix = "one_min"); + + /// возвращает путь root_path.server_name + static std::string getPerServerPath(const std::string & server_name, const std::string & root_path = "one_min"); +private: + template + void writeImpl(const T & data, time_t timestamp, const std::string & custom_root_path) + { + if (!timestamp) + timestamp = time(0); + + try + { + Poco::Net::SocketAddress socket_address(host, port); + Poco::Net::StreamSocket socket(socket_address); + socket.setSendTimeout(Poco::Timespan(timeout * 1000000)); + Poco::Net::SocketStream str(socket); + + out(str, data, timestamp, custom_root_path); + } + catch (const Poco::Exception & e) + { + LOG_WARNING(&Poco::Util::Application::instance().logger(), + "Fail to write to Graphite " << host << ":" << port << ". e.what() = " << e.what() << ", e.message() = " << e.message()); + } + } + + template + void out(std::ostream & os, const KeyValuePair & key_val, time_t timestamp, const std::string & custom_root_path) + { + os << (custom_root_path.empty() ? root_path : custom_root_path) << + '.' << key_val.first << ' ' << key_val.second << ' ' << timestamp << '\n'; + } + + template + void out(std::ostream & os, const KeyValueVector & key_val_vec, time_t timestamp, const std::string & custom_root_path) + { + for (const auto & key_val : key_val_vec) + out(os, key_val, timestamp, custom_root_path); + } + + std::string root_path; + + int port; + std::string host; + double timeout; +}; diff --git a/libs/libcommon/src/Daemon.cpp b/libs/libcommon/src/Daemon.cpp new file mode 100644 index 00000000000..8ed7d182fd5 --- /dev/null +++ b/libs/libcommon/src/Daemon.cpp @@ -0,0 +1,850 @@ +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + + +using Poco::Logger; +using Poco::AutoPtr; +using Poco::Observer; +using Poco::FormattingChannel; +using Poco::SplitterChannel; +using Poco::ConsoleChannel; +using Poco::FileChannel; +using Poco::Path; +using Poco::Message; +using Poco::Util::AbstractConfiguration; + + +/** Для передачи информации из обработчика сигнала для обработки в другом потоке. + * Если при получении сигнала надо делать что-нибудь серьёзное (например, вывести сообщение в лог), + * то передать нужную информацию через pipe в другой поток и сделать там всю работу + * - один из немногих безопасных способов сделать это. + */ +struct Pipe +{ + union + { + int fds[2]; + struct + { + int read_fd; + int write_fd; + }; + }; + + Pipe() + { + if (0 != pipe(fds)) + DB::throwFromErrno("Cannot create pipe"); + } + + ~Pipe() + { + close(fds[0]); + close(fds[1]); + } +}; + + +Pipe signal_pipe; + + +/** Устанавливает обработчик сигнала по умолчанию, и отправляет себе сигнал sig. + * Вызывается изнутри пользовательского обработчика сигнала, чтобы записать core dump. + */ +static void call_default_signal_handler(int sig) +{ + signal(sig, SIG_DFL); + kill(getpid(), sig); +} + + +typedef decltype(Poco::ThreadNumber::get()) ThreadNumber; +static const size_t buf_size = sizeof(int) + sizeof(siginfo_t) + sizeof(ucontext_t) + sizeof(ThreadNumber); + + +/** Обработчик сигналов HUP / USR1 */ +static void close_logs_signal_handler(int sig, siginfo_t * info, void * context) +{ + char buf[buf_size]; + DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); + DB::writeBinary(sig, out); + out.next(); +} + + +/** Обработчик некоторых сигналов. Выводит информацию в лог (если получится). + */ +static void fault_signal_handler(int sig, siginfo_t * info, void * context) +{ + char buf[buf_size]; + DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); + + DB::writeBinary(sig, out); + DB::writePODBinary(*info, out); + DB::writePODBinary(*reinterpret_cast(context), out); + DB::writeBinary(Poco::ThreadNumber::get(), out); + + out.next(); + + /// Время, за которое читающий из pipe поток должен попытаться успеть вывести в лог stack trace. + ::sleep(10); + + call_default_signal_handler(sig); +} + + +static bool already_printed_stack_trace = false; + + +/** Получает информацию через pipe. + * При получении сигнала HUP / USR1 закрывает лог-файлы. + * При получении информации из std::terminate, выводит её в лог. + * При получении других сигналов, выводит информацию в лог. + */ +class SignalListener : public Poco::Runnable +{ +public: + SignalListener() : log(&Logger::get("Daemon")) + { + } + + void run() + { + char buf[buf_size]; + DB::ReadBufferFromFileDescriptor in(signal_pipe.read_fd, buf_size, buf); + + while (!in.eof()) + { + int sig = 0; + DB::readBinary(sig, in); + + if (sig == SIGHUP || sig == SIGUSR1) + { + LOG_DEBUG(log, "Received signal to close logs."); + Daemon::instance().closeLogs(); + LOG_INFO(log, "Opened new log file after received signal."); + } + else if (sig == -1) /// -1 для обозначения std::terminate. + { + ThreadNumber thread_num; + std::string message; + + DB::readBinary(thread_num, in); + DB::readBinary(message, in); + + onTerminate(message, thread_num); + } + else + { + siginfo_t info; + ucontext_t context; + ThreadNumber thread_num; + + DB::readPODBinary(info, in); + DB::readPODBinary(context, in); + DB::readBinary(thread_num, in); + + onFault(sig, info, context, thread_num); + } + } + } + +private: + Logger * log; + + + void onTerminate(const std::string & message, ThreadNumber thread_num) const + { + LOG_ERROR(log, "(from thread " << thread_num << ") " << message); + } + + void onFault(int sig, siginfo_t & info, ucontext_t & context, ThreadNumber thread_num) const + { + LOG_ERROR(log, "########################################"); + LOG_ERROR(log, "(from thread " << thread_num << ") " + << "Received signal " << strsignal(sig) << " (" << sig << ")" << "."); + + if (sig == SIGSEGV) + { + /// Выводим информацию об адресе и о причине. + if (nullptr == info.si_addr) + LOG_ERROR(log, "Address: NULL pointer."); + else + LOG_ERROR(log, "Address: " << info.si_addr + << (info.si_code == SEGV_ACCERR ? ". Attempted access has violated the permissions assigned to the memory area." : "")); + } + + if (already_printed_stack_trace) + return; + + void * caller_address = nullptr; + + /// Get the address at the time the signal was raised from the RIP (x86-64) + caller_address = reinterpret_cast(context.uc_mcontext.gregs[REG_RIP]); + + static const int max_frames = 50; + void * frames[max_frames]; + int frames_size = backtrace(frames, max_frames); + + if (frames_size >= 2) + { + /// Overwrite sigaction with caller's address + if (caller_address && (frames_size < 3 || caller_address != frames[2])) + frames[1] = caller_address; + + char ** symbols = backtrace_symbols(frames, frames_size); + + if (!symbols) + { + if (caller_address) + LOG_ERROR(log, "Caller address: " << caller_address); + } + else + { + for (int i = 1; i < frames_size; ++i) + { + /// Делаем demangling имён. Имя находится в скобках, до символа '+'. + + char * name_start = nullptr; + char * name_end = nullptr; + char * demangled_name = nullptr; + int status = 0; + + if (nullptr != (name_start = strchr(symbols[i], '(')) + && nullptr != (name_end = strchr(name_start, '+'))) + { + ++name_start; + *name_end = '\0'; + demangled_name = abi::__cxa_demangle(name_start, 0, 0, &status); + *name_end = '+'; + } + + std::stringstream res; + + res << i << ". "; + + if (nullptr != demangled_name && 0 == status) + { + res.write(symbols[i], name_start - symbols[i]); + res << demangled_name << name_end; + } + else + res << symbols[i]; + + LOG_ERROR(log, res.rdbuf()); + } + } + } + } +}; + + +/** Для использования с помощью std::set_terminate. + * Собирает чуть больше информации, чем __gnu_cxx::__verbose_terminate_handler, + * и отправляет её в pipe. Другой поток читает из pipe и выводит её в лог. + * См. исходники в libstdc++-v3/libsupc++/vterminate.cc + */ +static void terminate_handler() +{ + static __thread bool terminating = false; + if (terminating) + { + abort(); + return; + } + + terminating = true; + + /// Сюда записываем информацию для логгирования. + std::stringstream log; + + std::type_info * t = abi::__cxa_current_exception_type(); + if (t) + { + /// Note that "name" is the mangled name. + char const * name = t->name(); + { + int status = -1; + char * dem = 0; + + dem = abi::__cxa_demangle(name, 0, 0, &status); + + log << "Terminate called after throwing an instance of " << (status == 0 ? dem : name) << std::endl; + + if (status == 0) + free(dem); + } + + already_printed_stack_trace = true; + + /// If the exception is derived from std::exception, we can give more information. + try + { + throw; + } + catch (DB::Exception & e) + { + log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl; + } + catch (Poco::Exception & e) + { + log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl; + } + catch (const std::exception & e) + { + log << "what(): " << e.what() << std::endl; + } + catch (...) + { + } + + log << "Stack trace:\n\n" << StackTrace().toString() << std::endl; + } + else + { + log << "Terminate called without an active exception" << std::endl; + } + + static const size_t buf_size = 1024; + + std::string log_message = log.str(); + if (log_message.size() > buf_size - 16) + log_message.resize(buf_size - 16); + + char buf[buf_size]; + DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); + + DB::writeBinary(-1, out); + DB::writeBinary(Poco::ThreadNumber::get(), out); + DB::writeBinary(log_message, out); + out.next(); + + abort(); +} + + +static std::string createDirectory(const std::string & _path) +{ + Poco::Path path(_path); + std::string str; + for(int j=0;j layer = daemon.getLayer(); + if (layer) + { + writeCString("layer[", wb); + DB::writeIntText(*layer, wb); + writeCString("]: ", wb); + } + } + + /// Выведем время с точностью до миллисекунд. + timeval tv; + if (0 != gettimeofday(&tv, nullptr)) + DB::throwFromErrno("Cannot gettimeofday"); + + /// Поменяем разделители у даты для совместимости. + DB::writeDateTimeText<'.', ':'>(tv.tv_sec, wb); + + int milliseconds = tv.tv_usec / 1000; + DB::writeChar('.', wb); + DB::writeChar('0' + ((milliseconds / 100) % 10), wb); + DB::writeChar('0' + ((milliseconds / 10) % 10), wb); + DB::writeChar('0' + (milliseconds % 10), wb); + + writeCString(" [ ", wb); + DB::writeIntText(Poco::ThreadNumber::get(), wb); + writeCString(" ] <", wb); + DB::writeString(getPriorityName(static_cast(msg.getPriority())), wb); + writeCString("> ", wb); + DB::writeString(msg.getSource(), wb); + writeCString(": ", wb); + DB::writeString(msg.getText(), wb); + } + +private: + const Daemon & daemon; + Options options; +}; + + +/// Для создания и уничтожения unique_ptr, который в заголовочном файле объявлен от incomplete type. +Daemon::Daemon() = default; +Daemon::~Daemon() = default; + + +void Daemon::terminate() +{ + getTaskManager().cancelAll(); + ServerApplication::terminate(); +} + +void Daemon::kill() +{ + pid.clear(); + Poco::Process::kill(getpid()); +} + +void Daemon::sleep(double seconds) +{ + wakeup_event.reset(); + wakeup_event.tryWait(seconds * 1000); +} + +void Daemon::wakeup() +{ + wakeup_event.set(); +} + + +/// Строит необходимые логгеры +void Daemon::buildLoggers() +{ + /// Сменим директорию для лога + if (config().hasProperty("logger.log") && !log_to_console) + { + std::string path = createDirectory(config().getString("logger.log")); + if (config().getBool("application.runAsDaemon", false) + && chdir(path.c_str()) != 0) + throw Poco::Exception("Cannot change directory to " + path); + } + else + { + if (config().getBool("application.runAsDaemon", false) + && chdir("/tmp") != 0) + throw Poco::Exception("Cannot change directory to /tmp"); + } + + if (config().hasProperty("logger.errorlog") && !log_to_console) + createDirectory(config().getString("logger.errorlog")); + + if (config().hasProperty("logger.log") && !log_to_console) + { + std::cerr << "Should logs to " << config().getString("logger.log") << std::endl; + + // splitter + Poco::AutoPtr split = new SplitterChannel; + + // set up two channel chains + Poco::AutoPtr pf = new OwnPatternFormatter(*this); + pf->setProperty("times", "local"); + Poco::AutoPtr log = new FormattingChannel(pf); + log_file = new FileChannel; + log_file->setProperty("path", Poco::Path(config().getString("logger.log")).absolute().toString()); + log_file->setProperty("rotation", config().getRawString("logger.size", "100M")); + log_file->setProperty("archive", "number"); + log_file->setProperty("compress", config().getRawString("logger.compress", "true")); + log_file->setProperty("purgeCount", config().getRawString("logger.count", "1")); + log->setChannel(log_file); + split->addChannel(log); + log_file->open(); + + if (config().hasProperty("logger.errorlog")) + { + std::cerr << "Should error logs to " << config().getString("logger.errorlog") << std::endl; + Poco::AutoPtr level = new Poco::LevelFilterChannel; + level->setLevel(Message::PRIO_NOTICE); + Poco::AutoPtr pf = new OwnPatternFormatter(*this); + pf->setProperty("times", "local"); + Poco::AutoPtr errorlog = new FormattingChannel(pf); + error_log_file = new FileChannel; + error_log_file->setProperty("path", Poco::Path(config().getString("logger.errorlog")).absolute().toString()); + error_log_file->setProperty("rotation", config().getRawString("logger.size", "100M")); + error_log_file->setProperty("archive", "number"); + error_log_file->setProperty("compress", config().getRawString("logger.compress", "true")); + error_log_file->setProperty("purgeCount", config().getRawString("logger.count", "1")); + errorlog->setChannel(error_log_file); + level->setChannel(errorlog); + split->addChannel(level); + errorlog->open(); + } + + if (config().getBool("logger.use_syslog", false) || config().getBool("dynamic_layer_selection", false)) + { + Poco::AutoPtr pf = new OwnPatternFormatter(*this, OwnPatternFormatter::ADD_LAYER_TAG); + pf->setProperty("times", "local"); + Poco::AutoPtr log = new FormattingChannel(pf); + syslog_channel = new Poco::SyslogChannel(commandName(), Poco::SyslogChannel::SYSLOG_CONS | Poco::SyslogChannel::SYSLOG_PID, Poco::SyslogChannel::SYSLOG_DAEMON); + log->setChannel(syslog_channel); + split->addChannel(log); + syslog_channel->open(); + } + + split->open(); + logger().close(); + logger().setChannel(split); + } + else + { + // Выводим на консоль + Poco::AutoPtr file = new ConsoleChannel; + Poco::AutoPtr pf = new OwnPatternFormatter(*this); + pf->setProperty("times", "local"); + Poco::AutoPtr log = new FormattingChannel(pf); + log->setChannel(file); + + logger().close(); + logger().setChannel(log); + logger().warning("Logging to console"); + } + + // Уровни для всех + logger().setLevel(config().getString("logger.level", "trace")); + + // Прикрутим к корневому логгеру + Logger::root().setLevel(logger().getLevel()); + Logger::root().setChannel(logger().getChannel()); + + // Уровни для явно указанных логгеров + AbstractConfiguration::Keys levels; + config().keys("logger.levels", levels); + + if(!levels.empty()) + for(AbstractConfiguration::Keys::iterator it = levels.begin(); it != levels.end(); ++it) + Logger::get(*it).setLevel(config().getString("logger.levels." + *it, "trace")); +} + + +void Daemon::closeLogs() +{ + if (log_file) + log_file->close(); + if (error_log_file) + error_log_file->close(); + + if (!log_file) + logger().warning("Logging to console but received signal to close log file (ignoring)."); +} + +void Daemon::initialize(Application& self) +{ + /// В случае падения - сохраняем коры + { + struct rlimit rlim; + if (getrlimit(RLIMIT_CORE, &rlim)) + throw Poco::Exception("Cannot getrlimit"); + rlim.rlim_cur = 1024 * 1024 * 1024; /// 1 GiB. Если больше - они слишком долго пишутся на диск. + if (setrlimit(RLIMIT_CORE, &rlim)) + throw Poco::Exception("Cannot setrlimit"); + } + + task_manager.reset(new Poco::TaskManager); + ServerApplication::initialize(self); + + bool is_daemon = config().getBool("application.runAsDaemon", false); + + if (is_daemon) + { + /** При создании pid файла и поиске конфигурации, будем интерпретировать относительные пути + * от директории запуска программы. + */ + std::string path = config().getString("application.path"); + if (0 != chdir(Poco::Path(path).setFileName("").toString().c_str())) + throw Poco::Exception("Cannot change directory to " + path); + + /// Создадим pid-file. + if (is_daemon && config().has("pid")) + pid.seed(config().getString("pid")); + } + + /// Считаем конфигурацию + reloadConfiguration(); + buildLoggers(); + + /// Ставим terminate_handler + std::set_terminate(terminate_handler); + + /// Ставим обработчики сигналов + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = fault_signal_handler; + sa.sa_flags = SA_SIGINFO; + + { + int signals[] = {SIGABRT, SIGSEGV, SIGILL, SIGBUS, SIGSYS, SIGFPE, SIGPIPE, 0}; + + if (sigemptyset(&sa.sa_mask)) + throw Poco::Exception("Cannot set signal handler."); + + for (size_t i = 0; signals[i]; ++i) + if (sigaddset(&sa.sa_mask, signals[i])) + throw Poco::Exception("Cannot set signal handler."); + + for (size_t i = 0; signals[i]; ++i) + if (sigaction(signals[i], &sa, 0)) + throw Poco::Exception("Cannot set signal handler."); + } + + sa.sa_sigaction = close_logs_signal_handler; + + { + int signals[] = {SIGHUP, SIGUSR1, 0}; + + if (sigemptyset(&sa.sa_mask)) + throw Poco::Exception("Cannot set signal handler."); + + for (size_t i = 0; signals[i]; ++i) + if (sigaddset(&sa.sa_mask, signals[i])) + throw Poco::Exception("Cannot set signal handler."); + + for (size_t i = 0; signals[i]; ++i) + if (sigaction(signals[i], &sa, 0)) + throw Poco::Exception("Cannot set signal handler."); + } + + /// Ставим ErrorHandler для потоков + static KillingErrorHandler killing_error_handler; + Poco::ErrorHandler::set(&killing_error_handler); + + /// Выведем ревизию демона + Logger::root().information("Starting daemon with revision " + Poco::NumberFormatter::format(Revision::get())); + + close_logs_listener.reset(new SignalListener); + close_logs_thread.start(*close_logs_listener); + + if (is_daemon) + { + /// Сменим директорию на ту, куда надо писать core файлы. + Poco::File opt_cores = "/opt/cores"; + std::string log_dir = config().getString("logger.log", ""); + size_t pos_of_last_slash = log_dir.rfind("/"); + if (pos_of_last_slash != std::string::npos) + log_dir.resize(pos_of_last_slash); + + std::string core_path = config().getString("core_path", + opt_cores.exists() && opt_cores.isDirectory() + ? "/opt/cores/" + : (!log_dir.empty() + ? log_dir + : "/opt/")); + + if (0 != chdir(core_path.c_str())) + throw Poco::Exception("Cannot change directory to " + core_path); + + /** Переназначим stdout, stderr в отдельные файлы в директориях с логами. + * Некоторые библиотеки пишут в stderr в случае ошибок или в отладочном режиме, + * и этот вывод иногда имеет смысл смотреть даже когда программа запущена в режиме демона. + */ + if (!log_dir.empty()) + { + std::string stdout_path = log_dir + "/stdout"; + if (!freopen(stdout_path.c_str(), "a+", stdout)) + throw Poco::OpenFileException("Cannot attach stdout to " + stdout_path); + + std::string stderr_path = log_dir + "/stderr"; + if (!freopen(stderr_path.c_str(), "a+", stderr)) + throw Poco::OpenFileException("Cannot attach stderr to " + stderr_path); + } + } + + graphite_writer.reset(new GraphiteWriter("graphite")); +} + + +/// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно +void Daemon::exitOnTaskError() +{ + Observer obs(*this, &Daemon::handleNotification); + getTaskManager().addObserver(obs); +} + +/// Используется при exitOnTaskError() +void Daemon::handleNotification(Poco::TaskFailedNotification *_tfn) +{ + AutoPtr fn(_tfn); + Logger *lg = &(logger()); + LOG_ERROR(lg, "Task '" << fn->task()->name() << "' failed. Daemon is shutting down. Reason - " << fn->reason().displayText()); + ServerApplication::terminate(); +} + +void Daemon::defineOptions(Poco::Util::OptionSet& _options) +{ + Poco::Util::ServerApplication::defineOptions (_options); + + _options.addOption( + Poco::Util::Option ("config-file", "C", "load configuration from a given file") + .required (false) + .repeatable (false) + .argument ("") + .binding("config-file") + ); + + _options.addOption( + Poco::Util::Option ("log-file", "L", "use given log file") + .required (false) + .repeatable (false) + .argument ("") + .binding("logger.log") + ); + + _options.addOption( + Poco::Util::Option ("errorlog-file", "E", "use given log file for errors only") + .required (false) + .repeatable (false) + .argument ("") + .binding("logger.errorlog") + ); + + _options.addOption( + Poco::Util::Option ("pid-file", "P", "use given pidfile") + .required (false) + .repeatable (false) + .argument ("") + .binding("pid") + ); +} + + +void Daemon::PID::seed(const std::string & file_) +{ + /// переведём путь в абсолютный + file = Poco::Path(file_).absolute().toString(); + + int fd = open(file.c_str(), + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + if (-1 == fd) + { + file.clear(); + if (EEXIST == errno) + throw Poco::Exception("Pid file exists, should not start daemon."); + throw Poco::CreateFileException("Cannot create pid file."); + } + + try + { + std::stringstream s; + s << getpid(); + if (static_cast(s.str().size()) != write(fd, s.str().c_str(), s.str().size())) + throw Poco::Exception("Cannot write to pid file."); + } + catch (...) + { + close(fd); + throw; + } + + close(fd); +} + +void Daemon::PID::clear() +{ + if (!file.empty()) + { + Poco::File(file).remove(); + file.clear(); + } +} diff --git a/libs/libcommon/src/GraphiteWriter.cpp b/libs/libcommon/src/GraphiteWriter.cpp new file mode 100644 index 00000000000..920bbf912e7 --- /dev/null +++ b/libs/libcommon/src/GraphiteWriter.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include + +#include +#include + + +GraphiteWriter::GraphiteWriter(const std::string & config_name, const std::string & sub_path) +{ + Poco::Util::LayeredConfiguration & config = Poco::Util::Application::instance().config(); + port = config.getInt(config_name + ".port", 42000); + host = config.getString(config_name + ".host", "127.0.0.1"); + timeout = config.getDouble(config_name + ".timeout", 0.1); + + root_path = config.getString(config_name + ".root_path", "one_min"); + if (root_path.size()) + root_path += "."; + + /// То же, что uname -n + std::string hostname_in_path = Poco::Net::DNS::hostName(); + + /// Заменяем точки на подчёркивания, чтобы Graphite не интерпретировал их, как разделители пути. + std::replace(std::begin(hostname_in_path), std::end(hostname_in_path), '.', '_'); + + root_path += hostname_in_path + config.getString(config_name + ".hostname_suffix", ""); + + if (sub_path.size()) + root_path += "." + sub_path; +} + +static std::string getPerLayerPathImpl(const std::string prefix) +{ + /// Угадываем имя среды по имени машинки + /// машинки имеют имена вида example01dt.yandex.ru + /// t - test + /// dev - development + /// никакого суффикса - production + + std::stringstream path_full; + + path_full << prefix << "."; + + std::string hostname = Poco::Net::DNS::hostName(); + hostname = hostname.substr(0, hostname.find('.')); + + const std::string development_suffix = "dev"; + if (hostname.back() == 't') + path_full << "test."; + else if (hostname.size() > development_suffix.size() && + hostname.substr(hostname.size() - development_suffix.size()) == development_suffix) + path_full << "development."; + else + path_full << "production."; + + const Daemon & daemon = Daemon::instance(); + + if (daemon.getLayer()) + path_full << "layer" << std::setfill('0') << std::setw(3) << *daemon.getLayer() << "."; + + /// Когда несколько демонов запускается на одной машине + /// к имени демона добавляется цифра. + /// Удалим последнюю цифру + std::locale locale; + std::string command_name = daemon.commandName(); + if (std::isdigit(command_name.back(), locale)) + command_name = command_name.substr(0, command_name.size() - 1); + path_full << command_name; + + return path_full.str(); +} + +const std::string & GraphiteWriter::getPerLayerPath(const std::string & prefix) +{ + static std::string path = getPerLayerPathImpl(prefix); /// thread-safe statics. + return path; +} + +std::string GraphiteWriter::getPerServerPath(const std::string & server_name, const std::string & root_path) +{ + std::string path = root_path + "." + server_name; + std::replace(path.begin() + root_path.size() + 1, path.end(), '.', '_'); + return path; +} From 015039c2f7fe112dd14bc553778a8267cdc6464a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 07:03:45 +0300 Subject: [PATCH 17/32] dbms: addition to prev. revision [#METR-17973]. --- .../include/common => libdaemon/include/daemon}/Daemon.h | 0 .../include/common => libdaemon/include/daemon}/GraphiteWriter.h | 0 libs/{libcommon => libdaemon}/src/Daemon.cpp | 0 libs/{libcommon => libdaemon}/src/GraphiteWriter.cpp | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename libs/{libcommon/include/common => libdaemon/include/daemon}/Daemon.h (100%) rename libs/{libcommon/include/common => libdaemon/include/daemon}/GraphiteWriter.h (100%) rename libs/{libcommon => libdaemon}/src/Daemon.cpp (100%) rename libs/{libcommon => libdaemon}/src/GraphiteWriter.cpp (100%) diff --git a/libs/libcommon/include/common/Daemon.h b/libs/libdaemon/include/daemon/Daemon.h similarity index 100% rename from libs/libcommon/include/common/Daemon.h rename to libs/libdaemon/include/daemon/Daemon.h diff --git a/libs/libcommon/include/common/GraphiteWriter.h b/libs/libdaemon/include/daemon/GraphiteWriter.h similarity index 100% rename from libs/libcommon/include/common/GraphiteWriter.h rename to libs/libdaemon/include/daemon/GraphiteWriter.h diff --git a/libs/libcommon/src/Daemon.cpp b/libs/libdaemon/src/Daemon.cpp similarity index 100% rename from libs/libcommon/src/Daemon.cpp rename to libs/libdaemon/src/Daemon.cpp diff --git a/libs/libcommon/src/GraphiteWriter.cpp b/libs/libdaemon/src/GraphiteWriter.cpp similarity index 100% rename from libs/libcommon/src/GraphiteWriter.cpp rename to libs/libdaemon/src/GraphiteWriter.cpp From 66924602491361aa11d6e9b384cba8359c929939 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 07:13:00 +0300 Subject: [PATCH 18/32] dbms: addition to prev. revision [#METR-17973]. --- dbms/src/Server/Server.h | 2 +- libs/libdaemon/include/daemon/Daemon.h | 2 +- libs/libdaemon/src/Daemon.cpp | 2 +- libs/libdaemon/src/GraphiteWriter.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dbms/src/Server/Server.h b/dbms/src/Server/Server.h index 0996b9d2a7e..b5e124a3174 100644 --- a/dbms/src/Server/Server.h +++ b/dbms/src/Server/Server.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include diff --git a/libs/libdaemon/include/daemon/Daemon.h b/libs/libdaemon/include/daemon/Daemon.h index ec22c7e6cca..b7f0eb71844 100644 --- a/libs/libdaemon/include/daemon/Daemon.h +++ b/libs/libdaemon/include/daemon/Daemon.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include diff --git a/libs/libdaemon/src/Daemon.cpp b/libs/libdaemon/src/Daemon.cpp index 8ed7d182fd5..cf32f55634a 100644 --- a/libs/libdaemon/src/Daemon.cpp +++ b/libs/libdaemon/src/Daemon.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/libs/libdaemon/src/GraphiteWriter.cpp b/libs/libdaemon/src/GraphiteWriter.cpp index 920bbf912e7..4f11659dcd2 100644 --- a/libs/libdaemon/src/GraphiteWriter.cpp +++ b/libs/libdaemon/src/GraphiteWriter.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include #include From bffc6ce8bd738d1762772a39515ffe615e5d49a7 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 15 Jan 2016 23:37:56 +0300 Subject: [PATCH 19/32] dbms: better [#TESTIRT-8223]. --- libs/libdaemon/src/Daemon.cpp | 86 +++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/libs/libdaemon/src/Daemon.cpp b/libs/libdaemon/src/Daemon.cpp index cf32f55634a..d6f6be6ea76 100644 --- a/libs/libdaemon/src/Daemon.cpp +++ b/libs/libdaemon/src/Daemon.cpp @@ -650,19 +650,62 @@ void Daemon::initialize(Application& self) /** При создании pid файла и поиске конфигурации, будем интерпретировать относительные пути * от директории запуска программы. */ - std::string path = config().getString("application.path"); - if (0 != chdir(Poco::Path(path).setFileName("").toString().c_str())) + std::string path = Poco::Path(config().getString("application.path")).setFileName("").toString(); + if (0 != chdir(path.c_str())) throw Poco::Exception("Cannot change directory to " + path); + } + + /// Считаем конфигурацию + reloadConfiguration(); + + std::string log_path = config().getString("logger.log", ""); + if (!log_path.empty()) + log_path = Poco::Path(log_path).setFileName("").toString(); + + if (is_daemon) + { + /** Переназначим stdout, stderr в отдельные файлы в директориях с логами. + * Некоторые библиотеки пишут в stderr в случае ошибок или в отладочном режиме, + * и этот вывод иногда имеет смысл смотреть даже когда программа запущена в режиме демона. + * Делаем это до buildLoggers, чтобы ошибки во время инициализации логгера, попали в эти файлы. + */ + if (!log_path.empty()) + { + std::string stdout_path = log_path + "/stdout"; + if (!freopen(stdout_path.c_str(), "a+", stdout)) + throw Poco::OpenFileException("Cannot attach stdout to " + stdout_path); + + std::string stderr_path = log_path + "/stderr"; + if (!freopen(stderr_path.c_str(), "a+", stderr)) + throw Poco::OpenFileException("Cannot attach stderr to " + stderr_path); + } /// Создадим pid-file. if (is_daemon && config().has("pid")) pid.seed(config().getString("pid")); } - /// Считаем конфигурацию - reloadConfiguration(); buildLoggers(); + if (is_daemon) + { + /** Сменим директорию на ту, куда надо писать core файлы. + * Делаем это после buildLoggers, чтобы не менять текущую директорию раньше. + * Это важно, если конфиги расположены в текущей директории. + */ + Poco::File opt_cores = "/opt/cores"; + + std::string core_path = config().getString("core_path", + opt_cores.exists() && opt_cores.isDirectory() + ? "/opt/cores/" + : (!log_path.empty() + ? log_path + : "/opt/")); + + if (0 != chdir(core_path.c_str())) + throw Poco::Exception("Cannot change directory to " + core_path); + } + /// Ставим terminate_handler std::set_terminate(terminate_handler); @@ -714,41 +757,6 @@ void Daemon::initialize(Application& self) close_logs_listener.reset(new SignalListener); close_logs_thread.start(*close_logs_listener); - if (is_daemon) - { - /// Сменим директорию на ту, куда надо писать core файлы. - Poco::File opt_cores = "/opt/cores"; - std::string log_dir = config().getString("logger.log", ""); - size_t pos_of_last_slash = log_dir.rfind("/"); - if (pos_of_last_slash != std::string::npos) - log_dir.resize(pos_of_last_slash); - - std::string core_path = config().getString("core_path", - opt_cores.exists() && opt_cores.isDirectory() - ? "/opt/cores/" - : (!log_dir.empty() - ? log_dir - : "/opt/")); - - if (0 != chdir(core_path.c_str())) - throw Poco::Exception("Cannot change directory to " + core_path); - - /** Переназначим stdout, stderr в отдельные файлы в директориях с логами. - * Некоторые библиотеки пишут в stderr в случае ошибок или в отладочном режиме, - * и этот вывод иногда имеет смысл смотреть даже когда программа запущена в режиме демона. - */ - if (!log_dir.empty()) - { - std::string stdout_path = log_dir + "/stdout"; - if (!freopen(stdout_path.c_str(), "a+", stdout)) - throw Poco::OpenFileException("Cannot attach stdout to " + stdout_path); - - std::string stderr_path = log_dir + "/stderr"; - if (!freopen(stderr_path.c_str(), "a+", stderr)) - throw Poco::OpenFileException("Cannot attach stderr to " + stderr_path); - } - } - graphite_writer.reset(new GraphiteWriter("graphite")); } From 1cc27117b923daea37486e50c311ea3edbb877f1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 16 Jan 2016 03:45:19 +0300 Subject: [PATCH 20/32] dbms: porting to aarch64 [#METR-19609]. --- dbms/include/DB/Common/ARMHelpers.h | 11 +++ dbms/include/DB/Common/ArenaWithFreeLists.h | 10 +-- dbms/include/DB/Common/StringSearcher.h | 87 ++++++++++++------- dbms/include/DB/Common/UTF8Helpers.h | 7 +- dbms/include/DB/Common/Volnitsky.h | 1 - dbms/include/DB/Functions/FunctionsRound.h | 96 +++++++++++++++++++-- 6 files changed, 166 insertions(+), 46 deletions(-) create mode 100644 dbms/include/DB/Common/ARMHelpers.h diff --git a/dbms/include/DB/Common/ARMHelpers.h b/dbms/include/DB/Common/ARMHelpers.h new file mode 100644 index 00000000000..268855f7885 --- /dev/null +++ b/dbms/include/DB/Common/ARMHelpers.h @@ -0,0 +1,11 @@ +#pragma once + + +#if !defined(__x86_64__) + +inline unsigned int _bit_scan_reverse(unsigned int x) +{ + return sizeof(unsigned int) * 8 - 1 - __builtin_clz(x); +} + +#endif diff --git a/dbms/include/DB/Common/ArenaWithFreeLists.h b/dbms/include/DB/Common/ArenaWithFreeLists.h index 135a9007e1c..b4c30899e6d 100644 --- a/dbms/include/DB/Common/ArenaWithFreeLists.h +++ b/dbms/include/DB/Common/ArenaWithFreeLists.h @@ -1,15 +1,7 @@ #pragma once #include - -#if !defined(__x86_64__) - -inline unsigned int _bit_scan_reverse(unsigned int x) -{ - return sizeof(unsigned int) * 8 - 1 - __builtin_clz(x); -} - -#endif +#include namespace DB diff --git a/dbms/include/DB/Common/StringSearcher.h b/dbms/include/DB/Common/StringSearcher.h index b6c0a376d08..8930a7bd650 100644 --- a/dbms/include/DB/Common/StringSearcher.h +++ b/dbms/include/DB/Common/StringSearcher.h @@ -7,6 +7,10 @@ #include #include +#if defined(__x86_64__) + #include +#endif + namespace DB { @@ -18,18 +22,30 @@ namespace ErrorCodes } +struct StringSearcherBase +{ +#if defined(__x86_64__) + static constexpr auto n = sizeof(__m128i); + const int page_size = getpagesize(); + + bool page_safe(const void * const ptr) const + { + return ((page_size - 1) & reinterpret_cast(ptr)) <= page_size - n; + } +#endif +}; + + /// Performs case-sensitive and case-insensitive search of UTF-8 strings template class StringSearcher; /// Case-insensitive UTF-8 searcher -template <> class StringSearcher +template <> +class StringSearcher : private StringSearcherBase { +private: using UTF8SequenceBuffer = UInt8[6]; - static constexpr auto n = sizeof(__m128i); - - const int page_size = getpagesize(); - /// string to be searched for const UInt8 * const needle; const std::size_t needle_size; @@ -38,6 +54,8 @@ template <> class StringSearcher bool first_needle_symbol_is_ascii{}; UInt8 l{}; UInt8 u{}; + +#if defined(__x86_64__) /// vectors filled with `l` and `u`, for determining leftmost position of the first symbol __m128i patl, patu; /// lower and uppercase vectors of first 16 characters of `needle` @@ -45,11 +63,7 @@ template <> class StringSearcher int cachemask{}; std::size_t cache_valid_len{}; std::size_t cache_actual_len{}; - - bool page_safe(const void * const ptr) const - { - return ((page_size - 1) & reinterpret_cast(ptr)) <= page_size - n; - } +#endif public: StringSearcher(const char * const needle_, const std::size_t needle_size) @@ -80,6 +94,7 @@ public: u = u_seq[0]; } +#if defined(__x86_64__) /// for detecting leftmost position of the first symbol patl = _mm_set1_epi8(l); patu = _mm_set1_epi8(u); @@ -133,12 +148,14 @@ public: } } } +#endif } bool compare(const UInt8 * pos) const { static const Poco::UTF8Encoding utf8; +#if defined(__x86_64__) if (page_safe(pos)) { const auto v_haystack = _mm_loadu_si128(reinterpret_cast(pos)); @@ -172,6 +189,7 @@ public: return false; } +#endif if (*pos == l || *pos == u) { @@ -202,6 +220,7 @@ public: while (haystack < haystack_end) { +#if defined(__x86_64__) if (haystack + n <= haystack_end && page_safe(haystack)) { const auto v_haystack = _mm_loadu_si128(reinterpret_cast(haystack)); @@ -257,6 +276,7 @@ public: continue; } } +#endif if (haystack == haystack_end) return haystack_end; @@ -286,13 +306,12 @@ public: } }; + /// Case-insensitive ASCII searcher -template <> class StringSearcher +template <> +class StringSearcher : private StringSearcherBase { - static constexpr auto n = sizeof(__m128i); - - const int page_size = getpagesize(); - +private: /// string to be searched for const UInt8 * const needle; const std::size_t needle_size; @@ -300,16 +319,14 @@ template <> class StringSearcher /// lower and uppercase variants of the first character in `needle` UInt8 l{}; UInt8 u{}; + +#if defined(__x86_64__) /// vectors filled with `l` and `u`, for determining leftmost position of the first symbol __m128i patl, patu; /// lower and uppercase vectors of first 16 characters of `needle` __m128i cachel = _mm_setzero_si128(), cacheu = _mm_setzero_si128(); int cachemask{}; - - bool page_safe(const void * const ptr) const - { - return ((page_size - 1) & reinterpret_cast(ptr)) <= page_size - n; - } +#endif public: StringSearcher(const char * const needle_, const std::size_t needle_size) @@ -321,6 +338,7 @@ public: l = static_cast(std::tolower(*needle)); u = static_cast(std::toupper(*needle)); +#if defined(__x86_64__) patl = _mm_set1_epi8(l); patu = _mm_set1_epi8(u); @@ -339,10 +357,12 @@ public: ++needle_pos; } } +#endif } bool compare(const UInt8 * pos) const { +#if defined(__x86_64__) if (page_safe(pos)) { const auto v_haystack = _mm_loadu_si128(reinterpret_cast(pos)); @@ -370,6 +390,7 @@ public: return false; } +#endif if (*pos == l || *pos == u) { @@ -393,6 +414,7 @@ public: while (haystack < haystack_end) { +#if defined(__x86_64__) if (haystack + n <= haystack_end && page_safe(haystack)) { const auto v_haystack = _mm_loadu_si128(reinterpret_cast(haystack)); @@ -441,6 +463,7 @@ public: continue; } } +#endif if (haystack == haystack_end) return haystack_end; @@ -465,29 +488,26 @@ public: } }; + /// Case-sensitive searcher (both ASCII and UTF-8) -template class StringSearcher +template +class StringSearcher : private StringSearcherBase { - static constexpr auto n = sizeof(__m128i); - - const int page_size = getpagesize(); - +private: /// string to be searched for const UInt8 * const needle; const std::size_t needle_size; const UInt8 * const needle_end = needle + needle_size; /// first character in `needle` UInt8 first{}; + +#if defined(__x86_64__) /// vector filled `first` for determining leftmost position of the first symbol __m128i pattern; /// vector of first 16 characters of `needle` __m128i cache = _mm_setzero_si128(); int cachemask{}; - - bool page_safe(const void * const ptr) const - { - return ((page_size - 1) & reinterpret_cast(ptr)) <= page_size - n; - } +#endif public: StringSearcher(const char * const needle_, const std::size_t needle_size) @@ -497,6 +517,8 @@ public: return; first = *needle; + +#if defined(__x86_64__) pattern = _mm_set1_epi8(first); auto needle_pos = needle; @@ -512,10 +534,12 @@ public: ++needle_pos; } } +#endif } bool compare(const UInt8 * pos) const { +#if defined(__x86_64__) if (page_safe(pos)) { const auto v_haystack = _mm_loadu_si128(reinterpret_cast(pos)); @@ -541,6 +565,7 @@ public: return false; } +#endif if (*pos == first) { @@ -564,6 +589,7 @@ public: while (haystack < haystack_end) { +#if defined(__x86_64__) if (haystack + n <= haystack_end && page_safe(haystack)) { /// find first character @@ -611,6 +637,7 @@ public: continue; } } +#endif if (haystack == haystack_end) return haystack_end; diff --git a/dbms/include/DB/Common/UTF8Helpers.h b/dbms/include/DB/Common/UTF8Helpers.h index 40b201a9ba4..d75de2d8157 100644 --- a/dbms/include/DB/Common/UTF8Helpers.h +++ b/dbms/include/DB/Common/UTF8Helpers.h @@ -1,7 +1,12 @@ #pragma once #include -#include + +#if defined(__x86_64__) + #include +#else + #include +#endif namespace DB diff --git a/dbms/include/DB/Common/Volnitsky.h b/dbms/include/DB/Common/Volnitsky.h index 7e40469df13..8b35d1956c3 100644 --- a/dbms/include/DB/Common/Volnitsky.h +++ b/dbms/include/DB/Common/Volnitsky.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include diff --git a/dbms/include/DB/Functions/FunctionsRound.h b/dbms/include/DB/Functions/FunctionsRound.h index 72c413d6900..5be7bbb82ca 100644 --- a/dbms/include/DB/Functions/FunctionsRound.h +++ b/dbms/include/DB/Functions/FunctionsRound.h @@ -5,6 +5,10 @@ #include #include +#if defined(__x86_64__) + #include +#endif + namespace DB { @@ -13,6 +17,7 @@ namespace DB * roundToExp2 - вниз до ближайшей степени двойки; * roundDuration - вниз до ближайшего из: 0, 1, 10, 30, 60, 120, 180, 240, 300, 600, 1200, 1800, 3600, 7200, 18000, 36000; * roundAge - вниз до ближайшего из: 0, 18, 25, 35, 45. + * * round(x, N) - арифметическое округление (N = 0 по умолчанию). * ceil(x, N) - наименьшее число, которое не меньше x (N = 0 по умолчанию). * floor(x, N) - наибольшее число, которое не больше x (N = 0 по умолчанию). @@ -257,10 +262,11 @@ namespace DB } }; - template +#if defined(__x86_64__) + template class BaseFloatRoundingComputation; - template<> + template <> class BaseFloatRoundingComputation { public: @@ -298,7 +304,7 @@ namespace DB } }; - template<> + template <> class BaseFloatRoundingComputation { public: @@ -522,6 +528,85 @@ namespace DB _mm_storeu_pd(out, val); } }; +#else + /// Реализация для ARM. Не векторизована. Не исправляет отрицательные нули. + + #define _MM_FROUND_NINT 0 + #define _MM_FROUND_FLOOR 1 + #define _MM_FROUND_CEIL 2 + + template + float roundWithMode(float x) + { + if (mode == _MM_FROUND_NINT) return roundf(x); + if (mode == _MM_FROUND_FLOOR) return floorf(x); + if (mode == _MM_FROUND_CEIL) return ceilf(x); + __builtin_unreachable(); + } + + template + double roundWithMode(double x) + { + if (mode == _MM_FROUND_NINT) return round(x); + if (mode == _MM_FROUND_FLOOR) return floor(x); + if (mode == _MM_FROUND_CEIL) return ceil(x); + __builtin_unreachable(); + } + + template + class BaseFloatRoundingComputation + { + public: + using Scale = T; + static const size_t data_count = 1; + + static inline void prepare(size_t scale, Scale & mm_scale) + { + mm_scale = static_cast(scale); + } + }; + + template + class FloatRoundingComputation; + + template + class FloatRoundingComputation + : public BaseFloatRoundingComputation + { + public: + static inline void compute(const T * __restrict in, const T & scale, T * __restrict out) + { + out[0] = roundWithMode(in[0] * scale) / scale; + } + }; + + template + class FloatRoundingComputation + : public BaseFloatRoundingComputation + { + public: + static inline void compute(const T * __restrict in, const T & scale, T * __restrict out) + { + out[0] = roundWithMode(in[0] / scale) * scale; + } + }; + + template + class FloatRoundingComputation + : public BaseFloatRoundingComputation + { + public: + static inline void prepare(size_t scale, T & mm_scale) + { + } + + static inline void compute(const T * __restrict in, const T & scale, T * __restrict out) + { + out[0] = roundWithMode(in[0]); + } + }; +#endif + /** Реализация высокоуровневых функций округления. */ @@ -906,7 +991,7 @@ namespace /** Выбрать подходящий алгоритм обработки в зависимости от масштаба. */ - template class U, int rounding_mode> + template class U, int rounding_mode> struct Dispatcher { static inline void apply(Block & block, U * col, const ColumnNumbers & arguments, size_t result) @@ -1053,9 +1138,10 @@ namespace typedef FunctionUnaryArithmetic FunctionRoundToExp2; typedef FunctionUnaryArithmetic FunctionRoundDuration; typedef FunctionUnaryArithmetic FunctionRoundAge; + typedef FunctionRounding FunctionRound; - typedef FunctionRounding FunctionCeil; typedef FunctionRounding FunctionFloor; + typedef FunctionRounding FunctionCeil; struct PositiveMonotonicity From 27bb0a4ea295f86955f1e0feb3efd3c868053a95 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 16 Jan 2016 04:18:42 +0300 Subject: [PATCH 21/32] dbms: porting to aarch64 [#METR-19609]. --- dbms/include/DB/Functions/FunctionsRound.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dbms/include/DB/Functions/FunctionsRound.h b/dbms/include/DB/Functions/FunctionsRound.h index 5be7bbb82ca..a709d0d6415 100644 --- a/dbms/include/DB/Functions/FunctionsRound.h +++ b/dbms/include/DB/Functions/FunctionsRound.h @@ -168,6 +168,12 @@ namespace DB NullScale // возвращать нулевое значение }; +#if !defined(_MM_FROUND_NINT) + #define _MM_FROUND_NINT 0 + #define _MM_FROUND_FLOOR 1 + #define _MM_FROUND_CEIL 2 +#endif + /** Реализация низкоуровневых функций округления для целочисленных значений. */ template @@ -531,10 +537,6 @@ namespace DB #else /// Реализация для ARM. Не векторизована. Не исправляет отрицательные нули. - #define _MM_FROUND_NINT 0 - #define _MM_FROUND_FLOOR 1 - #define _MM_FROUND_CEIL 2 - template float roundWithMode(float x) { From 3b3dab716aab00c0ee71cd410333796e00cdd758 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 16 Jan 2016 04:38:07 +0300 Subject: [PATCH 22/32] dbms: porting to aarch64 [#METR-19609]. --- libs/libdaemon/src/Daemon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/libdaemon/src/Daemon.cpp b/libs/libdaemon/src/Daemon.cpp index d6f6be6ea76..0d088fcd05f 100644 --- a/libs/libdaemon/src/Daemon.cpp +++ b/libs/libdaemon/src/Daemon.cpp @@ -232,8 +232,12 @@ private: void * caller_address = nullptr; +#if defined(__x86_64__) /// Get the address at the time the signal was raised from the RIP (x86-64) caller_address = reinterpret_cast(context.uc_mcontext.gregs[REG_RIP]); +#elif defined(__aarch64__) + caller_address = reinterpret_cast(context.uc_mcontext.pc); +#endif static const int max_frames = 50; void * frames[max_frames]; From a30532594b3b44cae0ace163febe1d5924d707b6 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 08:22:22 +0300 Subject: [PATCH 23/32] dbms: fixed error [#METR-19586]. --- .../ReplicatedMergeTreeBlockOutputStream.h | 264 +---------------- .../ReplicatedMergeTreeBlockOutputStream.cpp | 276 ++++++++++++++++++ .../MergeTree/ReplicatedMergeTreeQueue.cpp | 107 +++---- .../Storages/StorageReplicatedMergeTree.cpp | 35 ++- 4 files changed, 359 insertions(+), 323 deletions(-) create mode 100644 dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp diff --git a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h index ced848d3178..f90df18e555 100644 --- a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h @@ -1,269 +1,21 @@ #pragma once -#include -#include -#include #include -#include namespace DB { -namespace ErrorCodes -{ - extern const int TOO_LESS_LIVE_REPLICAS; - extern const int UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE; - extern const int CHECKSUM_DOESNT_MATCH; - extern const int UNEXPECTED_ZOOKEEPER_ERROR; - extern const int NO_ZOOKEEPER; -} +class StorageReplicatedMergeTree; class ReplicatedMergeTreeBlockOutputStream : public IBlockOutputStream { public: - ReplicatedMergeTreeBlockOutputStream(StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_) - : storage(storage_), insert_id(insert_id_), quorum(quorum_), - log(&Logger::get(storage.data.getLogName() + " (Replicated OutputStream)")) - { - /// Значение кворума 1 имеет такой же смысл, как если он отключён. - if (quorum == 1) - quorum = 0; - } + ReplicatedMergeTreeBlockOutputStream(StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_); - void writePrefix() override - { - /// TODO Можно ли здесь не блокировать структуру таблицы? - storage.data.delayInsertIfNeeded(&storage.restarting_thread->getWakeupEvent()); - } - - void write(const Block & block) override - { - auto zookeeper = storage.getZooKeeper(); - - assertSessionIsNotExpired(zookeeper); - - /** Если запись с кворумом, то проверим, что требуемое количество реплик сейчас живо, - * а также что для всех предыдущих кусков, для которых требуется кворум, этот кворум достигнут. - */ - String quorum_status_path = storage.zookeeper_path + "/quorum/status"; - if (quorum) - { - /// Список живых реплик. Все они регистрируют эфемерную ноду для leader_election. - auto live_replicas = zookeeper->getChildren(storage.zookeeper_path + "/leader_election"); - - if (live_replicas.size() < quorum) - throw Exception("Number of alive replicas (" - + toString(live_replicas.size()) + ") is less than requested quorum (" + toString(quorum) + ").", - ErrorCodes::TOO_LESS_LIVE_REPLICAS); - - /** Достигнут ли кворум для последнего куска, для которого нужен кворум? - * Запись всех кусков с включенным кворумом линейно упорядочена. - * Это значит, что в любой момент времени может быть только один кусок, - * для которого нужен, но ещё не достигнут кворум. - * Информация о таком куске будет расположена в ноде /quorum/status. - * Если кворум достигнут, то нода удаляется. - */ - - String quorum_status; - bool quorum_unsatisfied = zookeeper->tryGet(quorum_status_path, quorum_status); - - if (quorum_unsatisfied) - throw Exception("Quorum for previous write has not been satisfied yet. Status: " + quorum_status, ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE); - - /// Обе проверки неявно делаются и позже (иначе был бы race condition). - } - - auto part_blocks = storage.writer.splitBlockIntoParts(block); - - for (auto & current_block : part_blocks) - { - assertSessionIsNotExpired(zookeeper); - - ++block_index; - String block_id = insert_id.empty() ? "" : insert_id + "__" + toString(block_index); - String month_name = toString(DateLUT::instance().toNumYYYYMMDD(DayNum_t(current_block.min_date)) / 100); - - AbandonableLockInZooKeeper block_number_lock = storage.allocateBlockNumber(month_name); - - Int64 part_number = block_number_lock.getNumber(); - - MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, part_number); - String part_name = ActiveDataPartSet::getPartName(part->left_date, part->right_date, part->left, part->right, part->level); - - /// Хэш от данных. - SipHash hash; - part->checksums.summaryDataChecksum(hash); - union - { - char bytes[16]; - UInt64 lo, hi; - } hash_value; - hash.get128(hash_value.bytes); - - String checksum(hash_value.bytes, 16); - - /// Если в запросе не указан ID, возьмем в качестве ID хеш от данных. То есть, не вставляем одинаковые данные дважды. - /// NOTE: Если такая дедупликация не нужна, можно вместо этого оставлять block_id пустым. - /// Можно для этого сделать настройку или синтаксис в запросе (например, ID=null). - if (block_id.empty()) - { - block_id = toString(hash_value.lo) + "_" + toString(hash_value.hi); - - if (block_id.empty()) - throw Exception("Logical error: block_id is empty.", ErrorCodes::LOGICAL_ERROR); - } - - LOG_DEBUG(log, "Wrote block " << part_number << " with ID " << block_id << ", " << current_block.block.rows() << " rows"); - - StorageReplicatedMergeTree::LogEntry log_entry; - log_entry.type = StorageReplicatedMergeTree::LogEntry::GET_PART; - log_entry.create_time = time(0); - log_entry.source_replica = storage.replica_name; - log_entry.new_part_name = part_name; - log_entry.quorum = quorum; - log_entry.block_id = block_id; - - /// Одновременно добавим информацию о куске во все нужные места в ZooKeeper и снимем block_number_lock. - - /// Информация о блоке. - zkutil::Ops ops; - auto acl = zookeeper->getDefaultACL(); - - ops.push_back( - new zkutil::Op::Create( - storage.zookeeper_path + "/blocks/" + block_id, - "", - acl, - zkutil::CreateMode::Persistent)); - ops.push_back( - new zkutil::Op::Create( - storage.zookeeper_path + "/blocks/" + block_id + "/checksum", - checksum, - acl, - zkutil::CreateMode::Persistent)); - ops.push_back( - new zkutil::Op::Create( - storage.zookeeper_path + "/blocks/" + block_id + "/number", - toString(part_number), - acl, - zkutil::CreateMode::Persistent)); - - /// Информация о куске, в данных реплики. - storage.checkPartAndAddToZooKeeper(part, ops, part_name); - - /// Лог репликации. - ops.push_back(new zkutil::Op::Create( - storage.zookeeper_path + "/log/log-", - log_entry.toString(), - acl, - zkutil::CreateMode::PersistentSequential)); - - /// Удаление информации о том, что номер блока используется для записи. - block_number_lock.getUnlockOps(ops); - - /** Если нужен кворум - создание узла, в котором отслеживается кворум. - * (Если такой узел уже существует - значит кто-то успел одновременно сделать другую кворумную запись, но для неё кворум ещё не достигнут. - * Делать в это время следующую кворумную запись нельзя.) - */ - if (quorum) - { - ReplicatedMergeTreeQuorumEntry quorum_entry; - quorum_entry.part_name = part_name; - quorum_entry.required_number_of_replicas = quorum; - quorum_entry.replicas.insert(storage.replica_name); - - /** В данный момент, этот узел будет содержать информацию о том, что текущая реплика получила кусок. - * Когда другие реплики будут получать этот кусок (обычным способом, обрабатывая лог репликации), - * они будут добавлять себя в содержимое этого узла. - * Когда в нём будет информация о quorum количестве реплик, этот узел удаляется, - * что говорит о том, что кворум достигнут. - */ - - ops.push_back( - new zkutil::Op::Create( - quorum_status_path, - quorum_entry.toString(), - acl, - zkutil::CreateMode::Persistent)); - } - - MergeTreeData::Transaction transaction; /// Если не получится добавить кусок в ZK, снова уберем его из рабочего набора. - storage.data.renameTempPartAndAdd(part, nullptr, &transaction); - - try - { - auto code = zookeeper->tryMulti(ops); - if (code == ZOK) - { - transaction.commit(); - storage.merge_selecting_event.set(); - } - else if (code == ZNODEEXISTS) - { - /// Если блок с таким ID уже есть в таблице, откатим его вставку. - String expected_checksum; - if (!block_id.empty() && zookeeper->tryGet( - storage.zookeeper_path + "/blocks/" + block_id + "/checksum", expected_checksum)) - { - LOG_INFO(log, "Block with ID " << block_id << " already exists; ignoring it (removing part " << part->name << ")"); - - /// Если данные отличались от тех, что были вставлены ранее с тем же ID, бросим исключение. - if (expected_checksum != checksum) - { - if (!insert_id.empty()) - throw Exception("Attempt to insert block with same ID but different checksum", ErrorCodes::CHECKSUM_DOESNT_MATCH); - else - throw Exception("Logical error: got ZNODEEXISTS while inserting data, block ID is derived from checksum but checksum doesn't match", ErrorCodes::LOGICAL_ERROR); - } - - transaction.rollback(); - } - else if (zookeeper->exists(quorum_status_path)) - { - transaction.rollback(); - - throw Exception("Another quorum insert has been already started", ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE); - } - else - { - /// Сюда можем попасть также, если узел с кворумом существовал, но потом быстро был удалён. - - throw Exception("Unexpected ZNODEEXISTS while adding block " + toString(part_number) + " with ID " + block_id + ": " - + zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR); - } - } - else - { - throw Exception("Unexpected error while adding block " + toString(part_number) + " with ID " + block_id + ": " - + zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR); - } - } - catch (const zkutil::KeeperException & e) - { - /** Если потерялось соединение, и мы не знаем, применились ли изменения, нельзя удалять локальный кусок: - * если изменения применились, в /blocks/ появился вставленный блок, и его нельзя будет вставить снова. - */ - if (e.code == ZOPERATIONTIMEOUT || - e.code == ZCONNECTIONLOSS) - { - transaction.commit(); - storage.enqueuePartForCheck(part->name); - } - - throw; - } - - if (quorum) - { - /// Дожидаемся достижения кворума. TODO Настраиваемый таймаут. - LOG_TRACE(log, "Waiting for quorum"); - zookeeper->waitForDisappear(quorum_status_path); - LOG_TRACE(log, "Quorum satisfied"); - } - } - } + void writePrefix() override; + void write(const Block & block) override; private: StorageReplicatedMergeTree & storage; @@ -272,14 +24,6 @@ private: size_t block_index = 0; Logger * log; - - - /// Позволяет проверить, что сессия в ZooKeeper ещё жива. - void assertSessionIsNotExpired(zkutil::ZooKeeperPtr & zookeeper) - { - if (zookeeper->expired()) - throw Exception("ZooKeeper session has been expired.", ErrorCodes::NO_ZOOKEEPER); - } }; } diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp new file mode 100644 index 00000000000..8d371e388c4 --- /dev/null +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int TOO_LESS_LIVE_REPLICAS; + extern const int UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE; + extern const int CHECKSUM_DOESNT_MATCH; + extern const int UNEXPECTED_ZOOKEEPER_ERROR; + extern const int NO_ZOOKEEPER; +} + + +ReplicatedMergeTreeBlockOutputStream::ReplicatedMergeTreeBlockOutputStream( + StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_) + : storage(storage_), insert_id(insert_id_), quorum(quorum_), + log(&Logger::get(storage.data.getLogName() + " (Replicated OutputStream)")) +{ + /// Значение кворума 1 имеет такой же смысл, как если он отключён. + if (quorum == 1) + quorum = 0; +} + + +void ReplicatedMergeTreeBlockOutputStream::writePrefix() +{ + /// TODO Можно ли здесь не блокировать структуру таблицы? + storage.data.delayInsertIfNeeded(&storage.restarting_thread->getWakeupEvent()); +} + + +/// Позволяет проверить, что сессия в ZooKeeper ещё жива. +static void assertSessionIsNotExpired(zkutil::ZooKeeperPtr & zookeeper) +{ + if (zookeeper->expired()) + throw Exception("ZooKeeper session has been expired.", ErrorCodes::NO_ZOOKEEPER); +} + + +void ReplicatedMergeTreeBlockOutputStream::write(const Block & block) +{ + auto zookeeper = storage.getZooKeeper(); + + assertSessionIsNotExpired(zookeeper); + + /** Если запись с кворумом, то проверим, что требуемое количество реплик сейчас живо, + * а также что для всех предыдущих кусков, для которых требуется кворум, этот кворум достигнут. + */ + String quorum_status_path = storage.zookeeper_path + "/quorum/status"; + if (quorum) + { + /// Список живых реплик. Все они регистрируют эфемерную ноду для leader_election. + auto live_replicas = zookeeper->getChildren(storage.zookeeper_path + "/leader_election"); + + if (live_replicas.size() < quorum) + throw Exception("Number of alive replicas (" + + toString(live_replicas.size()) + ") is less than requested quorum (" + toString(quorum) + ").", + ErrorCodes::TOO_LESS_LIVE_REPLICAS); + + /** Достигнут ли кворум для последнего куска, для которого нужен кворум? + * Запись всех кусков с включенным кворумом линейно упорядочена. + * Это значит, что в любой момент времени может быть только один кусок, + * для которого нужен, но ещё не достигнут кворум. + * Информация о таком куске будет расположена в ноде /quorum/status. + * Если кворум достигнут, то нода удаляется. + */ + + String quorum_status; + bool quorum_unsatisfied = zookeeper->tryGet(quorum_status_path, quorum_status); + + if (quorum_unsatisfied) + throw Exception("Quorum for previous write has not been satisfied yet. Status: " + quorum_status, ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE); + + /// Обе проверки неявно делаются и позже (иначе был бы race condition). + } + + auto part_blocks = storage.writer.splitBlockIntoParts(block); + + for (auto & current_block : part_blocks) + { + assertSessionIsNotExpired(zookeeper); + + ++block_index; + String block_id = insert_id.empty() ? "" : insert_id + "__" + toString(block_index); + String month_name = toString(DateLUT::instance().toNumYYYYMMDD(DayNum_t(current_block.min_date)) / 100); + + AbandonableLockInZooKeeper block_number_lock = storage.allocateBlockNumber(month_name); + + Int64 part_number = block_number_lock.getNumber(); + + MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, part_number); + String part_name = ActiveDataPartSet::getPartName(part->left_date, part->right_date, part->left, part->right, part->level); + + /// Хэш от данных. + SipHash hash; + part->checksums.summaryDataChecksum(hash); + union + { + char bytes[16]; + UInt64 lo, hi; + } hash_value; + hash.get128(hash_value.bytes); + + String checksum(hash_value.bytes, 16); + + /// Если в запросе не указан ID, возьмем в качестве ID хеш от данных. То есть, не вставляем одинаковые данные дважды. + /// NOTE: Если такая дедупликация не нужна, можно вместо этого оставлять block_id пустым. + /// Можно для этого сделать настройку или синтаксис в запросе (например, ID=null). + if (block_id.empty()) + { + block_id = toString(hash_value.lo) + "_" + toString(hash_value.hi); + + if (block_id.empty()) + throw Exception("Logical error: block_id is empty.", ErrorCodes::LOGICAL_ERROR); + } + + LOG_DEBUG(log, "Wrote block " << part_number << " with ID " << block_id << ", " << current_block.block.rows() << " rows"); + + StorageReplicatedMergeTree::LogEntry log_entry; + log_entry.type = StorageReplicatedMergeTree::LogEntry::GET_PART; + log_entry.create_time = time(0); + log_entry.source_replica = storage.replica_name; + log_entry.new_part_name = part_name; + log_entry.quorum = quorum; + log_entry.block_id = block_id; + + /// Одновременно добавим информацию о куске во все нужные места в ZooKeeper и снимем block_number_lock. + + /// Информация о блоке. + zkutil::Ops ops; + auto acl = zookeeper->getDefaultACL(); + + ops.push_back( + new zkutil::Op::Create( + storage.zookeeper_path + "/blocks/" + block_id, + "", + acl, + zkutil::CreateMode::Persistent)); + ops.push_back( + new zkutil::Op::Create( + storage.zookeeper_path + "/blocks/" + block_id + "/checksum", + checksum, + acl, + zkutil::CreateMode::Persistent)); + ops.push_back( + new zkutil::Op::Create( + storage.zookeeper_path + "/blocks/" + block_id + "/number", + toString(part_number), + acl, + zkutil::CreateMode::Persistent)); + + /// Информация о куске, в данных реплики. + storage.checkPartAndAddToZooKeeper(part, ops, part_name); + + /// Лог репликации. + ops.push_back(new zkutil::Op::Create( + storage.zookeeper_path + "/log/log-", + log_entry.toString(), + acl, + zkutil::CreateMode::PersistentSequential)); + + /// Удаление информации о том, что номер блока используется для записи. + block_number_lock.getUnlockOps(ops); + + /** Если нужен кворум - создание узла, в котором отслеживается кворум. + * (Если такой узел уже существует - значит кто-то успел одновременно сделать другую кворумную запись, но для неё кворум ещё не достигнут. + * Делать в это время следующую кворумную запись нельзя.) + */ + if (quorum) + { + ReplicatedMergeTreeQuorumEntry quorum_entry; + quorum_entry.part_name = part_name; + quorum_entry.required_number_of_replicas = quorum; + quorum_entry.replicas.insert(storage.replica_name); + + /** В данный момент, этот узел будет содержать информацию о том, что текущая реплика получила кусок. + * Когда другие реплики будут получать этот кусок (обычным способом, обрабатывая лог репликации), + * они будут добавлять себя в содержимое этого узла. + * Когда в нём будет информация о quorum количестве реплик, этот узел удаляется, + * что говорит о том, что кворум достигнут. + */ + + ops.push_back( + new zkutil::Op::Create( + quorum_status_path, + quorum_entry.toString(), + acl, + zkutil::CreateMode::Persistent)); + } + + MergeTreeData::Transaction transaction; /// Если не получится добавить кусок в ZK, снова уберем его из рабочего набора. + storage.data.renameTempPartAndAdd(part, nullptr, &transaction); + + try + { + auto code = zookeeper->tryMulti(ops); + if (code == ZOK) + { + transaction.commit(); + storage.merge_selecting_event.set(); + } + else if (code == ZNODEEXISTS) + { + /// Если блок с таким ID уже есть в таблице, откатим его вставку. + String expected_checksum; + if (!block_id.empty() && zookeeper->tryGet( + storage.zookeeper_path + "/blocks/" + block_id + "/checksum", expected_checksum)) + { + LOG_INFO(log, "Block with ID " << block_id << " already exists; ignoring it (removing part " << part->name << ")"); + + /// Если данные отличались от тех, что были вставлены ранее с тем же ID, бросим исключение. + if (expected_checksum != checksum) + { + if (!insert_id.empty()) + throw Exception("Attempt to insert block with same ID but different checksum", ErrorCodes::CHECKSUM_DOESNT_MATCH); + else + throw Exception("Logical error: got ZNODEEXISTS while inserting data, block ID is derived from checksum but checksum doesn't match", ErrorCodes::LOGICAL_ERROR); + } + + transaction.rollback(); + } + else if (zookeeper->exists(quorum_status_path)) + { + transaction.rollback(); + + throw Exception("Another quorum insert has been already started", ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE); + } + else + { + /// Сюда можем попасть также, если узел с кворумом существовал, но потом быстро был удалён. + + throw Exception("Unexpected ZNODEEXISTS while adding block " + toString(part_number) + " with ID " + block_id + ": " + + zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR); + } + } + else + { + throw Exception("Unexpected error while adding block " + toString(part_number) + " with ID " + block_id + ": " + + zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR); + } + } + catch (const zkutil::KeeperException & e) + { + /** Если потерялось соединение, и мы не знаем, применились ли изменения, нельзя удалять локальный кусок: + * если изменения применились, в /blocks/ появился вставленный блок, и его нельзя будет вставить снова. + */ + if (e.code == ZOPERATIONTIMEOUT || + e.code == ZCONNECTIONLOSS) + { + transaction.commit(); + storage.enqueuePartForCheck(part->name); + } + + throw; + } + + if (quorum) + { + /// Дожидаемся достижения кворума. TODO Настраиваемый таймаут. + LOG_TRACE(log, "Waiting for quorum"); + zookeeper->waitForDisappear(quorum_status_path); + LOG_TRACE(log, "Quorum satisfied"); + } + } +} + + +} diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index fd2c43c940c..9e925c1a27a 100644 --- a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -156,67 +156,70 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z std::remove_if(log_entries.begin(), log_entries.end(), [&min_log_entry](const String & entry) { return entry < min_log_entry; }), log_entries.end()); - if (log_entries.empty()) - return false; - - std::sort(log_entries.begin(), log_entries.end()); - - String last_entry = log_entries.back(); - if (0 != last_entry.compare(0, strlen("log-"), "log-")) - throw Exception("Error in zookeeper data: unexpected node " + last_entry + " in " + zookeeper_path + "/log", - ErrorCodes::UNEXPECTED_NODE_IN_ZOOKEEPER); - - UInt64 last_entry_index = parse(last_entry.substr(strlen("log-"))); - - LOG_DEBUG(log, "Pulling " << log_entries.size() << " entries to queue: " << log_entries.front() << " - " << log_entries.back()); - - std::vector> futures; - futures.reserve(log_entries.size()); - - for (const String & entry : log_entries) - futures.emplace_back(entry, zookeeper->asyncGet(zookeeper_path + "/log/" + entry)); - - /// Одновременно добавим все новые записи в очередь и продвинем указатель на лог. - - zkutil::Ops ops; - std::vector copied_entries; - copied_entries.reserve(log_entries.size()); - - for (auto & future : futures) + if (!log_entries.empty()) { - zkutil::ZooKeeper::ValueAndStat res = future.second.get(); - copied_entries.emplace_back(LogEntry::parse(res.value, res.stat)); + std::sort(log_entries.begin(), log_entries.end()); - ops.push_back(new zkutil::Op::Create( - replica_path + "/queue/queue-", res.value, zookeeper->getDefaultACL(), zkutil::CreateMode::PersistentSequential)); - } + String last_entry = log_entries.back(); + if (0 != last_entry.compare(0, strlen("log-"), "log-")) + throw Exception("Error in zookeeper data: unexpected node " + last_entry + " in " + zookeeper_path + "/log", + ErrorCodes::UNEXPECTED_NODE_IN_ZOOKEEPER); - ops.push_back(new zkutil::Op::SetData( - replica_path + "/log_pointer", toString(last_entry_index + 1), -1)); + UInt64 last_entry_index = parse(last_entry.substr(strlen("log-"))); - auto results = zookeeper->multi(ops); + LOG_DEBUG(log, "Pulling " << log_entries.size() << " entries to queue: " << log_entries.front() << " - " << log_entries.back()); - /// Сейчас мы успешно обновили очередь в ZooKeeper. Обновим её в оперативке. + std::vector> futures; + futures.reserve(log_entries.size()); - try - { - std::lock_guard lock(mutex); + for (const String & entry : log_entries) + futures.emplace_back(entry, zookeeper->asyncGet(zookeeper_path + "/log/" + entry)); - for (size_t i = 0, size = copied_entries.size(); i < size; ++i) + /// Одновременно добавим все новые записи в очередь и продвинем указатель на лог. + + zkutil::Ops ops; + std::vector copied_entries; + copied_entries.reserve(log_entries.size()); + + for (auto & future : futures) { - String path_created = dynamic_cast(ops[i]).getPathCreated(); - copied_entries[i]->znode_name = path_created.substr(path_created.find_last_of('/') + 1); + zkutil::ZooKeeper::ValueAndStat res = future.second.get(); + copied_entries.emplace_back(LogEntry::parse(res.value, res.stat)); - insertUnlocked(copied_entries[i]); + ops.push_back(new zkutil::Op::Create( + replica_path + "/queue/queue-", res.value, zookeeper->getDefaultACL(), zkutil::CreateMode::PersistentSequential)); } - last_queue_update = time(0); - } - catch (...) - { - /// Если не удалось, то данные в оперативке некорректные. Во избежание возможной дальнейшей порчи данных в ZK, убъёмся. - /// Попадание сюда возможно лишь в случае неизвестной логической ошибки. - std::terminate(); + ops.push_back(new zkutil::Op::SetData( + replica_path + "/log_pointer", toString(last_entry_index + 1), -1)); + + auto results = zookeeper->multi(ops); + + /// Сейчас мы успешно обновили очередь в ZooKeeper. Обновим её в оперативке. + + try + { + std::lock_guard lock(mutex); + + for (size_t i = 0, size = copied_entries.size(); i < size; ++i) + { + String path_created = dynamic_cast(ops[i]).getPathCreated(); + copied_entries[i]->znode_name = path_created.substr(path_created.find_last_of('/') + 1); + + insertUnlocked(copied_entries[i]); + } + + last_queue_update = time(0); + } + catch (...) + { + /// Если не удалось, то данные в оперативке некорректные. Во избежание возможной дальнейшей порчи данных в ZK, убъёмся. + /// Попадание сюда возможно лишь в случае неизвестной логической ошибки. + std::terminate(); + } + + if (!copied_entries.empty()) + LOG_DEBUG(log, "Pulled " << copied_entries.size() << " entries to queue."); } if (next_update_event) @@ -225,9 +228,7 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z next_update_event->set(); } - LOG_DEBUG(log, "Pulled " << copied_entries.size() << " entries to queue."); - - return true; + return !log_entries.empty(); } diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index e074af24909..8b227419317 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -1,5 +1,11 @@ +#include #include + +#include +#include + #include + #include #include #include @@ -9,22 +15,27 @@ #include #include #include + #include +#include + #include #include #include + #include -#include -#include + #include #include #include #include + #include +#include #include #include + #include -#include namespace DB @@ -56,6 +67,12 @@ namespace ErrorCodes const auto ERROR_SLEEP_MS = 1000; + +/// Если ждём какого-то события с помощью watch-а, то просыпаться на всякий случай вхолостую раз в указанное время. +const auto WAIT_FOR_NEW_LOGS_SLEEP_MS = 60 * 1000; +const auto WAIT_FOR_ALTER_SLEEP_MS = 300 * 1000; +const auto WAIT_FOR_REPLICA_QUEUE_MS = 10 * 1000; + const auto MERGE_SELECTING_SLEEP_MS = 5 * 1000; const Int64 RESERVED_BLOCK_NUMBERS = 200; @@ -1206,7 +1223,7 @@ void StorageReplicatedMergeTree::queueUpdatingThread() try { pullLogsToQueue(queue_updating_event); - queue_updating_event->wait(); + queue_updating_event->tryWait(WAIT_FOR_NEW_LOGS_SLEEP_MS); } catch (const zkutil::KeeperException & e) { @@ -1214,13 +1231,11 @@ void StorageReplicatedMergeTree::queueUpdatingThread() restarting_thread->wakeup(); tryLogCurrentException(__PRETTY_FUNCTION__); - queue_updating_event->tryWait(ERROR_SLEEP_MS); } catch (...) { tryLogCurrentException(__PRETTY_FUNCTION__); - queue_updating_event->tryWait(ERROR_SLEEP_MS); } } @@ -1677,7 +1692,7 @@ void StorageReplicatedMergeTree::alterThread() /// Важно, что уничтожается parts и merge_blocker перед wait-ом. } - alter_thread_event->wait(); + alter_thread_event->tryWait(WAIT_FOR_ALTER_SLEEP_MS); } catch (...) { @@ -2472,7 +2487,7 @@ void StorageReplicatedMergeTree::alter(const AlterCommands & params, if (stat.version != replica_columns_version) continue; - alter_query_event->wait(); + alter_query_event->tryWait(WAIT_FOR_ALTER_SLEEP_MS); } if (shutdown_called) @@ -2860,7 +2875,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String & if (!log_pointer.empty() && parse(log_pointer) > log_index) break; - event->wait(); + event->tryWait(WAIT_FOR_REPLICA_QUEUE_MS); } } else if (0 == entry.znode_name.compare(0, strlen("queue-"), "queue-")) @@ -2905,7 +2920,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String & if (!log_pointer.empty() && parse(log_pointer) > log_index) break; - event->wait(); + event->tryWait(WAIT_FOR_REPLICA_QUEUE_MS); } } } From 8f1df43f032a6e57ecb9fab35caafee2f91c43d9 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 10:53:58 +0300 Subject: [PATCH 24/32] dbms: separated lock for zookeeper in Context [#METR-2944]. --- dbms/src/Interpreters/Context.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 9ce2c12d658..38132a74a3f 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -72,8 +72,12 @@ struct ContextShared { Logger * log = &Logger::get("Context"); /// Логгер. - mutable Poco::Mutex mutex; /// Для доступа и модификации разделяемых объектов. - mutable Poco::Mutex external_dictionaries_mutex; /// Для доступа к внешним словарям. Отдельный мьютекс, чтобы избежать локов при обращении сервера к самому себе. + /// Для доступа и модификации разделяемых объектов. Рекурсивный mutex. + mutable Poco::Mutex mutex; + /// Для доступа к внешним словарям. Отдельный мьютекс, чтобы избежать локов при обращении сервера к самому себе. + mutable std::mutex external_dictionaries_mutex; + /// Отдельный mutex для переинициализации zookeeper-а. Эта операция может заблокироваться на существенное время и не должна мешать остальным. + mutable std::mutex zookeeper_mutex; mutable zkutil::ZooKeeperPtr zookeeper; /// Клиент для ZooKeeper. @@ -726,7 +730,7 @@ const Dictionaries & Context::getDictionariesImpl(const bool throw_on_error) con const ExternalDictionaries & Context::getExternalDictionariesImpl(const bool throw_on_error) const { - Poco::ScopedLock lock(shared->external_dictionaries_mutex); + std::lock_guard lock(shared->external_dictionaries_mutex); if (!shared->external_dictionaries) { @@ -829,7 +833,7 @@ void Context::resetCaches() const void Context::setZooKeeper(zkutil::ZooKeeperPtr zookeeper) { - Poco::ScopedLock lock(shared->mutex); + std::lock_guard lock(shared->zookeeper_mutex); if (shared->zookeeper) throw Exception("ZooKeeper client has already been set.", ErrorCodes::LOGICAL_ERROR); @@ -839,7 +843,7 @@ void Context::setZooKeeper(zkutil::ZooKeeperPtr zookeeper) zkutil::ZooKeeperPtr Context::getZooKeeper() const { - Poco::ScopedLock lock(shared->mutex); + std::lock_guard lock(shared->zookeeper_mutex); if (shared->zookeeper && shared->zookeeper->expired()) shared->zookeeper = shared->zookeeper->startNewSession(); From d603d0a12de948b90bf04ff4b30bd00bf004a5ef Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 11:12:48 +0300 Subject: [PATCH 25/32] Merge --- .../DB/Storages/StorageReplicatedMergeTree.h | 17 +++---- .../ReplicatedMergeTreeBlockOutputStream.cpp | 3 ++ .../Storages/StorageReplicatedMergeTree.cpp | 46 +++++++++++++++++-- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/dbms/include/DB/Storages/StorageReplicatedMergeTree.h b/dbms/include/DB/Storages/StorageReplicatedMergeTree.h index e73ad1e999f..5af688c71a7 100644 --- a/dbms/include/DB/Storages/StorageReplicatedMergeTree.h +++ b/dbms/include/DB/Storages/StorageReplicatedMergeTree.h @@ -193,17 +193,9 @@ private: zkutil::ZooKeeperPtr current_zookeeper; /// Используйте только с помощью методов ниже. std::mutex current_zookeeper_mutex; /// Для пересоздания сессии в фоновом потоке. - zkutil::ZooKeeperPtr getZooKeeper() - { - std::lock_guard lock(current_zookeeper_mutex); - return current_zookeeper; - } - - void setZooKeeper(zkutil::ZooKeeperPtr zookeeper) - { - std::lock_guard lock(current_zookeeper_mutex); - current_zookeeper = zookeeper; - } + zkutil::ZooKeeperPtr tryGetZooKeeper(); + zkutil::ZooKeeperPtr getZooKeeper(); + void setZooKeeper(zkutil::ZooKeeperPtr zookeeper); /// Если true, таблица в офлайновом режиме, и в нее нельзя писать. bool is_readonly = false; @@ -421,6 +413,9 @@ private: /** Дождаться, пока указанная реплика выполнит указанное действие из лога. */ void waitForReplicaToProcessLogEntry(const String & replica_name, const LogEntry & entry); + + /// Кинуть исключение, если таблица readonly. + void assertNotReadonly() const; }; } diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp index 8d371e388c4..f47c0e061da 100644 --- a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp @@ -40,6 +40,9 @@ void ReplicatedMergeTreeBlockOutputStream::writePrefix() /// Позволяет проверить, что сессия в ZooKeeper ещё жива. static void assertSessionIsNotExpired(zkutil::ZooKeeperPtr & zookeeper) { + if (!zookeeper) + throw Exception("No ZooKeeper session.", ErrorCodes::NO_ZOOKEEPER); + if (zookeeper->expired()) throw Exception("ZooKeeper session has been expired.", ErrorCodes::NO_ZOOKEEPER); } diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index 8b227419317..35be2caf250 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -115,6 +115,27 @@ const Int64 RESERVED_BLOCK_NUMBERS = 200; const auto MAX_AGE_OF_LOCAL_PART_THAT_WASNT_ADDED_TO_ZOOKEEPER = 5 * 60; +void StorageReplicatedMergeTree::setZooKeeper(zkutil::ZooKeeperPtr zookeeper) +{ + std::lock_guard lock(current_zookeeper_mutex); + current_zookeeper = zookeeper; +} + +zkutil::ZooKeeperPtr StorageReplicatedMergeTree::tryGetZooKeeper() +{ + std::lock_guard lock(current_zookeeper_mutex); + return current_zookeeper; +} + +zkutil::ZooKeeperPtr StorageReplicatedMergeTree::getZooKeeper() +{ + auto res = tryGetZooKeeper(); + if (!res) + throw Exception("Cannot get ZooKeeper", ErrorCodes::NO_ZOOKEEPER); + return res; +} + + StorageReplicatedMergeTree::StorageReplicatedMergeTree( const String & zookeeper_path_, const String & replica_name_, @@ -184,6 +205,8 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( throw Exception("Can't create replicated table without ZooKeeper", ErrorCodes::NO_ZOOKEEPER); /// Не активируем реплику. Она будет в режиме readonly. + LOG_ERROR(log, "No ZooKeeper: table will be in readonly mode."); + is_readonly = true; return; } @@ -2304,6 +2327,7 @@ BlockInputStreams StorageReplicatedMergeTree::read( if (settings.select_sequential_consistency) { auto zookeeper = getZooKeeper(); + String last_part; zookeeper->tryGet(zookeeper_path + "/quorum/last_part", last_part); @@ -2350,10 +2374,16 @@ BlockInputStreams StorageReplicatedMergeTree::read( } -BlockOutputStreamPtr StorageReplicatedMergeTree::write(ASTPtr query, const Settings & settings) +void StorageReplicatedMergeTree::assertNotReadonly() const { if (is_readonly) throw Exception("Table is in readonly mode", ErrorCodes::TABLE_IS_READ_ONLY); +} + + +BlockOutputStreamPtr StorageReplicatedMergeTree::write(ASTPtr query, const Settings & settings) +{ + assertNotReadonly(); String insert_id; if (query) @@ -2392,6 +2422,8 @@ bool StorageReplicatedMergeTree::optimize(const Settings & settings) void StorageReplicatedMergeTree::alter(const AlterCommands & params, const String & database_name, const String & table_name, Context & context) { + assertNotReadonly(); + auto zookeeper = getZooKeeper(); const MergeTreeMergeBlocker merge_blocker{merger}; const auto unreplicated_merge_blocker = unreplicated_merger ? @@ -2553,6 +2585,8 @@ void StorageReplicatedMergeTree::dropPartition(ASTPtr query, const Field & field return; } + assertNotReadonly(); + auto zookeeper = getZooKeeper(); String month_name = MergeTreeData::getMonthName(field); @@ -2646,6 +2680,8 @@ void StorageReplicatedMergeTree::dropPartition(ASTPtr query, const Field & field void StorageReplicatedMergeTree::attachPartition(ASTPtr query, const Field & field, bool unreplicated, bool attach_part, const Settings & settings) { + assertNotReadonly(); + auto zookeeper = getZooKeeper(); String partition; @@ -2751,13 +2787,13 @@ void StorageReplicatedMergeTree::attachPartition(ASTPtr query, const Field & fie void StorageReplicatedMergeTree::drop() { - if (is_readonly) + auto zookeeper = tryGetZooKeeper(); + + if (is_readonly || !zookeeper) throw Exception("Can't drop readonly replicated table (need to drop data in ZooKeeper as well)", ErrorCodes::TABLE_IS_READ_ONLY); shutdown(); - auto zookeeper = getZooKeeper(); - if (zookeeper->expired()) throw Exception("Table was not dropped because ZooKeeper session has been expired.", ErrorCodes::TABLE_WAS_NOT_DROPPED); @@ -2967,7 +3003,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String & void StorageReplicatedMergeTree::getStatus(Status & res, bool with_zk_fields) { - auto zookeeper = getZooKeeper(); + auto zookeeper = tryGetZooKeeper(); res.is_leader = is_leader_node; res.is_readonly = is_readonly; From d1882fe766ad5fa21ad3a367686d8b74480a25b1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 11:27:52 +0300 Subject: [PATCH 26/32] dbms: addition to prev. revision [#METR-2944]. --- dbms/src/Storages/StorageReplicatedMergeTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index 35be2caf250..72e564deb37 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -304,7 +304,7 @@ StoragePtr StorageReplicatedMergeTree::create( StoragePtr res_ptr = res->thisPtr(); - if (res->getZooKeeper()) + if (res->tryGetZooKeeper()) { String endpoint_name = "ReplicatedMergeTree:" + res->replica_path; InterserverIOEndpointPtr endpoint = new ReplicatedMergeTreePartsServer(res->data, *res); From 82a96d22b19a0f736d58cb71a61bc119d0141185 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 16:00:42 +0300 Subject: [PATCH 27/32] dbms: tracking replicas delays [#METR-17573]. --- .../MergeTree/ReplicatedMergeTreeQueue.h | 38 +++- .../ReplicatedMergeTreeRestartingThread.h | 14 -- .../DB/Storages/StorageReplicatedMergeTree.h | 2 +- dbms/src/Server/Server.cpp | 33 +++- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 177 +++++++++++++++--- .../ReplicatedMergeTreeRestartingThread.cpp | 43 ++--- .../Storages/StorageReplicatedMergeTree.cpp | 56 +++++- 7 files changed, 284 insertions(+), 79 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 881a3529caf..206601331cc 100644 --- a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -24,6 +24,19 @@ private: using Queue = std::list; + struct ByTime + { + bool operator()(const LogEntryPtr & lhs, const LogEntryPtr & rhs) const + { + return std::forward_as_tuple(lhs.get()->create_time, lhs.get()) + < std::forward_as_tuple(rhs.get()->create_time, rhs.get()); + } + }; + + /// Для вычисления min_unprocessed_insert_time, max_processed_insert_time, по которым вычисляется отставание реплик. + using InsertsByTime = std::set; + + String zookeeper_path; String replica_path; String logger_name; @@ -33,6 +46,10 @@ private: */ Queue queue; + InsertsByTime inserts_by_time; + time_t min_unprocessed_insert_time = 0; + time_t max_processed_insert_time = 0; + time_t last_queue_update = 0; /// Куски, которые появятся в результате действий, выполняемых прямо сейчас фоновыми потоками (этих действий нет в очереди). @@ -71,16 +88,28 @@ private: */ bool shouldExecuteLogEntry(const LogEntry & entry, String & out_postpone_reason, MergeTreeDataMerger & merger); + /// После удаления элемента очереди, обновить времена insert-ов в оперативке. Выполняется под queue_mutex. + /// Возвращает информацию, какие времена изменились - эту информацию можно передать в updateTimesInZooKeeper. + void updateTimesOnRemoval(const LogEntryPtr & entry, bool & min_unprocessed_insert_time_changed, bool & max_processed_insert_time_changed); + + /// Обновить времена insert-ов в ZooKeeper. + void updateTimesInZooKeeper(zkutil::ZooKeeperPtr zookeeper, bool min_unprocessed_insert_time_changed, bool max_processed_insert_time_changed); + public: ReplicatedMergeTreeQueue() {} void initialize(const String & zookeeper_path_, const String & replica_path_, const String & logger_name_, const MergeTreeData::DataParts & parts, zkutil::ZooKeeperPtr zookeeper); - /** Вставить действие в конец очереди. */ - void insert(LogEntryPtr & entry); + /** Вставить действие в конец очереди. + * Для восстановления битых кусков во время работы. + * Не вставляет само действие в ZK (сделайте это самостоятельно). + */ + void insert(zkutil::ZooKeeperPtr zookeeper, LogEntryPtr & entry); - /** Удалить действие с указанным куском (в качестве new_part_name) из очереди. */ + /** Удалить действие с указанным куском (в качестве new_part_name) из очереди. + * Вызывается для невыполнимых действий в очереди - старых потерянных кусков. + */ bool remove(zkutil::ZooKeeperPtr zookeeper, const String & part_name); /** Скопировать новые записи из общего лога в очередь этой реплики. Установить log_pointer в соответствующее значение. @@ -142,6 +171,9 @@ public: /// Получить данные элементов очереди. using LogEntriesData = std::vector; void getEntries(LogEntriesData & res); + + /// Получить информацию о временах insert-ов. + void getInsertTimes(time_t & out_min_unprocessed_insert_time, time_t & out_max_processed_insert_time) const; }; diff --git a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.h b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.h index b9a454b3238..de50cb3648e 100644 --- a/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.h +++ b/dbms/include/DB/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.h @@ -45,12 +45,6 @@ public: wakeup(); } - void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const - { - out_absolute_delay = absolute_delay.load(std::memory_order_relaxed); - out_relative_delay = relative_delay.load(std::memory_order_relaxed); - } - private: StorageReplicatedMergeTree & storage; Logger * log; @@ -62,11 +56,6 @@ private: std::thread thread; - /// Отставание реплики. - std::atomic absolute_delay {}; - std::atomic relative_delay {}; - - void run(); /// Запустить или остановить фоновые потоки. Используется для частичной переинициализации при пересоздании сессии в ZooKeeper. @@ -85,9 +74,6 @@ private: /// Запретить запись в таблицу и завершить все фоновые потоки. void goReadOnlyPermanently(); - - /// Получить информацию об отставании реплик. - void checkReplicationDelays(time_t & out_absolute_delay, time_t & out_relative_delay); }; diff --git a/dbms/include/DB/Storages/StorageReplicatedMergeTree.h b/dbms/include/DB/Storages/StorageReplicatedMergeTree.h index 5af688c71a7..f2824f880ba 100644 --- a/dbms/include/DB/Storages/StorageReplicatedMergeTree.h +++ b/dbms/include/DB/Storages/StorageReplicatedMergeTree.h @@ -172,7 +172,7 @@ public: using LogEntriesData = std::vector; void getQueue(LogEntriesData & res, String & replica_name); - void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const; + void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay); private: void dropUnreplicatedPartition(const Field & partition, bool detach, const Settings & settings); diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index baad718b1dd..e193e7462eb 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -197,6 +197,11 @@ public: { try { + HTMLForm params(request); + + /// Даже в случае, когда отставание небольшое, выводить подробную информацию об отставании. + bool verbose = params.get("verbose", "") == "1"; + /// Собираем набор реплицируемых таблиц. Databases replicated_tables; { @@ -210,28 +215,28 @@ public: const MergeTreeSettings & settings = context.getMergeTreeSettings(); - bool ok = /*true*/false; + bool ok = true; std::stringstream message; for (const auto & db : replicated_tables) { - for (const auto & table : db.second) + for (auto & table : db.second) { time_t absolute_delay = 0; time_t relative_delay = 0; - static_cast(*table.second).getReplicaDelays(absolute_delay, relative_delay); + static_cast(*table.second).getReplicaDelays(absolute_delay, relative_delay); if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast(settings.min_absolute_delay_to_close)) || (settings.min_relative_delay_to_close && relative_delay >= static_cast(settings.min_relative_delay_to_close))) ok = false; message << backQuoteIfNeed(db.first) << "." << backQuoteIfNeed(table.first) - << "\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n"; + << ":\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n"; } } - if (ok) + if (ok && !verbose) { const char * data = "Ok.\n"; response.sendBuffer(data, strlen(data)); @@ -243,8 +248,22 @@ public: } catch (...) { - /// TODO Отправлять клиенту. tryLogCurrentException("ReplicasStatusHandler"); + + try + { + response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); + + if (!response.sent()) + { + /// Ещё ничего не отправляли, и даже не знаем, нужно ли сжимать ответ. + response.send() << getCurrentExceptionMessage(false) << std::endl; + } + } + catch (...) + { + LOG_ERROR((&Logger::get("ReplicasStatusHandler")), "Cannot send exception to client"); + } } } }; @@ -309,7 +328,7 @@ public: { if (uri == "/" || uri == "/ping") return new PingRequestHandler; - else if (uri == "/replicas_status") + else if (0 == uri.compare(0, strlen("/replicas_status"), "/replicas_status")) return new ReplicasStatusHandler(*server.global_context); else return new NotFoundHandler; diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 9e925c1a27a..f521ca67828 100644 --- a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -49,6 +49,8 @@ void ReplicatedMergeTreeQueue::load(zkutil::ZooKeeperPtr zookeeper) insertUnlocked(entry); } + updateTimesInZooKeeper(zookeeper, true, false); + LOG_TRACE(log, "Loaded queue"); } @@ -71,13 +73,88 @@ void ReplicatedMergeTreeQueue::insertUnlocked(LogEntryPtr & entry) { virtual_parts.add(entry->new_part_name); queue.push_back(entry); + + if (entry->type == LogEntry::GET_PART) + { + inserts_by_time.insert(entry); + + if (entry->create_time && (!min_unprocessed_insert_time || entry->create_time < min_unprocessed_insert_time)) + min_unprocessed_insert_time = entry->create_time; + } } -void ReplicatedMergeTreeQueue::insert(LogEntryPtr & entry) +void ReplicatedMergeTreeQueue::insert(zkutil::ZooKeeperPtr zookeeper, LogEntryPtr & entry) { - std::lock_guard lock(mutex); - insertUnlocked(entry); + time_t prev_min_unprocessed_insert_time; + + { + std::lock_guard lock(mutex); + prev_min_unprocessed_insert_time = min_unprocessed_insert_time; + insertUnlocked(entry); + } + + if (min_unprocessed_insert_time != prev_min_unprocessed_insert_time) + updateTimesInZooKeeper(zookeeper, true, false); +} + + +void ReplicatedMergeTreeQueue::updateTimesOnRemoval( + const LogEntryPtr & entry, + bool & min_unprocessed_insert_time_changed, + bool & max_processed_insert_time_changed) +{ + if (entry->type != LogEntry::GET_PART) + return; + + inserts_by_time.erase(entry); + + if (inserts_by_time.empty()) + { + min_unprocessed_insert_time = 0; + min_unprocessed_insert_time_changed = true; + } + else if ((*inserts_by_time.begin())->create_time > min_unprocessed_insert_time) + { + min_unprocessed_insert_time = (*inserts_by_time.begin())->create_time; + min_unprocessed_insert_time_changed = true; + } + + if (entry->create_time > max_processed_insert_time) + { + max_processed_insert_time = entry->create_time; + max_processed_insert_time_changed = true; + } +} + + +void ReplicatedMergeTreeQueue::updateTimesInZooKeeper( + zkutil::ZooKeeperPtr zookeeper, + bool min_unprocessed_insert_time_changed, + bool max_processed_insert_time_changed) +{ + /// Здесь может быть race condition (при одновременном выполнении разных remove). + /// Считаем его несущественным (в течение небольшого времени, в ZK будет записано немного отличающееся значение времени). + /// Также читаем значения переменных min_unprocessed_insert_time, max_processed_insert_time без синхронизации. + zkutil::Ops ops; + + if (min_unprocessed_insert_time_changed) + ops.push_back(new zkutil::Op::SetData( + replica_path + "/min_unprocessed_insert_time", toString(min_unprocessed_insert_time), -1)); + + if (max_processed_insert_time_changed) + ops.push_back(new zkutil::Op::SetData( + replica_path + "/max_processed_insert_time", toString(max_processed_insert_time), -1)); + + if (!ops.empty()) + { + auto code = zookeeper->tryMulti(ops); + + if (code != ZOK) + LOG_ERROR(log, "Couldn't set value of nodes for insert times (" + << replica_path << "/min_unprocessed_insert_time, max_processed_insert_time)" << ": " + << zkutil::ZooKeeper::error2string(code) + ". This shouldn't happen often."); + } } @@ -86,44 +163,67 @@ void ReplicatedMergeTreeQueue::remove(zkutil::ZooKeeperPtr zookeeper, LogEntryPt auto code = zookeeper->tryRemove(replica_path + "/queue/" + entry->znode_name); if (code != ZOK) - LOG_ERROR(log, "Couldn't remove " << replica_path + "/queue/" + entry->znode_name << ": " - << zkutil::ZooKeeper::error2string(code) + ". This shouldn't happen often."); + LOG_ERROR(log, "Couldn't remove " << replica_path << "/queue/" << entry->znode_name << ": " + << zkutil::ZooKeeper::error2string(code) << ". This shouldn't happen often."); - std::lock_guard lock(mutex); + bool min_unprocessed_insert_time_changed = false; + bool max_processed_insert_time_changed = false; - /// Удалим задание из очереди в оперативке. - /// Нельзя просто обратиться по заранее сохраненному итератору, потому что задание мог успеть удалить кто-то другой. - /// Почему просматриваем очередь с конца? - /// - потому что задание к выполнению сначала перемещается в конец очереди, чтобы в случае неуспеха оно осталось в конце. - for (Queue::iterator it = queue.end(); it != queue.begin();) { - --it; - if (*it == entry) + std::lock_guard lock(mutex); + + /// Удалим задание из очереди в оперативке. + /// Нельзя просто обратиться по заранее сохраненному итератору, потому что задание мог успеть удалить кто-то другой. + /// Почему просматриваем очередь с конца? + /// - потому что задание к выполнению сначала перемещается в конец очереди, чтобы в случае неуспеха оно осталось в конце. + for (Queue::iterator it = queue.end(); it != queue.begin();) { - queue.erase(it); - break; + --it; + if (*it == entry) + { + queue.erase(it); + break; + } } + + updateTimesOnRemoval(entry, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); } + + updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); } bool ReplicatedMergeTreeQueue::remove(zkutil::ZooKeeperPtr zookeeper, const String & part_name) { - std::lock_guard lock(mutex); + LogEntryPtr found; + + bool min_unprocessed_insert_time_changed = false; + bool max_processed_insert_time_changed = false; - for (Queue::iterator it = queue.begin(); it != queue.end();) { - if ((*it)->new_part_name == part_name) + std::lock_guard lock(mutex); + + for (Queue::iterator it = queue.begin(); it != queue.end();) { - zookeeper->tryRemove(replica_path + "/queue/" + (*it)->znode_name); /// NOTE Может быть, стоит избежать блокировки в это время. - queue.erase(it++); - return true; + if ((*it)->new_part_name == part_name) + { + found = *it; + queue.erase(it++); + updateTimesOnRemoval(found, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); + break; + } + else + ++it; } - else - ++it; } - return false; + if (!found) + return false; + + zookeeper->tryRemove(replica_path + "/queue/" + found->znode_name); + updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); + + return true; } @@ -181,6 +281,8 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z std::vector copied_entries; copied_entries.reserve(log_entries.size()); + bool min_unprocessed_insert_time_changed = false; + for (auto & future : futures) { zkutil::ZooKeeper::ValueAndStat res = future.second.get(); @@ -188,11 +290,25 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z ops.push_back(new zkutil::Op::Create( replica_path + "/queue/queue-", res.value, zookeeper->getDefaultACL(), zkutil::CreateMode::PersistentSequential)); + + const auto & entry = *copied_entries.back(); + if (entry.type == LogEntry::GET_PART) + { + if (entry.create_time && (!min_unprocessed_insert_time || entry.create_time < min_unprocessed_insert_time)) + { + min_unprocessed_insert_time = entry.create_time; + min_unprocessed_insert_time_changed = true; + } + } } ops.push_back(new zkutil::Op::SetData( replica_path + "/log_pointer", toString(last_entry_index + 1), -1)); + if (min_unprocessed_insert_time_changed) + ops.push_back(new zkutil::Op::SetData( + replica_path + "/min_unprocessed_insert_time", toString(min_unprocessed_insert_time), -1)); + auto results = zookeeper->multi(ops); /// Сейчас мы успешно обновили очередь в ZooKeeper. Обновим её в оперативке. @@ -280,6 +396,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z { Queue to_wait; size_t removed_entries = 0; + bool min_unprocessed_insert_time_changed = false; + bool max_processed_insert_time_changed = false; /// Удалим из очереди операции с кусками, содержащимися в удаляемом диапазоне. std::unique_lock lock(mutex); @@ -294,6 +412,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z if (code != ZOK) LOG_INFO(log, "Couldn't remove " << replica_path + "/queue/" + (*it)->znode_name << ": " << zkutil::ZooKeeper::error2string(code)); + + updateTimesOnRemoval(*it, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); queue.erase(it++); ++removed_entries; } @@ -301,6 +421,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z ++it; } + updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed); + LOG_DEBUG(log, "Removed " << removed_entries << " entries from queue. " "Waiting for " << to_wait.size() << " entries that are currently executing."); @@ -574,6 +696,13 @@ void ReplicatedMergeTreeQueue::countMerges(size_t & all_merges, size_t & big_mer } +void ReplicatedMergeTreeQueue::getInsertTimes(time_t & out_min_unprocessed_insert_time, time_t & out_max_processed_insert_time) const +{ + out_min_unprocessed_insert_time = min_unprocessed_insert_time; + out_max_processed_insert_time = max_processed_insert_time; +} + + String padIndex(Int64 index) { String index_str = toString(index); diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp index 7faf545e754..ac48ff1d889 100644 --- a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp @@ -107,22 +107,32 @@ void ReplicatedMergeTreeRestartingThread::run() if (current_time >= prev_time_of_check_delay + static_cast(storage.data.settings.check_delay_period)) { /// Выясняем отставания реплик. - time_t new_absolute_delay = 0; - time_t new_relative_delay = 0; + time_t absolute_delay = 0; + time_t relative_delay = 0; - /// TODO Ловить здесь исключение. - checkReplicationDelays(new_absolute_delay, new_relative_delay); - - absolute_delay.store(new_absolute_delay, std::memory_order_relaxed); - relative_delay.store(new_relative_delay, std::memory_order_relaxed); + bool error = false; + try + { + storage.getReplicaDelays(absolute_delay, relative_delay); + LOG_TRACE(log, "Absolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << "."); + } + catch (...) + { + tryLogCurrentException("__PRETTY_FUNCTION__", "Cannot get replica delays"); + error = true; + } prev_time_of_check_delay = current_time; /// Уступаем лидерство, если относительное отставание больше порога. - if (storage.is_leader_node && new_relative_delay > static_cast(storage.data.settings.min_relative_delay_to_yield_leadership)) + if (storage.is_leader_node + && (error || relative_delay > static_cast(storage.data.settings.min_relative_delay_to_yield_leadership))) { - LOG_INFO(log, "Relative replica delay (" << new_relative_delay << " seconds) is bigger than threshold (" - << storage.data.settings.min_relative_delay_to_yield_leadership << "). Will yield leadership."); + if (error) + LOG_INFO(log, "Will yield leadership."); + else + LOG_INFO(log, "Relative replica delay (" << relative_delay << " seconds) is bigger than threshold (" + << storage.data.settings.min_relative_delay_to_yield_leadership << "). Will yield leadership."); need_restart = true; continue; @@ -374,17 +384,4 @@ void ReplicatedMergeTreeRestartingThread::goReadOnlyPermanently() } -void ReplicatedMergeTreeRestartingThread::checkReplicationDelays(time_t & out_absolute_delay, time_t & out_relative_delay) -{ - out_absolute_delay = 0; - out_relative_delay = 0; - - auto zookeeper = storage.getZooKeeper(); - - // TODO - - LOG_TRACE(log, "Absolute delay: " << out_absolute_delay << ". Relative delay: " << out_relative_delay << "."); -} - - } diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index 72e564deb37..32e160f83e1 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -272,6 +272,7 @@ void StorageReplicatedMergeTree::createNewZooKeeperNodes() /// Отслеживание отставания реплик. zookeeper->createIfNotExists(replica_path + "/min_unprocessed_insert_time", ""); + zookeeper->createIfNotExists(replica_path + "/max_processed_insert_time", ""); } @@ -592,7 +593,7 @@ void StorageReplicatedMergeTree::createReplica() zookeeper->create(replica_path + "/queue/queue-", entry, zkutil::CreateMode::PersistentSequential); } - /// Далее оно будет загружено в переменную queue в методе queue.load. + /// Далее оно будет загружено в переменную queue в методе queue.initialize. LOG_DEBUG(log, "Copied " << source_queue.size() << " queue entries"); } @@ -731,7 +732,7 @@ void StorageReplicatedMergeTree::checkParts(bool skip_sanity_checks) log_entry.new_part_name = name; log_entry.create_time = tryGetPartCreateTime(zookeeper, replica_path, name); - /// Полагаемся, что это происходит до загрузки очереди (queue.load). + /// Полагаемся, что это происходит до загрузки очереди (queue.initialize). zkutil::Ops ops; removePartFromZooKeeper(name, ops); ops.push_back(new zkutil::Op::Create( @@ -1764,7 +1765,7 @@ void StorageReplicatedMergeTree::removePartAndEnqueueFetch(const String & part_n String path_created = dynamic_cast(ops[0]).getPathCreated(); log_entry->znode_name = path_created.substr(path_created.find_last_of('/') + 1); - queue.insert(log_entry); + queue.insert(zookeeper, log_entry); } @@ -3063,12 +3064,53 @@ void StorageReplicatedMergeTree::getQueue(LogEntriesData & res, String & replica } -void StorageReplicatedMergeTree::getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const +void StorageReplicatedMergeTree::getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) { - if (!restarting_thread) - throw Exception("Table was shutted down or is in readonly mode.", ErrorCodes::TABLE_IS_READ_ONLY); + assertNotReadonly(); - restarting_thread->getReplicaDelays(out_absolute_delay, out_relative_delay); + /** Абсолютная задержка - задержка отставания текущей реплики от реального времени. + */ + + time_t min_unprocessed_insert_time = 0; + time_t max_processed_insert_time = 0; + queue.getInsertTimes(min_unprocessed_insert_time, max_processed_insert_time); + + time_t current_time = time(0); + out_absolute_delay = 0; + out_relative_delay = 0; + + if (min_unprocessed_insert_time) + out_absolute_delay = current_time - min_unprocessed_insert_time; + + /** Относительная задержка - максимальная разница абсолютной задержки от какой-либо другой реплики, + * (если эта реплика отстаёт от какой-либо другой реплики, или ноль, иначе). + * Вычисляется только если абсолютная задержка достаточно большая. + */ + + if (out_absolute_delay < static_cast(data.settings.min_relative_delay_to_yield_leadership)) + return; + + auto zookeeper = getZooKeeper(); + + time_t max_replicas_unprocessed_insert_time = 0; + Strings replicas = zookeeper->getChildren(zookeeper_path + "/replicas"); + + for (const auto & replica : replicas) + { + if (replica == replica_name) + continue; + + String value; + if (!zookeeper->tryGet(zookeeper_path + "/replicas/" + replica + "/min_unprocessed_insert_time", value)) + continue; + + time_t replica_time = value.empty() ? 0 : parse(value); + if (replica_time > max_replicas_unprocessed_insert_time) + max_replicas_unprocessed_insert_time = replica_time; + } + + if (max_replicas_unprocessed_insert_time > min_unprocessed_insert_time) + out_relative_delay = max_replicas_unprocessed_insert_time - min_unprocessed_insert_time; } From 3356bfdeec429540c8e4665d34fcdd53e1593a8f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 16:34:36 +0300 Subject: [PATCH 28/32] dbms: implemented TODO [#METR-17573]. --- dbms/src/Server/MetricsTransmitter.cpp | 70 ++++ dbms/src/Server/MetricsTransmitter.h | 35 ++ dbms/src/Server/ReplicasStatusHandler.cpp | 96 +++++ dbms/src/Server/ReplicasStatusHandler.h | 24 ++ dbms/src/Server/Server.cpp | 411 +--------------------- dbms/src/Server/StatusFile.cpp | 97 +++++ dbms/src/Server/StatusFile.h | 25 ++ dbms/src/Server/UsersConfigReloader.cpp | 135 +++++++ dbms/src/Server/UsersConfigReloader.h | 41 +++ 9 files changed, 532 insertions(+), 402 deletions(-) create mode 100644 dbms/src/Server/MetricsTransmitter.cpp create mode 100644 dbms/src/Server/MetricsTransmitter.h create mode 100644 dbms/src/Server/ReplicasStatusHandler.cpp create mode 100644 dbms/src/Server/ReplicasStatusHandler.h create mode 100644 dbms/src/Server/StatusFile.cpp create mode 100644 dbms/src/Server/StatusFile.h create mode 100644 dbms/src/Server/UsersConfigReloader.cpp create mode 100644 dbms/src/Server/UsersConfigReloader.h diff --git a/dbms/src/Server/MetricsTransmitter.cpp b/dbms/src/Server/MetricsTransmitter.cpp new file mode 100644 index 00000000000..8139e218ca3 --- /dev/null +++ b/dbms/src/Server/MetricsTransmitter.cpp @@ -0,0 +1,70 @@ +#include "MetricsTransmitter.h" + +#include +#include + + +namespace DB +{ + +MetricsTransmitter::~MetricsTransmitter() +{ + try + { + { + std::lock_guard lock{mutex}; + quit = true; + } + + cond.notify_one(); + + thread.join(); + } + catch (...) + { + DB::tryLogCurrentException(__FUNCTION__); + } +} + + +void MetricsTransmitter::run() +{ + setThreadName("ProfileEventsTx"); + + const auto get_next_minute = [] { + return std::chrono::time_point_cast( + std::chrono::system_clock::now() + std::chrono::minutes(1) + ); + }; + + std::unique_lock lock{mutex}; + + while (true) + { + if (cond.wait_until(lock, get_next_minute(), [this] { return quit; })) + break; + + transmitCounters(); + } +} + + +void MetricsTransmitter::transmitCounters() +{ + GraphiteWriter::KeyValueVector key_vals{}; + key_vals.reserve(ProfileEvents::END); + + for (size_t i = 0; i < ProfileEvents::END; ++i) + { + const auto counter = ProfileEvents::counters[i]; + const auto counter_increment = counter - prev_counters[i]; + prev_counters[i] = counter; + + std::string key{ProfileEvents::getDescription(static_cast(i))}; + key_vals.emplace_back(event_path_prefix + key, counter_increment); + } + + Daemon::instance().writeToGraphite(key_vals); +} + +} diff --git a/dbms/src/Server/MetricsTransmitter.h b/dbms/src/Server/MetricsTransmitter.h new file mode 100644 index 00000000000..abbadaaa350 --- /dev/null +++ b/dbms/src/Server/MetricsTransmitter.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#include + + +namespace DB +{ + +/** Automatically sends difference of ProfileEvents to Graphite at beginning of every minute + */ +class MetricsTransmitter +{ +public: + ~MetricsTransmitter(); + +private: + void run(); + void transmitCounters(); + + /// Значения счётчиков при предыдущей отправке (или нули, если ни разу не отправляли). + decltype(ProfileEvents::counters) prev_counters{}; + + bool quit = false; + std::mutex mutex; + std::condition_variable cond; + std::thread thread {&MetricsTransmitter::run, this}; + + static constexpr auto event_path_prefix = "ClickHouse.ProfileEvents."; +}; + +} diff --git a/dbms/src/Server/ReplicasStatusHandler.cpp b/dbms/src/Server/ReplicasStatusHandler.cpp new file mode 100644 index 00000000000..fe351a143f5 --- /dev/null +++ b/dbms/src/Server/ReplicasStatusHandler.cpp @@ -0,0 +1,96 @@ +#include "ReplicasStatusHandler.h" + +#include +#include +#include + +#include +#include + + +namespace DB +{ + + +ReplicasStatusHandler::ReplicasStatusHandler(Context & context_) + : context(context_) +{ +} + + +void ReplicasStatusHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) +{ + try + { + HTMLForm params(request); + + /// Даже в случае, когда отставание небольшое, выводить подробную информацию об отставании. + bool verbose = params.get("verbose", "") == "1"; + + /// Собираем набор реплицируемых таблиц. + Databases replicated_tables; + { + Poco::ScopedLock lock(context.getMutex()); + + for (const auto & db : context.getDatabases()) + for (const auto & table : db.second) + if (typeid_cast(table.second.get())) + replicated_tables[db.first][table.first] = table.second; + } + + const MergeTreeSettings & settings = context.getMergeTreeSettings(); + + bool ok = true; + std::stringstream message; + + for (const auto & db : replicated_tables) + { + for (auto & table : db.second) + { + time_t absolute_delay = 0; + time_t relative_delay = 0; + + static_cast(*table.second).getReplicaDelays(absolute_delay, relative_delay); + + if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast(settings.min_absolute_delay_to_close)) + || (settings.min_relative_delay_to_close && relative_delay >= static_cast(settings.min_relative_delay_to_close))) + ok = false; + + message << backQuoteIfNeed(db.first) << "." << backQuoteIfNeed(table.first) + << ":\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n"; + } + } + + if (ok && !verbose) + { + const char * data = "Ok.\n"; + response.sendBuffer(data, strlen(data)); + } + else + { + response.send() << message.rdbuf(); + } + } + catch (...) + { + tryLogCurrentException("ReplicasStatusHandler"); + + try + { + response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); + + if (!response.sent()) + { + /// Ещё ничего не отправляли, и даже не знаем, нужно ли сжимать ответ. + response.send() << getCurrentExceptionMessage(false) << std::endl; + } + } + catch (...) + { + LOG_ERROR((&Logger::get("ReplicasStatusHandler")), "Cannot send exception to client"); + } + } +} + + +} diff --git a/dbms/src/Server/ReplicasStatusHandler.h b/dbms/src/Server/ReplicasStatusHandler.h new file mode 100644 index 00000000000..ac2fc4c66ba --- /dev/null +++ b/dbms/src/Server/ReplicasStatusHandler.h @@ -0,0 +1,24 @@ +#pragma once + +#include + + +namespace DB +{ + +class Context; + +/// Отвечает "Ok.\n", если все реплики на этом сервере не слишком сильно отстают. Иначе выводит информацию об отставании. TODO Вынести в отдельный файл. +class ReplicasStatusHandler : public Poco::Net::HTTPRequestHandler +{ +private: + Context & context; + +public: + ReplicasStatusHandler(Context & context_); + + void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response); +}; + + +} diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index e193e7462eb..88419d66ec3 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -1,27 +1,23 @@ #include -#include #include #include #include +#include #include #include -#include -#include #include #include -#include -#include -#include #include #include -#include + #include #include + #include #include #include @@ -38,129 +34,23 @@ #include #include #include -#include - -#include -#include -#include -#include #include #include "Server.h" #include "HTTPHandler.h" +#include "ReplicasStatusHandler.h" #include "InterserverIOHTTPHandler.h" #include "OLAPHTTPHandler.h" #include "TCPHandler.h" +#include "MetricsTransmitter.h" +#include "UsersConfigReloader.h" +#include "StatusFile.h" -namespace -{ - -/** Automatically sends difference of ProfileEvents to Graphite at beginning of every minute -*/ -class ProfileEventsTransmitter -{ -public: - ~ProfileEventsTransmitter() - { - try - { - { - std::lock_guard lock{mutex}; - quit = true; - } - - cond.notify_one(); - - thread.join(); - } - catch (...) - { - DB::tryLogCurrentException(__FUNCTION__); - } - } - -private: - void run() - { - setThreadName("ProfileEventsTx"); - - const auto get_next_minute = [] { - return std::chrono::time_point_cast( - std::chrono::system_clock::now() + std::chrono::minutes(1) - ); - }; - - std::unique_lock lock{mutex}; - - while (true) - { - if (cond.wait_until(lock, get_next_minute(), [this] { return quit; })) - break; - - transmitCounters(); - } - } - - void transmitCounters() - { - GraphiteWriter::KeyValueVector key_vals{}; - key_vals.reserve(ProfileEvents::END); - - for (size_t i = 0; i < ProfileEvents::END; ++i) - { - const auto counter = ProfileEvents::counters[i]; - const auto counter_increment = counter - prev_counters[i]; - prev_counters[i] = counter; - - std::string key{ProfileEvents::getDescription(static_cast(i))}; - key_vals.emplace_back(event_path_prefix + key, counter_increment); - } - - Daemon::instance().writeToGraphite(key_vals); - } - - /// Значения счётчиков при предыдущей отправке (или нули, если ни разу не отправляли). - decltype(ProfileEvents::counters) prev_counters{}; - - bool quit = false; - std::mutex mutex; - std::condition_variable cond; - std::thread thread{&ProfileEventsTransmitter::run, this}; - - static constexpr auto event_path_prefix = "ClickHouse.ProfileEvents."; -}; - -} - namespace DB { -/** Каждые две секунды проверяет, не изменился ли конфиг. - * Когда изменился, запускает на нем ConfigProcessor и вызывает setUsersConfig у контекста. - * NOTE: Не перезагружает конфиг, если изменились другие файлы, влияющие на обработку конфига: metrika.xml - * и содержимое conf.d и users.d. Это можно исправить, переместив проверку времени изменения файлов в ConfigProcessor. - */ -class UsersConfigReloader -{ -public: - UsersConfigReloader(const std::string & path, Context * context); - ~UsersConfigReloader(); -private: - std::string path; - Context * context; - - time_t file_modification_time; - std::atomic quit; - std::thread thread; - - Logger * log; - - void reloadIfNewer(bool force); - void run(); -}; - /// Отвечает "Ok.\n". Используется для проверки живости. class PingRequestHandler : public Poco::Net::HTTPRequestHandler @@ -181,94 +71,6 @@ public: }; -/// Отвечает "Ok.\n", если все реплики на этом сервере не слишком сильно отстают. Иначе выводит информацию об отставании. TODO Вынести в отдельный файл. -class ReplicasStatusHandler : public Poco::Net::HTTPRequestHandler -{ -private: - Context & context; - -public: - ReplicasStatusHandler(Context & context_) - : context(context_) - { - } - - void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) - { - try - { - HTMLForm params(request); - - /// Даже в случае, когда отставание небольшое, выводить подробную информацию об отставании. - bool verbose = params.get("verbose", "") == "1"; - - /// Собираем набор реплицируемых таблиц. - Databases replicated_tables; - { - Poco::ScopedLock lock(context.getMutex()); - - for (const auto & db : context.getDatabases()) - for (const auto & table : db.second) - if (typeid_cast(table.second.get())) - replicated_tables[db.first][table.first] = table.second; - } - - const MergeTreeSettings & settings = context.getMergeTreeSettings(); - - bool ok = true; - std::stringstream message; - - for (const auto & db : replicated_tables) - { - for (auto & table : db.second) - { - time_t absolute_delay = 0; - time_t relative_delay = 0; - - static_cast(*table.second).getReplicaDelays(absolute_delay, relative_delay); - - if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast(settings.min_absolute_delay_to_close)) - || (settings.min_relative_delay_to_close && relative_delay >= static_cast(settings.min_relative_delay_to_close))) - ok = false; - - message << backQuoteIfNeed(db.first) << "." << backQuoteIfNeed(table.first) - << ":\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n"; - } - } - - if (ok && !verbose) - { - const char * data = "Ok.\n"; - response.sendBuffer(data, strlen(data)); - } - else - { - response.send() << message.rdbuf(); - } - } - catch (...) - { - tryLogCurrentException("ReplicasStatusHandler"); - - try - { - response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); - - if (!response.sent()) - { - /// Ещё ничего не отправляли, и даже не знаем, нужно ли сжимать ответ. - response.send() << getCurrentExceptionMessage(false) << std::endl; - } - } - catch (...) - { - LOG_ERROR((&Logger::get("ReplicasStatusHandler")), "Cannot send exception to client"); - } - } - } -}; - - /// Отвечает 404 с подробным объяснением. class NotFoundHandler : public Poco::Net::HTTPRequestHandler { @@ -357,201 +159,6 @@ public: }; -UsersConfigReloader::UsersConfigReloader(const std::string & path_, Context * context_) - : path(path_), context(context_), file_modification_time(0), quit(false), log(&Logger::get("UsersConfigReloader")) -{ - /// Если путь к конфигу не абсолютный, угадаем, относительно чего он задан. - /// Сначала поищем его рядом с основным конфигом, потом - в текущей директории. - if (path.empty() || path[0] != '/') - { - std::string main_config_path = Poco::Util::Application::instance().config().getString("config-file", "config.xml"); - std::string config_dir = Poco::Path(main_config_path).parent().toString(); - if (Poco::File(config_dir + path).exists()) - path = config_dir + path; - } - - reloadIfNewer(true); - thread = std::thread(&UsersConfigReloader::run, this); -} - -UsersConfigReloader::~UsersConfigReloader() -{ - try - { - quit = true; - thread.join(); - } - catch (...) - { - tryLogCurrentException("~UsersConfigReloader"); - } -} - -void UsersConfigReloader::run() -{ - setThreadName("UserConfReload"); - - while (!quit) - { - std::this_thread::sleep_for(std::chrono::seconds(2)); - reloadIfNewer(false); - } -} - -void UsersConfigReloader::reloadIfNewer(bool force) -{ - Poco::File f(path); - if (!f.exists()) - { - if (force) - throw Exception("Users config not found at: " + path, ErrorCodes::FILE_DOESNT_EXIST); - if (file_modification_time) - { - LOG_ERROR(log, "Users config not found at: " << path); - file_modification_time = 0; - } - return; - } - time_t new_modification_time = f.getLastModified().epochTime(); - if (!force && new_modification_time == file_modification_time) - return; - file_modification_time = new_modification_time; - - LOG_DEBUG(log, "Loading users config"); - - ConfigurationPtr config; - - try - { - config = ConfigProcessor(!force).loadConfig(path); - } - catch (Poco::Exception & e) - { - if (force) - throw; - - LOG_ERROR(log, "Error loading users config: " << e.what() << ": " << e.displayText()); - return; - } - catch (...) - { - if (force) - throw; - - LOG_ERROR(log, "Error loading users config."); - return; - } - - try - { - context->setUsersConfig(config); - } - catch (Exception & e) - { - if (force) - throw; - - LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText() << "\n" << e.getStackTrace().toString()); - } - catch (Poco::Exception & e) - { - if (force) - throw; - - LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText()); - } - catch (...) - { - if (force) - throw; - - LOG_ERROR(log, "Error updating users config."); - } -} - - -/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера. - */ -class StatusFile : private boost::noncopyable -{ -public: - StatusFile(const std::string & path_) - : path(path_) - { - /// Если файл уже существует. NOTE Незначительный race condition. - if (Poco::File(path).exists()) - { - std::string contents; - { - ReadBufferFromFile in(path, 1024); - LimitReadBuffer limit_in(in, 1024); - WriteBufferFromString out(contents); - copyData(limit_in, out); - } - - if (!contents.empty()) - LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents); - else - LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart."); - } - - fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666); - - if (-1 == fd) - throwFromErrno("Cannot open file " + path); - - try - { - int flock_ret = flock(fd, LOCK_EX | LOCK_NB); - if (-1 == flock_ret) - { - if (errno == EWOULDBLOCK) - throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running."); - else - throwFromErrno("Cannot lock file " + path); - } - - if (0 != ftruncate(fd, 0)) - throwFromErrno("Cannot ftruncate " + path); - - if (0 != lseek(fd, 0, SEEK_SET)) - throwFromErrno("Cannot lseek " + path); - - /// Записываем в файл информацию о текущем экземпляре сервера. - { - WriteBufferFromFileDescriptor out(fd, 1024); - out - << "PID: " << getpid() << "\n" - << "Started at: " << mysqlxx::DateTime(time(0)) << "\n" - << "Revision: " << Revision::get() << "\n"; - } - } - catch (...) - { - close(fd); - throw; - } - } - - ~StatusFile() - { - char buf[128]; - - if (0 != close(fd)) - LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: " - << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); - - if (0 != unlink(path.c_str())) - LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: " - << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); - } - -private: - const std::string path; - int fd = -1; -}; - - int Server::main(const std::vector & args) { Logger * log = &logger(); @@ -723,8 +330,8 @@ int Server::main(const std::vector & args) ); { - const auto profile_events_transmitter = config().getBool("use_graphite", true) - ? std::make_unique() + const auto metrics_transmitter = config().getBool("use_graphite", true) + ? std::make_unique() : nullptr; const std::string listen_host = config().getString("listen_host", "::"); diff --git a/dbms/src/Server/StatusFile.cpp b/dbms/src/Server/StatusFile.cpp new file mode 100644 index 00000000000..7be5064e7ed --- /dev/null +++ b/dbms/src/Server/StatusFile.cpp @@ -0,0 +1,97 @@ +#include "StatusFile.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + + +StatusFile::StatusFile(const std::string & path_) + : path(path_) +{ + /// Если файл уже существует. NOTE Незначительный race condition. + if (Poco::File(path).exists()) + { + std::string contents; + { + ReadBufferFromFile in(path, 1024); + LimitReadBuffer limit_in(in, 1024); + WriteBufferFromString out(contents); + copyData(limit_in, out); + } + + if (!contents.empty()) + LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents); + else + LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart."); + } + + fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666); + + if (-1 == fd) + throwFromErrno("Cannot open file " + path); + + try + { + int flock_ret = flock(fd, LOCK_EX | LOCK_NB); + if (-1 == flock_ret) + { + if (errno == EWOULDBLOCK) + throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running."); + else + throwFromErrno("Cannot lock file " + path); + } + + if (0 != ftruncate(fd, 0)) + throwFromErrno("Cannot ftruncate " + path); + + if (0 != lseek(fd, 0, SEEK_SET)) + throwFromErrno("Cannot lseek " + path); + + /// Записываем в файл информацию о текущем экземпляре сервера. + { + WriteBufferFromFileDescriptor out(fd, 1024); + out + << "PID: " << getpid() << "\n" + << "Started at: " << mysqlxx::DateTime(time(0)) << "\n" + << "Revision: " << Revision::get() << "\n"; + } + } + catch (...) + { + close(fd); + throw; + } +} + + +StatusFile::~StatusFile() +{ + char buf[128]; + + if (0 != close(fd)) + LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: " + << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); + + if (0 != unlink(path.c_str())) + LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: " + << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); +} + +} diff --git a/dbms/src/Server/StatusFile.h b/dbms/src/Server/StatusFile.h new file mode 100644 index 00000000000..a94db21e0cb --- /dev/null +++ b/dbms/src/Server/StatusFile.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + + +namespace DB +{ + + +/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера. + */ +class StatusFile : private boost::noncopyable +{ +public: + StatusFile(const std::string & path_); + ~StatusFile(); + +private: + const std::string path; + int fd = -1; +}; + + +} diff --git a/dbms/src/Server/UsersConfigReloader.cpp b/dbms/src/Server/UsersConfigReloader.cpp new file mode 100644 index 00000000000..88fdb709530 --- /dev/null +++ b/dbms/src/Server/UsersConfigReloader.cpp @@ -0,0 +1,135 @@ +#include "UsersConfigReloader.h" + +#include +#include + +#include + +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes { extern const int FILE_DOESNT_EXIST; } + + +UsersConfigReloader::UsersConfigReloader(const std::string & path_, Context * context_) + : path(path_), context(context_), file_modification_time(0), quit(false), log(&Logger::get("UsersConfigReloader")) +{ + /// Если путь к конфигу не абсолютный, угадаем, относительно чего он задан. + /// Сначала поищем его рядом с основным конфигом, потом - в текущей директории. + if (path.empty() || path[0] != '/') + { + std::string main_config_path = Poco::Util::Application::instance().config().getString("config-file", "config.xml"); + std::string config_dir = Poco::Path(main_config_path).parent().toString(); + if (Poco::File(config_dir + path).exists()) + path = config_dir + path; + } + + reloadIfNewer(true); + thread = std::thread(&UsersConfigReloader::run, this); +} + + +UsersConfigReloader::~UsersConfigReloader() +{ + try + { + quit = true; + thread.join(); + } + catch (...) + { + tryLogCurrentException("~UsersConfigReloader"); + } +} + + +void UsersConfigReloader::run() +{ + setThreadName("UserConfReload"); + + while (!quit) + { + std::this_thread::sleep_for(std::chrono::seconds(2)); + reloadIfNewer(false); + } +} + + +void UsersConfigReloader::reloadIfNewer(bool force) +{ + Poco::File f(path); + if (!f.exists()) + { + if (force) + throw Exception("Users config not found at: " + path, ErrorCodes::FILE_DOESNT_EXIST); + if (file_modification_time) + { + LOG_ERROR(log, "Users config not found at: " << path); + file_modification_time = 0; + } + return; + } + time_t new_modification_time = f.getLastModified().epochTime(); + if (!force && new_modification_time == file_modification_time) + return; + file_modification_time = new_modification_time; + + LOG_DEBUG(log, "Loading users config"); + + ConfigurationPtr config; + + try + { + config = ConfigProcessor(!force).loadConfig(path); + } + catch (Poco::Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Error loading users config: " << e.what() << ": " << e.displayText()); + return; + } + catch (...) + { + if (force) + throw; + + LOG_ERROR(log, "Error loading users config."); + return; + } + + try + { + context->setUsersConfig(config); + } + catch (Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText() << "\n" << e.getStackTrace().toString()); + } + catch (Poco::Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText()); + } + catch (...) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config."); + } +} + + +} diff --git a/dbms/src/Server/UsersConfigReloader.h b/dbms/src/Server/UsersConfigReloader.h new file mode 100644 index 00000000000..d26299ed87d --- /dev/null +++ b/dbms/src/Server/UsersConfigReloader.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + + +namespace Poco { class Logger; } + +namespace DB +{ + +class Context; + +/** Каждые две секунды проверяет, не изменился ли конфиг. + * Когда изменился, запускает на нем ConfigProcessor и вызывает setUsersConfig у контекста. + * NOTE: Не перезагружает конфиг, если изменились другие файлы, влияющие на обработку конфига: metrika.xml + * и содержимое conf.d и users.d. Это можно исправить, переместив проверку времени изменения файлов в ConfigProcessor. + */ +class UsersConfigReloader +{ +public: + UsersConfigReloader(const std::string & path, Context * context); + ~UsersConfigReloader(); + +private: + std::string path; + Context * context; + + time_t file_modification_time; + std::atomic quit; + std::thread thread; + + Poco::Logger * log; + + void reloadIfNewer(bool force); + void run(); +}; + +} From 83fe68727a0771c62356f844c0a844a4c633a360 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 17 Jan 2016 16:41:36 +0300 Subject: [PATCH 29/32] dbms: addition [#METR-17573]. --- dbms/src/Server/Server.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 88419d66ec3..df6de3732d1 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -120,23 +120,29 @@ public: const auto & uri = request.getURI(); - if (uri.find('?') != std::string::npos - || request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) - { - return new HandlerType(server); - } - else if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET + if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET || request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD) { if (uri == "/" || uri == "/ping") return new PingRequestHandler; else if (0 == uri.compare(0, strlen("/replicas_status"), "/replicas_status")) return new ReplicasStatusHandler(*server.global_context); - else - return new NotFoundHandler; } - else - return nullptr; + + if (uri.find('?') != std::string::npos + || request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) + { + return new HandlerType(server); + } + + if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET + || request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD + || request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) + { + return new NotFoundHandler; + } + + return nullptr; } }; From 25036fec3112edf8f0aae44aa9248651879a76bc Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 18 Jan 2016 19:27:27 +0300 Subject: [PATCH 30/32] dbms: addition [#METR-19586]. --- dbms/src/Storages/StorageReplicatedMergeTree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index e074af24909..9c1de98ffdb 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -1353,8 +1353,6 @@ void StorageReplicatedMergeTree::mergeSelectingThread() try { - std::lock_guard merge_selecting_lock(merge_selecting_mutex); - if (need_pull) { /// Нужно загрузить новые записи в очередь перед тем, как выбирать куски для слияния. @@ -1363,6 +1361,8 @@ void StorageReplicatedMergeTree::mergeSelectingThread() need_pull = false; } + std::lock_guard merge_selecting_lock(merge_selecting_mutex); + /** Сколько в очереди или в фоновом потоке мерджей крупных кусков. * Если их больше половины от размера пула потоков для мерджа, то можно мерджить только мелкие куски. */ From c3381e4a3fbc02c9c661b80bba0639264c9917fa Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 19 Jan 2016 00:33:05 +0300 Subject: [PATCH 31/32] dbms: fixed error [#METR-19561]. --- dbms/src/Interpreters/ProcessList.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dbms/src/Interpreters/ProcessList.cpp b/dbms/src/Interpreters/ProcessList.cpp index e9fcbbf4816..2b35aa42d19 100644 --- a/dbms/src/Interpreters/ProcessList.cpp +++ b/dbms/src/Interpreters/ProcessList.cpp @@ -88,16 +88,20 @@ ProcessListEntry::~ProcessListEntry() /// Важен порядок удаления memory_tracker-ов. + String user = it->user; + String query_id = it->query_id; + bool is_cancelled = it->is_cancelled; + /// Здесь удаляется memory_tracker одного запроса. parent.cont.erase(it); - ProcessList::UserToQueries::iterator user_process_list = parent.user_to_queries.find(it->user); + ProcessList::UserToQueries::iterator user_process_list = parent.user_to_queries.find(user); if (user_process_list != parent.user_to_queries.end()) { /// В случае, если запрос отменяется, данные о нем удаляются из мапа в момент отмены, а не здесь. - if (!it->is_cancelled && !it->query_id.empty()) + if (!is_cancelled && !query_id.empty()) { - ProcessListForUser::QueryToElement::iterator element = user_process_list->second.queries.find(it->query_id); + ProcessListForUser::QueryToElement::iterator element = user_process_list->second.queries.find(query_id); if (element != user_process_list->second.queries.end()) user_process_list->second.queries.erase(element); } From f8189770926ea6a77567605101003691128b7246 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 19 Jan 2016 05:25:07 +0300 Subject: [PATCH 32/32] dbms: fixed dynamic compilation [#METR-2944]. --- dbms/include/DB/AggregateFunctions/IAggregateFunction.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dbms/include/DB/AggregateFunctions/IAggregateFunction.h b/dbms/include/DB/AggregateFunctions/IAggregateFunction.h index 31b026aafe8..615502e99a0 100644 --- a/dbms/include/DB/AggregateFunctions/IAggregateFunction.h +++ b/dbms/include/DB/AggregateFunctions/IAggregateFunction.h @@ -17,6 +17,7 @@ namespace ErrorCodes extern const int AGGREGATE_FUNCTION_DOESNT_ALLOW_PARAMETERS; extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int ARGUMENT_OUT_OF_BOUND; } using AggregateDataPtr = char *;