This commit is contained in:
Vsevolod Orlov 2016-01-20 18:48:02 +03:00
commit 5c53f51ef5
46 changed files with 3598 additions and 926 deletions

View File

@ -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 *;

View File

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

View File

@ -1,15 +1,7 @@
#pragma once
#include <DB/Common/Arena.h>
#if !defined(__x86_64__)
inline unsigned int _bit_scan_reverse(unsigned int x)
{
return sizeof(unsigned int) * 8 - 1 - __builtin_clz(x);
}
#endif
#include <DB/Common/ARMHelpers.h>
namespace DB

View File

@ -7,6 +7,10 @@
#include <stdint.h>
#include <string.h>
#if defined(__x86_64__)
#include <smmintrin.h>
#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<std::uintptr_t>(ptr)) <= page_size - n;
}
#endif
};
/// Performs case-sensitive and case-insensitive search of UTF-8 strings
template <bool CaseSensitive, bool ASCII> class StringSearcher;
/// Case-insensitive UTF-8 searcher
template <> class StringSearcher<false, false>
template <>
class StringSearcher<false, false> : 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<false, false>
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<false, false>
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<std::uintptr_t>(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<const __m128i *>(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<const __m128i *>(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<false, true>
template <>
class StringSearcher<false, true> : 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<false, true>
/// 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<std::uintptr_t>(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<UInt8>(std::tolower(*needle));
u = static_cast<UInt8>(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<const __m128i *>(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<const __m128i *>(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 <bool ASCII> class StringSearcher<true, ASCII>
template <bool ASCII>
class StringSearcher<true, ASCII> : 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<std::uintptr_t>(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<const __m128i *>(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;

View File

@ -1,7 +1,12 @@
#pragma once
#include <DB/Core/Types.h>
#include <x86intrin.h>
#if defined(__x86_64__)
#include <x86intrin.h>
#else
#include <DB/Common/ARMHelpers.h>
#endif
namespace DB

View File

@ -4,7 +4,6 @@
#include <Poco/UTF8Encoding.h>
#include <Poco/Unicode.h>
#include <ext/range.hpp>
#include <x86intrin.h>
#include <stdint.h>
#include <string.h>

View File

@ -0,0 +1,80 @@
#pragma once
#include <DB/Dictionaries/Embedded/RegionsHierarchy.h>
#include <Poco/DirectoryIterator.h>
/** Содержит несколько иерархий регионов, загружаемых из нескольких разных файлов.
* Используется, чтобы поддержать несколько разных точек зрения о принадлежности регионов странам.
* В первую очередь, для Крыма (Российская и Украинская точки зрения).
*/
class RegionsHierarchies
{
private:
typedef std::unordered_map<std::string, RegionsHierarchy> 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;
}
};

View File

@ -0,0 +1,301 @@
#pragma once
#include <Poco/Util/Application.h>
#include <Poco/Exception.h>
#include <Poco/File.h>
#include <common/logger_useful.h>
#include <common/singleton.h>
#include <DB/IO/ReadBufferFromFile.h>
#include <DB/IO/ReadHelpers.h>
#include <boost/noncopyable.hpp>
#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<RegionID> RegionParents;
/// тип региона
typedef std::vector<RegionType> RegionTypes;
/// глубина в дереве, начиная от страны (страна: 1, корень: 0)
typedef std::vector<RegionDepth> RegionDepths;
/// население региона. Если больше 2^32 - 1, то приравнивается к этому максимуму.
typedef std::vector<RegionPopulation> 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<RegionPopulation>::max()
? std::numeric_limits<RegionPopulation>::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<int>(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<size_t>(lhs) >= parents.size())
return false;
while (lhs != 0 && lhs != rhs)
lhs = parents[lhs];
return lhs != 0;
}
RegionID toCity(RegionID region) const
{
if (static_cast<size_t>(region) >= city.size())
return 0;
return city[region];
}
RegionID toCountry(RegionID region) const
{
if (static_cast<size_t>(region) >= country.size())
return 0;
return country[region];
}
RegionID toArea(RegionID region) const
{
if (static_cast<size_t>(region) >= area.size())
return 0;
return area[region];
}
RegionID toDistrict(RegionID region) const
{
if (static_cast<size_t>(region) >= district.size())
return 0;
return district[region];
}
RegionID toContinent(RegionID region) const
{
if (static_cast<size_t>(region) >= continent.size())
return 0;
return continent[region];
}
RegionID toParent(RegionID region) const
{
if (static_cast<size_t>(region) >= parents.size())
return 0;
return parents[region];
}
RegionDepth getDepth(RegionID region) const
{
if (static_cast<size_t>(region) >= depths.size())
return 0;
return depths[region];
}
RegionPopulation getPopulation(RegionID region) const
{
if (static_cast<size_t>(region) >= populations.size())
return 0;
return populations[region];
}
};
class RegionsHierarchySingleton : public Singleton<RegionsHierarchySingleton>, public RegionsHierarchy
{
friend class Singleton<RegionsHierarchySingleton>;
protected:
RegionsHierarchySingleton()
{
}
};

View File

@ -0,0 +1,208 @@
#pragma once
#include <google/dense_hash_map>
#include <Poco/File.h>
#include <Poco/NumberParser.h>
#include <Poco/Util/Application.h>
#include <Poco/Exception.h>
#include <common/Common.h>
#include <common/logger_useful.h>
#include <DB/Core/StringRef.h>
#include <DB/IO/ReadHelpers.h>
#include <DB/IO/ReadBufferFromFile.h>
/** @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<char> Chars_t;
typedef std::vector<Chars_t> CharsForLanguageID_t;
typedef std::vector<time_t> ModificationTimes_t;
typedef std::vector<StringRef> StringRefs_t; /// Lookup table RegionID -> StringRef
typedef std::vector<StringRefs_t> 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<int>(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<size_t>(language);
if (static_cast<size_t>(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);
};

View File

@ -0,0 +1,117 @@
#pragma once
#include <string.h>
#include <common/logger_useful.h>
#include <common/singleton.h>
#include <mysqlxx/PoolWithFailover.h>
/** @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<TechDataHierarchySingleton>, public TechDataHierarchy {};

View File

@ -5,6 +5,10 @@
#include <type_traits>
#include <array>
#if defined(__x86_64__)
#include <smmintrin.h>
#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 по умолчанию).
@ -163,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<typename T, int rounding_mode, ScaleMode scale_mode, typename Enable = void>
@ -257,10 +268,11 @@ namespace DB
}
};
template<typename T>
#if defined(__x86_64__)
template <typename T>
class BaseFloatRoundingComputation;
template<>
template <>
class BaseFloatRoundingComputation<Float32>
{
public:
@ -298,7 +310,7 @@ namespace DB
}
};
template<>
template <>
class BaseFloatRoundingComputation<Float64>
{
public:
@ -522,6 +534,81 @@ namespace DB
_mm_storeu_pd(out, val);
}
};
#else
/// Реализация для ARM. Не векторизована. Не исправляет отрицательные нули.
template <int mode>
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 <int mode>
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 <typename T>
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<T>(scale);
}
};
template <typename T, int rounding_mode, ScaleMode scale_mode>
class FloatRoundingComputation;
template <typename T, int rounding_mode>
class FloatRoundingComputation<T, rounding_mode, PositiveScale>
: public BaseFloatRoundingComputation<T>
{
public:
static inline void compute(const T * __restrict in, const T & scale, T * __restrict out)
{
out[0] = roundWithMode<rounding_mode>(in[0] * scale) / scale;
}
};
template <typename T, int rounding_mode>
class FloatRoundingComputation<T, rounding_mode, NegativeScale>
: public BaseFloatRoundingComputation<T>
{
public:
static inline void compute(const T * __restrict in, const T & scale, T * __restrict out)
{
out[0] = roundWithMode<rounding_mode>(in[0] / scale) * scale;
}
};
template <typename T, int rounding_mode>
class FloatRoundingComputation<T, rounding_mode, ZeroScale>
: public BaseFloatRoundingComputation<T>
{
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<rounding_mode>(in[0]);
}
};
#endif
/** Реализация высокоуровневых функций округления.
*/
@ -906,7 +993,7 @@ namespace
/** Выбрать подходящий алгоритм обработки в зависимости от масштаба.
*/
template<typename T, template<typename> class U, int rounding_mode>
template<typename T, template <typename> class U, int rounding_mode>
struct Dispatcher
{
static inline void apply(Block & block, U<T> * col, const ColumnNumbers & arguments, size_t result)
@ -1053,9 +1140,10 @@ namespace
typedef FunctionUnaryArithmetic<RoundToExp2Impl, NameRoundToExp2> FunctionRoundToExp2;
typedef FunctionUnaryArithmetic<RoundDurationImpl, NameRoundDuration> FunctionRoundDuration;
typedef FunctionUnaryArithmetic<RoundAgeImpl, NameRoundAge> FunctionRoundAge;
typedef FunctionRounding<NameRound, _MM_FROUND_NINT> FunctionRound;
typedef FunctionRounding<NameCeil, _MM_FROUND_CEIL> FunctionCeil;
typedef FunctionRounding<NameFloor, _MM_FROUND_FLOOR> FunctionFloor;
typedef FunctionRounding<NameCeil, _MM_FROUND_CEIL> FunctionCeil;
struct PositiveMonotonicity

View File

@ -3,9 +3,9 @@
#include <thread>
#include <common/MultiVersion.h>
#include <common/logger_useful.h>
#include <statdaemons/RegionsHierarchies.h>
#include <statdaemons/TechDataHierarchy.h>
#include <statdaemons/RegionsNames.h>
#include <DB/Dictionaries/Embedded/RegionsHierarchies.h>
#include <DB/Dictionaries/Embedded/TechDataHierarchy.h>
#include <DB/Dictionaries/Embedded/RegionsNames.h>
#include <DB/Common/setThreadName.h>

View File

@ -1,269 +1,21 @@
#pragma once
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/Storages/MergeTree/AbandonableLockInZooKeeper.h>
#include <DB/Storages/MergeTree/ReplicatedMergeTreeQuorumEntry.h>
#include <DB/DataStreams/IBlockOutputStream.h>
#include <DB/IO/Operators.h>
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);
}
};
}

View File

@ -24,6 +24,19 @@ private:
using Queue = std::list<LogEntryPtr>;
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<LogEntryPtr, ByTime>;
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<ReplicatedMergeTreeLogEntryData>;
void getEntries(LogEntriesData & res);
/// Получить информацию о временах insert-ов.
void getInsertTimes(time_t & out_min_unprocessed_insert_time, time_t & out_max_processed_insert_time) const;
};

View File

@ -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<time_t> absolute_delay {};
std::atomic<time_t> relative_delay {};
void run();
/// Запустить или остановить фоновые потоки. Используется для частичной переинициализации при пересоздании сессии в ZooKeeper.
@ -85,9 +74,6 @@ private:
/// Запретить запись в таблицу и завершить все фоновые потоки.
void goReadOnlyPermanently();
/// Получить информацию об отставании реплик.
void checkReplicationDelays(time_t & out_absolute_delay, time_t & out_relative_delay);
};

View File

@ -172,7 +172,7 @@ public:
using LogEntriesData = std::vector<ReplicatedMergeTreeLogEntryData>;
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);
@ -193,17 +193,9 @@ private:
zkutil::ZooKeeperPtr current_zookeeper; /// Используйте только с помощью методов ниже.
std::mutex current_zookeeper_mutex; /// Для пересоздания сессии в фоновом потоке.
zkutil::ZooKeeperPtr getZooKeeper()
{
std::lock_guard<std::mutex> lock(current_zookeeper_mutex);
return current_zookeeper;
}
void setZooKeeper(zkutil::ZooKeeperPtr zookeeper)
{
std::lock_guard<std::mutex> 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;
};
}

View File

@ -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: $?";

View File

@ -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<Poco::Mutex> lock(shared->external_dictionaries_mutex);
std::lock_guard<std::mutex> 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<Poco::Mutex> lock(shared->mutex);
std::lock_guard<std::mutex> 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<Poco::Mutex> lock(shared->mutex);
std::lock_guard<std::mutex> lock(shared->zookeeper_mutex);
if (shared->zookeeper && shared->zookeeper->expired())
shared->zookeeper = shared->zookeeper->startNewSession();

View File

@ -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);
}

View File

@ -0,0 +1,70 @@
#include "MetricsTransmitter.h"
#include <daemon/Daemon.h>
#include <DB/Common/setThreadName.h>
namespace DB
{
MetricsTransmitter::~MetricsTransmitter()
{
try
{
{
std::lock_guard<std::mutex> 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::minutes, std::chrono::system_clock>(
std::chrono::system_clock::now() + std::chrono::minutes(1)
);
};
std::unique_lock<std::mutex> lock{mutex};
while (true)
{
if (cond.wait_until(lock, get_next_minute(), [this] { return quit; }))
break;
transmitCounters();
}
}
void MetricsTransmitter::transmitCounters()
{
GraphiteWriter::KeyValueVector<size_t> 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<ProfileEvents::Event>(i))};
key_vals.emplace_back(event_path_prefix + key, counter_increment);
}
Daemon::instance().writeToGraphite(key_vals);
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <thread>
#include <mutex>
#include <condition_variable>
#include <DB/Common/ProfileEvents.h>
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.";
};
}

View File

@ -12,9 +12,9 @@
#include <DB/IO/WriteHelpers.h>
#include <common/DateLUT.h>
#include <statdaemons/RegionsHierarchy.h>
#include <statdaemons/TechDataHierarchy.h>
#include <statdaemons/Interests.h>
#include <DB/Dictionaries/Embedded/RegionsHierarchy.h>
#include <DB/Dictionaries/Embedded/TechDataHierarchy.h>
/// Код в основном взят из из OLAP-server. Здесь нужен только для парсинга значений атрибутов.

View File

@ -0,0 +1,96 @@
#include "ReplicasStatusHandler.h"
#include <DB/Interpreters/Context.h>
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/Common/HTMLForm.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
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<Poco::Mutex> lock(context.getMutex());
for (const auto & db : context.getDatabases())
for (const auto & table : db.second)
if (typeid_cast<const StorageReplicatedMergeTree *>(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<StorageReplicatedMergeTree &>(*table.second).getReplicaDelays(absolute_delay, relative_delay);
if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast<time_t>(settings.min_absolute_delay_to_close))
|| (settings.min_relative_delay_to_close && relative_delay >= static_cast<time_t>(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");
}
}
}
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <Poco/Net/HTTPRequestHandler.h>
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);
};
}

View File

@ -1,27 +1,23 @@
#include <sys/resource.h>
#include <sys/file.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/DNS.h>
#include <Poco/Util/XMLConfiguration.h>
#include <Poco/DirectoryIterator.h>
#include <common/ApplicationServerExt.h>
#include <common/ErrorHandlers.h>
#include <common/Revision.h>
#include <DB/Common/ConfigProcessor.h>
#include <ext/scope_guard.hpp>
#include <memory>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <DB/Common/Macros.h>
#include <DB/Common/getFQDNOrHostName.h>
#include <DB/Common/setThreadName.h>
#include <DB/Interpreters/loadMetadata.h>
#include <DB/Interpreters/ProcessList.h>
#include <DB/Storages/System/StorageSystemNumbers.h>
#include <DB/Storages/System/StorageSystemTables.h>
#include <DB/Storages/System/StorageSystemParts.h>
@ -38,129 +34,23 @@
#include <DB/Storages/System/StorageSystemColumns.h>
#include <DB/Storages/System/StorageSystemFunctions.h>
#include <DB/Storages/System/StorageSystemClusters.h>
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/IO/copyData.h>
#include <DB/IO/LimitReadBuffer.h>
#include <DB/IO/WriteBufferFromFileDescriptor.h>
#include <DB/IO/Operators.h>
#include <zkutil/ZooKeeper.h>
#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<std::mutex> 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::minutes, std::chrono::system_clock>(
std::chrono::system_clock::now() + std::chrono::minutes(1)
);
};
std::unique_lock<std::mutex> lock{mutex};
while (true)
{
if (cond.wait_until(lock, get_next_minute(), [this] { return quit; }))
break;
transmitCounters();
}
}
void transmitCounters()
{
GraphiteWriter::KeyValueVector<size_t> 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<ProfileEvents::Event>(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<bool> quit;
std::thread thread;
Logger * log;
void reloadIfNewer(bool force);
void run();
};
/// Отвечает "Ok.\n". Используется для проверки живости.
class PingRequestHandler : public Poco::Net::HTTPRequestHandler
@ -181,75 +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
{
/// Собираем набор реплицируемых таблиц.
Databases replicated_tables;
{
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
for (const auto & db : context.getDatabases())
for (const auto & table : db.second)
if (typeid_cast<const StorageReplicatedMergeTree *>(table.second.get()))
replicated_tables[db.first][table.first] = table.second;
}
const MergeTreeSettings & settings = context.getMergeTreeSettings();
bool ok = /*true*/false;
std::stringstream message;
for (const auto & db : replicated_tables)
{
for (const auto & table : db.second)
{
time_t absolute_delay = 0;
time_t relative_delay = 0;
static_cast<const StorageReplicatedMergeTree &>(*table.second).getReplicaDelays(absolute_delay, relative_delay);
if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast<time_t>(settings.min_absolute_delay_to_close))
|| (settings.min_relative_delay_to_close && relative_delay >= static_cast<time_t>(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)
{
const char * data = "Ok.\n";
response.sendBuffer(data, strlen(data));
}
else
{
response.send() << message.rdbuf();
}
}
catch (...)
{
/// TODO Отправлять клиенту.
tryLogCurrentException("ReplicasStatusHandler");
}
}
};
/// Отвечает 404 с подробным объяснением.
class NotFoundHandler : public Poco::Net::HTTPRequestHandler
{
@ -299,23 +120,29 @@ public:
const auto & uri = request.getURI();
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);
}
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
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD)
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
if (uri == "/" || uri == "/ping")
return new PingRequestHandler;
else if (uri == "/replicas_status")
return new ReplicasStatusHandler(*server.global_context);
else
return new NotFoundHandler;
return new NotFoundHandler;
}
else
return nullptr;
return nullptr;
}
};
@ -338,201 +165,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 = 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<std::string> & args)
{
Logger * log = &logger();
@ -704,8 +336,8 @@ int Server::main(const std::vector<std::string> & args)
);
{
const auto profile_events_transmitter = config().getBool("use_graphite", true)
? std::make_unique<ProfileEventsTransmitter>()
const auto metrics_transmitter = config().getBool("use_graphite", true)
? std::make_unique<MetricsTransmitter>()
: nullptr;
const std::string listen_host = config().getString("listen_host", "::");

View File

@ -18,7 +18,7 @@
#include <Poco/Net/TCPServerConnection.h>
#include <common/logger_useful.h>
#include <statdaemons/daemon.h>
#include <daemon/Daemon.h>
#include <DB/Common/HTMLForm.h>
#include <DB/Interpreters/Context.h>

View File

@ -0,0 +1,97 @@
#include "StatusFile.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <Poco/File.h>
#include <common/logger_useful.h>
#include <common/Revision.h>
#include <mysqlxx/DateTime.h>
#include <DB/IO/copyData.h>
#include <DB/IO/ReadBufferFromFile.h>
#include <DB/IO/LimitReadBuffer.h>
#include <DB/IO/WriteBufferFromString.h>
#include <DB/IO/WriteBufferFromFileDescriptor.h>
#include <DB/IO/Operators.h>
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)));
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <boost/noncopyable.hpp>
namespace DB
{
/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера.
*/
class StatusFile : private boost::noncopyable
{
public:
StatusFile(const std::string & path_);
~StatusFile();
private:
const std::string path;
int fd = -1;
};
}

View File

@ -0,0 +1,135 @@
#include "UsersConfigReloader.h"
#include <Poco/Util/Application.h>
#include <Poco/File.h>
#include <common/logger_useful.h>
#include <DB/Interpreters/Context.h>
#include <DB/Common/setThreadName.h>
#include <DB/Common/ConfigProcessor.h>
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.");
}
}
}

View File

@ -0,0 +1,41 @@
#pragma once
#include <time.h>
#include <string>
#include <thread>
#include <atomic>
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<bool> quit;
std::thread thread;
Poco::Logger * log;
void reloadIfNewer(bool force);
void run();
};
}

View File

@ -0,0 +1,279 @@
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/Storages/MergeTree/AbandonableLockInZooKeeper.h>
#include <DB/Storages/MergeTree/ReplicatedMergeTreeQuorumEntry.h>
#include <DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
#include <DB/IO/Operators.h>
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)
throw Exception("No ZooKeeper session.", ErrorCodes::NO_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");
}
}
}
}

View File

@ -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<std::mutex> lock(mutex);
insertUnlocked(entry);
time_t prev_min_unprocessed_insert_time;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}
@ -156,67 +256,86 @@ 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<UInt64>(last_entry.substr(strlen("log-")));
LOG_DEBUG(log, "Pulling " << log_entries.size() << " entries to queue: " << log_entries.front() << " - " << log_entries.back());
std::vector<std::pair<String, zkutil::ZooKeeper::GetFuture>> 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<LogEntryPtr> 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<UInt64>(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<std::pair<String, zkutil::ZooKeeper::GetFuture>> futures;
futures.reserve(log_entries.size());
try
{
std::lock_guard<std::mutex> 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<LogEntryPtr> copied_entries;
copied_entries.reserve(log_entries.size());
bool min_unprocessed_insert_time_changed = false;
for (auto & future : futures)
{
String path_created = dynamic_cast<zkutil::Op::Create &>(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));
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;
}
}
}
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));
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. Обновим её в оперативке.
try
{
std::lock_guard<std::mutex> lock(mutex);
for (size_t i = 0, size = copied_entries.size(); i < size; ++i)
{
String path_created = dynamic_cast<zkutil::Op::Create &>(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 +344,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();
}
@ -279,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<std::mutex> lock(mutex);
@ -293,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;
}
@ -300,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.");
@ -573,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);

View File

@ -107,22 +107,32 @@ void ReplicatedMergeTreeRestartingThread::run()
if (current_time >= prev_time_of_check_delay + static_cast<time_t>(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<time_t>(storage.data.settings.min_relative_delay_to_yield_leadership))
if (storage.is_leader_node
&& (error || relative_delay > static_cast<time_t>(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 << ".");
}
}

View File

@ -1,5 +1,11 @@
#include <time.h>
#include <ext/range.hpp>
#include <zkutil/Types.h>
#include <zkutil/KeeperException.h>
#include <DB/Core/FieldVisitors.h>
#include <DB/Storages/ColumnsDescription.h>
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h>
@ -9,22 +15,27 @@
#include <DB/Storages/MergeTree/MergeList.h>
#include <DB/Storages/MergeTree/MergeTreeWhereOptimizer.h>
#include <DB/Storages/MergeTree/ReplicatedMergeTreeAddress.h>
#include <DB/Parsers/formatAST.h>
#include <DB/Parsers/ASTInsertQuery.h>
#include <DB/IO/WriteBufferFromOStream.h>
#include <DB/IO/ReadBufferFromString.h>
#include <DB/IO/Operators.h>
#include <DB/Interpreters/InterpreterAlterQuery.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/Parsers/ASTInsertQuery.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/DataStreams/RemoteBlockInputStream.h>
#include <DB/DataStreams/NullBlockOutputStream.h>
#include <DB/DataStreams/copyData.h>
#include <DB/Common/Macros.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/Common/formatReadable.h>
#include <DB/Common/setThreadName.h>
#include <Poco/DirectoryIterator.h>
#include <time.h>
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;
@ -98,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<std::mutex> lock(current_zookeeper_mutex);
current_zookeeper = zookeeper;
}
zkutil::ZooKeeperPtr StorageReplicatedMergeTree::tryGetZooKeeper()
{
std::lock_guard<std::mutex> 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_,
@ -167,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;
}
@ -232,6 +272,7 @@ void StorageReplicatedMergeTree::createNewZooKeeperNodes()
/// Отслеживание отставания реплик.
zookeeper->createIfNotExists(replica_path + "/min_unprocessed_insert_time", "");
zookeeper->createIfNotExists(replica_path + "/max_processed_insert_time", "");
}
@ -264,7 +305,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);
@ -552,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");
}
@ -691,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(
@ -1206,7 +1247,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 +1255,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);
}
}
@ -1353,8 +1392,6 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
try
{
std::lock_guard<std::mutex> merge_selecting_lock(merge_selecting_mutex);
if (need_pull)
{
/// Нужно загрузить новые записи в очередь перед тем, как выбирать куски для слияния.
@ -1363,6 +1400,8 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
need_pull = false;
}
std::lock_guard<std::mutex> merge_selecting_lock(merge_selecting_mutex);
/** Сколько в очереди или в фоновом потоке мерджей крупных кусков.
* Если их больше половины от размера пула потоков для мерджа, то можно мерджить только мелкие куски.
*/
@ -1677,7 +1716,7 @@ void StorageReplicatedMergeTree::alterThread()
/// Важно, что уничтожается parts и merge_blocker перед wait-ом.
}
alter_thread_event->wait();
alter_thread_event->tryWait(WAIT_FOR_ALTER_SLEEP_MS);
}
catch (...)
{
@ -1726,7 +1765,7 @@ void StorageReplicatedMergeTree::removePartAndEnqueueFetch(const String & part_n
String path_created = dynamic_cast<zkutil::Op::Create &>(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);
}
@ -2289,6 +2328,7 @@ BlockInputStreams StorageReplicatedMergeTree::read(
if (settings.select_sequential_consistency)
{
auto zookeeper = getZooKeeper();
String last_part;
zookeeper->tryGet(zookeeper_path + "/quorum/last_part", last_part);
@ -2335,10 +2375,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)
@ -2377,6 +2423,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 ?
@ -2472,7 +2520,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)
@ -2538,6 +2586,8 @@ void StorageReplicatedMergeTree::dropPartition(ASTPtr query, const Field & field
return;
}
assertNotReadonly();
auto zookeeper = getZooKeeper();
String month_name = MergeTreeData::getMonthName(field);
@ -2631,6 +2681,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;
@ -2736,13 +2788,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);
@ -2860,7 +2912,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String &
if (!log_pointer.empty() && parse<UInt64>(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 +2957,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String &
if (!log_pointer.empty() && parse<UInt64>(log_pointer) > log_index)
break;
event->wait();
event->tryWait(WAIT_FOR_REPLICA_QUEUE_MS);
}
}
}
@ -2952,7 +3004,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;
@ -3012,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<time_t>(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<time_t>(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;
}

View File

@ -0,0 +1,174 @@
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <Poco/Process.h>
#include <Poco/ThreadPool.h>
#include <Poco/TaskNotification.h>
#include <Poco/NumberFormatter.h>
#include <Poco/Util/Application.h>
#include <Poco/Util/ServerApplication.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/FileChannel.h>
#include <Poco/SyslogChannel.h>
#include <common/Common.h>
#include <common/logger_useful.h>
#include <daemon/GraphiteWriter.h>
#include <boost/optional.hpp>
#include <zkutil/ZooKeeperHolder.h>
namespace Poco { class TaskManager; }
/// \brief Базовый класс для демонов
///
/// \code
/// # Список возможных опций командной строки обрабатываемых демоном:
/// # --config-file или --config - имя файла конфигурации. По умолчанию - config.xml
/// # --pid-file - имя PID файла. По умолчанию - pid
/// # --log-file - имя лог файла
/// # --error-file - имя лог файла, в который будут помещаться только ошибки
/// # --daemon - запустить в режиме демона; если не указан - логгирование будет вестись на консоль
/// <daemon_name> --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<Daemon &>(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 <class T>
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 <class T>
void writeToGraphite(const GraphiteWriter::KeyValueVector<T> & key_vals, time_t timestamp = 0, const std::string & custom_root_path = "")
{
graphite_writer->write(key_vals, timestamp, custom_root_path);
}
boost::optional<size_t> getLayer() const
{
return layer; /// layer выставляется в классе-наследнике BaseDaemonApplication.
}
protected:
/// Используется при exitOnTaskError()
void handleNotification(Poco::TaskFailedNotification *);
std::unique_ptr<Poco::TaskManager> 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<Poco::Runnable> close_logs_listener;
/// Файлы с логами.
Poco::AutoPtr<Poco::FileChannel> log_file;
Poco::AutoPtr<Poco::FileChannel> error_log_file;
Poco::AutoPtr<Poco::SyslogChannel> syslog_channel;
std::unique_ptr<GraphiteWriter> graphite_writer;
boost::optional<size_t> layer;
};

View File

@ -0,0 +1,82 @@
#pragma once
#include <string>
#include <time.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketStream.h>
#include <Poco/Util/Application.h>
#include <common/logger_useful.h>
/// пишет в 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 <typename T> using KeyValuePair = std::pair<std::string, T>;
template <typename T> using KeyValueVector = std::vector<KeyValuePair<T>>;
template <typename T> void write(const std::string & key, const T & value,
time_t timestamp = 0, const std::string & custom_root_path = "")
{
writeImpl(KeyValuePair<T>{ key, value }, timestamp, custom_root_path);
}
template <typename T> void write(const KeyValueVector<T> & 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 <typename T>
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 <typename T>
void out(std::ostream & os, const KeyValuePair<T> & 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 <typename T>
void out(std::ostream & os, const KeyValueVector<T> & 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;
};

View File

@ -0,0 +1,862 @@
#include <daemon/Daemon.h>
#include <DB/Common/ConfigProcessor.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <ucontext.h>
#include <typeinfo>
#include <common/logger_useful.h>
#include <common/ErrorHandlers.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <iostream>
#include <Poco/Observer.h>
#include <Poco/Logger.h>
#include <Poco/AutoPtr.h>
#include <Poco/SplitterChannel.h>
#include <Poco/Ext/LevelFilterChannel.h>
#include <Poco/Ext/ThreadNumber.h>
#include <Poco/FormattingChannel.h>
#include <Poco/PatternFormatter.h>
#include <Poco/ConsoleChannel.h>
#include <Poco/TaskManager.h>
#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/Message.h>
#include <Poco/Util/AbstractConfiguration.h>
#include <Poco/Util/XMLConfiguration.h>
#include <Poco/ScopedLock.h>
#include <Poco/Exception.h>
#include <Poco/ErrorHandler.h>
#include <Poco/NumberFormatter.h>
#include <Poco/Condition.h>
#include <Poco/SyslogChannel.h>
#include <DB/Common/Exception.h>
#include <DB/IO/WriteBufferFromFileDescriptor.h>
#include <DB/IO/ReadBufferFromFileDescriptor.h>
#include <DB/IO/ReadHelpers.h>
#include <DB/IO/WriteHelpers.h>
#include <common/Revision.h>
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<const ucontext_t *>(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;
#if defined(__x86_64__)
/// Get the address at the time the signal was raised from the RIP (x86-64)
caller_address = reinterpret_cast<void *>(context.uc_mcontext.gregs[REG_RIP]);
#elif defined(__aarch64__)
caller_address = reinterpret_cast<void *>(context.uc_mcontext.pc);
#endif
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<path.depth();++j)
{
str += "/";
str += path[j];
int res = ::mkdir(str.c_str(), 0700);
if( res && (errno!=EEXIST) )
{
throw std::runtime_error(std::string("Can't create dir - ") + str + " - " + strerror(errno));
}
}
return str;
};
void Daemon::reloadConfiguration()
{
/** Если программа запущена не в режиме демона, и не указан параметр config-file,
* то будем использовать параметры из файла config.xml в текущей директории,
* но игнорировать заданные в нём параметры логгирования.
* (Чтобы при запуске с минимумом параметров, лог выводился в консоль.)
* При этом, параметры логгирования, заданные в командной строке, не игнорируются.
*/
std::string log_command_line_option = config().getString("logger.log", "");
ConfigurationPtr processed_config = ConfigProcessor(false, true).loadConfig(config().getString("config-file", "config.xml"));
config().add(processed_config.duplicate(), PRIO_DEFAULT, false);
log_to_console = !config().getBool("application.runAsDaemon", false) && log_command_line_option.empty();
}
/** Форматирует по своему.
* Некоторые детали невозможно получить, используя только Poco::PatternFormatter.
*
* Во-первых, используется номер потока не среди потоков Poco::Thread,
* а среди всех потоков, для которых был получен номер (см. ThreadNumber.h)
*
* Во-вторых, корректно выводится локальная дата и время.
* Poco::PatternFormatter плохо работает с локальным временем,
* в ситуациях, когда в ближайшем будущем намечается отмена или введение daylight saving time.
* - см. исходники Poco и http://thread.gmane.org/gmane.comp.time.tz/8883
*
* Также сделан чуть более эффективным (что имеет мало значения).
*/
class OwnPatternFormatter : public Poco::PatternFormatter
{
public:
enum Options
{
ADD_NOTHING = 0,
ADD_LAYER_TAG = 1 << 0
};
OwnPatternFormatter(const Daemon & daemon_, Options options_ = ADD_NOTHING) : Poco::PatternFormatter(""), daemon(daemon_), options(options_) {}
void format(const Message & msg, std::string & text) override
{
DB::WriteBufferFromString wb(text);
/// в syslog тэг идет перед сообщением и до первого пробела.
if (options & ADD_LAYER_TAG)
{
boost::optional<size_t> 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<int>(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<SplitterChannel> split = new SplitterChannel;
// set up two channel chains
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
pf->setProperty("times", "local");
Poco::AutoPtr<FormattingChannel> 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<Poco::LevelFilterChannel> level = new Poco::LevelFilterChannel;
level->setLevel(Message::PRIO_NOTICE);
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
pf->setProperty("times", "local");
Poco::AutoPtr<FormattingChannel> 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<OwnPatternFormatter> pf = new OwnPatternFormatter(*this, OwnPatternFormatter::ADD_LAYER_TAG);
pf->setProperty("times", "local");
Poco::AutoPtr<FormattingChannel> 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<ConsoleChannel> file = new ConsoleChannel;
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
pf->setProperty("times", "local");
Poco::AutoPtr<FormattingChannel> 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 = 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"));
}
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);
/// Ставим обработчики сигналов
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);
graphite_writer.reset(new GraphiteWriter("graphite"));
}
/// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно
void Daemon::exitOnTaskError()
{
Observer<Daemon, Poco::TaskFailedNotification> obs(*this, &Daemon::handleNotification);
getTaskManager().addObserver(obs);
}
/// Используется при exitOnTaskError()
void Daemon::handleNotification(Poco::TaskFailedNotification *_tfn)
{
AutoPtr<Poco::TaskFailedNotification> 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 ("<file>")
.binding("config-file")
);
_options.addOption(
Poco::Util::Option ("log-file", "L", "use given log file")
.required (false)
.repeatable (false)
.argument ("<file>")
.binding("logger.log")
);
_options.addOption(
Poco::Util::Option ("errorlog-file", "E", "use given log file for errors only")
.required (false)
.repeatable (false)
.argument ("<file>")
.binding("logger.errorlog")
);
_options.addOption(
Poco::Util::Option ("pid-file", "P", "use given pidfile")
.required (false)
.repeatable (false)
.argument ("<file>")
.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<ssize_t>(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();
}
}

View File

@ -0,0 +1,86 @@
#include <daemon/GraphiteWriter.h>
#include <daemon/Daemon.h>
#include <Poco/Util/LayeredConfiguration.h>
#include <Poco/Util/Application.h>
#include <Poco/Net/DNS.h>
#include <mutex>
#include <iomanip>
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;
}

View File

@ -1,6 +1,6 @@
#pragma once
#include <zkutil/ZooKeeper.h>
#include <zkutil/ZooKeeperHolder.h>
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");
};

View File

@ -43,6 +43,13 @@ public:
return code == ZINVALIDSTATE || code == ZSESSIONEXPIRED || code == ZSESSIONMOVED;
}
/// любая ошибка связанная с работой сети, перевыбором мастера
/// при этих ошибках надо либо повторить запрос повторно, либо переинициализировать сессию (см. isUnrecoverable())
bool isHardwareError() const
{
return isUnrecoverable() || code == ZCONNECTIONLOSS || code == ZOPERATIONTIMEOUT;
}
const int32_t code;
private:

View File

@ -1,6 +1,6 @@
#pragma once
#include <zkutil/ZooKeeper.h>
#include <zkutil/ZooKeeperHolder.h>
#include <common/logger_useful.h>
#include <DB/Common/Exception.h>
@ -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_, "");
}
@ -46,9 +55,7 @@ namespace zkutil
std::string status2String(Status status);
/// проверяет создана ли эфемерная нода и кто ее владелец.
/// если мы сами создавали эфемерную ноду, то надо вызывать этот метод, чтобы убедится,
/// что сессия с зукипером не порвалось
Status check();
Status tryCheck() const;
void unlock();
@ -58,11 +65,14 @@ namespace zkutil
const std::string & getPath() { return lock_path; }
private:
Status checkImpl();
zkutil::ZooKeeperPtr zookeeper;
zkutil::ZooKeeperHolderPtr zookeeper_holder;
/// пока храним указатель на хендлер, никто не может переиницализировать сессию с zookeeper
using ZooKeeperHandler = zkutil::ZooKeeperHolder::UnstorableZookeeperHandler;
std::unique_ptr<ZooKeeperHandler> locked;
std::string lock_path;
std::string lock_message;
Logger * log;
bool locked = false;
};
}

View File

@ -0,0 +1,83 @@
#pragma once
#include <zkutil/ZooKeeper.h>
#include <mutex>
#include <boost/noncopyable.hpp>
namespace zkutil
{
class Lock;
class ZooKeeperHolder : public boost::noncopyable
{
friend class zkutil::Lock;
protected:
class UnstorableZookeeperHandler;
public:
ZooKeeperHolder() = default;
/// вызывать из одного потока - не thread safe
template <class... Args>
void init(Args&&... args);
/// был ли класс инициализирован
bool isInitialized() const { return ptr != nullptr; }
UnstorableZookeeperHandler getZooKeeper();
bool replaceZooKeeperSessionToNewOne();
bool isSessionExpired() const;
protected:
/** Хендлер для подсчета количества используемых ссылок на ZooKeeper
*
* Запрещается переинициализировать ZooKeeper пока, хранится хотя бы один хендлер на него.
* Большинство классов должны хранить хендлер на стеке и не хранить как член класса.
* Т.е. хранить holder и запрашивать хендлер перед каждым использованием.
* Поэтому класс специально объявлен, как protected.
*
* Исключение - классы, работающие с эфимерными нодами. Пример: zkutil::Lock
*
* Как использовать:
* auto zookeeper = zookeeper_holder->getZooKeeper();
* zookeeper->get(path);
*/
class UnstorableZookeeperHandler
{
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); }
/// в случае nullptr методы разыменования кидают исключение,
/// с более подробным текстом, чем просто nullptr
ZooKeeper * operator->();
const ZooKeeper * operator->() const;
ZooKeeper & operator*();
const ZooKeeper & operator*() const;
private:
ZooKeeper::Ptr zk_ptr;
};
private:
mutable std::mutex mutex;
ZooKeeper::Ptr ptr;
Logger * log = &Logger::get("ZooKeeperHolder");
static std::string nullptr_exception_message;
};
template <class... Args>
void ZooKeeperHolder::init(Args&&... args)
{
ptr = std::make_shared<ZooKeeper>(std::forward<Args>(args)...);
}
using ZooKeeperHolderPtr = std::shared_ptr<ZooKeeperHolder>;
}; /*namespace zkutil*/

View File

@ -4,12 +4,15 @@ using namespace zkutil;
bool Lock::tryLock()
{
auto zookeeper = zookeeper_holder->getZooKeeper();
if (locked)
{
/// проверим, что нода создана и я ее владелец
check();
if (tryCheck() != Status::LOCKED_BY_ME)
locked.reset(nullptr);
}
else
if (!locked)
{
size_t attempt;
std::string dummy;
@ -18,80 +21,92 @@ 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
{
throw zkutil::KeeperException(code);
}
}
return locked;
return bool(locked);
}
void Lock::unlock()
{
auto zookeeper = zookeeper_holder->getZooKeeper();
if (locked)
{
/// проверим, что до сих пор мы владельцы ноды
check();
size_t attempt;
int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt);
if (attempt)
try
{
if (code != ZOK)
throw zkutil::KeeperException(code);
if (tryCheck() == Status::LOCKED_BY_ME)
{
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);
}
}
}
else
catch (const zkutil::KeeperException & e)
{
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);
/// если сессия находится в невостанавливаемом состоянии, то эфимерные ноды нам больше не принадлежат
/// и лок через таймаут будет отпущен
if (!e.isUnrecoverable())
throw;
}
locked = false;
locked.reset(nullptr);
}
}
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)
@ -101,3 +116,4 @@ std::string Lock::status2String(Status status)
static const char * names[] = {"Unlocked", "Locked by me", "Locked by other"};
return names[status];
}

View File

@ -0,0 +1,69 @@
#include <zkutil/ZooKeeperHolder.h>
using namespace zkutil;
ZooKeeperHolder::UnstorableZookeeperHandler ZooKeeperHolder::getZooKeeper()
{
std::unique_lock<std::mutex> lock(mutex);
return UnstorableZookeeperHandler(ptr);
}
bool ZooKeeperHolder::replaceZooKeeperSessionToNewOne()
{
std::unique_lock<std::mutex> lock(mutex);
if (ptr.unique())
{
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
{
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;
}

View File

@ -6,10 +6,13 @@ int main()
try
{
zkutil::Lock l(std::make_shared<zkutil::ZooKeeper>("localhost:2181"), "/test", "test_lock");
std::cout << "check " << l.check() << std::endl;
auto zookeeper_holder = std::make_shared<zkutil::ZooKeeperHolder>();
zookeeper_holder->init("localhost:2181");
zkutil::Lock l(zookeeper_holder, "/test", "test_lock");
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)
{

View File

@ -0,0 +1,33 @@
#include <zkutil/ZooKeeperHolder.h>
#include <iostream>
#include <statdaemons/Test.h>
#include <Poco/Util/Application.h>
int main()
{
Test::initLogger();
zkutil::ZooKeeperHolder zk_holder;
zk_holder.init("localhost:2181");
{
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 = zk_holder.replaceZooKeeperSessionToNewOne();
std::cerr << "Started new session: " << started_new_session << "\n";
auto zk_handler = zk_holder.getZooKeeper();
if (zk_handler != nullptr)
std::cerr << "get / " << zk_handler->get("/") << "\n";
}
return 0;
}