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

wiy4c950com5s6o4a-baz8vncde.png

Меня зовут Юрий, я руководитель группы системного администрирования в Ситимобил. Сегодня поделюсь опытом работы с технологией тонкого резервирования (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×32 ГБ DDR4
  • 8×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 перед тем, как делать снапшот? Ведь мы можем снять его с работающей реплики. Все верно, вот только новый экземпляр БД на этом снапшоте будет по умолчанию считаться поврежденным и потребует полного сканирования при запуске. Останавливать реплику определенно быстрее, хоть это в итоге и является самой длительной операцией во всем процессе.

В результате мы получили более приемлемые тайминги и готовый к работе стенд. Хотя, как видно по наиболее красноречивому графику отставания репликации основной реплики, ситуация все еще далека от идеала:
3xdjilsj18hpucicdkd-jxclyrq.jpeg

Из других недостатков стоит отметить практическую невозможность мониторинга пула 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):
97uaw0f_5pbkxpnol_jycrjcol4.jpeg

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

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


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


Что дальше?

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


Итоги

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


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

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

© Habrahabr.ru