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

Подготовка ClickHouse для хранения истории и тенденций Zabbix

Создание таблиц истории

Подключиться к серверу Clickhouse можно при помощи команды следующего вида:
$ clickhouse-client -u zabbix --ask-password zabbix
В Glaber'е используется одна таблица вместо таблиц history, history_uint, history_str и history_text. Таблица history_log не поддерживается. В отличие от Glaber, я решил скрупулёзно воспроизвести схему данных, принятую в Zabbix.

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

Чтобы Clickhouse своевременно сливал фрагменты таблиц в фоновом режиме, нужно чтобы в каждом фрагменте было не мнее 8192 строк. Но даже если ваш сервер Zabbix генерирует более 8192 новых значений в секунду, они будут во-первых распределяться между процессами DBSyncers (количество которых настраивается через опцию конфигурации StartDBSyncers), а во-вторых - они будут распределяться между разными таблицами истории. В моей практике наибольшая доля данных приходилась на таблицу history_uint, а history_log во многих случаях не использовалась вовсе.

По умолчанию процессы DBSyncers запускаются раз в секунду. Уменьшать их количество не всегда возможно, т.к. эти же процессы используются и для чтения данных из таблиц истории, когда необходимых данных нет в кэше значений. Особенно высока потребность в большом количестве процессов DBSyncers при старте Zabbix, когда кэш значений ещё пуст. Я пробовал делать заплатку для Zabbix, которая добавляет поддержку опции конфигурации DBSyncersPeriod и позволяет настраивать периодичность записи процессами DBSyncers. Такое решение не прошло проверку практикой, при малом количестве новых значений в секунду и большом количестве DBSyncers для накопления достаточного объёма данных приходится выполнять запись раз в 2-5 минут. И это без учёта неравномерности распределения данных по разным таблицам!

Таким образом, решение использовать буферную таблицу в Glaber было вполне оправданным. Поэтому настоящие таблицы истории в моём варианте можно создать при помощи следующих запросов:
CREATE TABLE real_history_uint
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock);

CREATE TABLE real_history
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock);

CREATE TABLE real_history_str
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock);

CREATE TABLE real_history_text
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock);

CREATE TABLE real_history_log
(
    itemid UInt64,
    clock UInt32,
    timestamp DateTime,
    source FixedString(64),
    severity UInt32,
    value String,
    logeventid UInt32,
    ns UInt32
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock);
Как можно заметить, в отличие от Glaber, здесь таблицы разделены не на помесячные, а на посуточные секции. Для удаления ненужных секций в дальнейшем можно будет воспользоваться запросами следующего вида:
ALTER TABLE real_history_uint DROP PARTITION 20190125;

Создание буферных таблиц истории

Теперь нужно создать буферные таблицы, с которыми непосредственно будет работать сам Zabbix. При вставке данных в буферную таблицу данные сохраняются в оперативной памяти и не записываются в нижележащую таблицу, пока не будет достигнуто одно из условий. При чтении данных из буферной таблицы данные ищутся как в самой буферной таблице, так и в нижележащей таблице на диске. Я создал буферные таблицы следующим образом:
CREATE TABLE history_uint
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value UInt64
) ENGINE = Buffer(zabbix, real_history_uint, 8, 30, 60, 8192, 65536, 262144, 67108864);

CREATE TABLE history
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value Float64
) ENGINE = Buffer(zabbix, real_history, 8, 30, 60, 8192, 65536, 262144, 67108864);

CREATE TABLE history_str
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value String
) ENGINE = Buffer(zabbix, real_history_str, 8, 30, 60, 8192, 65536, 262144, 67108864);

CREATE TABLE history_text
(
    itemid UInt64,
    clock UInt32,
    ns UInt32,
    value String
) ENGINE = Buffer(zabbix, real_history_text, 8, 30, 60, 8192, 65536, 262144, 67108864);

CREATE TABLE history_log
(
    itemid UInt64,
    clock UInt32,
    timestamp DateTime,
    source FixedString(64),
    severity UInt32,
    value String,
    logeventid UInt32,
    ns UInt32
) ENGINE = Buffer(zabbix, real_history_log, 8, 30, 60, 8192, 65536, 262144, 67108864);
Для всех созданных буферных таблиц действуют следующие условия записи данных в реальную таблицу:
  • 30 секунд - минимальное время, которое должно пройти со момента предыдущей записи в реальную таблицу, прежде чем буферная таблица запишет данные в реальную таблицу,
  • 60 секунд - максимальное время с момента предыдущей записи в реальную таблицу, по прошествии которого операция записи будет выполнена вне зависимости от всех остальных условий,
  • 8192 строк - минимальное количество записей, которое должно быть в буферной таблице, прежде чем буферная таблица запишет данные в реальную таблицу,
  • 65536 строк - максимальное количество записей, которое должно быть в буферной таблице, по достижении которого операция записи будет выполнена вне зависимости от всех остальных условий,
  • 256 килобайт - минимальный объём данных, который должен накопиться в буферной таблице, прежде чем буферная таблица запишет данные в реальную таблицу,
  • 64 мегабайта - максимальный объём данных, который должен накопиться в буферной таблице, по достижении которого операция записи будет выполнена вне зависимости от всех остальных условий.
Итак, буферная таблица ждёт либо выполнения одного из условий максимума, либо выполнения всех условий минимума, после чего данные будут записаны в реальную таблицу.

Виртуальные таблицы тенденций в ClickHouse

Т.к. в моём случае внедрение Zabbix состоялось давно и вокруг него уже было написано значительное количество различных скриптов, использующих данные из таблиц тенденций, то мне нужно было сделать переход на ClickHouse максимально мягким. Для этого я воспользовался готовым решением Михаила Макурова, которое он продемонстрировал на одном из слайдов своей презентации. Воспользуемся агрегирующими материализованными представлениями ClickHouse, создав их при помощи следующих запросов:
CREATE MATERIALIZED VIEW trends
ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock)
AS SELECT
  itemid,
  toUInt32(toStartOfHour(toDateTime(clock))) AS clock,
  count(value) AS num,
  min(value) AS value_min,
  avg(value) AS value_avg,
  max(value) AS value_max
FROM real_history
GROUP BY itemid, clock;

CREATE MATERIALIZED VIEW trends_uint
ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMMDD(toDateTime(clock))
ORDER BY (itemid, clock)
AS SELECT
  itemid,
  toUInt32(toStartOfHour(toDateTime(clock))) AS clock,
  count(value) AS num,
  min(value) AS value_min,
  toUInt64(avg(value)) AS value_avg,
  max(value) AS value_max
FROM real_history_uint
GROUP BY itemid, clock;
Эти представления материализованные, а это значит, что они будут вычисляться не при поступлении запроса, а будут храниться на диске. Представления будут автоматически обновляться сервером Clickhouse по мере вставки новых данных в таблицы истории. Серверу Zabbix не нужно будет вычислять эти данные самостоятельно, т.к. всю необходимую работу за него будет делать Clickhouse.

Скрипты

Итак, структура таблиц готова, теперь нужно разобраться с обслуживанием таблиц, в том числе удалением устаревших секций таблиц истории и материализованных видов таблиц тенденций, а также с переносом данных. Для обеих задач я решил написать скрипты на Python, воспользовавшись модулем Python для работы с Clickhouse, который называется clickhouse-driver. Из всех рассмотренных мной модулей для языка Python этот модуль приглянулся по следующим причинам:
  • единственный, который использует двоичный протокол ClickHouse, а не использует доступ по HTTP,
  • снабжён файлом README и каталогом с документацией. Документация также доступна онлайн: Welcome to clickhouse-driver.
Я собрал deb-пакеты с модулем clickhouse-driver, воспользовавшись своей статьёй Создание deb-пакетов для модулей Python. Вместе с этим модулем понадобилось также собрать deb-пакеты с требуемыми ей модулями clickhouse-cityhash и zstd.

Обслуживание таблиц

Для удаления устаревших секций таблиц и для принудительного слияния фрагментов секций я написал скрипт на Python, который назвал maintein_tables.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from clickhouse_driver import Client
from datetime import datetime, timedelta

try:
    c = Client(host='localhost',
               port=9000,
               connect_timeout=3,
               database='zabbix',
               user='zabbix',
               password='zabbix')
except clickhouse_driver.errors.ServerException:
    print >>sys.stderr, 'Cannot connect to database'
    sys.exit(1)

def maintein_table(c, database, table, keep_interval):
    """
    Удаление устаревших разделов указанной таблицы и оптимизация оставшихся разделов
    
    c - подключение к базе данных 
    database - имя базы данных, в которой нужно произвести усечение таблицы
    table - имя таблицы, которую нужно усечь
    keep_interval - период, данные за который нужно сохранить, тип - timedelta
    """
    now = datetime.now()

    rows = c.execute('''SELECT partition,
                               COUNT(*)
                        FROM system.parts
                        WHERE database = '%s'
                          AND table = '%s'
                        GROUP BY partition
                        ORDER BY partition
                     ''' % (database, table))
    for partition, num in rows:
        if now - datetime.strptime(partition, '%Y%m%d') > keep_interval:
            print 'drop partition %s %s %s' % (database, table, partition)
            c.execute('ALTER TABLE %s.%s DROP PARTITION %s' % (database, table, partition))
        elif num > 1:
            print 'optimize partition %s %s %s' % (database, table, partition)
            c.execute('OPTIMIZE TABLE %s.%s PARTITION %s FINAL DEDUPLICATE' % (database, table, partition))

maintein_table(c, 'zabbix', 'trends', timedelta(days=3650))
maintein_table(c, 'zabbix', 'trends_uint', timedelta(days=3650))
maintein_table(c, 'zabbix', 'real_history', timedelta(days=365))
maintein_table(c, 'zabbix', 'real_history_uint', timedelta(days=365))
maintein_table(c, 'zabbix', 'real_history_str', timedelta(days=7))
maintein_table(c, 'zabbix', 'real_history_text', timedelta(days=7))
maintein_table(c, 'zabbix', 'real_history_log', timedelta(days=7))
c.disconnect()
Запрос для удаления устаревших секций таблиц уже был приведён, а для слияния фрагментов секций таблиц в скрипте используется запрос следующего вида:
OPTIMIZE TABLE history PARTITION 20200521 FINAL DEDUPLICATE;
В примере скрипт настроен на хранение числовых исторических данных в течение года, текстовых и журнальных данных - в течение семи дней и тенденций в течение 10 лет. При необходимости можно поменять настройки подключения к серверу Clickhouse и настройки длительности хранения данных в таблицах.

Скрипт можно также скачать по ссылке maintein_tables.py

Копирование данных

Михаил Макуров реализовал поддержку хранения исторических данных Zabbix в ClickHouse на основе поддержки хранения исторических данных в ElasticSearch. В документации Zabbix упоминается, что при хранении исторических данных в ElasticSearch таблицы тенденций не используются. Стало быть, таблицы тенденций не используются и при хранении исторических данных в ClickHouse. Для решения этой проблемы были созданы материализованные представления trends и trends_uint, которые описаны выше.

При переключении существующей инсталляции Zabbix на использование ClickHouse или ElasticSearch не составляет особого труда перенести содержимое таблиц истории из старого хранилища в новое. А вот таблицы тенденций копировать просто некуда. Скопировать их можно было бы в таблицы истории, но в таблицах тенденций нет точных значений, а есть лишь минимальные, средние и максимальные значения за час, а также количество значений в исходной выборке. Чтобы сохранить возможность видеть данные из таблиц тенденций на графиках, можно попытаться сгенерировать выборку, удовлетворяющую этим условиям, и поместить получившиеся значения в таблицы истории.

Поскольку и в дальнейшем хотелось бы иметь возможность просматривать графики за тот период, который изначально был выбран для таблиц тенденций, такой подход позволил бы сразу оценить:
  • сколько места на диске займёт точная история за период, аналогичный периоду хранения таблиц тенденций,
  • насколько хорошо ClickHouse будет справляться с такой нагрузкой.
Итак, кроме функций копирования содержимого таблиц истории, понадобятся также функции для генерирования правдоподобных исторических данных на основе таблиц тенденций.

Скрипт copy_data.py выполняет полное копирование таблиц истории, а также дополняет таблицы истории правдоподобными данными, сгенерированными на основе таблиц тенденций.

В начале скрипта можно найти настройки, которые будут использоваться для подключения к базе данных с исходными данными и к целевой базе данных. В конце скрипта можно найти вызовы функций копирования данных:
copy_history('history_str', ('itemid', 'clock', 'ns', 'value'))
copy_history('history_text', ('itemid', 'clock', 'ns', 'value'))
copy_history('history_log', ('itemid', 'clock', 'timestamp', 'source', 'severity', 'value', 'logeventid', 'ns'))

clock_min, clock_max = copy_history('history', ('itemid', 'clock', 'ns', 'value'), interval=10800)
copy_trends('trends', 'history', clock_max=clock_min)

clock_min, clock_max = copy_history('history_uint', ('itemid', 'clock', 'ns', 'value'), interval=1800)
copy_trends('trends_uint', 'history_uint', clock_max=clock_min, int_mode=True)
Сначала копируется содержимое таблиц history_str, history_text и history_log, потом копируется таблица history порциями по 3 часа, потом в таблицу history вносятся данные, сгенерированные из данных таблицы trends, и, наконец, таблица history_uint копируется порциями по полчаса и в неё вносятся данные, сгенерированные из данных таблицы trends_uint.

Функция copy_history перед началом работы выполняет запрос, который находит минимальное и максимальное значение отметок времени в таблице. Этот запрос может выполняться очень долго, поэтому в функциях предусмотрена возможность указания минимального и максимального значения отметок времени в аргументах clock_min и clock_max. Кроме того, указывая эти значения вручную, можно точно настраивать период времени, данные за который нужно обработать. Это может быть полезно, например, для того, чтобы скопировать данные до конца суток, предшествующих переключению Zabbix на Clickhouse. После переключения можно указать период времени, за который накопились новые данные с момента прошлого копирования.

Если вы не собираетесь копировать данные из таблиц тенденций, то вызовы функций copy_trends можно закомментировать. Можно скопировать тенденции после переключения Zabbix на Clickhouse - скрипт не имеет жёстко предписанной последовательности действий и может быть адаптирован под необходимую вам последовательность действий.

Скрипт осуществляет вставку данных в ClickHouse порциями по 1048576 строк. Размер порции можно настраивать при помощи аргумента portion функций copy_history и copy_trends. При настройке portion стоит учитывать, что объём вставляемых за один раз данных не должно превышать значения настройки max_memory_usage из файла конфигурации /etc/clickhouse-server/users.xml сервера Clickhouse.

В процессе тестирования скрипта при переносе данных из базы данных PostgreSQL выяснилась интересная особенность: PostgreSQL не поддерживает колонки с числами с плавающей запятой, а вместо этого используется тип Decimal, имеющий более ограниченную точность в представлении данных. Из-за меньшей точности данные в таблице тенденций чисел с плавающей запятой оказывается невозможно сгенерировать такую правдоподобную выборку данных, которая точно соответствовала бы указанным свойствам. Если сгенерированная выборка не удовлетворяет указанным свойствам, то сгенерированные данные всё-таки вставляются в таблицу истории, но скрипт выдаёт предупреждение о подозрительных данных тенденций.

Скрипт не приведён в статье из-за его относительно большого объёма. Скрипт можно взять по ссылке copy_data.py

Пасхальное яйцо

При выходе из клиента ClickHouse 1 января 2020 года заметил поздравление с новым годом:
db.server.tld :) exit
Happy new year.

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

Установка и настройка сервера ClickHouse

Пакеты с клиентом и сервером Clickhouse имеются в официальных репозиториях Debian Buster. Для их установки можно воспользоваться следующей командой:
# apt-get install clickhouse-server clickhouse-client
Для работы серверу Clickhouse требуется поддержка дополнительных процессорных инструкций SSE 4.2. Чтобы проверить наличие поддержки этих инструкций и пересобрать Clickhouse, если они не поддерживаются, обратитесь к статье Пересборка Clickhouse для процессоров без поддержки SSE 4.2.

В каталоге /etc/clickhouse-server находится файл config.xml с настройками сервера и файл users.xml с настройками пользователей. Оба файла хорошо прокомментированы, но из-за обилия настроек ориентироваться в них довольно тяжело. Я переименовал эти файлы, чтобы создать более компактные файлы конфигурации:
# cd /etc/clickhouse-server/
# cp users.xml users.xml.sample
# cp config.xml config.xml.sample
В файл конфигурации config.xml я вписал следующие настройки:
<?xml version="1.0"?>
<yandex>
    <logger>
        <level>warning</level>
        <log>/var/log/clickhouse-server/clickhouse-server.log</log>
        <errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
        <size>10M</size>
        <count>10</count>
    </logger>
    <display_name>ufa</display_name>
    <http_port>8123</http_port>
    <tcp_port>9000</tcp_port>
    <listen_host>0.0.0.0</listen_host>
    <max_connections>4096</max_connections>
    <keep_alive_timeout>3</keep_alive_timeout>
    <max_concurrent_queries>16</max_concurrent_queries>
    <uncompressed_cache_size>1073741824</uncompressed_cache_size>
    <mark_cache_size>5368709120</mark_cache_size>
    <path>/var/lib/clickhouse/</path>
    <tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
    <user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
    <users_config>users.xml</users_config>
    <default_profile>default</default_profile>
    <default_database>zabbix</default_database>
    <timezone>Asia/Yekaterinburg</timezone>
    <mlock_executable>true</mlock_executable>
    <builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>
    <max_session_timeout>3600</max_session_timeout>
    <default_session_timeout>60</default_session_timeout>
    <max_table_size_to_drop>0</max_table_size_to_drop>
    <max_partition_size_to_drop>0</max_partition_size_to_drop>
    <format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
</yandex>
Смысл большинства настроек можно понять из их названия. Кратко опишу некоторые из них:
  • display_name - отображаемое в клиенте имя сервера,
  • max_connections - максимальное количество подключений от клиентов,
  • max_concurrent_queries - максимальное количество одновременно обрабатываемых запросов. Т.к. каждый запрос обслуживается конвейером из нескольких потоков, то каждый запрос порождает нагрузку как минимум на одно процессорное ядро. Лучше всего будет выполнять одновременно количество запросов, не превышающее количество процессорных ядер сервера или виртуальной машины.
  • uncompressed_cache_size задаёт размер кэша несжатых данных в байтах. Если предполагается, что на сервере часто будут выполняться короткие запросы, этот кэш поможет снизить нагрузку на дисковую подсистему. Обратите внимание, что в настройках пользователя должно быть разрешено использование кэша несжатых данных в опции use_uncompressed_cache.
  • mark_cache_size - кэш меток. Метки являются своего рода индексами данных. Сервер Clickhouse не хочет запускаться, если значение этой настройки меньше 5 гигабайт. Хорошая новость в том, что память под этот кэш будет выделяться по мере необходимости.
  • path - путь к файлам базы данных,
  • default_database - имя базы данных, с которой будут работать клиенты, не указавшие какую-то определённую базу данных,
  • timezone - часовой пояс сервера.
Файл users.xml я привёл к следующему виду:
<?xml version="1.0"?>
<yandex>
    <users>
        <zabbix>
            <password>zabbix</password>
            <networks>
                <ip>127.0.0.1</ip>
            </networks>
            <profile>default</profile>
            <quota>default</quota>
        </zabbix>
    </users>
    <profiles>
        <default>
            <max_memory_usage>2147483648</max_memory_usage>
            <max_query_size>1048576</max_query_size>
            <max_ast_elements>1000000</max_ast_elements>
            <use_uncompressed_cache>1</use_uncompressed_cache>
            <load_balancing>random</load_balancing>
            <readonly>0</readonly>
        </default>
        <readonly>
            <readonly>1</readonly>
        </readonly>
    </profiles>
    <quotas>
        <default>
            <interval>
                <duration>3600</duration>
                <queries>0</queries>
                <errors>0</errors>
                <result_rows>0</result_rows>
                <read_rows>0</read_rows>
                <execution_time>0</execution_time>
            </interval>
        </default>
    </quotas>
</yandex>
Файл состоит из трёх секций:
  • users - пользователи базы данных. Каждый пользователь содержит ссылку на профиль и квоту,
  • profiles - профили содержат настройки пользователей,
  • quotas - квоты содержат ограничения на выполнение запросов от пользователей.
В примере конфигурации выше описан пользователь zabbix с паролем zabbix, который может устанавливать подключения к серверу только с IP-адреса 127.0.0.1, использует профиль default и квоту default.

В профиле default выставлены следующие настройки:
  • max_memory_usage - максимальный объём памяти, который сервер может выделить пользователю для обработки его запросов, в примере настроено ограничение в 2 гигабайта,
  • max_query_size - максимальный размер одного запроса, по умолчанию - 256 килобайт, в примере - 1 мегабайт,
  • max_ast_elements - максимальное количество элементов в дереве синтаксического разбора, по умолчанию - 50 тысяч элементов, в примере - 1 миллион элементов,
  • use_uncompressed_cache - значение этой опции разрешает или запрещает использование кэша несжатых данных, в примере значение 1 разрешает его использование,
  • readonly - значение этой опции разрешает или запрещает запросы на изменение данных, в примере значение 0 разрешает изменение данных.
В квоте default выставлено единственное ограничение - длительность обработки запроса ограничена одним часом.

Включим автозапуск сервера:
# systemctl enable clickhouse-server.service
Запустим сервер:
# systemctl start clickhouse-server.service

Решение проблем

Если спустя некоторое время в журнале /var/log/clickhouse-server/clickhouse-server.err.log появляются ошибки следующего вида:
2020.04.17 10:44:51.741280 [ 6317714 ] {} <Error> HTTPHandler: std::exception. Code: 1001, type: std::system_error, e.what() = Resource temporarily unavailable
То может помочь увеличение переменной ядра vm.max_map_count следующей командой:
# sysctl -w vm.max_map_count = 524288
Если изменение этой настройки помогло справиться с проблемой, можно прописать её в файл /etc/sysctl.conf, чтобы оно автоматически применялось при загрузке системы:
vm.max_map_count=524288
В документации ядра Linux эта переменная ядра объясняется следующим образом:
This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries.

While most applications need less than a thousand maps, certain programs, particularly malloc debuggers, may consume lots of them, e.g., up to one or two maps per allocation.

The default value is 65536.
Перевод:
Этот файл содержит максимальное количество участков памяти, которое может иметь процесс. Участки памяти косвенно создаются при вызове malloc, а напрямую - при вызове mmap и mprotect, а также при загрузке разделяемых библиотек.

Хотя большинству приложений требуется меньше тысячи участков, некоторые программы, в частности отладчики malloc, могут потреблять значительное их количество, от одного до двух участков при каждом выделении памяти.

Значение по умолчанию - 65536.

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

Пересборка ClickHouse для процессоров без поддержки SSE 4.2

В официальных репозиториях Debian Buster появились пакеты с сервером и клиентом Clickhouse. По умолчанию сервер Clickhouse собран с использованием процессорных инструкций SSE 4.2, т.к. именно такие системные требования указаны на официальной странице проекта.

Для проверки, поддерживает ли процессор SSE 4.2, можно воспользоваться следующей командой:
$ grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 not supported"
При попытке запустить сервер ClickHouse на процессоре, не поддерживающем этот набор инструкций, в журнале /var/log/messages можно будет обнаружить сообщения следующего вида:
Aug  9 18:33:45 buster kernel: [    7.571795] traps: clickhouse-serv[257] trap invalid opcode ip:7f89f23 sp:7ffda2789a98 error:0 in clickhouse[400000+f8a5000]
Мой домашний компьютер не отличается новизной, поэтому для экспериментов дома мне пришлось пересобрать пакеты с Clickhouse. Сборочные скрипты автоматически определяют поддержку инструкций SSE 4.2 и при её отсутствии выполняют сборку так, чтобы пакеты работали без них.

Впишем в файл /etc/apt/sources.list дополнительные репозитории с исходными текстами:
deb-src http://mirror.yandex.ru/debian/ buster main contrib non-free
deb-src http://mirror.yandex.ru/debian/ buster-updates main contrib non-free
deb-src http://mirror.yandex.ru/debian/ buster-proposed-updates main contrib non-free
deb-src http://mirror.yandex.ru/debian-security/ buster/updates main contrib non-free
Обновим список пакетов, доступных через репозитории:
# apt-get update
Установим пакеты, которые потребуются нам для сборки ClickHouse из исходных текстов:
# apt-get build-dep clickhouse
И скачаем пакет с исходными текстами:
$ apt-get source clickhouse
Переходим в каталог с распакованными исходными текстами, запускаем dch и описываем изменения.
$ cd clickhouse-18.16.1+ds
$ dch -i
В открывшемся редакторе дописываем к номеру версии fix1 и описываем изменения:
clickhouse (18.16.1+ds-4fix1) UNRELEASED; urgency=medium

  * Version with no need CPU with support SSE4.2 instruction set..

 -- Vladimir Stupin <vladimir@stupin.su>  Tue, 14 Jan 2020 11:28:11 +0500
Собираем пакет:
$ debuild -us -uc
Для сборки потребуется довольно много оперативной памяти. Я пытался собрать пакет на виртуальной машине с 2 гигабайтами оперативной памяти, потом увеличил до 3 и до 4, но этого объёма оказывалось по-прежнему недостаточно для того, чтобы собрать библиотеку libclickhouse.so из объектных файлов. Вернул виртуальной машине 2 гигабайта оперативной памяти и подключил раздел подкачки размером 8 гигабайт. Сборка шла долго, но всё-таки завершилась успешно.

Если сборка завершается неудачно, а в тексте ошибки имеются такие строки:
CMake Error: Error: generator : Unix Makefiles
Does not match the generator used previously: Ninja
Either remove the CMakeCache.txt file and CMakeFiles directory or choose a different binary directory.
То можно попробовать удалить пакет ninja-build:
# apt-get purge ninja-build
Затем можно попробовать запустить сборку пакета снова.

После успешной сборки можно будет выйти из сборочного каталога и установить появившиеся рядом с ним двоичные пакеты:
# dpkg -i clickhouse-server_18.16.1+ds-4fix1_amd64.deb clickhouse-client_18.16.1+ds-4fix1_amd64.deb clickhouse-common_18.16.1+ds-4fix1_amd64.deb
Или можно воспользоваться утилитой aptly, чтобы создать собственный репозиторий и поместить в него эти пакеты. В таком случае для установки пакетов в систему будет достаточно:
  1. подключить этот репозиторий в файле /etc/apt/sources.list,
  2. обновить список пакетов, доступных через репозитории, командой apt-get update,
  3. поставить пакеты, например, командой apt-get install clickhouse-server clickhouse-client

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

Ошибки аутентификации SNMPv3 в Zabbix 3.4

Для контроля доступности устройства по SNMP обычно я использую элементы данных типа «Внутренний Zabbix» с ключом «zabbix[host,snmp,available]», который описан на странице документации 8 Внутренние проверки. Подробнее о недоступности узлов можно прочитать здесь: 12 Настройки недостижимости/недоступности хостов. Итак, когда сервер Zabbix решает, что узел больше не доступен по SNMP, значение элемента данных с ключом «zabbix[host,snmp,available]» становится равным нулю. Можно настроить триггер, который будет срабатывать при нулевом значении этого ключа.

После того, как в сети появились узлы, опрашиваемые по SNMPv3, отсутствие срабатываний этого триггера перестало быть надёжным критерием доступности узла по SNMP. Все элементы данных SNMPv3 на узле могут быть в неподдерживаемом состоянии, однако Zabbix при этом считает узел доступным по SNMP и триггер не срабатывает. Как выяснилось, Zabbix не считает проблемой, если узел ответил ошибкой аутентификации. Формально устройство действительно отвечает по протоколу SNMP, но фактически данные с него не снимаются.

Я решил испрвить эту ситуацию, в очередной раз внеся правку в исходный текст Zabbix. К счастью, сделать это оказалось совсем не сложно. Интересующий нас фрагмент кода находится в файле src/zabbix_server/poller/checks_snmp.c в функции zbx_get_snmp_response_error. Удалим специальную обработку ошибок аутентификации SNMPv3, интерпретируя эти ошибки как недоступность элемента данных:
Index: zabbix-3.4.12-1+buster/src/zabbix_server/poller/checks_snmp.c
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/zabbix_server/poller/checks_snmp.c
+++ zabbix-3.4.12-1+buster/src/zabbix_server/poller/checks_snmp.c
@@ -391,17 +391,7 @@ static int zbx_get_snmp_response_error(c
        {
                zbx_snprintf(error, max_error_len, "Cannot connect to \"%s:%hu\": %s.",
                                interface->addr, interface->port, snmp_api_errstring(ss->s_snmp_errno));
-
-               switch (ss->s_snmp_errno)
-               {
-                       case SNMPERR_UNKNOWN_USER_NAME:
-                       case SNMPERR_UNSUPPORTED_SEC_LEVEL:
-                       case SNMPERR_AUTHENTICATION_FAILURE:
-                               ret = NOTSUPPORTED;
-                               break;
-                       default:
-                               ret = NETWORK_ERROR;
-               }
+               ret = NETWORK_ERROR;
        }
        else if (STAT_TIMEOUT == status)
        {
Эту тривиальную заплатку можно взять по ссылке zabbix3_4_12_snmpv3_auth_errors.patch.