У Джонни Кэша есть песня «One piece at a time» 1976 года. В ней рассказывается история об автомеханике, собирающем собственный Cadillac из деталей, которые он в течение 25 лет по одной тырил с производственного конвейера General Motors.

Некоторое время назад пользователь Practical ZFS задал обманчиво простой вопрос:

«У меня есть пул Proxmox из трёх RAIDz1 vdev (virtual device, виртуальное устройство) по 4 диска. Проблема в том, что во время работы VM все двенадцать дисков минимум раз в секунду издают громкий звук, причём в течение всего дня. Что может быть причиной, и как это устранить?»

Естественно, на этот простой вопрос есть простой ответ: «Рассматриваемая VM наверняка просто непрерывно выполняет поток записей на диск, и эти записи раз в секунду синхронизируются с физическим диском».

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

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

Неудачная ширина vdev


При рассмотрении обозначенной пользователем проблемы, одним из первых вариантов на ум приходит топология vdev: у человека всего 12 дисков, выстроенных в три vdev RAIDz1 по четыре штуки. Но это неидеальная ширина.

И чтобы понять почему, нужно немного поговорить о принципе работы RAIDz. В своей сути, каждый блок — фундаментальная единица хранения данных в ZFS, чей размер определяется свойствами recordsize или volblocksize — разделён на n-p частей, где n представляет общее число дисков vdev, а p — уровень чётности.

Поскольку размер блока должен быть чётной степенью 2, то если вы хотите, чтобы фрагменты каждого блока поровну делились между дисками vdev, тогда величины n-p также должны быть равны чётной степени 2. Посмотрим, как это работает на практике:

  • Блок размером 128 КиБ сохраняется в vdev шириной три диска. Поскольку 128 КиБ / (3-1) == 64 КиБ, значит, на каждый диск записывается по 16 секторов размером 4 КиБ. Получается, блок 128 КиБ занимает на диске ровно 128 КиБ (без учёта чётности), делая эту ширину идеальной.
  • Блок 128 КиБ сохраняется в RAIDz1 vdev шириной 4 диска. Мы не можем поделить 128 КиБ ровно по трём дискам, поэтому записываем данные полноразмерными полосами, за которыми следует полоса неполной ширины. Говоря конкретнее, мы пишем десять полос из трёх секторов данных и одного сектора чётности, сопровождаемые узкой полосой с оставшимися двумя секторами и их сектором чётности. В итоге общая эффективность хранилища составляет 32 / 44 секторов данных, то есть 72.7%, а не наивно ожидаемые 75%… Но это базовый размер блока файловой системы, равный 128 КиБ, а не предустановленный размер блока тома в 8 КиБ или 16 КиБ, в случае которых всё сильно усугубляется.
  • Блок размером 128 КиБ сохраняется в RAIDz2 vdev шириной восемь дисков. То есть нам потребуется записать 32 сектора данных — и поскольку 32/6==5.3, нужно будет писать блок в пять полных полос из шести секторов данных + 2 сектора чётности, сопровождаемых узкой полосой из двух секторов данных + 2 сектора чётности. Получаются те же 32 сектора из 44, которые мы наблюдали в случае Z1 шириной в четыре диска, то есть всего 72,7% эффективности хранилища вместо ожидаемых 75%.

Тем не менее ничто из этого не ведёт к трагедии — к примеру, создатель OpenZFS Мэтт Аренс сказал, что перестал беспокоиться и научился любить RAIDz. С него следует брать пример. В частности, пулы, которые в основном хранят сжимаемые данные, минимально подвержены отрицательному влиянию неидеальной ширины vdev, поскольку сжатие OpenZFS в любом случае ведёт к получению полос с неполным размером.

Но лично я с Мэттом не согласен — обычно влияние неидеальной ширины vdev довольно незначительно, и я редко советую кому-либо демонтировать рабочий пул, только из-за использования неоптимальной ширины.

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

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

Неудачный размер блоков


Следующая проблема уже чуть тоньше и для своего выявления требует некоторых знаний о реализации распределения в ZFS — задавший вопрос человек использует Proxmox, основанную на дистрибутиве Debian. Эта система упрощает работу с виртуальными машинами (VM), хранящимися поверх OpenZFS.

Proxmox — это очень предвзятый дистрибутив — он требует от вас использования для виртуальных машин zvols (датасетов блочной системы хранения данных), а не файловых систем (датасетов файлового хранилища), и по умолчанию задействует очень маленький volblocksize — 8 КиБ. Это касается всех релизов, кроме последнего, где этот размер увеличен до 16 КиБ.

▍ Размер volblocksize в 8K нежелателен


Помните наши расчёты, когда мы пытались понять, является ли оптимальной та или иная ширина vdev? В Proxmox при использовании предустановленных параметров обычно идеальной ширины RAIDz vdev нет — блоки слишком малы, чтобы делить их между дисками.

Для тех, кто пользуется Proxmox v7 или более ранними версиями, параметр volblocksize = 8 КиБ означает, что каждый блок содержит всего два сектора данных, то есть не может быть разделён равным образом между более, чем двумя дисками.

Жалующийся на шум форумчанин использует Z1 шириной в четыре диска, то есть каждый блок 8 КиБ — два сектора данных — может занимать только три сектора (два сектора данных и один сектор чётности). И это ширина всего в три диска — значит, OpenZFS сохраняет каждый блок 8 КиБ только на трёх дисках, каждый из которых записывает всего один сектор.

Поскольку каждую полосу мы записываем всего на три диска, то получаем лишь 67% эффективности хранилища вместо 75%, которые могли бы ожидать, если бы ширина Z1 составляла четыре диска — или даже 72,7%, которые должны получиться при такой же конфигурации с учётом лишних полос.

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

▍ Volblocksize = 16K ненамного лучше


Начиная с Proxmox 8, предустановленный volblocksize для новых VM составляет уже не 8 КиБ, а 16 КиБ. И во многих типичных сценариях этого по-прежнему очень мало.

Как мы видели в предыдущем разделе, нельзя разделить блок ровно на три части — поэтому в конце каждого блока нужна одна узкая полоса. К сожалению, мы работаем единовременно всего с 16 КиБ, а не 128 КиБ, которые разбирали ранее, что ведёт к более пагубным последствиям.

Каждый блок 16 КиБ необходимо разделять на четыре сектора по 4 КиБ. Три этих сектора вместе с сектором чётности отправляются на одну полноразмерную полосу, после чего оставшийся сектор получает свой сектор чётности во второй, более узкой полосе — в результате чего общая эффективность хранилища при 4 секторах данных / 2 секторах чётности == 67%.

Но это не означает, что при каждом чтении или записи блока половина дисков должна проделывать 8 К операций ввода-вывода вместо 4 К. Это несколько повысит производительность и может слегка уменьшить шум…но в обоих случаях вряд ли заметным образом.

Уменьшение шума и повышение производительности


Теперь, когда мы вручную собрали кадиллак Джонни Кэша, можно, наконец, обсудить его мойку.

Немного подытожим. Наш пользователь жалуется на сильный шум дисков его сервера на платформе Proxmox. Нам известно, что в сервере работает 12 дисков, разбитых по трём RAIDz1 vedv шириной четыре диска каждый. Это первый сервер Proxmox у пользователя, то есть он наверняка использует его базовые установки с размером volblocksize = 8K или volblocksize = 16K.

Как мы уже поняли, комбинация zvol, небольшого volblocksize и неоптимальной ширины RAIDz vdev ведёт к тому, что записывается больше секторов, чем нужно — потенциально намного больше, чем можно предполагать из простейших математических расчётов.

К сожалению, ни один из этих неудачных вариантов конфигурации легко не исправить — RAIDz vdev перекроить на данный момент нельзя, и volblocksize после своей установки остаётся неизменным. Поэтому в надежде разрешить сложившуюся ситуацию мы готовимся практически к полному демонтажу пула.

▍ Доработка топологии пула


В отношении топологии пула мы можем предположить, что пользователь хотел получить те самые 75% эффективности хранилища, которые предполагает (но обычно не обеспечивает) Z1 при ширине в четыре диска. Не знаем же мы то, насколько ему фактически важно это обещанное дополнительное пространство хранилища.

Если пользователя реально интересует именно 75% эффективности, то ему сильно не повезло — этого уровня не удастся достичь, пока вы не соберёте RAIDz2 шириной в десять дисков, которая является идеальной и обещает 80% эффективности хранилища.

У нашего пользователя 12 дисков, значит Z2 с шириной в десять, по крайне мере, организовать можно. Но в этом случае количество vdev понизится с трёх до одного, и потребуется минимальный volblocksize = 32 КиБ (восемь секторов по 4 КиБ), только чтобы обеспечить запись всей ширины по одному сектору на диск.

Если пользователя меньше интересует общий объём, то, возможно, разумнее будет рассмотреть одну из следующих топологий:

  • Шесть зеркал шириной в два диска: шесть vdev + полная запись volblocksize на каждый диск + двойной объём операций чтения в секунду == максимум производительности с минимумом шума, при 50% эффективности хранилища и однократном резервировании.
  • Четыре Z1 шириной в три диска: четыре vdev + запись половины volblocksize на каждый диск == очень высокая производительность со значительно меньшим шумом, при 67% эффективности и однократном резервировании.
  • Три Z2 шириной четыре диска: три vdev + запись половины volblocksize на каждый диск == высокое быстродействие со значительно меньшим шумом, при 50% эффективности и двойном резервировании.
  • Два Z2 по шесть дисков: два vdev + запись четверти volblocksize на каждый диск == средняя производительность с возможным уменьшением шума, при 67% эффективности (максимум, подробнее в следующем разделе) и двойным резервированием.

▍ Оптимизация volblocksize


Говоря в общем, чем больше volblocksize, тем выше максимальная пропускная способность может быть достигнута ценой увеличения задержки при каждой отдельной операции ввода-вывода. Эта максимальная производительность может быть получена, когда volblocksize соответствует или слегка превышает размер самой частой произвольной операции ввода-вывода в выполняемой рабочей нагрузке.

Это означает, что для базы данных PostgreSQL, использующей страницы по 8 КиБ, нужно использовать volblocksize равный 8 КиБ или 16 КиБ. И хотя 8 КиБ обеспечивает полное соответствие, 16 КиБ может быть предпочтительнее — больший volblocksize обеспечивает более высокий потенциал сжатия и больше размер операции ввода-вывода на диск (то есть повышенное быстродействие каждого диска). Что касается жалобы на шум — нам определённо нужно придерживаться большего volblocksize, поскольку больший размер отдельных операций означает меньше позиционирований головки, а значит, меньше шума от приводов.

Тем не менее большинство виртуальных машин не работают исключительно с PostgreSQL. Даже в мире движков баз данных — самых чувствительных к задержкам приложений — MySQL InnoDB по умолчанию использует страницы по 16 КиБ, а в MSSQL их размер обычно составляет 64 КиБ. Тем временем VM, используемые для масштабного хранения «ISO Linux» чаще всего перемещают целые файлы по несколько ГиБ.

Для виртуальных машин общего назначения я рекомендую volblocksize = 64 КиБ. Это будет золотая середина между минимальной задержкой и максимальной пропускной способностью при хорошем потенциале к сжатию.

Итак


При volblocksize = 16 КиБ в пуле, состоящем из трёх Z1 vdev шириной в 4 диска, наш пользователь неоправданно теряет значительную долю эффективности, получая при этом лишний шум. Каждая запись в каждом vdev производится всего на три из четырёх дисков и состоит из всего одного сектора на диск — максимально увеличивая фрагментацию, которая, в свою очередь, минимизирует быстродействие и повышает шум. Всё это сопровождается потерей общей эффективности хранилища, которую пользователь наверняка рассчитывал получить.

И хотя мы не знаем точно, какую рабочую нагрузку этот пользователь предполагает для своей VM, можно предположить некую комбинацию операций с файлами и работы в десктопном UI — иными словами, стандартное использование. В таком случае обычно желательно, чтобы размер блока составлял примерно 64 КиБ.

Если мы решим перекроить пул, сделав четыре Z1 из 3 дисков вместо трёх Z1 из четырёх, а также поменяв volblocksize с 16 КиБ на 64 КиБ, то значительно повысим эффективность и производительность хранилища, попутно уменьшив шум.

Разберём процесс записи 64 КиБ в наш исходный пул и тех же 64 КиБ в его уже изменённую форму при всё тех же двенадцати дисках:

  • В нашем изначальном пуле используются блоки по 16 КиБ и Z1 шириной в четыре диска. Каждый записываемый блок делится на четыре операции записи по два сектора (три для данных и одна для чётности). В итоге для передачи 64 КиБ данных всего требуется четыре блока и шестнадцать отдельных операций записи.
  • В нашем изменённом пуле используются блоки по 64 КиБ и Z1 шириной в три диска. Каждый записываемый блок делится на три операции записи по 8 секторов (две для записи данных и одна для чётности). В связи с этим для передачи тех же 64 КиБ данных требуется всего один общий блок и три отдельных операции записи.

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

Самое же главное — поскольку основной проблемой пользователя был шум, а не производительность или ёмкость — 20% из всех операций записи означает 20% дополнительных возможностей для выполнения позиционирования головки. То есть теперь мы можем ожидать, что наш пул из 12 дисков будет работать намного тише.

И если этого мало, пул из шести зеркал шириной в два диска требует для передачи тех же 64 КиБ данных всего двух операций записи шириной в 16 секторов. И это всего 12,5% от отдельных операций записи, которые требовалось выполнять при исходной конфигурации.

Визуализация


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

Мы покажем все 12 дисков и то, как они упорядочены в пуле, после чего продемонстрируем каждую последовательную операцию записи, необходимую для передачи в этот пул 64 КиБ. Серые ячейки представляют не пустую область — они просто показывают, что для операции записи этот диск не использовался.


Изображение: Jim Salter, CC-BY-SA 4.0

Если бы мы работали с Proxmox 7 или ниже, то в качестве volblocksize использовали величину 8 КиБ. Но блок 8 КиБ никак не растянуть по RAIDz1 шириной четыре, поэтому мы последовательно записываем полосы из 3 блоков по группам из четырёх дисков.

Это даёт ужасные результаты в плане производительности и шума — каждый из дисков при каждой записи в vdev вынужден выполнять отдельные операции по 4 КиБ (один сектор).

Нам нужно выполнить 24 отдельных операции записи, каждая шириной в один сектор… в результате чего остаётся лишь надеяться, что OpenZFS хотя бы удастся смежно расположить основную часть этих операций сейчас и в будущем.


Изображение: Jim Salter, CC-BY-SA 4.0

Начиная с Proxmox 8, базовый volblocksize стал равен 16 КиБ. В целом это улучшает ситуацию, но всё же оставляет нас в затруднительном положении из-за неоптимального размера vdev.

Для того, чтобы записывать блоки по 16 КиБ, OpenZFS записывает на каждое vdev по одной полной полосе (по одному сектору на каждый диск), сопровождаемых записью частичной полосы. Первая полоса содержит три из четырёх секторов данных, которые нам нужно записать, а также один сектор чётности. Вторая полоса состоит из оставшегося сектора данных, а также соответствующего ему сектора чётности.

Эта мешанина позволяет дискам выполнять операции по 8 КиБ примерно вдвое быстрее, а операции по 4 КиБ ещё вдвое быстрее. Это определённо улучшение, но для передачи 64 КиБ на диск нам всё равно нужно 18 отдельных операций записи.


Изображение: Jim Salter, CC-BY-SA 4.0

На этот раз мы смотрим на Z1 vdev с идеальной шириной и volblocksize = 64 КиБ, наблюдая кардинальные отличия. Здесь нам нужно записывать всего один блок, и этот блок задействует лишь три диска.

Мы передали на диск те же 64 КиБ, но дали приводам в шесть раз меньше возможностей для позиционирования головки (напомним: лишнее позиционирование снижает производительность и создаёт шум).

Кроме того, мы в общей сложности использовали также 24 сектора по 4 КиБ, как и в случае с двумя Z1 vdev из четырёх дисков. Хорошо запомните этот урок — если vdev немного шире, это не обязательно обеспечит повышенную эффективность хранилища.


Изображение: Jim Salter, CC-BY-SA 4.0

Наконец, рассмотрим мою любимую топологию для относительно небольших пулов — зеркальные vdev. Мы по-прежнему используем volblocksize = 64K, поэтому также отправляем в пул по одному блоку — но теперь делаем запись только на два диска, а не три, потенциально на 1/3 снижая шум, который создавал RAIDz1 из 3 дисков выше.

Наши зеркальные vdev предлагают чуть более высокую производительность, чем даже vdev RAIDz1 из 3 дисков: мы получаем вдвое меньше vdev и удваиваем количество IOPS (операций ввода-вывода в секунду) на каждое. При этом также слегка повышается устойчивость к сбоям — каждое vdev выдерживает лишь один сбой работы диска, но сейчас в каждом vdev присутствует меньше дисков.

Если вы хотите ещё больше повысить устойчивость к сбоям, рассмотрите вариант с RAIDz2 шириной в 4 или 6 дисков.

Альтернативный подход


Несмотря на то, что мы успешно ответили на вопрос: «Почему мои диски шумят?», мы не до конца ответили на ещё более важный вопрос «Как избавиться от их шума?».

Путём грамотной настройки топологии пула и размера блоков можно значительно снизить количество позиционирований головок приводов, что, в свою очередь, сделает их менее шумными. Но тут следует задаться таким вопросом: «А как избавиться от шума полностью?»

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

Демпфирование вибраций на корпус — желательно с тяжёлыми стенками (в идеале не геймерскими стекляшками) — способно чудесным образом значительно снизить издаваемый вашими приводами шум.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (1)


  1. ky0
    04.10.2024 14:40

    Рассуждения выглядят убедительно, но я бы всё-таки на месте автора провёл натурный эксперимент.