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

Настройки таймаута и количества попыток для запросов SNMP в Zabbix 3.4

До версии 2.2.0 в Zabbix для опроса по SNMP использовались настройки опроса по умолчанию. По умолчанию в библиотеке SNMP таймаут опроса составлял 1 секунду, а в случае неудачи делалось до 5 дополнительных попыток опроса. В Zabbix 2.2 для опроса по SNMP используется значение опции Timeout из файла конфигурации и делается только одна попытка опроса.

Ранее, чтобы уменьшить количество вызовов скриптов внешнего опроса, в конфигурации моих сереров Zabbix было выставлено максимально возможное значение таймаута - 30 секунд, а каждый такой скрипт запрашивал как можно больше значений у устройства и отправлял полученные значения в Zabbix при помощи утилиты zabbix_sender. Если скрипт успевал снять все необходимые данные, укладываясь в отведённые для его работы 30 секунд, то всё хорошо. Если скрипт не укладывался в 30 секунд, то процедуру опроса делили на несколько частей, так чтобы каждая из них уложилась в 30 секунд. Затем в Zabbix'е заводили по отдельному элементу данных для вызова скрипта, указывая ему какую именно часть данных нужно снять с устройства.

При переходе с Zabbix 2.0 на 2.2 изменение настроек таймаута SNMP привело к большим проблемам: использование процессов Poller выросло до 100% и сервер перестал успевать опрашивать оборудование в необходимом темпе. Происходило это потому, что первый же запрос SNMP к недоступному устройству растягивался до 30 секунд, в течение которых процесс просто ждал ответа от устройства, не занимаясь больше ничем.

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

Получившиеся исправления ранее уже были описаны в рамках более крупной статьи Установка и настройка Zabbix 2.2.0 в Debian Wheezy, но там им не уделялось достаточного внимания. На этот раз я опишу заплатку более подробно.

Задеклалируем изменения, которые собираемся внести, отредактировав пример файла конфигурации 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
@@ -439,6 +439,26 @@ DBUser=zabbix
 
 Timeout=4
 
+### Option: SNMPTimeout
+#      Specifies how long we wait for SNMP device (in seconds).
+#
+# Mandatory: no
+# Range: 1-30
+# Default:
+# SNMPTimeout=1
+
+SNMPTimeout=1
+
+### Option: SNMPRetries
+#      Specifies how many times to trying request for SNMP device
+#
+# Mandatory: no
+# Range: 1-10
+# Default:
+# SNMPRetries=3
+
+SNMPRetries=3
+
 ### Option: TrapperTimeout
 #      Specifies how many seconds trapper may spend processing new data.
 #
Объявим переменные CONFIG_SNMP_TIMEOUT и CONFIG_SNMP_RETRIES в том же файле, где объявлена переменная CONFIG_TIMEOUT. Это файл src/libs/zbxconf/cfg.c:
Index: zabbix-3.4.12-1+buster/src/libs/zbxconf/cfg.c
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/libs/zbxconf/cfg.c
+++ zabbix-3.4.12-1+buster/src/libs/zbxconf/cfg.c
@@ -31,6 +31,8 @@ char  *CONFIG_LOG_FILE        = NULL;
 int    CONFIG_LOG_FILE_SIZE    = 1;
 int    CONFIG_ALLOW_ROOT       = 0;
 int    CONFIG_TIMEOUT          = 3;
+int    CONFIG_SNMP_TIMEOUT     = 1;
+int    CONFIG_SNMP_RETRIES     = 3;
 
 static int     __parse_cfg_file(const char *cfg_file, struct cfg_line *cfg, int level, int optional, int strict);
 
Пропишем эти же объявления в заголовочный файл include/cfg.h, на этот раз - как внешние объявления:
Index: zabbix-3.4.12-1+buster/include/cfg.h
===================================================================
--- zabbix-3.4.12-1+buster.orig/include/cfg.h
+++ zabbix-3.4.12-1+buster/include/cfg.h
@@ -46,6 +46,8 @@ extern char   *CONFIG_LOG_FILE;
 extern int     CONFIG_LOG_FILE_SIZE;
 extern int     CONFIG_ALLOW_ROOT;
 extern int     CONFIG_TIMEOUT;
+extern int     CONFIG_SNMP_TIMEOUT;
+extern int     CONFIG_SNMP_RETRIES;
 
 struct cfg_line
 {
Теперь нужно научить сервер Zabbix и Zabbix-прокси извлекать значения из файлов конфигурации в эти переменные. Отредактируем файлы src/zabbix_server/server.c и src/zabbix_proxy/proxy.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
@@ -602,6 +602,10 @@ static void        zbx_load_config(ZBX_TASK_EX
                        PARM_OPT,       0,                      0},
                {"Timeout",                     &CONFIG_TIMEOUT,                        TYPE_INT,
                        PARM_OPT,       1,                      30},
+               {"SNMPTimeout",                 &CONFIG_SNMP_TIMEOUT,                   TYPE_INT,
+                       PARM_OPT,       1,                      30},
+               {"SNMPRetries",                 &CONFIG_SNMP_RETRIES,                   TYPE_INT,
+                       PARM_OPT,       1,                      10},
                {"TrapperTimeout",              &CONFIG_TRAPPER_TIMEOUT,                TYPE_INT,
                        PARM_OPT,       1,                      300},
                {"UnreachablePeriod",           &CONFIG_UNREACHABLE_PERIOD,             TYPE_INT,
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
@@ -623,6 +623,10 @@ static void        zbx_load_config(ZBX_TASK_EX
                        PARM_OPT,       0,                      0},
                {"Timeout",                     &CONFIG_TIMEOUT,                        TYPE_INT,
                        PARM_OPT,       1,                      30},
+               {"SNMPTimeout",                 &CONFIG_SNMP_TIMEOUT,                   TYPE_INT,
+                       PARM_OPT,       1,                      30},
+               {"SNMPRetries",                 &CONFIG_SNMP_RETRIES,                   TYPE_INT,
+                       PARM_OPT,       1,                      10},
                {"TrapperTimeout",              &CONFIG_TRAPPER_TIMEOUT,                TYPE_INT,
                        PARM_OPT,       1,                      300},
                {"UnreachablePeriod",           &CONFIG_UNREACHABLE_PERIOD,             TYPE_INT,
Реализация функций опроса по SNMP находится в файле src/zabbix_server/poller/checks_snmp.c, но прежде чем редактировать его, заменим в заголовочном файле src/zabbix_server/poller/checks_snmp.h объявление внешней переменной CONFIG_TIMEOUT на объявления внешних переменных CONFIG_SNMP_TIMEOUT и CONFIG_SNMP_RETRIES:
Index: zabbix-3.4.12-1+buster/src/zabbix_server/poller/checks_snmp.h
===================================================================
--- zabbix-3.4.12-1+buster.orig/src/zabbix_server/poller/checks_snmp.h
+++ zabbix-3.4.12-1+buster/src/zabbix_server/poller/checks_snmp.h
@@ -26,7 +26,8 @@
 #include "sysinfo.h"
 
 extern char    *CONFIG_SOURCE_IP;
-extern int     CONFIG_TIMEOUT;
+extern int     CONFIG_SNMP_TIMEOUT;
+extern int     CONFIG_SNMP_RETRIES;
 
 #ifdef HAVE_NETSNMP
 void   zbx_init_snmp(void);
А теперь можно отредактировать сам файл src/zabbix_server/poller/checks_snmp.c:
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
@@ -456,8 +456,10 @@ static struct snmp_session *zbx_snmp_ope
                        break;
        }
 
-       session.timeout = CONFIG_TIMEOUT * 1000 * 1000; /* timeout of one attempt in microseconds */
-                                                       /* (net-snmp default = 1 second) */
+       session.timeout = CONFIG_SNMP_TIMEOUT * 1000 * 1000;    /* timeout of one attempt in microseconds */
+                                                               /* (net-snmp default = 1 second) */
+       session.retries = CONFIG_SNMP_RETRIES - 1;              /* number of retries after failed attempt */
+                                                               /* (net-snmp default = 5) */
 
 #ifdef HAVE_IPV6
        if (SUCCEED != get_address_family(item->interface.addr, &family, error, max_error_len))
@@ -1095,7 +1097,7 @@ static int        zbx_snmp_walk(struct snmp_ses
                        pdu->max_repetitions = max_vars;
                }
 
-               ss->retries = (0 == bulk || (1 == max_vars && == level) ? 1 : 0);
+               ss->retries = (0 == bulk || (1 == max_vars && 0 == level) ? 1 : 0) * (CONFIG_SNMP_RETRIES - 1);
 
                /* communicate with agent */
                status = snmp_synch_response(ss, pdu, &response);
@@ -1304,7 +1306,7 @@ static int        zbx_snmp_get_values(struct sn
                goto out;
        }
 
-       ss->retries = (1 == mapping_num && 0 == level ? 1 : 0);
+       ss->retries = (1 == mapping_num && 0 == level ? 1 : 0) * (CONFIG_SNMP_RETRIES - 1);
 retry:
        status = snmp_synch_response(ss, pdu, &response);
 
При помощи команды «grep -R CONFIG_TIMEOUT | grep -i snmp» в исходных текстах можно найти ещё один любопытный фрагмент в файле libs/zbxdbcache/dbconfig.c, где можно увидеть отключение повторных попыток опроса по SNMP всего узла, если он не отвечает по SNMP:
static void DCincrease_disable_until(const ZBX_DC_ITEM *item, ZBX_DC_HOST *host, int now)
{
    switch (item->type)
    {
        case ITEM_TYPE_ZABBIX:
            if (0 != host->errors_from)
                host->disable_until = now + CONFIG_TIMEOUT;
            break;
        case ITEM_TYPE_SNMPv1:
        case ITEM_TYPE_SNMPv2c:
        case ITEM_TYPE_SNMPv3:
            if (0 != host->snmp_errors_from)
                host->snmp_disable_until = now + CONFIG_TIMEOUT;
            break;
        case ITEM_TYPE_IPMI:
            if (0 != host->ipmi_errors_from)
                host->ipmi_disable_until = now + CONFIG_TIMEOUT;
            break;
        case ITEM_TYPE_JMX:
            if (0 != host->jmx_errors_from)
                host->jmx_disable_until = now + CONFIG_TIMEOUT;
            break;
        default:
            /* nothing to do */;
    }
}
Вопрос, стоит ли исправлять значение таймаута для проверок SNMP в этой функции, я оставлю на размышление. Если значение CONFIG_SNMP_TIMEOUT меньше CONFIG_TIMEOUT, то опрос по SNMP после временных перебоев будет приостанавливаться на меньшее время и, соответственно, восстанавливаться быстрее. Нагрузка на процессы Poller при этом может слегка повыситься, т.к. доступность узлов SNMP будет проверяться чаще. Если вы всё же решитесь поменять значение таймаута в этой функции, то вот очевидный патч для файла libs/zbxdbcache/dbconfig.c:
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
@@ -448,7 +448,7 @@ static void DCincrease_disable_until(con
                case ITEM_TYPE_SNMPv2c:
                case ITEM_TYPE_SNMPv3:
                        if (0 != host->snmp_errors_from)
-                               host->snmp_disable_until = now + CONFIG_TIMEOUT;
+                               host->snmp_disable_until = now + CONFIG_SNMP_TIMEOUT;
                        break;
                case ITEM_TYPE_IPMI:
                        if (0 != host->ipmi_errors_from)
Полную версию описанной здесь заплатки можно взять по ссылке zabbix3_4_12_snmp_timeout_retries.patch.

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

Исправление доступа к периодам обслуживания в Zabbix 3.4

В статье Исправление ручного закрытия проблем в Zabbix 3.4 я уже описывал, как дать права на ручное закрытие проблем пользователям, не имеющим прав редактирования узла. Похожая ситуация возникла и с редактированием периодов обслуживания. Для того, чтобы предоставить пользователю права редактировать периоды обслуживания, нужно дать ему права на редактирование самого узла. Пользователь, имеющий доступ к редактированию узла в Zabbix, может злоупотребить своими правами и просто снять с контроля узел на время его обслуживания. Ещё хуже, если этот пользователь по окончании обслуживания забудет вернуть узел на контроль.

Чтобы избежать подобных злоупотреблений, может быть лучше выдать права создавать и редактировать периоды обслуживания всем пользователям, имеющим права на просмотр соответствующей группы узлов. Именно такое изменение внёс в исходный код веб-интерфейса Zabbix мой коллега Кирилл. Изменил он два файла: frontends/php/include/classes/api/services/CMaintenance.php и frontends/php/maintenance.php. Первый файл исправляет права доступа в методах API maintenance, причём права удалять периоды обслуживания при помощи метода delete пользователю, не имеющему доступа к группе узлов, не предоставляется. Второй файл исправляет права доступа к периодам обслуживания непосредственно через сам веб-интерфейс Zabbix. Получившаяся заплатка выглядит следующим образом:
Index: zabbix-3.4.12-1+buster/frontends/php/include/classes/api/services/CMaintenance.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/include/classes/api/services/CMaintenance.php
+++ zabbix-3.4.12-1+buster/frontends/php/include/classes/api/services/CMaintenance.php
@@ -276,7 +276,6 @@ class CMaintenance extends CApiService {
                // hosts permissions
                $options = [
                        'hostids' => $hostids,
-                       'editable' => true,
                        'output' => ['hostid'],
                        'preservekeys' => true
                ];
@@ -289,7 +288,6 @@ class CMaintenance extends CApiService {
                // groups permissions
                $options = [
                        'groupids' => $groupids,
-                       'editable' => true,
                        'output' => ['groupid'],
                        'preservekeys' => true
                ];
@@ -458,7 +456,6 @@ class CMaintenance extends CApiService {
                        'selectGroups' => ['groupid'],
                        'selectHosts' => ['hostid'],
                        'selectTimeperiods' => API_OUTPUT_EXTEND,
-                       'editable' => true,
                        'preservekeys' => true
                ]);
 
@@ -580,7 +577,6 @@ class CMaintenance extends CApiService {
                        $db_hosts = API::Host()->get([
                                'output' => [],
                                'hostids' => $hostids,
-                               'editable' => true,
                                'preservekeys' => true
                        ]);
 
@@ -598,7 +594,6 @@ class CMaintenance extends CApiService {
                        $db_groups = API::HostGroup()->get([
                                'output' => [],
                                'groupids' => $groupids,
-                               'editable' => true,
                                'preservekeys' => true
                        ]);
 
Index: zabbix-3.4.12-1+buster/frontends/php/maintenance.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/maintenance.php
+++ zabbix-3.4.12-1+buster/frontends/php/maintenance.php
@@ -105,7 +105,6 @@ if (isset($_REQUEST['maintenanceid'])) {
        $dbMaintenance = API::Maintenance()->get([
                'output' => API_OUTPUT_EXTEND,
                'selectTimeperiods' => API_OUTPUT_EXTEND,
-               'editable' => true,
                'maintenanceids' => getRequest('maintenanceid'),
        ]);
        if (empty($dbMaintenance)) {
@@ -412,7 +411,6 @@ if (!empty($data['form'])) {
                        'maintenanceids' => $data['maintenanceid'],
                        'real_hosts' => true,
                        'output' => ['hostid'],
-                       'editable' => true
                ]);
                $data['hostids'] = zbx_objectValues($data['hostids'], 'hostid');
 
@@ -458,7 +456,6 @@ if (!empty($data['form'])) {
 
        // get groups
        $data['all_groups'] = API::HostGroup()->get([
-               'editable' => true,
                'output' => ['groupid', 'name'],
                'real_hosts' => true,
                'preservekeys' => true
@@ -475,7 +472,6 @@ if (!empty($data['form'])) {
        $data['hosts'] = API::Host()->get([
                'output' => ['hostid', 'name'],
                'real_hosts' => true,
-               'editable' => true,
                'groupids' => $data['twb_groupid']
        ]);
 
@@ -483,7 +479,6 @@ if (!empty($data['form'])) {
        $hostsSelected = API::Host()->get([
                'output' => ['hostid', 'name'],
                'real_hosts' => true,
-               'editable' => true,
                'hostids' => $data['hostids']
        ]);
        $data['hosts'] = array_merge($data['hosts'], $hostsSelected);
@@ -532,7 +527,6 @@ else {
                'search' => [
                        'name' => ($filter['name'] === '') ? null : $filter['name']
                ],
-               'editable' => true,
                'sortfield' => $sortField,
                'sortorder' => $sortOrder,
                'limit' => $config['search_limit'] + 1
Эту заплатку можно взять по ссылке zabbix3_4_12_permit_edit_maintenances.patch.

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

Местонахождение устройства в панели проблем Zabbix 3.4

На стартовой странице веб-интерфейса Zabbix по умолчанию отображается список актуальных проблем. Самая важная информация в этом списке - это время начала проблемы, узел сети, срабтавший триггер и длительность проблемы, но нет никакой информации о местоположении устройства. Если у вас небольшая сеть, расположенная не более чем по нескольким десяткам адресов, то хорошая система именования устройств может решить проблему поиска местонахождения устройства. Если же количество адресов, по которым находятся устройства, достигает нескольких тысяч, то правильное именование устройств становится трудной задачей.

В Zabbix'е к каждому устройству можно прикрепить так называемые «инвентарные данные», среди которых есть поле адреса. Было бы неплохо показывать это поле в списке проблем, чтобы можно было без лишних телодвижений определить адрес устройства. К сожалению, Zabbix не предоставляет для этого штатных средств. Но к счастью, это можно сделать, внеся в исходный текст веб-интерфейса Zabbix небольшую правку.

Интересующий нас виджет находится в файле frontends/php/app/views/monitoring.widget.problems.view.php

Этот виджет фигурирует в списке маршрутов в файле frontends/php/include/classes/mvc/CRouter.php:
'widget.problems.view'  => ['CControllerWidgetProblemsView',    'layout.widget',                'monitoring.widget.problems.view'],
Класс CControllerWidgetProblemsView описан в файле frontends/php/app/controllers/CControllerWidgetProblemsView.php. Именно в этом классе готовятся данные, которые потом будут использованы в виджете для отображения. Данные об узлах, связанных с триггерами, в этом классе формируется при помощи функции getTriggersHostsList.

Определение функции getTriggersHostsList находится в файле frontends/php/include/triggers.inc.php, для получения списка узлов с триггерами используется метод API host.get:
$db_hosts = $hostids
    ? API::Host()->get([
        'output' => ['hostid', 'name', 'status', 'maintenanceid', 'maintenance_status', 'maintenance_type'],
        'selectGraphs' => API_OUTPUT_COUNT,
        'selectScreens' => API_OUTPUT_COUNT,
        'hostids' => array_keys($hostids),
        'preservekeys' => true
    ])
    : [];
Внесём правку, которая добавит в этот список строку местоположения устройства из его инвентарных данных:
Index: zabbix-3.4.12-1+buster/frontends/php/include/triggers.inc.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/include/triggers.inc.php
+++ zabbix-3.4.12-1+buster/frontends/php/include/triggers.inc.php
@@ -2170,6 +2170,7 @@ function getTriggersHostsList(array $tri
                        'output' => ['hostid', 'name', 'status', 'maintenanceid', 'maintenance_status', 'maintenance_type'],
                        'selectGraphs' => API_OUTPUT_COUNT,
                        'selectScreens' => API_OUTPUT_COUNT,
+                       'selectInventory' => ['location'],
                        'hostids' => array_keys($hostids),
                        'preservekeys' => true
                ])
Теперь эти данные нужно отобразить в виджете. Внесём соответствующую правку в файл frontends/php/app/views/monitoring.widget.problems.view.php:
Index: zabbix-3.4.12-1+buster/frontends/php/app/views/monitoring.widget.problems.view.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/app/views/monitoring.widget.problems.view.php
+++ zabbix-3.4.12-1+buster/frontends/php/app/views/monitoring.widget.problems.view.php
@@ -54,6 +54,7 @@ $table = (new CTableInfo())
                $show_recovery_data ? _('Status') : null,
                _('Info'),
                ($data['sortfield'] === 'host') ? [_('Host'), $sort_div] : _('Host'),
+               ($data['sortfield'] === 'location') ? [_('Location'), $sort_div] : _('Location'),
                [
                        ($data['sortfield'] === 'problem') ? [_('Problem'), $sort_div] : _('Problem'),
                        ' • ',
@@ -198,11 +199,19 @@ foreach ($data['data']['problems'] as $e
                ];
        }
 
+       $trigger_hosts = array_values($data['data']['triggers_hosts'][$trigger['triggerid']]);
+       $locations = array();
+       foreach($trigger_hosts as $host)
+       {
+               $locations[] = $host['inventory']['location'];
+       }
+
        $table->addRow(array_merge($row, [
                $show_recovery_data ? $cell_r_clock : null,
                $show_recovery_data ? $cell_status : null,
                makeInformationList($info_icons),
                $triggers_hosts[$trigger['triggerid']],
+               join(', ', $locations),
                $description,
                (new CCol(
                        ($problem['r_eventid'] != 0)
Как видно, в правке:
  1. в таблицу был добавлен заголовок новой колонки Location,
  2. по каждому из триггеров формируется строка со списком адресов узлов, на значения элементов данных из которых опирается этот триггер,
  3. строки с адресами через запятую с пробелом склеиваются в одну строку,
  4. полученная строка добавляется в строку таблицы, в колонку Location.
Готовую заплатку можно взять по ссылке zabbix3_4_12_frontend_location.patch.

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

Оптимизация Linux при использовании SSD

В прошлых статьях были описаны процедура обновления прошивки твердотельного накопителя Micron модели SSD 5200 MAX и шаблон Zabbix для контроля основных показателей его состояния. В этой статье пойдёт речь о том, какие дополнительные настройки Linux можно сделать для того, чтобы увеличить производительность системы при работе с твердотельными накопителями и увеличить срок службы самих накопителей.

Изменение планировщика ввода-вывода

По умолчанию Linux использует планировщик ввода-вывода cfq, который стремится упорядочить блоки данных так, чтобы уменьшить количество позиционирований головки с дорожки на дорожку диска. Для SSD это не имеет смысла, но приводит к задержке мелких операций ввода-вывода. Вместо планировщика cfq рекомендуется использовать планировщик deadline, который стремится сократить время ожидания выполнения каждой из операций ввода-вывода.

Изменить планировщик диска sda можно при помощи следующей команды:
# echo "deadline" > /sys/block/sda/queue/scheduler
Для того, чтобы выбранный планировщик диска применялся при загрузке системы, можно поставить пакет sysfsutils:
Оптимизация Linux при использовании SSD
# apt-get install sysfsutils
И прописать планировщик в файл /etc/sysfs.conf:
block/sda/queue/scheduler = deadline
Другой способ сделать изменения постоянными - создать файл /etc/udev/rules.d/60-ssd.rules со следующими правилами udev:
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="deadline"
Это правило для всех не вращающихся дисков с именем sd* будет устанавливать планировщик deadline.

Размер страницы

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

Например, по данным SMART размер логического сектора диска равен 512 байтам, а размер страницы равен 4096 байт:
# smartctl -i /dev/sda | grep Sector
Sector Sizes:     512 bytes logical, 4096 bytes physical
Убедиться в том, что ядро операционной системы Linux знает о размере физического сектора, можно следующим образом:
# cat /sys/block/sda/queue/physical_block_size 
4096

Увеличение резерва страниц

Т.к. каждая страница SSD имеет ограниченный ресурс перезаписи, контроллер SSD в общем случае не записывает изменившиеся данные в ту же страницу, а использует другую. Чтобы с точки зрения операционной системы записанные данные оставались доступными по тому же адресу, контроллер использует специальный каталог соответствия страниц, который отображает линейное адресное пространство диска в реальные страницы. Чем больше неиспользуемых страниц имеется в распоряжении контроллера, тем больше у него возможностей выбирать наименее изношенные страницы для очередной операции записи, тем больше возможностей для уменьшения количества операций очистки блоков.

Часть общего объёма страниц диска закладывается в резерв. Напрмер, SSD объёмом 480 Гигабайт может иметь реальный объём 512 Гигабайт, а разница используется как раз для равномерного использования ресурса всех страниц.

Кроме того, в файловой системе может иметься свободное место, не занятое никакими данными. Это свободное место на SSD можно приобщить к резерву. Для этого операционная система может сообщать диску о неиспользуемых ею страницах при помощи ATA-команды TRIM. Для этого SSD должен поддерживать операцию TRIM, а файловая система должна поддерживать опцию монитрования discard.

Проверить наличие поддержки TRIM в SSD можно при помощи утилиты hdparm:
# hdparm -I /dev/sda | grep TRIM
           * Data Set Management TRIM supported (limit 8 blocks)
           * Deterministic read ZEROs after TRIM
Вторая строчка означает, что секторы, над которыми произведена команда TRIM, при попытке чтения будут возвращать нули. Другим возможным режимом может быть «Deterministic read after TRIM», когда при чтении возвращаются не нули, а какая-то другая всегда одинаковая последовательность данных.

Если на странице руководства man mount среди опций интересующей файловой системы имеется опция discard, то файловую систему можно перемонтировать с поддержкой этой опции.

Сначала посмотрим, с какими опциями смонтирована файловая система:
# findmnt /
TARGET SOURCE   FSTYPE OPTIONS
/      /dev/md0 ext4   rw,relatime,errors=remount-ro,data=ordered
Перемонтируем файловую систему, добавив к списку опций remount и discard:
# mount -o remount,rw,relatime,errors=remount-ro,data=ordered,discard /
Убеждаемся, что новая опция добавилась к текущему списку:
# findmnt /
TARGET SOURCE   FSTYPE OPTIONS
/      /dev/md0 ext4   rw,relatime,discard,errors=remount-ro,data=ordered
Чтобы отключить опцию discard, можно повторить процедуру перемонтирования, указав вместо опции discard опцию nodiscard.

Чтобы при перезагрузке операционная система монтировала файловую систему с опцией discard, нужно добавить её к списку опций монитрования в файле /etc/fstab. Например, строчка монтирования может выглядеть следующим образом:
UUID=324f1a70-5229-4376-afbb-eb274c8e60aa /               ext4    errors=remount-ro,discard 0       1
Чтобы сообщить диску о неиспользуемых секторах, которые были освобождены до включения опции discard, или при отключенной опции discard, можно воспользоваться командой fstrim:
# fstrim -v /
/: 146,6 MiB (153755648 bytes) trimmed
Кроме увеличения ресурса диска, использование TRIM и discard может приводить к увеличению скорости операций записи и чтения. Т.к. у контроллера есть в распоряжении много очищенных блоков, ему не придётся тратить время на их очистку для записи новых данных. При этом операция очистки блока может выполняться в фоновом режиме, когда SSD не занят выполнением операций чтения или записи.

Если на пути между файловой системой и диском имеются менеджер томов LVM или RAID-массив, то информация о неиспользуемых секторах может застревать в этих подсистемах и не доходить до SSD. Чтобы LVM сообщал о неиспользуемых секторах на нижележащий уровень, нужно в секции devices из файла конфигурации /etc/lvm/lvm.conf выставить следующую опцию:
issue_discards = 1
Убедиться в том, что TRIM корректно передаётся нижележащему хранилищу, можно при помощи команды:
# lsblk -D
Если TRIM поддерживается на всех уровнях, то в столбцах DISC-GRAN и DISC-MAX будут ненулевые значения:
NAME    DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda            0        4K       2G         0
└─sda1         0        4K       2G         0
  └─md0        0        4K       2G         0
sdb            0        4K       2G         0
└─sdb1         0        4K       2G         0
  └─md0        0        4K       2G         0
Если же TRIM не используется, то можно увидеть такую картину:
NAME                     DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda                             0        0B       0B         0
├─sda1                          0        0B       0B         0
│ └─md0                         0        0B       0B         0
└─sda2                          0        0B       0B         0
  └─md1                         0        0B       0B         0
    ├─vg0-mon--disk             0        0B       0B         0
    └─vg0-mon--swap             0        0B       0B         0
sdb                             0        0B       0B         0
├─sdb1                          0        0B       0B         0
│ └─md0                         0        0B       0B         0
└─sdb2                          0        0B       0B         0
  └─md1                         0        0B       0B         0
    ├─vg0-mon--disk             0        0B       0B         0
    └─vg0-mon--swap             0        0B       0B         0
Возможны промежуточные варианты, когда файловая система отправляет операции TRIM на нижележащий уровень, но дальше эти операции не проходят.

Файловые системы в оперативной памяти

Т.к. интенсивный ввод-вывод снижает ресурс SSD, лучше избегать использовать SSD для хранения временных файлов. Например, раздел /tmp можно расположить в оперативной памяти. Временно это можно сделать при помощи такой команды:
# mount -t tmpfs tmpfs -o relatime,nodev,nosuid,noexec,mode=1777 /tmp
Если нужно ограничить максимальный размер файлов во временной файловой системе, к опциям noatime и nosuid можно добавить опцию size с указанием этого размера:
# mount -t tmpfs tmpfs -o relatime,nodev,nosuid,noexec,mode=1777,size=1G /tmp
Если временную файловую систему нужно монтировать автоматически при загрузке системы, нужно добавить в файл /etc/fstab соответствующую строчку:
tmpfs /tmp tmpfs relatime,nodev,nosuid,noexec,mode=1777,size=1G 0 0
Если файловая система уже не смонтирована, то теперь смонтировать её можно простой командоу:
# mount /tmp
Аналогичным образом можно монтировать другие временные файловые системы. Например:
tmpfs /var/tftp tmpfs relatime,nodev,nosuid,noexec,uid=tftp,gid=tftp,mode=0760,size=32M 0 0

Использованные материалы