Настройка резервного копирования уверенно занимает одно из важнейших мест в деятельности администратора. В зависимости от задач резервного копирования, типов приложений и вида данных резервное копирование может осуществляться с помощью различных инструментов, таких как rsync, duplicity, rdiff-backup, bacula и других, коих существует огромное множество.


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


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


И, тем не менее, основной ��опрос остается открытым. Как же осуществлять резервное копирование таким образом, чтобы основные приложения получали приемлемое качество обслуживания? Операционные системы семейства UNIX предоставляют штатный механизм управления приоритетами ввода-вывода для приложений, который называется ionice, кроме того, конкретные реализации UNIX предоставляют свои механизмы, которые позволяют наложить дополнительные ограничения. К примеру, в случае GNU/Linux существует механизм cgroups, который позволяет ограничить полосу пропускания (для физически подключенных устройств) и установить относительный приоритет для группы процессов.


Тем не менее, в некоторых случаях таких решений недостаточно и необходимо ориентироваться на фактическое "самочувствие" системных процессов, которое отражают такие параметры системы как Load Average или %IOWait. В этом случае на помощь может прийти подход, который я успешно применяю уже достаточно продолжительное время при осуществлении резервного копирования данных с LVM2 с помощью dd.


Описание задачи


Имеется сервер GNU/Linux, на котором настроено хранилище, использующее LVM2 и для данного сервера каждую ночь осуществляется процедура резервного копирования тома, которая выполняется с помощью создания снимка раздела и запуска dd + gzip:


ionice -c3 dd if=/dev/vg/volume-snap bs=1M | gzip --fast | ncftpput ...

При осуществлении резервного копирования хочется выполнить его максимально быстро, но опытным путем замечено, что при повышение %IOWait до 30%, качество обслуживание дисковой системой приложений становится неприемлемым, поэтому необходимо держать его ниже данного уровня. Требуется реализовать ограничительный механизм, который бы обеспечивал обработку в предельно допустимых значениях %IOWait.


Поиск решения


Изначально для решения был применен подход с ionice -с3, но он не давал стабильного результата. Механизмы, основанные на cpipe и cgroups (throttling) были отброшены как не дающие возможности копировать данные быстро, если %IOWait в норме. В итоге было выбрано решение, основанное на мониторинге %IOWait и приостановке/возобновлении процесса dd с помощью сигналов SIGSTOP, SIGCONT совместно с сервисом статистики sar.


Решение


Схематично решение выглядит следующим образом:


  1. Запрашиваем статистику в течение N секунд и получаем среднее значение %IOWait;
  2. Определяем действие:
    a. Если значение %IOWait < 30, то возобновляем процесс (SIGCONT);
    b. Если значение %IOWait > 30, останавливаем процесс (SIGSTOP), увеличиваем счетчик;
  3. Если процесс остановлен дольше чем N x K, возобновляем процесс и останавливаем его снова через 2 секунды

Скорее всего п.3 вызывает вопросы. Зачем такое странное действие? Дело в том, что в рамках резервного копирования осуществляется передача данных по FTP на удаленный сервер и если процесс копирования остановлен достаточно продолжительное время, то мы можем потерять соединение по таймауту. Для того, чтобы этого не произошло, мы выполняем принудительное возобновление и остановку процесса копирования даже в том случае, если находимся в "красной" зоне.

К��д решения приведен ниже.


#!/bin/bash

INTERVAL=10
CNTR=0

while :
do
    CUR_LA=`LANG=C sar 1 $INTERVAL | grep Average | awk '{print $6}' | perl -pe 'if ($_ > 30) { print "HIGH "} else {print "LOW "}'`
    echo $CUR_LA
    MARKER=`echo $CUR_LA | awk '{print $1}'`
    if [ "$MARKER" = "LOW" ]
    then
        CNTR=0
        pkill dd -x --signal CONT
        continue
    else
        let "CNTR=$CNTR+1"
                pkill dd -x --signal STOP
    fi
    if [ "$CNTR" = "5" ]
    then
        echo "CNTR = $CNTR - CONT / 2 sec / STOP to avoid socket timeouts"
        CNTR=0
        pkill dd -x --signal CONT
        sleep 2
        pkill dd -x --signal STOP
    fi
done

Данное решение успешно решило проблему с перегрузкой IO на сервере, при этом не ограничивает скорость жестко, и уже несколько месяцев служит верой и правдой, в то время, как решения, основанные на предназначенных для этого механизмах, не дали положительного результата. Стоит отметить, что значение параметра, получаемое sar может быть легко заменено на Load Average и иные параметры, которые коррелируют с деградацией сервиса. Данный скрипт вполне подходит и для задач, в которых применяется не LVM2 + dd, а, к примеру, Rsync или другие инструменты резервного копирования.


C помощью cgroups возможно таким же образом реализовать не остановку, а ограничение полосы, если речь идет о копировании данных с физического блочного устройства.


PS: Скрипт приведен без редактуры в оригинальном виде.