Сервер Zabbix при запуске начинает оценивать истинность выражений триггеров, для чего ему нужны значения элементов данных, фигурирующих в выражении. В выражениях триггеров могут фигурировать функции, обращающиеся не только к последним значениям, но и к ряду значений за определённый период времени. Так как при запуске сервера Zabbix его кэш значений ещё пуст, Zabbix начинает выполнять огромное количество мелких запросов к таблицам истории. Классические транзакционные СУБД справляются с подобной нагрузкой относительно легко, хотя и для них она может оказаться тяжёлой при использовании медленной дисковой подсистемы и малом количестве оперативной памяти.
При хранении исторических данных в таблицах со сжатием, или в таблицах с индексами, оптимизированных для выполнения запросов, извлекающих данные длинными последовательностями, запуск сервера Zabbix становится более тяжёлым. Например, при хранении исторических данных в таблицах на движке TokuDB сервера MySQL со сжатием, запуск сервера Zabbix закономерно порождает высокую нагрузку от СУБД на процессор.
В случае с ClickHouse ситуация ещё тяжелее: для поиска единичного результата ClickHouse фактически читает с диска, расжимает и просматривает данные колонок всех секций, попадающих под условия запроса. Большой запас свободной оперативной памяти может снизить нагрузку на диски, т.к. горячие данные с файловой системы могут уместиться в буферный кэш ядра операционной системы. В ClickHouse имеется возможность использовать кэш разжатых данных, но по моим наблюдениям в этот кэш сохраняются только те данные, которые были выданы клиенту в качестве результатов запросов. Т.к. у сервера Zabbix есть собственный кэш значений, в который он сохраняет результаты прошлых запросов, то кэш разжатых данных ClickHouse оказывается бесполезен. Поэтому нагрузка на процессор, возникающая из-за необходимости многократно разжимать одни и те же данные, никуда не денется.
В качестве паллиатива в Glaber были реализованы два подхода к решению проблемы:
Оба решения я назвал паллиативными неспроста. У них имеется ряд недостатков, не позволяющих считать их идеальными:
Идею реализации предзагрузки кэша значений из ClickHouse можно понять вот из этой правки в репозитории Glaber. Правка не полная, некоторые используемые в ней функции попали в другие правки.
Проблема, во-первых в том, что у меня для хранения исторических данных используются отдельные таблицы и эта правка мне не годится.
Во-вторых, мне не нравится в этой правке то, что библиотеки zbxdbcache и zbxhistory становятся зависимыми друг от друга. Несмотря на то, что библиотека zbxdbcache должна пользоваться библиотекой zbxhistory, в этой правке добавляетя зависимость библиотеки zbxhistory от zbxdbcache. Чтобы не возникло циклической зависимости импортируемых заголовочных файлов, в библиотеке zbxhistory просто сделано объявление, что необходимая функция находится где-то за пределами файла. Связывание библиотек друг с другом при этом выполняется компоновщиком.
Я пошёл более сложным путём и реализовал в библиотеке zbxhistory отдельную функцию zbx_history_preload_values, подобную zbx_history_get_values, с той лишь разницей, что она загружает исторические данные не для одного элемента данных, а для списка элементов данных.
Функция zbx_history_preload_values затем используется в библиотеке zbxdbcache, в функции zbx_vc_preload, которая, в свою очередь, вызывается уже сервером Zabbix.
Готовую заплатку с реализацией предзагрузки данных в кэш значений из ClickHouse при запуске сервера Zabbix можно найти по ссылке zabbix3_4_12_valuecache_preloading.patch. Заплатка с реализацией временного запрета на чтение из истории после запуска сервера Zabbix будет описана в отдельной статье. Ниже я подробно опишу первую заплатку.
Для управления предзагрузкой кэша значений введём в файл конфигурации две новые опции: ValueCachePreloadAge и ValueCachePreloadCount. Опция ValueCachePreloadAge будет указывать период времени, значения за который необходимо загрузить в кэш. Опция ValueCachePreloadCount ограничивает количество загружаемых значений для каждого из элементов данных. В качестве декларации наших намерений доработаем пример файла конфигурации conf/zabbix_server.conf:
Index: zabbix-3.4.12-1+buster/conf/zabbix_server.conf =================================================================== --- zabbix-3.4.12-1+buster.orig/conf/zabbix_server.conf +++ zabbix-3.4.12-1+buster/conf/zabbix_server.conf @@ -450,6 +450,26 @@ DBUser=zabbix # Default: # ValueCacheSize=8M +### Option: ValueCachePreloadAge +# Maximum age of values to prefill value cache on start server. +# Will be loaded history values for all items with triggers. +# Setting to 0 ValueCachePreloadAge and ValueCachePreloadCount disables preloading value cache. +# +# Mandatory: no +# Range: 0-2592000 +# Default: +# ValueCachePreloadAge=0 + +### Option: ValueCachePreloadCount +# Maximum number of values for every item to prefill value cache on start server. +# Will be loaded history values for all items with triggers. +# Setting to 0 ValueCachePreloadAge and ValueCachePreloadCount disables preloading value cache. +# +# Mandatory: no +# Range: 0-86400 +# Default: +# ValueCachePreloadCount=0 + ### Option: Timeout # Specifies how long we wait for agent, SNMP device or external check (in seconds). #
Теперь добавим в сервер Zabbix поддержку чтения этих опций конфигурации в соответствующие переменные:
Index: zabbix-3.4.12-1+buster/src/zabbix_server/server.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/zabbix_server/server.c +++ zabbix-3.4.12-1+buster/src/zabbix_server/server.c @@ -191,6 +191,9 @@ zbx_uint64_t CONFIG_TRENDS_CACHE_SIZE = zbx_uint64_t CONFIG_VALUE_CACHE_SIZE = 8 * ZBX_MEBIBYTE; zbx_uint64_t CONFIG_VMWARE_CACHE_SIZE = 8 * ZBX_MEBIBYTE; +int CONFIG_VALUE_CACHE_PRELOAD_AGE = 0; +int CONFIG_VALUE_CACHE_PRELOAD_COUNT = 0; + int CONFIG_UNREACHABLE_PERIOD = 45; int CONFIG_UNREACHABLE_DELAY = 15; int CONFIG_UNAVAILABLE_DELAY = 60; @@ -591,6 +594,10 @@ static void zbx_load_config(ZBX_TASK_EX PARM_OPT, 128 * ZBX_KIBIBYTE, __UINT64_C(2) * ZBX_GIBIBYTE}, {"ValueCacheSize", &CONFIG_VALUE_CACHE_SIZE, TYPE_UINT64, PARM_OPT, 0, __UINT64_C(64) * ZBX_GIBIBYTE}, + {"ValueCachePreloadAge", &CONFIG_VALUE_CACHE_PRELOAD_AGE, TYPE_INT, + PARM_OPT, 0, SEC_PER_MONTH}, + {"ValueCachePreloadCount", &CONFIG_VALUE_CACHE_PRELOAD_COUNT, TYPE_INT, + PARM_OPT, 0, 86400}, {"CacheUpdateFrequency", &CONFIG_CONFSYNCER_FREQUENCY, TYPE_INT, PARM_OPT, 1, SEC_PER_HOUR}, {"HousekeepingFrequency", &CONFIG_HOUSEKEEPING_FREQUENCY, TYPE_INT,
Аналогичные фиктивные изменения внесём в Zabbix-прокси:
Index: zabbix-3.4.12-1+buster/src/zabbix_proxy/proxy.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/zabbix_proxy/proxy.c +++ zabbix-3.4.12-1+buster/src/zabbix_proxy/proxy.c @@ -187,6 +187,9 @@ zbx_uint64_t CONFIG_TRENDS_CACHE_SIZE = zbx_uint64_t CONFIG_VALUE_CACHE_SIZE = 0; zbx_uint64_t CONFIG_VMWARE_CACHE_SIZE = 8 * ZBX_MEBIBYTE; +int CONFIG_VALUE_CACHE_PRELOAD_AGE = 0; +int CONFIG_VALUE_CACHE_PRELOAD_COUNT = 0; + int CONFIG_UNREACHABLE_PERIOD = 45; int CONFIG_UNREACHABLE_DELAY = 15; int CONFIG_UNAVAILABLE_DELAY = 60;
Для временного хранения значений, загруженных из таблиц истории, но ещё не помещённых в кэш значений, нам понадобится новая структура данных - массив из элементов, содержащих идентификатор элемента данных, тип его значения, отметку времени и само значение. Структура данных, которая будет содержать все необходимые поля одного элемента, будет называться zbx_valuecache_record_t и будет сделана по аналогии со структурой zbx_history_record_t, объявленной в файле include/zbxhistory.h.
Для работы с массивом, состоящим из таких структур, нужно будет реализовать соответствующие операции. К счастью, в Zabbix уже есть макроопределение ZBX_VECTOR_DECL(<название>, <тип_элемента>), позволяющее создавать функции zbx_vector_<название>_<операция> для работы с массивами необходимых структур данных. Правда, эти функции не умеют очищать и освобождать память, динамически распределённую внутри самих элементов данных, поэтому нужно определить дополнительные функции, которые будут это делать.
Итак, внесём необходимые правки в файлы src/libs/zbxhistory/history.h и src/libs/zbxhistory/history.c:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.h =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history.h +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.h @@ -29,6 +29,8 @@ typedef void (*zbx_history_destroy_func_ typedef int (*zbx_history_add_values_func_t)(struct zbx_history_iface *hist, const zbx_vector_ptr_t *history); typedef int (*zbx_history_get_values_func_t)(struct zbx_history_iface *hist, zbx_uint64_t itemid, int start, int count, int end, zbx_vector_history_record_t *values); +typedef int (*zbx_history_preload_values_func_t)(struct zbx_history_iface *hist, const zbx_vector_uint64_t *itemids, + int age, int count, zbx_vector_valuecache_record_t *values); typedef void (*zbx_history_flush_func_t)(struct zbx_history_iface *hist); struct zbx_history_iface @@ -40,6 +42,7 @@ struct zbx_history_iface zbx_history_destroy_func_t destroy; zbx_history_add_values_func_t add_values; zbx_history_get_values_func_t get_values; + zbx_history_preload_values_func_t preload_values; zbx_history_flush_func_t flush; }; Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history.c +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.c @@ -26,6 +26,7 @@ #include "../zbxalgo/vectorimpl.h" ZBX_VECTOR_IMPL(history_record, zbx_history_record_t); +ZBX_VECTOR_IMPL(valuecache_record, zbx_valuecache_record_t); extern char *CONFIG_HISTORY_STORAGE; extern char *CONFIG_HISTORY_UINT_STORAGE; @@ -248,6 +313,27 @@ void zbx_history_record_vector_destroy(z /****************************************************************************** * * + * Function: zbx_valuecache_record_vector_destroy * + * * + * Purpose: destroys value vector and frees resources allocated for it * + * * + * Parameters: vector - [IN] the value vector * + * * + * Comments: Use this function to destroy value vectors created by * + * zbx_vc_get_values_by_* functions. * + * * + ******************************************************************************/ +void zbx_valuecache_record_vector_destroy(zbx_vector_valuecache_record_t *vector) +{ + if (NULL != vector->values) + { + zbx_valuecache_record_vector_clean(vector); + zbx_vector_valuecache_record_destroy(vector); + } +} + +/****************************************************************************** + * * * Function: zbx_history_record_clear * * * * Purpose: frees resources allocated by a cached value * @@ -271,6 +357,28 @@ void zbx_history_record_clear(zbx_histor /****************************************************************************** * * + * Function: zbx_valuecache_record_clear * + * * + * Purpose: frees resources allocated by a cached value * + * * + * Parameters: value - [IN] the cached value to clear * + * * + ******************************************************************************/ +void zbx_valuecache_record_clear(zbx_valuecache_record_t *value) +{ + switch (value->value_type) + { + case ITEM_VALUE_TYPE_STR: + case ITEM_VALUE_TYPE_TEXT: + zbx_free(value->value.str); + break; + case ITEM_VALUE_TYPE_LOG: + history_logfree(value->value.log); + } +} + +/****************************************************************************** + * * * Function: zbx_history_value2str * * * * Purpose: converts history value to string format * @@ -329,3 +437,30 @@ void zbx_history_record_vector_clean(zbx zbx_vector_history_record_clear(vector); } + +/****************************************************************************** + * * + * Function: zbx_valuecache_record_vector_clean * + * * + * Purpose: releases resources allocated to store history records * + * * + * Parameters: vector - [IN] the history record vector * + * * + ******************************************************************************/ +void zbx_valuecache_record_vector_clean(zbx_vector_valuecache_record_t *vector) +{ + int i; + + for (i = 0; i < vector->values_num; i++) + switch (vector->values[i].value_type) + { + case ITEM_VALUE_TYPE_STR: + case ITEM_VALUE_TYPE_TEXT: + zbx_free(vector->values[i].value.str); + break; + case ITEM_VALUE_TYPE_LOG: + history_logfree(vector->values[i].value.log); + } + + zbx_vector_valuecache_record_clear(vector); +}
Теперь, когда выполнена необходимая подготовка, можно приступить к реализации функции zbx_history_preload_values, которая будет обращаться к реализациям различных типов хранилищ и просить их загрузить значения в указанный массив.
Добавим функцию zbx_history_preload_values в файл src/libs/zbxhistory/history.c:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history.c +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.c @@ -189,6 +190,70 @@ int zbx_history_get_values(zbx_uint64_t /************************************************************************************ * * + * Function: zbx_history_preload_values * + * * + * Purpose: gets item values from history storage * + * * + * Parameters: itemids - [IN] the required item identifiers * + * value_type - [IN] the item value type * + * age - [IN] the maximum age of values in seconds * + * count - [IN] the maximum number of item values to read * + * values - [OUT] the item history data values * + * * + * Return value: SUCCEED - the history data were read successfully * + * FAIL - otherwise * + * * + * Comments: This function reads <count> values of every specified item, * + * but not older than <age> in seconds. * + * If <count> is zero, will be readed all values not older than <age>. * + * If <age> is zero, will be readed <count> values of every specified * + * item. * + * If <count> and <age> is zeros both, loading will return no data. * + * * + ************************************************************************************/ +int zbx_history_preload_values(const zbx_vector_uint64_t *itemids, int value_type, int age, + int count, zbx_vector_valuecache_record_t *values) +{ + const char *__function_name = "zbx_history_preload_values"; + int ret, pos; + zbx_history_iface_t *writer = &history_ifaces[value_type]; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s() value_type:%d age:%d count:%d", + __function_name, value_type, age, count); + + if (NULL == writer->preload_values) + { + zabbix_log(LOG_LEVEL_DEBUG, "End of %s(): value_type:%d, " + "no function for preloading values", __function_name, value_type); + return SUCCEED; + } + + pos = values->values_num; + ret = writer->preload_values(writer, itemids, age, count, values); + + if (SUCCEED == ret && SUCCEED == zabbix_check_log_level(LOG_LEVEL_TRACE)) + { + int i; + char buffer[MAX_STRING_LEN]; + + for (i = pos; i < values->values_num; i++) + { + zbx_valuecache_record_t *h = &values->values[i]; + + zbx_history_value2str(buffer, sizeof(buffer), &h->value, value_type); + zabbix_log(LOG_LEVEL_TRACE, ZBX_FS_UI64 " %d.%09d %s", + h->itemid, h->timestamp.sec, h->timestamp.ns, buffer); + } + } + + zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s values:%d", __function_name, zbx_result_string(ret), + values->values_num - pos); + + return ret; +} + +/************************************************************************************ + * * * Function: zbx_history_requires_trends * * * * Purpose: checks if the value type requires trends data calculations *
Внесём объявление этой функции в заголовочный файл include/zbxhistory.h:
Index: zabbix-3.4.12-1+buster/include/zbxhistory.h =================================================================== --- zabbix-3.4.12-1+buster.orig/include/zbxhistory.h +++ zabbix-3.4.12-1+buster/include/zbxhistory.h @@ -47,6 +63,8 @@ int zbx_history_init(char **error); void zbx_history_add_values(const zbx_vector_ptr_t *values); int zbx_history_get_values(zbx_uint64_t itemid, int value_type, int start, int count, int end, zbx_vector_history_record_t *values); +int zbx_history_preload_values(const zbx_vector_uint64_t *itemids, int value_type, int age, + int count, zbx_vector_valuecache_record_t *values); int zbx_history_requires_trends(int value_type);
Функция zbx_history_preload_values обращается к реализациям подобной же функции для конкретных типов хранилищ. Нужно предусмотреть в структуре с реализацией типа хранилища zbx_history_iface соответствующее поле preload_values и объявить определение типа с указателем на функцию нужной нам сигнатуры. Обе правки внесём в заголовочный файл src/libs/zbxhistory/history.h:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.h =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history.h +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history.h @@ -29,6 +29,8 @@ typedef void (*zbx_history_destroy_func_ typedef int (*zbx_history_add_values_func_t)(struct zbx_history_iface *hist, const zbx_vector_ptr_t *history); typedef int (*zbx_history_get_values_func_t)(struct zbx_history_iface *hist, zbx_uint64_t itemid, int start, int count, int end, zbx_vector_history_record_t *values); +typedef int (*zbx_history_preload_values_func_t)(struct zbx_history_iface *hist, const zbx_vector_uint64_t *itemids, + int age, int count, zbx_vector_valuecache_record_t *values); typedef void (*zbx_history_flush_func_t)(struct zbx_history_iface *hist); struct zbx_history_iface @@ -40,6 +42,7 @@ struct zbx_history_iface zbx_history_destroy_func_t destroy; zbx_history_add_values_func_t add_values; zbx_history_get_values_func_t get_values; + zbx_history_preload_values_func_t preload_values; zbx_history_flush_func_t flush; };
Библиотека zbxhistory почти готова, осталось внести правки в конкретные реализации различных типов хранилищ.
Первым делом доработаем хранилища типов SQL и Elasticsearch. Т.к. реализовывать предзагрузку в этих хранилищах я не собираюсь, но поле preload_values структуры zbx_history_iface всё же нужно инициализировать, сделаем это, отредактировав файлы src/libs/zbxhistory/history_sql.c и src/libs/zbxhistory/history_elastic.c следующим образом:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_elastic.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history_elastic.c +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_elastic.c @@ -934,6 +934,7 @@ int zbx_history_elastic_init(zbx_history hist->add_values = elastic_add_values; hist->flush = elastic_flush; hist->get_values = elastic_get_values; + hist->preload_values = NULL; hist->requires_trends = 0; return SUCCEED; Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_sql.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history_sql.c +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_sql.c @@ -718,6 +718,7 @@ int zbx_history_sql_init(zbx_history_ifa hist->add_values = sql_add_values; hist->flush = sql_flush; hist->get_values = sql_get_values; + hist->preload_values = NULL; switch (value_type) {
Теперь займёмся хранилищем ClickHouse. Реализация функции clickhouse_preload_values выполнена по аналогии с функцией clickhouse_get_value. Введена также вспомогательная функция history_parse_valuecache, которая извлекает данные из строки в формате JSON и заполняет структуру типа zbx_valuecache_record_t. Внесённые правки в файле src/libs/zbxhistory/history_clickhouse.c выглядят следующим образом:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_clickhouse.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/history_clickhouse.c +++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/history_clickhouse.c @@ -166,6 +166,67 @@ out: return ret; } +static int history_parse_valuecache(struct zbx_json_parse *jp, unsigned char value_type, + zbx_valuecache_record_t *vcr) +{ + char *value = NULL; + size_t value_alloc = 0; + int ret = FAIL; + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "itemid", &value, &value_alloc)) + goto out; + + vcr->itemid = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "clock", &value, &value_alloc)) + goto out; + + vcr->timestamp.sec = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "ns", &value, &value_alloc)) + goto out; + + vcr->timestamp.ns = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "value", &value, &value_alloc)) + goto out; + + vcr->value = history_str2value(value, value_type); + + if (ITEM_VALUE_TYPE_LOG == value_type) + { + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "timestamp", &value, &value_alloc)) + goto out; + + vcr->value.log->timestamp = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "logeventid", &value, &value_alloc)) + goto out; + + vcr->value.log->logeventid = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "severity", &value, &value_alloc)) + goto out; + + vcr->value.log->severity = atoi(value); + + if (SUCCEED != zbx_json_value_by_name_dyn(jp, "source", &value, &value_alloc)) + goto out; + + vcr->value.log->source = zbx_strdup(NULL, value); + } + + vcr->value_type = value_type; + + ret = SUCCEED; + +out: + zbx_free(value); + + return ret; +} + static void clickhouse_log_error(CURL *handle, CURLcode error, const char *errbuf) { long http_code; @@ -457,6 +518,158 @@ static void clickhouse_destroy(zbx_histo /************************************************************************************ * * + * Function: clickhouse_preload_values * + * * + * Purpose: gets history data from history storage for warming up the values cache * + * * + * Parameters: hist - [IN] the history storage interface * + * age - [IN] the maximum age of values in seconds * + * count - [IN] the maximum number of item values to read * + * values - [OUT] the item history data values * + * * + * Return value: SUCCEED - the history data were read successfully * + * FAIL - otherwise * + * * + * Comments: This function reads <count> values of every specified item, * + * but not older than <age> in seconds. * + * If <count> is zero, will be readed all values not older than <age>. * + * If <age> is zero, will be readed <count> values of every specified * + * item. * + * If <count> and <age> is zeros both, loading will return no data. * + * * + ************************************************************************************/ +static int clickhouse_preload_values(zbx_history_iface_t *hist, const zbx_vector_uint64_t *itemids, + int age, int count, zbx_vector_valuecache_record_t *values) +{ + const char *__function_name = "clickhouse_preload_values"; + + zbx_clickhouse_data_t *data = hist->data; + size_t sql_alloc = 0, sql_offset; + int ret = SUCCEED, num = 0, i; + CURLcode err; + struct curl_slist *curl_headers = NULL; + char *sql = NULL, errbuf[CURL_ERROR_SIZE]; + const char *p = NULL; + struct zbx_json_parse jp, jp_sub, jp_data, jp_item; + zbx_valuecache_record_t vcr; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); + + if ((0 == age) && (0 == count)) + { + zabbix_log(LOG_LEVEL_INFORMATION, "skipped preload from ClickHouse table %s", + value_type_table[hist->value_type]); + return SUCCEED; + } + + if (0 == itemids->values_num) + { + zabbix_log(LOG_LEVEL_INFORMATION, "nothing to preload from ClickHouse table %s", + value_type_table[hist->value_type]); + return SUCCEED; + } + + if (NULL == (data->handle = curl_easy_init())) + { + zabbix_log(LOG_LEVEL_ERR, "cannot initialize cURL session"); + + return FAIL; + } + + if (ITEM_VALUE_TYPE_LOG == hist->value_type) + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, + "SELECT itemid, clock, ns, value, timestamp, source, severity, logeventid" + " FROM %s" + " WHERE itemid IN (" ZBX_FS_UI64, + value_type_table[hist->value_type], + itemids->values[0]); + else + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, + "SELECT itemid, clock, ns, value" + " FROM %s" + " WHERE itemid IN (" ZBX_FS_UI64, + value_type_table[hist->value_type], + itemids->values[0]); + + for (i = 1; i < itemids->values_num; i++) + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, "," ZBX_FS_UI64, itemids->values[i]); + + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, ")"); + + if (age > 0) + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, + " AND clock > toUInt32(now()) - %d", + age); + + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " ORDER BY itemid ASC, clock DESC"); + + if (count > 0) + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, + " LIMIT %d BY itemid", + count); + + zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " FORMAT JSON"); + + curl_headers = curl_slist_append(curl_headers, "Content-Type: application/json"); + + curl_easy_setopt(data->handle, CURLOPT_URL, data->base_url); + curl_easy_setopt(data->handle, CURLOPT_POSTFIELDS, sql); + curl_easy_setopt(data->handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(data->handle, CURLOPT_WRITEDATA, &page_r); + curl_easy_setopt(data->handle, CURLOPT_HTTPHEADER, curl_headers); + curl_easy_setopt(data->handle, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(data->handle, CURLOPT_ERRORBUFFER, errbuf); + + zabbix_log(LOG_LEVEL_DEBUG, "sending query to %s; post data: %s", data->base_url, sql); + + page_r.offset = 0; + *errbuf = '\0'; + if (CURLE_OK != (err = curl_easy_perform(data->handle))) + { + clickhouse_log_error(data->handle, err, errbuf); + ret = FAIL; + goto out; + } + + zabbix_log(LOG_LEVEL_DEBUG, "received from ClickHouse: %s", page_r.data); + + zbx_json_open(page_r.data, &jp); + zbx_json_brackets_open(jp.start, &jp_sub); + zbx_json_brackets_by_name(&jp_sub, "data", &jp_data); + + while (NULL != (p = zbx_json_next(&jp_data, p))) + { + if (SUCCEED != zbx_json_brackets_open(p, &jp_item)) + continue; + + if (SUCCEED != history_parse_valuecache(&jp_item, hist->value_type, &vcr)) + continue; + + zbx_vector_valuecache_record_append_ptr(values, &vcr); + num++; + } + + if (0 < num) + zabbix_log(LOG_LEVEL_INFORMATION, "%d values were preloaded from ClickHouse table %s", + num, value_type_table[hist->value_type]); + else + zabbix_log(LOG_LEVEL_INFORMATION, "no values were preloaded from ClickHouse table %s", + value_type_table[hist->value_type]); + +out: + clickhouse_close(hist); + + curl_slist_free_all(curl_headers); + + zbx_free(sql); + + zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); + + return ret; +} + +/************************************************************************************ + * * * Function: clickhouse_get_values * * * * Purpose: gets item history data from history storage * @@ -723,6 +936,7 @@ int zbx_history_clickhouse_init(zbx_hist hist->add_values = clickhouse_add_values; hist->flush = clickhouse_flush; hist->get_values = clickhouse_get_values; + hist->preload_values = clickhouse_preload_values; hist->requires_trends = 0; return SUCCEED;
Нам понадобится функция, которая вернёт список идентификаторов элементов данных, к которым привязаны триггеры и для которых поэтому нужно загрузить данные в кэш значений. Для загрузки значений разных типов нам понядобятся разные списки, т.к. значения хранятся в разных таблицах и могут обрабатываться разными реализациями хранилищ. Добавим в файл src/libs/zbxdbcache/dbconfig.c функцию с именем DCconfig_get_itemids_by_valuetype, которая будет возвращать идентификаторы элементов данных с указанным типом значения:
Index: zabbix-3.4.12-1+buster/src/libs/zbxdbcache/dbconfig.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxdbcache/dbconfig.c +++ zabbix-3.4.12-1+buster/src/libs/zbxdbcache/dbconfig.c @@ -6101,6 +6101,39 @@ void DCconfig_get_items_by_itemids(DC_IT /****************************************************************************** * * + * Function: DCconfig_get_itemids_by_valuetype * + * * + * Purpose: Get item IDs for specified value type * + * * + * Parameters: value_type - [IN] value type * + * itemids - [OUT] vector with item IDs * + * Return value: * + * Number of elements found * + * * + ******************************************************************************/ +void DCconfig_get_itemids_by_valuetype(int value_type, zbx_vector_uint64_t *itemids) +{ + const ZBX_DC_ITEM *item; + + zbx_hashset_iter_t iter; + + LOCK_CACHE; + + zbx_hashset_iter_reset(&config->items, &iter); + + while (NULL != (item = zbx_hashset_iter_next(&iter))) + { + if ((item->value_type == value_type) && (NULL != item->triggers)) + { + zbx_vector_uint64_append(itemids, item->itemid); + } + } + + UNLOCK_CACHE; +} + +/****************************************************************************** + * * * Function: dc_preproc_item_init * * * * Purpose: initialize new preprocessor item from configuration cache *
Эта функция будет нужна в файле src/libs/zbxdbcache/valuecache.c, поэтому для её использования там её нужно объявить в заголовочном файле include/dbcache.h:
Index: zabbix-3.4.12-1+buster/include/dbcache.h =================================================================== --- zabbix-3.4.12-1+buster.orig/include/dbcache.h +++ zabbix-3.4.12-1+buster/include/dbcache.h @@ -558,6 +558,7 @@ void DCconfig_clean_items(DC_ITEM *items int DCget_host_by_hostid(DC_HOST *host, zbx_uint64_t hostid); void DCconfig_get_hosts_by_itemids(DC_HOST *hosts, const zbx_uint64_t *itemids, int *errcodes, size_t num); void DCconfig_get_items_by_keys(DC_ITEM *items, zbx_host_key_t *keys, int *errcodes, size_t num); +void DCconfig_get_itemids_by_valuetype(int value_type, zbx_vector_uint64_t *itemids); void DCconfig_get_items_by_itemids(DC_ITEM *items, const zbx_uint64_t *itemids, int *errcodes, size_t num); void DCconfig_get_preprocessable_items(zbx_hashset_t *items, int *timestamp); void DCconfig_get_functions_by_functionids(DC_FUNCTION *functions,
Добавим в файл src/libs/zbxdbcache/valuecache.c функцию zbx_vc_preload_values, которая будет принимать массив значений типа zbx_valuecache_record_t, загруженный функцией zbx_history_preload_values и добавлять их в кэш значений. Функция zbx_vc_preload_values сделана на основе функции zbx_vc_add_value, добавляющей в кэш значений одно значение.
Index: zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxdbcache/valuecache.c +++ zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.c @@ -2609,6 +2615,65 @@ out: } /****************************************************************************** + * * + * Function: zbx_vc_preload_values * + * * + * Purpose: adds items values to the value cache * + * * + * Parameters: values - [IN] values, that needed to load to value cache * + * * + ******************************************************************************/ +void zbx_vc_preload_values(zbx_vector_valuecache_record_t *values) +{ + zbx_vc_item_t *item; + int i, failed = 0; + zbx_valuecache_record_t *value; + + if (NULL == vc_cache) + return; + + vc_try_lock(); + + /* Adding values from the tail to the head, because the list of values + * is ordered in descending order of clock field */ + for (i = values->values_num - 1; i >= 0; i--) + { + value = &values->values[i]; + + if (NULL != (item = zbx_hashset_search(&vc_cache->items, &value->itemid))) + { + zbx_history_record_t record = {value->timestamp, value->value}; + + if (0 == (item->state & ZBX_ITEM_STATE_REMOVE_PENDING)) + { + vc_item_addref(item); + + /* If the new value type does not match the item's type in cache we can't */ + /* change the cache because other processes might still be accessing it */ + /* at the same time. The only thing that can be done - mark it for removal */ + /* so it could be added later with new type. */ + /* Also mark it for removal if the value adding failed. In this case we */ + /* won't have the latest data in cache - so the requests must go directly */ + /* to the database. */ + if (item->value_type != value->value_type || + FAIL == vch_item_add_value_at_head(item, &record)) + { + item->state |= ZBX_ITEM_STATE_REMOVE_PENDING; + failed++; + } + + vc_item_release(item); + } + } + } + + zabbix_log(LOG_LEVEL_INFORMATION, "%d values successfully loaded to value cache", + values->values_num - failed); + + vc_try_unlock(); +} + +/****************************************************************************** * * * Function: zbx_vc_destroy * * *
Теперь осталось реализовать функцию zbx_vc_preload в том же файле src/libs/zbxdbcache/valuecache.c, которая пройдётся по всем типам значений, сформирует список элементов данных этого типа значений при помощи функции DCconfig_get_itemids_by_valuetype, запросит значения у соответствующего хранилища при помощи функции zbx_history_preload_values и отправит эти значения в кэш значений при помощи функции zbx_vc_preload_values. Настройки периода загрузки и ограничение на количество значений для одного элемента данных функция zbx_vc_preload возьмёт из переменных CONFIG_VALUE_CACHE_PRELOAD_AGE и CONFIG_VALUE_CACHE_PRELOAD_COUNT, которые были заполнены сервером Zabbix значениями из файла конфигурации.
Index: zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxdbcache/valuecache.c +++ zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.c @@ -75,6 +75,12 @@ static int vc_locked = 0; /* the value cache size */ extern zbx_uint64_t CONFIG_VALUE_CACHE_SIZE; +/* the maximum age of data to preloading to value cache */ +extern int CONFIG_VALUE_CACHE_PRELOAD_AGE; + +/* the maximum number of values per one item to preloading to value cache */ +extern int CONFIG_VALUE_CACHE_PRELOAD_COUNT; + ZBX_MEM_FUNC_IMPL(__vc, vc_mem) #define VC_STRPOOL_INIT_SIZE (1000) @@ -2674,6 +2680,35 @@ out: /****************************************************************************** * * + * Function: zbx_vc_preload * + * * + * Purpose: preload value cache * + * * + ******************************************************************************/ +void zbx_vc_preload() +{ + zbx_vector_valuecache_record_t values; + zbx_vector_uint64_t itemids; + int value_type; + + zbx_vector_valuecache_record_create(&values); + + zbx_vector_uint64_create(&itemids); + for (value_type = 0; value_type < ITEM_VALUE_TYPE_MAX; value_type++) + { + DCconfig_get_itemids_by_valuetype(value_type, &itemids); + zbx_history_preload_values(&itemids, value_type, CONFIG_VALUE_CACHE_PRELOAD_AGE, + CONFIG_VALUE_CACHE_PRELOAD_COUNT, &values); + zbx_vector_uint64_clear(&itemids); + } + zbx_vector_uint64_destroy(&itemids); + + zbx_vc_preload_values(&values); + zbx_vector_valuecache_record_destroy(&values); +} + +/****************************************************************************** + * * * Function: zbx_vc_destroy * * * * Purpose: destroys value cache *
Добавим объявление этой функции в файл src/libs/zbxdbcache/valuecache.h, чтобы её можно было вызвать в процессе запуска сервера Zabbix:
Index: zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.h =================================================================== --- zabbix-3.4.12-1+buster.orig/src/libs/zbxdbcache/valuecache.h +++ zabbix-3.4.12-1+buster/src/libs/zbxdbcache/valuecache.h @@ -76,6 +76,8 @@ zbx_vc_stats_t; int zbx_vc_init(char **error); +void zbx_vc_preload(); + void zbx_vc_destroy(void); void zbx_vc_lock(void);
Сделаем финальный штрих и добавим вызов функции zbx_vc_preload в файл src/zabbix_server/server.c после инициализации кэша значений, после загрузки конфигурации из базы данных, но перед тем, как главный процесс сервера Zabbix запустит подчинённые процессы:
jIndex: zabbix-3.4.12-1+buster/src/zabbix_server/server.c =================================================================== --- zabbix-3.4.12-1+buster.orig/src/zabbix_server/server.c +++ zabbix-3.4.12-1+buster/src/zabbix_server/server.c @@ -1021,6 +1028,9 @@ int MAIN_ZABBIX_ENTRY(int flags) DBclose(); + /* preload values to values cache */ + zbx_vc_preload(); + if (0 != CONFIG_IPMIPOLLER_FORKS) CONFIG_IPMIMANAGER_FORKS = 1;
Для оценки среднего количества значений, используемого в триггерах, можно воспользоваться таким запросом:
SELECT AVG(CASE WHEN functions.parameter LIKE '%s' THEN CAST(REPLACE(functions.parameter, 's', '') AS DECIMAL) WHEN functions.parameter LIKE '%m' THEN CAST(REPLACE(functions.parameter, 'm', '') AS DECIMAL) * 60 WHEN functions.parameter LIKE '%h' THEN CAST(REPLACE(functions.parameter, 'h', '') AS DECIMAL) * 3600 WHEN functions.parameter LIKE '%d' THEN CAST(REPLACE(functions.parameter, 'd', '') AS DECIMAL) * 86400 WHEN functions.parameter LIKE '%w' THEN CAST(REPLACE(functions.parameter, 'w', '') AS DECIMAL) * 604800 ELSE 0 END / CASE WHEN items.delay LIKE '%s' THEN CAST(REPLACE(items.delay, 's', '') AS DECIMAL) WHEN items.delay LIKE '%m' THEN CAST(REPLACE(items.delay, 'm', '') AS DECIMAL) * 60 WHEN items.delay LIKE '%h' THEN CAST(REPLACE(items.delay, 'h', '') AS DECIMAL) * 3600 WHEN items.delay LIKE '%d' THEN CAST(REPLACE(items.delay, 'd', '') AS DECIMAL) * 86400 WHEN items.delay LIKE '%w' THEN CAST(REPLACE(items.delay, 'w', '') AS DECIMAL) * 604800 ELSE 0 END) AS count FROM functions JOIN triggers ON triggers.triggerid = functions.triggerid AND triggers.status = 0 JOIN items ON items.itemid = functions.itemid AND items.status = 0 JOIN hosts ON hosts.hostid = items.hostid AND hosts.status = 0 WHERE functions.function IN ('min', 'max', 'avg', 'count', 'delta', 'nodata');
Поскольку это среднее значение, то в опцию ValueCachePreloadCount можно вписать удвоенное значение из результата запроса.
Для оценки среднего временного интервала, используемого в триггерах, можно воспользоваться таким запросом:
SELECT AVG(CASE WHEN functions.parameter LIKE '%s' THEN CAST(REPLACE(functions.parameter, 's', '') AS DECIMAL) WHEN functions.parameter LIKE '%m' THEN CAST(REPLACE(functions.parameter, 'm', '') AS DECIMAL) * 60 WHEN functions.parameter LIKE '%h' THEN CAST(REPLACE(functions.parameter, 'h', '') AS DECIMAL) * 3600 WHEN functions.parameter LIKE '%d' THEN CAST(REPLACE(functions.parameter, 'd', '') AS DECIMAL) * 86400 WHEN functions.parameter LIKE '%w' THEN CAST(REPLACE(functions.parameter, 'w', '') AS DECIMAL) * 604800 ELSE 0 END) AS age FROM functions JOIN triggers ON triggers.triggerid = functions.triggerid AND triggers.status = 0 JOIN items ON items.itemid = functions.itemid AND items.status = 0 JOIN hosts ON hosts.hostid = items.hostid AND hosts.status = 0 WHERE functions.function IN ('min', 'max', 'avg', 'count', 'delta', 'nodata');
Аналогично, поскольку это среднее значение, то в опцию ValueCachePreloadAge можно вписать удвоенное значение из результата запроса.
Если вам захочется, чтобы в кэш гарантированно попадали данные для расчёта всех триггеров, тогда можно заменить в запросах функцию AVG на MAX и подставить в файл конфигурации полученные значения. Если вам хватит оперативной памяти для такого большого кэша значений, то вам повезло :)