Тонкое резервирование файловых систем Linux. Как создавать рабочие копии трехтерабайтной СУБД MySQL за 20 секунд

Тонкое резервирование файловых систем Linux. Как создавать рабочие копии трехтерабайтной СУБД MySQL за 20 секунд

Меня зовут Юрий, я руководитель группы системного администрирования в Ситимобил. Сегодня поделюсь опытом работы с технологией тонкого резервирования (thin provisioning) файловых систем Linux и расскажу, как ее можно применять в технологических CI/CD-процессах компании. Мы разберем ситуацию, когда для автоматического тестирования кода при доставке его в production нам как можно быстрее необходимы копии БД MySQL, максимально приближенные к «боевой» версии, доступные на чтение и на запись.

Введение: зачем давать вредные советы?

Логичный вопрос, ведь есть отработанные механизмы миграций схем БД в тестовые среды. Зачем вообще доводить основную нешардированную СУБД до таких объемов? Да и для тестирования нужны не все данные. Постараюсь объяснить.

Примерно год назад на фоне активного роста нашего агрегатора такси (за 2018 год выросли по завершенным поездкам примерно в 15 раз), выросли объемы данных, нагрузка на серверы, частота выкаток. Мы оказались в следующей ситуации:

  • Основная БД MySQL увеличилась примерно до 1000 таблиц общим объемом в 2,5 Тб, и продолжала расти.
  • Не было возможности быстро зашардироваться и разнести базу. Это не позволял старый подход «пишу в базу что хочу и как хочу», куча JOIN’ов и внутренних зависимостей таблиц.
  • Не было механизма миграций схемы БД в тестовые окружения.
  • Не было автоматического тестирования кода при выкатке в эксплуатацию.

Последнюю проблему хотелось решить максимально быстро. Были уже написаны тесты Postman для проверки основного PHP-монолита, но не хватало актуальной базы данных. При этом мы не могли создавать ночью реплику, делать ее мастером и отдавать на растерзание днем: очень большое количество выкаток и изменений, в том числе в данных и схеме БД, сделали бы стенд неработоспособным уже к середине дня. Да и ограничивать выкатки всего лишь рабочим днем было бы неэффективно.

Тем не менее, задача была выполнена: первый рабочий стенд мы получили уже через две недели. За прошедший год он претерпел много изменений и продолжает использоваться.

Далее я подробно опишу все шаги и этапы развития нашего решения. Вы убедитесь, что этот метод заслуживает право на существование.

Что такое «тонкое резервирование»?
Это аппаратная или программная технология (другое название — sparse volumes), позволяющая выделять большее количество требуемого ресурса, чем есть в наличии. При этом выделяемый объем должен соответствовать критериям just-enough (столько, сколько нужно) и just-in-time (за необходимое время). В основном тонкое резервирование применяется в различных СХД, чтобы предоставлять дисковое пространство в необходимых объемах, превышающих фактически доступные. Технология поддерживается различными файловыми системами, например, LVM2, ZFS, BTRFS. Она широко используется в гипервизорах виртуализации. Нам тонкое резервирование позволяло быстро создать из снапшотов основного раздела с данными столько копий этого раздела, сколько нам нужно (data-директория СУБД MySQL).

Первый стенд, технология Thin LVM

Эту главу можно ещё назвать «Как сделать максимально быстрые снапшоты больших объемов данных с помощью Thin LVM, уменьшив стабильность файловой системы и СУБД MySQL до неприличных показателей».

Поскольку мы уже использовали LVM для построения основных разделов ОС, то решили начать именно с нее. Для начала нам потребовалась отдельная физическая машина — реплика нашей основной базы MySQL, на которой мы могли бы по запросу создавать снапшот реплики и поднимать его рядом отдельным экземпляром MySQL. На время тестирования мы разрешили применять на этом экземпляре изменяющие операции, и по завершении тестов благополучно его удаляли. Конфигурация сервера была такой:

  • 2 x Intel Silver 4114 (10×2,2 ГГц HT)
  • 8 x 32 ГБ DDR4
  • 8 x 1920 ГБ Intel SSD в RAID-контроллер Adaptec в RAID-10

На тему выбора между RAID-контроллером и программным RAID MD можно писать отдельную статью. Скажу лишь, что наш выбор повлияли два фактора:

  • Во времена постановки задачи мы все СУБД ставили на RAID-контроллеры, поэтому можно сказать, что так сложилось исторически.
  • Различие в производительности на синтетических тестах файловой системы и тестах с различными операциями в MySQL было минимальным.

Мы разделили получившийся RAID-10: сделали единый Volume Group (VG) на весь объем (с накладными расходами примерно 6,7 Гб) и создали логический раздел (Logical Volume, LV) под систему на 50 Гб. В обычной ситуации все остальное место мы определяем под раздел c MySQL. Но нам нужно было тонкое резервирование, поэтому сначала мы создали так называемый pool, внутри которого создали раздел под /var/lib/mysql на 3,5 Тб (исходя из предполагаемых объемов БД):

lvcreate -l 100%FREE -T vga/thin
lvcreate -V 3.5T -T vga/thin -n mysql

Отформатировали раздел в ext4, примонтировали его, записали реплику и получили исходный стенд. Затем сделали обвязку в виде API, который должен создавать снапшоты, поднимать экземпляр БД MySQL на заданном порте и удалять созданный экземпляр. Поскольку при этом используются исключительно системные вызовы, в качестве языка написания скриптов мы выбрали обычный bash, а в качестве провязки API HTTP → bash развернули open source-решение goexpose, написанное на Go.

Когда-нибудь мы выложим наши bash-скрипты в open source, а пока я просто опишу основной алгоритм:

Создание основного снапшота snapmain:

  1. Останавливаем основную реплику.
  2. Ставим блокировку на операции со снапшотом snapmain.
  3. Создаем новый снапшот snapmain.
  4. Запускаем MySQL и убираем блокировку.

Создание БД на произвольном порте из snapmain:

  1. Ставим блокировку на конкретный экземпляр БД (порт).
  2. Проверяем наличие блокировки создания основного снапшота. Если она есть, то ждем и перепроверяем каждые 5 секунд.
  3. Проверяем, есть ли старый LV-раздел экземпляра.
    3.1 Если есть, то останавливаем с помощью kill -9 экземпляр MySQL и удаляем LV-раздел.
  4. Создаем из snapmain новый экземпляр.
  5. Готовим и монтируем директории для этого экземпляра.
  6. Убираем признаки слэйва (файлы) и запускаем экземпляр MySQL.
  7. Делаем из него мастер.
  8. Убираем блокировку.

Удаление БД на произвольном порте:

  1. Ставим блокировку на конкретный экземпляр БД (порт).
  2. Убиваем экземпляр MySQL с помощью kill -9.
  3. Отмонтируем директории.
  4. Удаляем LV-раздел и снимаем блокировку.

Пример команд для клонирования разделов нового экземпляра БД:

lvcreate -n stage_3307 -s vga/snapmain
lvchange -ay -K vga/stage_3307
mount -o noatime,nodiratime,data=writeback /dev/mapper/vga-stage_3307 /mnt/stage_3307

Теперь расскажу про основную проблему, с которой мы столкнулись при использовании тонкого резервирования. Мы уперлись в производительность SSD-дисков. Произошло это из-за особенностей Thin LVM: она в своей основе оперирует на уровне устройства низкоуровневыми чанками размером по умолчанию в 4 Мб. Как это выглядело:

  1. Создаем снапшот из основного раздела /var/lib/mysql.
  2. Запускаем репликацию, чтобы догнать мастера.
  3. Любое изменение в таблицах реплики заставляет сохранять старые, неизмененные чанки данных в разделе снапшота.
  4. Любое изменение в поднятом тестовом экземпляре заставляет сохранять старые, неизмененные чанки данных в разделе склонированного снапшота для этого экземпляра.
  5. Получаем загруженность операций ввода-вывода в 100% на устройство, замедление любых операций и постепенное отставание реплики.
  6. К концу рабочего дня получаем отставший на несколько часов стенд.

Как мы с этим боролись, чтобы получить более вменяемый результат (основные моменты):

RAID-контроллер:

  • Выключили по умолчанию все виды кэширования.
  • Выставили writeback (при попадании данных в буфер запись завершается прежде, чем выполняется фактическое сохранение на диск).

Файловая система:

  • В точке монтирования /var/lib/mysql прописали noatime,nodiratime,data=writeback
  • Выключили журналирование ext4 с помощью tune2fs.

MySQL:

  • Прописали innodb_flush_method = O_DSYNC (увеличили скорость записи, снизив тем самым надежность).
  • Отключили журналирование, логи нам не нужны.
  • Прописали innodb_buffer_pool_size = 4G (чем меньше размер пула InnoDB, тем быстрее погаснет MySQL при остановке, и тем быстрее мы создадим снапшот).

Это далеко не полный список, особенно по MySQL. Впрочем, остальные изменения минорны и зачастую применимы не всегда и не точно. Например, в попытке разгрузить диски мы даже унесли innodb_parallel_doublewrite_path в /dev/shm, что в некоторых случаях при старте некорректно завершенного экземпляра экономило нам до 5 секунд.

Почему мы останавливаем MySQL перед тем, как делать снапшот? Ведь мы можем снять его с работающей реплики. Все верно, вот только новый экземпляр БД на этом снапшоте будет по умолчанию считаться поврежденным и потребует полного сканирования при запуске. Останавливать реплику определенно быстрее, хоть это в итоге и является самой длительной операцией во всем процессе.

В результате мы получили более приемлемые тайминги и готовый к работе стенд. Хотя, как видно по наиболее красноречивому графику отставания репликации основной реплики, ситуация все еще далека от идеала:
Тонкое резервирование файловых систем Linux. Как создавать рабочие копии трехтерабайтной СУБД MySQL за 20 секунд

Из других недостатков стоит отметить практическую невозможность мониторинга пула Thin LVM: помимо системных стандартных функций iostat, понять, например, какой элемент пула сейчас производит наибольшую нагрузку на файловую систему невозможно.

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

За какими метриками стоит следить в процессе эксплуатации Thin LVM:

  • Thin pool data %
  • Thin pool metadata %

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

Файловая система внутри пула со временем очень сильно фрагментируется. Рекомендую раз в сутки запускать по крону команду fstrim -v /var/lib/mysql.

Промежуточные итоги:

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

Второй стенд, технология ZFS

Когда-то давно я имел дело с файловой системой ZFS, но тогда достоверно хорошо ZFS работала на своем родном семействе ОС Solaris. Существовала портированная на FreeBSD версия с достаточно хорошим уровнем реализации. Был также недоделанный порт на Linux, который мало кто применял. Из-за структуры хранения данных B-tree (кстати, такая же структура хранения и у InnoDB MySQL) ZFS плохо себя показывала на инсталляциях с очень большим количеством файлов. Всё это в совокупности с необходимостью учить матчасть перед использованием надолго вычеркнуло из моей практики эту файловую систему. Появились ext4 и xfs, которые превратились в стандарт. Но учитывая, что под нашу задачу ZFS более чем подходит, да и Linux-версия, судя по отзывам, выросла во вполне вменяемый продукт (хоть и не на полной поддержке, из-за чего поставить систему на ZFS полностью с нуля можно только при помощи различных шаманств), мы решили её попробовать.

По понятным причинам стенд выбрали аналогичной конфигурации (за исключением RAID-контроллера). Установили восемь SSD-дисков по 1920 Гб. Не было желания писать свой сетевой образ для заливки сервера на голую ZFS, поэтому мы откусили от всех дисков по 50 Гб и сделали на них MD RAID-10 под систему. Оставшиеся 1950 Гб на каждом диске объединили в ZFS-аналог RAID-10:

zpool create zpool mirror /dev/sda2 /dev/sdb2 mirror /dev/sdc2 /dev/sdd2 mirror /dev/sde2 /dev/sdf2 mirror /dev/sdg2 /dev/sdh2

Сделали разделы под MySQL:

zfs create zpool/mysql
zfs set compression=gzip zpool/mysql
zfs set recordsize=128k zpool/mysql
zfs set atime=off zpool/mysql
zfs create zpool/mysql/data
zfs set recordsize=16k zpool/mysql/data
zfs set primarycache=metadata zpool/mysql/data
zfs set mountpoint=/var/lib/mysql zpool/mysql/data

Обратите внимание, что мы включили штатное сжатие данных gzip. Процессорных ресурсов у нас на сервере много и они не полностью используются В результате 3 Тб нашей БД превратились в 1,6 Тб, и поскольку слабым звеном, как и в прошлом случае, является максимальная производительность дисков, то чем меньше данных — тем лучше, мы с самого начала получаем отличный бонус от ZFS! В час пик на полной нагрузке на поддержание работы gzip уходит до 4 ядер, но нам и не жалко.

Дальше внедрение пошло быстрее. Под копирку перенесли c LVM-стенда настройки реплики MySQL. Пришлось потратить некоторое время на переписывание скриптов на команды ZFS, но в целом алгоритмы остались прежними. Пример создания снапшота:

zfs set snapdir=visible zpool/mysql/data
zfs create zpool/stage_3307
zfs clone zpool/mysql/data@snapmain zpool/stage_3307/data
zfs set mountpoint=/mnt/stage_3307 zpool/stage_3307/data

Из дополнительного тюнинга: вынесли в память ZFS-разделы с метаданными и логами l2arc и zil. Для нашей задачи, как оказалось в дальнейшем, это было избыточно, но пока что мы оставили эту оптимизацию, поменять при случае несложно. Из негативных эффектов — приходится после перезагрузки сервера пересоздавать соответствующие области памяти. Данные при этом не теряются. Вырезка zpool status:

logs
      /dev/shm/zil_slog.img  ONLINE       0     0     0
cache
      /dev/shm/l2arc.img     ONLINE       0     0     0

В такой конфигурации мы начали тестировать стенд и получили прекрасные результаты: с двумя одновременно работающими экземплярами БД (и активной основной репликой) на снапшотах мы получили загруженность дисков на 50-60 %.

Мы избавились от нашей основной проблемы, что видно на графике отставания репликации (сравните с предыдущим графиком в разделе Thin LVM):
Тонкое резервирование файловых систем Linux. Как создавать рабочие копии трехтерабайтной СУБД MySQL за 20 секунд

Помимо и благодаря этому мы сильно ускорились во всех операциях: полное создание снапшота с остановкой и запуском реплики занимает до 40 секунд, развёртывание из снапшота нового экземпляра MySQL занимает до 20 секунд. Что более чем устраивает как нас, так и наши тесты программного кода.

Промежуточные итоги:

  • Результаты полностью удовлетворили нашу потребность в получении копии боевой БД для тестирования кода.
  • Технология требует вхождения: нужно понимать, что такое ZFS и как с ней работать.
  • Мы не проверяли текущий статус работы ZFS с большим количеством (от 1 млн) маленьких файлов. Но предполагаем, что проблема сохраняется, поэтому я не стал бы рекомендовать эту файловую систему для каких-либо файловых хранилищ.

Что дальше?

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

Итоги

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

  • Thin LVM — на небольших БД и когда не хочется или нет времени изучать ZFS.
  • ZFS — если есть опыт работы с ней или возможность потратить время на изучение в любых ситуациях.

На более высоком уровне представления эта статья — не просто сравнение технологии двух файловых систем. Основная идея, которую я хотел бы передать и закрепить, заключается в том, что не стоит бояться мыслить нестандартно в ситуациях, критичных для бизнеса, и брать только готовые рецепты. Когда-то мы могли всем техническим департаментом покачать головой и сказать, что задача создания трехтерабайтных копий БД меньше чем за минуту невыполнима, и нам не нужны рискованные технологии, давайте сделаем как надо. Это было возможно, но мы потеряли бы примерно полгода-год и много поездок клиентов (поездки — наш основной бизнес-показатель) без тестов и во время внедрения. Поступив нестандартно, мы потеряли не так много времени на внедрение, получили опыт в новых и забытых старых технологиях, и предоставили тестирование именно в тот момент, когда мы в нем очень нуждались. Несомненно, это положительно сказалось на всех наших показателях. Выбор всегда за вами, а мы со своей стороны продолжим рассказывать в нашем блоге про интересные текущие и будущие достижения.

Источник: habr.com