воскресенье, 29 ноября 2020 г.

Временный запрет на чтение данных из таблиц истории при запуске Zabbix 3.4

В статье Предзагрузка кэша значений при запуске сервера Zabbix я уже анонсировал эту функциональность и теперь настало время описать соответствующую заплатку.

Готовую заплатку с реализацией временного запрета на чтение данных из таблиц истори при запуске сервера Zabbix можно найти по ссылке zabbix3_4_12_valuecache_grace_period.patch.

В файл конфигурации conf/zabbix_server.conf добавим опцию с названием DBSyncersGracePeriod, которая будет принимать время с момента запуска сервера в секундах, в течение которого чтение данных из хранилищ истории будет запрещено:
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
@@ -413,6 +413,14 @@ DBUser=zabbix
 # Default:
 # StartDBSyncers=4
 
+### Option: DBSyncersGracePeriod
+#       Time after server startup, during which reading from history storage will be prohibited.
+#
+# Mandatory: no
+# Range: 0-86400
+# Default:
+# DBSyncersGracePeriod=0
+
 ### Option: HistoryCacheSize
 #      Size of history cache, in bytes.
 #      Shared memory size for storing history data.
Теперь добавим в сервер Zabbix загрузку этой опции из файла конфигурации. Отредактируем файл src/zabbix_server/server.c следующим образом:
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
@@ -176,6 +176,8 @@ int CONFIG_HOUSEKEEPING_FREQUENCY   = 1;
 int    CONFIG_MAX_HOUSEKEEPER_DELETE   = 5000;         /* applies for every separate field value */
 int    CONFIG_HISTSYNCER_FORKS         = 4;
 int    CONFIG_HISTSYNCER_FREQUENCY     = 1;
+int    CONFIG_HISTSYNCER_GRACE_PERIOD  = 0;
+
 int    CONFIG_CONFSYNCER_FORKS         = 1;
 int    CONFIG_CONFSYNCER_FREQUENCY     = 60;
 
@@ -556,6 +558,8 @@ static void zbx_load_config(ZBX_TASK_EX
                        MANDATORY,      MIN,                    MAX */
                {"StartDBSyncers",              &CONFIG_HISTSYNCER_FORKS,               TYPE_INT,
                        PARM_OPT,       1,                      100},
+               {"DBSyncersGracePeriod",        &CONFIG_HISTSYNCER_GRACE_PERIOD,        TYPE_INT,
+                       PARM_OPT,       0,                      SEC_PER_DAY},
                {"StartDiscoverers",            &CONFIG_DISCOVERER_FORKS,               TYPE_INT,
                        PARM_OPT,       0,                      250},
                {"StartHTTPPollers",            &CONFIG_HTTPPOLLER_FORKS,               TYPE_INT,
Внесём аналогичные фиктивные изменения в Zabbix-прокси, отредактировав файл src/libs/zabbix_proxy/proxy.c следующим образом:
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
@@ -173,6 +173,7 @@ int CONFIG_PROXYDATA_FREQUENCY      = 1;
 
 int    CONFIG_HISTSYNCER_FORKS         = 4;
 int    CONFIG_HISTSYNCER_FREQUENCY     = 1;
+int    CONFIG_HISTSYNCER_GRACE_PERIOD  = 0;
 int    CONFIG_CONFSYNCER_FORKS         = 1;
 
 int    CONFIG_VMWARE_FORKS             = 0;
К счастью, в исходном коде уже имеется переменная CONFIG_SERVER_STARTUP_TIME, содержащая отметку времени запуска сервера Zabbix. Осталось только внести условие в функцию zbx_history_get_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
@@ -34,6 +34,9 @@ extern char   *CONFIG_HISTORY_STR_STORAGE;
 extern char    *CONFIG_HISTORY_TEXT_STORAGE;
 extern char    *CONFIG_HISTORY_LOG_STORAGE;
 
+extern int     CONFIG_SERVER_STARTUP_TIME;
+extern int     CONFIG_HISTSYNCER_GRACE_PERIOD;
+
 zbx_history_iface_t    history_ifaces[ITEM_VALUE_TYPE_MAX];
 
 /************************************************************************************
@@ -162,6 +165,12 @@ int        zbx_history_get_values(zbx_uint64_t
        int                     ret, pos;
        zbx_history_iface_t     *writer = &history_ifaces[value_type];
 
+       if (time(NULL) - CONFIG_SERVER_STARTUP_TIME < CONFIG_HISTSYNCER_GRACE_PERIOD)
+       {
+               zabbix_log(LOG_LEVEL_DEBUG, "waiting for cache load, exiting");
+               return FAIL;
+       }
+
        zabbix_log(LOG_LEVEL_DEBUG, "In %s() itemid:" ZBX_FS_UI64 " value_type:%d start:%d count:%d end:%d",
                        __function_name, itemid, value_type, start, count, end);
 
Я пробовал возвращать ответ SUCCEED, как это сделано в Glaber, но в таком случае срабатывают триггеры с функцией str. Видимо в таком случае функция str считает, что значение есть, но оно пустое. В итоге функция не находит искомую подстроку и срабатывает триггер. Если же возвращать ответ FAIL, как это сделано в заплатке выше, триггеры и вычисляемые элементы данных в подобных случаях просто переходят в неподдерживаемое состояние.

воскресенье, 22 ноября 2020 г.

Предзагрузка кэша значений при запуске сервера Zabbix 3.4

Сервер Zabbix при запуске начинает оценивать истинность выражений триггеров, для чего ему нужны значения элементов данных, фигурирующих в выражении. В выражениях триггеров могут фигурировать функции, обращающиеся не только к последним значениям, но и к ряду значений за определённый период времени. Так как при запуске сервера Zabbix его кэш значений ещё пуст, Zabbix начинает выполнять огромное количество мелких запросов к таблицам истории. Классические транзакционные СУБД справляются с подобной нагрузкой относительно легко, хотя и для них она может оказаться тяжёлой при использовании медленной дисковой подсистемы и малом количестве оперативной памяти.

При хранении исторических данных в таблицах со сжатием, или в таблицах с индексами, оптимизированных для выполнения запросов, извлекающих данные длинными последовательностями, запуск сервера Zabbix становится более тяжёлым. Например, при хранении исторических данных в таблицах на движке TokuDB сервера MySQL со сжатием, запуск сервера Zabbix закономерно порождает высокую нагрузку от СУБД на процессор.

В случае с ClickHouse ситуация ещё тяжелее: для поиска единичного результата ClickHouse фактически читает с диска, расжимает и просматривает данные колонок всех секций, попадающих под условия запроса. Большой запас свободной оперативной памяти может снизить нагрузку на диски, т.к. горячие данные с файловой системы могут уместиться в буферный кэш ядра операционной системы. В ClickHouse имеется возможность использовать кэш разжатых данных, но по моим наблюдениям в этот кэш сохраняются только те данные, которые были выданы клиенту в качестве результатов запросов. Т.к. у сервера Zabbix есть собственный кэш значений, в который он сохраняет результаты прошлых запросов, то кэш разжатых данных ClickHouse оказывается бесполезен. Поэтому нагрузка на процессор, возникающая из-за необходимости многократно разжимать одни и те же данные, никуда не денется.

В качестве паллиатива в Glaber были реализованы два подхода к решению проблемы:
  1. Сервер Zabbix, прежде чем приступить к опросу и оценке истинности выражений триггеров, заполняет кэш значений данными из таблиц истории за указанный период времени, не более указанного количества значений для каждого из элементов данных. Таким образом повышается вероятность найти необходимые данные в кэше значений при последующих попытках оценить истинность выражений триггеров.
  2. Временный запрет на чтение данных из таблиц истории, действующий после запуска сервера Zabbix. Пока действует временный запрет, кэш значений наполняется данными, снятыми непосредственно с контролируемого оборудования. По мере наполнения кэша значений этими данными становится возможным оценить выражения триггеров, не обращаясь к таблицам истории. После снятия запрета количество данных, которые необходимо прочитать из таблиц истории, значительно снижается.
Оба решения я назвал паллиативными неспроста. У них имеется ряд недостатков, не позволяющих считать их идеальными:
  1. На практике некоторые триггеры могут использовать данные, находящиеся далеко за пределами загруженного в кэш значений периода времени. При попытках увеличить период времени загружаемых данных в кэш значений попадёт много данных, которые не будут востребованы и будут просто бесполезно занимать оперативную память. Кроме того, если сервер Zabbix по каким-то причинам долгое время не писал данные в таблицы истории, то при запуске в кэш значений не будет прочитано никаких данных и мы вернёмся к первоначальной ситуации. Наконец, запросы на чтение значений из таблиц хранилища порождают не только триггеры, но и вычисляемые элементы данных. Если у элемента данных, фигурирующего в выражении вычисляемого элемента данных, нет триггера, то значения этого элемента данных не будут загружены при запуске сервера Zabbix и по-прежнему будут извлекаться единичными запросами к хранилищу.
  2. При временном запрете не чтение данных из таблиц истории триггеры и вычисляемые элементы данных, которые не нашли в кэше значений нужных им данных, будут переходить в неподдерживаемое состояние. Их оценка будет восстанавливаться либо при накоплении достаточных данных в кэше значений, либо по истечении периода временного запрета на чтение из таблиц истории. Пока триггер находится в неподдерживаемом состоянии, он заведомо не может сработать и часть имеющихся проблем будет скрыта, пока триггер вновь не стане поддерживаемым.
Идею реализации предзагрузки кэша значений из 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 и Zabbix-прокси

Теперь добавим в сервер 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);
+}

Базовая доработка zbxhistory

Теперь, когда выполнена необходимая подготовка, можно приступить к реализации функции 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 почти готова, осталось внести правки в конкретные реализации различных типов хранилищ.

Доработка хранилищ 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;

Доработка zbxdbcache

Нам понадобится функция, которая вернёт список идентификаторов элементов данных, к которым привязаны триггеры и для которых поэтому нужно загрузить данные в кэш значений. Для загрузки значений разных типов нам понядобятся разные списки, т.к. значения хранятся в разных таблицах и могут обрабатываться разными реализациями хранилищ. Добавим в файл 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);

Доработка сервера Zabbix

Сделаем финальный штрих и добавим вызов функции zbx_vc_preload в файл src/zabbix_server/server.c после инициализации кэша значений, после загрузки конфигурации из базы данных, но перед тем, как главный процесс сервера 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
@@ -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 и подставить в файл конфигурации полученные значения. Если вам хватит оперативной памяти для такого большого кэша значений, то вам повезло :)

воскресенье, 15 ноября 2020 г.

Поддержка хранилища ClickHouse в сервере Zabbix 3.4

Продолжаем доработку сервера Zabbix. В прошлый раз мы добавили в библиотеку zbxhistory поддержку возможности использования хранилищ разного типа. На этот раз нужно добавить поддержку хранилища нового типа, которую будем реализовывать на базе поддержки хранилища Elasticsearch, частично заимствуя фрагменты из кода поддержки SQL-хранилищ.

Готовую заплатку для сервера Zabbix с реализацией поддержки хранения исторических данных в ClickHouse можно найти по ссылке zabbix3_4_12_server_storage_per_table.patch.

Ниже описаны внесённые заплаткой доработки и объяснение их логики.

У меня ушло некоторое время на изучение функций для работы со структурами данных в формате JSON. Чтобы не пришлось вспоминать их снова, опишу те из них, которыми я пользовался непосредственно в описываемой заплатке.

Функции Zabbix для формирования JSON

Заголовочный файл include/zbxjson.h, файл с реализацией функций - src/libs/zbxjson/json.c

zbx_json_init(json)

Создание JSON, в котором корневым элементом является словарь. Фактически, в пустой буфер будут добавлены фигурные скобки {}, текущий указатель будет указывать на закрывающую скобку, уровень вложенности увеличится с 0 до 1, а в статусе будет ZBX_JSON_EMPTY.

zbx_json_initarray(json)

Работает аналогично zbx_json_init, но корневым элементом структуры JSON будет массив, а в буфер вместо фигурных скобок {} будут вставлены квадратные скобки [].

zbx_json_clean(json)

Очищает буфер от хранящейся в нём структуры JSON. Сама память при этом не освобождается.

zbx_json_free(json)

Освобождает буфер, который был занят сформированной структурой JSON.

zbx_json_addobject(json, name)

Вставляет в то место буфера, куда указывает текущий указатель:
  1. запятую ,, если текущий статус равен ZBX_JSON_COMMA,
  2. текст "name":, если аргумент name не равен NULL,
  3. пару фигурных скобок {}.
Текущий указатель передвигается на вставленную закрывающую фигурную скобку, уровень вложенности увеличивается на единицу, в статус записывается ZBX_JSON_EMPTY.

Весь текст, который ранее лежал в буфере после указателя, сдвигается так, что оказывается позади вставленного текста.

zbx_json_addarray(json, name)

Работает аналогично zbx_json_addobject, но вместо фигурных скобок вставляются квадратные [].

Вставляет в то место буфера, куда указывает текущий указатель:
  1. запятую ,, если текущий статус равен ZBX_JSON_COMMA,
  2. текст "name":, если аргумент name не равен NULL,
  3. пару квадратных скобок [].
Текущий указатель передвигается на вставленную закрывающую квадратную скобку, уровень вложенности увеличивается на единицу, в статус записывается ZBX_JSON_EMPTY.

Весь текст, который ранее лежал в буфере после указателя, сдвигается так, что оказывается позади вставленного текста.

zbx_json_addstring(json, name, string, type)

Вставляет в то место буфера, куда указывает текущий указатель:
  1. запятую ,, если текущий статус равен ZBX_JSON_COMMA,
  2. текст "name":, если аргумент name не равен NULL,
  3. строку string.
Если тип строки type равен ZBX_JSON_TYPE_STRING, то строка string заключается в двойные кавычки. Если string равен NULL, то добавляется строка null без кавычек.

Текущий указатель передвигается на символ, следующий за последним вставленным, уровень вложенности не меняется, в статус записывается ZBX_JSON_COMMA.

Весь текст, который ранее лежал в буфере после указателя, сдвигается так, что оказывается позади вставленного текста.

zbx_json_adduint64(json, name, value)

Вставляет в то место буфера, куда указывает текущий указатель:
  1. запятую ,, если текущий статус равен ZBX_JSON_COMMA,
  2. текст "name":, если аргумент name не равен NULL,
  3. 64-битное беззнаковое число value.
Текущий указатель передвигается на символ, следующий за последним вставленным, уровень вложенности не меняется, в статус записывается ZBX_JSON_COMMA.

Весь текст, который ранее лежал в буфере после указателя, сдвигается так, что оказывается позади вставленного текста.

zbx_json_addint64(json, name, value)

Вставляет в то место буфера, куда указывает текущий указатель:
  1. запятую ,, если текущий статус равен ZBX_JSON_COMMA,
  2. текст "name":, если аргумент name не равен NULL,
  3. 64-битное число value со знаком.
Текущий указатель передвигается на символ, следующий за последним вставленным, уровень вложенности не меняется, в статус записывается ZBX_JSON_COMMA.

Весь текст, который ранее лежал в буфере после указателя, сдвигается так, что оказывается позади вставленного текста.

zbx_json_close(json)

Передвигает текущий указатель так, что пропускается закрывающая фигурная } или квадратная скобка ].

Уровень вложенности уменьшается на единицу, статус меняется на ZBX_JSON_COMMA.

Функции Zabbix для разбора JSON

zbx_json_open(buffer, json)

buffer - строка с завершающим нулём, содержащая текст JSON.

json - структура с указателями на начало и конец фрагмента JSON в буфере buffer.

Проверяет, что текст в буфере buffer является правильным JSON, инициализирует структуру json. При ошибках возвращает FAIL, в случае успеха - SUCCEED.

zbx_json_next(json, p)

json - структура с указателями на начало и конец фрагмента JSON, являющегося массивом.

p - указатель внутри фрагмента json, указывающий на начальный символ элемента массива или словаря.

Ищет следующий элемент массива или словаря и возвращает указатель на его начальный символ. Если указанный в p элемент был последним, возвращает NULL.

zbx_json_pair_by_name(json, name)

json - структура с указателями на начало и конец фрагмента JSON, являющегося словарём.

name - имя ключа в словаре, значение которого нужно найти.

Возвращает указатель на первый символ значения или NULL, если указанного ключа нет в словаре.

zbx_json_brackets_open(p, json)

p - указатель на открывающую скобку, указывающую на начало фрагмента JSON, который нужно найти.

json - структура с указателями на начало и конец фрагмента JSON, являющегося массивом или словарём.

Ищет в указанном фрагменте JSON открывающую скобку, затем находит парную ей закрывающую скобку и записывает указатели на начало и конец найденного фрагмента в структуру json.

При ошибках возвращает FAIL, при успешном завершении - SUCCEED.

zbx_json_brackets_by_name(json, name, json_out)

json - структура с указателями на начало и конец фрагмента JSON, являющегося словарём.

name - имя ключа в словаре, значение которого нужно найти.

json_out - фрагмент JSON, являющийся значением ключа name.

Ищет в указанном фрагменте JSON указанный ключ, находит открывающую и закрывающую скобки, в структуру json_out записывает указатели на начало и конец найденного фрагмента.

При ошибках возвращает FAIL, при успешном завершении - SUCCEED.

zbx_json_value_by_name_dyn(json, name, string, string_alloc)

json - структура с указателями на начало и конец фрагмента JSON, являющегося словарём.

name - имя ключа в словаре, значение которого нужно найти.

string - указатель на указатель на буфер, в который будет помещено найденное значение.

string_alloc - указатель на переменную с размером буфера.

Ищет в указанном фрагменте JSON, являющемся словарём, значение элемента с указанным ключом name. Найденное значение записывается в буфер. Если в буфере не было достаточно места для сохранения найденного значения, функция выделяет под буфер другой фрагмент памяти, обновляет указатель на буфер и его размер.

Новая функция zbx_json_adddbl

Немного опережая события, заранее добавим в код Zabbix дополнительную функцию zbx_json_adddbl, которая позже понадобится нам для формирования JSON с данными, вставляемыми в таблицу history с числами с плавающей запятой. Объявление функции добавим в файл include/zbxjson.h, а реализацию функции добавим в файл src/libs/zbxjson/json.c следующим образом:
Index: zabbix-3.4.12-1+buster/include/zbxjson.h
===================================================================
--- zabbix-3.4.12-1+buster.orig/include/zbxjson.h
+++ zabbix-3.4.12-1+buster/include/zbxjson.h
@@ -160,6 +160,7 @@ void        zbx_json_addarray(struct zbx_json *
 void   zbx_json_addstring(struct zbx_json *j, const char *name, const char *string, zbx_json_type_t type);
 void   zbx_json_adduint64(struct zbx_json *j, const char *name, zbx_uint64_t value);
 void   zbx_json_addint64(struct zbx_json *j, const char *name, zbx_int64_t value);
+void   zbx_json_adddbl(struct zbx_json *j, const char *name, double value);
 int    zbx_json_close(struct zbx_json *j);
 
 int            zbx_json_open(const char *buffer, struct zbx_json_parse *jp);
Index: zabbix-3.4.12-1+buster/src/libs/zbxjson/json.c
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/libs/zbxjson/json.c
+++ zabbix-3.4.12-1+buster/src/libs/zbxjson/json.c
@@ -385,6 +385,14 @@ void       zbx_json_addint64(struct zbx_json *
        zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
 }
 
+void    zbx_json_adddbl(struct zbx_json *j, const char *name, double value)
+{
+        char    buffer[MAX_ID_LEN];
+
+        zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_DBL, value);
+        zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
+}
+
 int    zbx_json_close(struct zbx_json *j)
 {
        if (1 == j->level)

Доработка основы библиотеки zbxhistory

В файле src/libs/zbxhistory/history.c раскомментируем ранее добавленный нами комментарий с намёком на поддержку ClickHouse:
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
@@ -62,8 +63,8 @@ int   zbx_history_init(char **error)
        {
                if (elastic_url = zbx_strstartswith(opts[i], "elastic,"))
                        ret = zbx_history_elastic_init(&history_ifaces[i], i, elastic_url, error);
-               /*else if (clickhouse_url = zbx_strstartswith(opts[i], "clickhouse,"))
-                       ret = zbx_history_clickhouse_init(&history_ifaces[i], i, clickhouse_url, error);*/
+               else if (clickhouse_url = zbx_strstartswith(opts[i], "clickhouse,"))
+                       ret = zbx_history_clickhouse_init(&history_ifaces[i], i, clickhouse_url, error);
                else
                        ret = zbx_history_sql_init(&history_ifaces[i], i, error);
В добавленном коде используется указатель на строку clickhouse_url, добавим его объявление:
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
@@ -50,6 +50,7 @@ int   zbx_history_init(char **error)
 {
        int             i, ret;
        char            *elastic_url;
+       char            *clickhouse_url;
        const char      *opts[] = {
                                CONFIG_HISTORY_STORAGE,
                                CONFIG_HISTORY_STR_STORAGE,
Объявление функции zbx_history_clickhouse_init нужно добавить в заголовочный файл 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
@@ -49,4 +49,7 @@ int   zbx_history_sql_init(zbx_history_ifa
 /* elastic hist */
 int    zbx_history_elastic_init(zbx_history_iface_t *hist, unsigned char value_type, const char *url, char **error);
 
+/* ClickHouse hist */
+int    zbx_history_clickhouse_init(zbx_history_iface_t *hist, unsigned char value_type, const char *url, char **error);
+
 #endif

Добавление файла history_clickhouse.c

Перед дальнейшими действиями скопируем файл src/libs/zbxhistory/history_elastic.c в файл src/libs/zbxhistory/history_clickhouse.c и заменим все упоминания Elasticsearch на ClickHouse, в том числе в отладочных сообщениях, комментариях и именах функций.

Теперь нужно прописать новый файл history_clickhouse.c в Make-файлы src/libs/zbxhistory/Makefile.am и src/libs/zbxhistory/Makefile.in, чтобы они участвовали в процессе сборки:
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/Makefile.am
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/Makefile.am
+++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/Makefile.am
@@ -5,4 +5,5 @@ noinst_LIBRARIES = libzbxhistory.a
 libzbxhistory_a_SOURCES = \
        history.c history.h \
        history_sql.c \
-       history_elastic.c 
+       history_elastic.c \
+       history_clickhouse.c
Index: zabbix-3.4.12-1+buster/src/libs/zbxhistory/Makefile.in
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/libs/zbxhistory/Makefile.in
+++ zabbix-3.4.12-1+buster/src/libs/zbxhistory/Makefile.in
@@ -120,7 +120,8 @@ am__v_AR_1 =
 libzbxhistory_a_AR = $(AR) $(ARFLAGS)
 libzbxhistory_a_LIBADD =
 am_libzbxhistory_a_OBJECTS = history.$(OBJEXT) history_sql.$(OBJEXT) \
-       history_elastic.$(OBJEXT)
+       history_elastic.$(OBJEXT) \
+       history_clickhouse.$(OBJEXT)
 libzbxhistory_a_OBJECTS = $(am_libzbxhistory_a_OBJECTS)
 AM_V_P = $(am__v_P_@AM_V@)
 am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
@@ -366,7 +367,8 @@ noinst_LIBRARIES = libzbxhistory.a
 libzbxhistory_a_SOURCES = \
        history.c history.h \
        history_sql.c \
-       history_elastic.c 
+       history_elastic.c \
+       history_clickhouse.c
 
 all: all-am
 
@@ -417,6 +419,7 @@ distclean-compile:
        -rm -f *.tab.c
 
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history_clickhouse.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history_elastic.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history_sql.Po@am__quote@
 
Получилось два полностью аналогичных по сути модуля поддержки хранилищ с разными именами. Продолжим переделку нового модуля. Сначала пройдусь кратко по мелким изменениям.

В функции clickhouse_writer_flush была удалена обработка сообщений об ошибках Elasticsearch при успешном коде ответа HTTP, т.к. ClickHouse о любых ошибках выполнения запросов всегда сообщает соответствующим кодом статуса HTTP:
@@ -402,19 +401,6 @@
                                zbx_vector_ptr_append(&retries, msg->easy_handle);
                                curl_multi_remove_handle(writer.handle, msg->easy_handle);
                        }
-                       else if (CURLE_OK == curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&curl_page)
-                                       && SUCCEED == clickhouse_is_error_present(&curl_page->page, &error))
-                       {
-                               zabbix_log(LOG_LEVEL_WARNING, "%s() %s: %s", __function_name,
-                                               "cannot send data to ClickHouse", error);
-                               zbx_free(error);
-
-                               /* If the error is due to ClickHouse internal problems (for example an index */
-                               /* became read-only), we put the handle in a retry list and */
-                               /* remove it from the current execution loop */
-                               zbx_vector_ptr_append(&retries, msg->easy_handle);
-                               curl_multi_remove_handle(writer.handle, msg->easy_handle);
-                       }
                }
 
                previous = running;
Было удалено макроопределение константы ZBX_IDX_JSON_ALLOCATE, т.к. в коде поддержки ClickHouse оно не использовалось.

В структуре zbx_clickhouse_data_t было удалено поле post_url, т.к. оказалось достаточно уже имеющегося в структуре поля base_url.

Были удалены функции history_value2str и clickhouse_is_error_present (бывшая elastic_is_error_present), т.к. они больше не используются.

Что касается доработок по существу, то они затрагивают функции clickhouse_get_values и clickhouse_add_values. Приведу обе функции полностью в окончательном виде:
/************************************************************************************
 *                                                                                  *
 * Function: clickhouse_get_values                                                  *
 *                                                                                  *
 * Purpose: gets item history data from history storage                             *
 *                                                                                  *
 * Parameters:  hist    - [IN] the history storage interface                        *
 *              itemid  - [IN] the itemid                                           *
 *              start   - [IN] the period start timestamp                           *
 *              count   - [IN] the number of values to read                         *
 *              end     - [IN] the period end timestamp                             *
 *              values  - [OUT] the item history data values                        *
 *                                                                                  *
 * Return value: SUCCEED - the history data were read successfully                  *
 *               FAIL - otherwise                                                   *
 *                                                                                  *
 * Comments: This function reads <count> values from [<start>,<end>] interval or    *
 *           all values from the specified interval if count is zero.               *
 *                                                                                  *
 ************************************************************************************/
static int      clickhouse_get_values(zbx_history_iface_t *hist, zbx_uint64_t itemid, int start, int count, int end,
                zbx_vector_history_record_t *values)
{
        const char              *__function_name = "clickhouse_get_values";

        zbx_clickhouse_data_t   *data = hist->data;
        size_t                  sql_alloc = 0, sql_offset;
        int                     ret = SUCCEED;
        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_history_record_t    hr;

        zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);

        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 clock, ns, value, timestamp, source, severity, logeventid"
                                " FROM %s"
                                " WHERE itemid=" ZBX_FS_UI64,
                                value_type_table[hist->value_type], itemid);
        else
                zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset,
                                "SELECT clock, ns, value"
                                " FROM %s"
                                " WHERE itemid=" ZBX_FS_UI64,
                                value_type_table[hist->value_type], itemid);

        if (0 < start)
                zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " AND clock>%d", start);

        if (0 < end)
                zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " AND clock<=%d", end);

        zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " ORDER BY clock DESC");

        if (0 < count)
                zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " LIMIT %d", 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_value(&jp_item, hist->value_type, &hr))
                        continue;

                zbx_vector_history_record_append_ptr(values, &hr);
        }

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_add_values                                                  *
 *                                                                                  *
 * Purpose: sends history data to the storage                                       *
 *                                                                                  *
 * Parameters:  hist    - [IN] the history storage interface                        *
 *              history - [IN] the history data vector (may have mixed value types) *
 *                                                                                  *
 ************************************************************************************/
static int      clickhouse_add_values(zbx_history_iface_t *hist, const zbx_vector_ptr_t *history)
{
        const char      *__function_name = "clickhouse_add_values";

        zbx_clickhouse_data_t   *data = hist->data;
        int                     i, num = 0;
        ZBX_DC_HISTORY          *h;
        struct zbx_json         json;
        size_t                  buf_offset = 0;

        zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);

        if (ITEM_VALUE_TYPE_LOG == hist->value_type)
                zbx_snprintf_alloc(&data->buf, &data->buf_alloc, &buf_offset,
                                "INSERT INTO %s(itemid, value, timestamp, source, severity, logeventid, clock, ns)"
                                " FORMAT JSONEachRow\n", value_type_table[hist->value_type]);
        else
                zbx_snprintf_alloc(&data->buf, &data->buf_alloc, &buf_offset,
                                "INSERT INTO %s(itemid, value, clock, ns) FORMAT JSONEachRow\n",
                                value_type_table[hist->value_type]);

        for (i = 0; i < history->values_num; i++)
        {
                h = (ZBX_DC_HISTORY *)history->values[i];

                if (hist->value_type != h->value_type)
                        continue;

                zbx_json_init(&json, ZBX_JSON_ALLOCATE);

                zbx_json_adduint64(&json, "itemid", h->itemid);

                switch (h->value_type)
                {
                        case ITEM_VALUE_TYPE_STR:
                        case ITEM_VALUE_TYPE_TEXT:
                                zbx_json_addstring(&json, "value", h->value.str, ZBX_JSON_TYPE_STRING);
                                break;
                        case ITEM_VALUE_TYPE_LOG:
                                zbx_json_addstring(&json, "value", h->value.log->value, ZBX_JSON_TYPE_STRING);
                                break;
                        case ITEM_VALUE_TYPE_FLOAT:
                                zbx_json_adddbl(&json, "value", h->value.dbl);
                                break;
                        case ITEM_VALUE_TYPE_UINT64:
                                zbx_json_adduint64(&json, "value", h->value.ui64);
                                break;
                }

                if (ITEM_VALUE_TYPE_LOG == h->value_type)
                {
                        const zbx_log_value_t   *log;

                        log = h->value.log;

                        zbx_json_adduint64(&json, "timestamp", log->timestamp);
                        zbx_json_addstring(&json, "source", ZBX_NULL2EMPTY_STR(log->source), ZBX_JSON_TYPE_STRING);
                        zbx_json_adduint64(&json, "severity", log->severity);
                        zbx_json_adduint64(&json, "logeventid", log->logeventid);
                }

                zbx_json_adduint64(&json, "clock", h->ts.sec);
                zbx_json_adduint64(&json, "ns", h->ts.ns);

                zbx_json_close(&json);

                zbx_snprintf_alloc(&data->buf, &data->buf_alloc, &buf_offset, "%s\n", json.buffer);

                zbx_json_free(&json);

                num++;
        }

        if (num > 0)
                clickhouse_writer_add_iface(hist);

        zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);

        return num;
}
При доработке функции clickhouse_get_values массив строковых констант value_type_str был заменён на массив строковых констант value_type_table:
-const char      *value_type_str[] = {"dbl", "str", "log", "uint", "text"};
+const char      *value_type_table[] = {"history", "history_str", "history_log", "history_uint", "history_text"};
Из всех сделанных изменений отдельно остановлюсь на исправлении одной из ошибок, которая перекочевала в файл history_clickhouse.c из файла history_elastic.c. Не могу скзать, является ли это ошибкой в исходном файле, но в коде поддержки ClickHouse эта проблема проявлялась следующим образом: в журнале сервера Zabbix при попытках вставки новых данных в таблицы истории в файле /var/log/zabbix/zabbix_server.log появлялись ошибки "400 Bad Request", хотя на первый взгляд данные в таблицы всё-таки записывались.

Оказалось, что часть запросов к ClickHouse были попросту пустыми POST-запросами. Более пристальное изучение причин проблемы позволило обнаружить ошибку: при формировании запроса к ClickHouse на вставку данных иногда, при попытке добавить в него очередное значение, данные в буфере попросту очищались. Получившийся пустой запрос и выполнялся, из-за чего ClickHouse периодически сообщал об ошибках, а графики в веб-интерфейсе прерывались.

В функции добавки значений использовался уже распределённый ранее буфер hist->data->buf, но при каждом вызове этой функции считалось, что его размер buf_alloc равен нулю. Вот как это выглядит в исходном модуле history_elastic.c:
static int      elastic_add_values(zbx_history_iface_t *hist, const zbx_vector_ptr_t *history)
{
        const char      *__function_name = "elastic_add_values";

        zbx_elastic_data_t      *data = hist->data;
        int                     i, num = 0;
        ZBX_DC_HISTORY          *h;
        struct zbx_json         json_idx, json;
        size_t                  buf_alloc = 0, buf_offset = 0;
Чтобы устранить ошибку, я решил вынести переменную с размером буфера из тела функции в структуру, содержащую указатель на буфер:
 typedef struct
 {
         char    *base_url;
         char    *buf;
+        size_t  buf_alloc;
         CURL    *handle;
 }
 zbx_clickhouse_data_t;
После этой доработки сервер Zabbix, наконец, начал исправно писать данные в ClickHouse.