Glusterfs + erasure coding: когда надо много, дешево и надежно

Гластер в России мало у кого есть, и любой опыт интересен. У нас он большой и промышленный и, судя по дискуссии в прошлом посте, востребованный. Я рассказывал о самом начале опыта переноса бекапов с Enterprise хранилища на Glusterfs.

Это недостаточно хардкорно. Мы не остановились и решили собрать что-то более серьёзное. Поэтому здесь речь пойдёт о таких вещах, как erasure coding, шардинг, ребалансировка и её троттлинг, нагрузочное тестирование и так далее.

8tol2dsnki7fr_jfcvhxwldsxdk.jpeg

  • Больше теории волюмы/сабволюмы
  • hot spare
  • heal / heal full / rebalance
  • Выводы после ребута 3 нод (никогда так не делайте)
  • Как влияет на нагрузку сабволюма запись с разной скоростью от разных ВМ и shard on/off
  • rebalance после вылета диска
  • fast rebalance


Что хотели


Задача простая: собрать дешёвый, но надёжный сторадж. Дешёвый насколько можно, надёжный — чтобы не было страшно хранить на нём наши собственные файлы прода. Пока. Потом, после долгих тестов и бекапов на другую систему хранения — ещё и клиентские.

Применение (последовательное IO):

— Бекапы
— Тестовые инфраструктуры
— Тестовое хранилище для тяжёлых медиафайлов.
Мы находимся здесь:
— Боевая файлопомойка и серьёзные тестовые инфраструктуры
— Хранилище для важных данных.

Как и прошлый раз, главное требование — скорость сети между инстансами Гластера. 10G поначалу — нормально.

Теория: что такое dispersed volume?


Dispersed volume основан на технологии erasure coding (EC), что обеспечивает достаточно эффективную защиту от сбоев дисков или серверов. Это как RAID 5 или 6, но не совсем. Он хранит закодированный фрагмент файла для каждого брика таким образом, что для восстановления файла требуется только подмножество фрагментов, хранящихся на оставшихся бриках. Количество бриков, которые могут быть недоступны без потери доступа к данным, настраивается администратором во время создания тома.

rtbrt12s-0oyc9avxlsp32qzsus.png

Что такое сабволюм (subvolume)?


Сущность сабволюма в терминологии GlusterFS проявляется вместе с distributed волюмами. В distributed-disperced erasure coding будет работать как раз в рамках сабволюма. А в случае, например, с distributed-replicated данные будут реплицироваться в рамках сабволюма.
Каждый из них разнесён на разные сервера, что позволяет их свободно терять или выводить на синк. На рисунке зелёным отмечены сервера (физические), пунктиром — сабволюмы. Каждый из них представлен как диск (том) серверу приложений:

w-fpdjqguwigusvhtprq_6su3z8.png

Было решено, что конфигурация distributed-dispersed 4+2 на 6 нодах выглядит достаточно надежно, мы можем потерять 2 любых сервера или по 2 диска в рамках каждого сабволюма, продолжая иметь доступ к данным.

В нашем распоряжении было 6 стареньких DELL PowerEdge R510 с 12 дисковыми слотами и 48×2ТБ 3.5 SATA дисков. В принципе, если есть сервера с 12 дисковыми слотами, и имея на рынке диски до 12тб мы можем собрать хранилку размером до 576тб полезного пространства. Но не забывайте, что хоть максимальные размеры HDD продолжают из года в год расти, их производительность стоит на месте и ребилд диска размером 10–12ТБ может занять у вас неделю.

xoud6fl7sdtknj7vrbn_5fgslpu.png

Создание волюма:
Подробное описание, как подготавливать брики, вы можете прочитать в моём предыдущем посте

gluster volume create freezer disperse-data 4 redundancy 2 transport tcp \
$(for i in {0..7} ; do echo {sl051s,sl052s,sl053s,sl064s,sl075s,sl078s}:/export/brick$i/freezer ; done)


Создаем, но не спешим запускать и монтировать, так как нам еще придется применить несколько важных параметров.

Что мы получили:

8jile9swj-qws3hgdjo3gtht3oo.png

Всё выглядит вполне нормально, но есть один нюанс.

Он заключается в записи на брики такого волюма:
Файлы кладутся поочерёдно в сабволюмы, а не равномерно размазываются по ним, следовательно, рано или поздно мы упрёмся в его размер, а не в размер всего волюма. Максимальный размер файла, который мы можем положить в это хранилище, = полезный размер сабволюма минус уже занятое на нём пространство. В моем случае это < 8 Тб.

Что же делать? Как же быть?
Эту проблему решает шардинг или страйп вольюм, но, как показала практика, страйп работает из рук вон плохо.

Поэтому будем пробовать шардинг.

Что такое шардинг, подробно тут.

Что такое шардинг, коротко:
Каждый файл, который вы кладёте в волюм, будет разделён на части (шарды), которые относительно равномерно раскладываются по сабволюмам. Размер шарда указывается администратором, стандартное значение 4 МБ.

Включаем шардинг после создания волюма, но до того как стартовали его:

gluster volume set freezer features.shard on


Выставляем размер шарда (какой оптимальный? Чуваки из oVirt рекомендуют 512MB):

gluster volume set freezer features.shard-block-size 512MB


Опытным путём выясняется, что фактический размер шарда в брике при использовании dispersed волюма 4 + 2 получается равен shard-block-size/4, в нашем случае 512M/4 = 128M.

Каждый шард по логике erasure coding раскладывается по брикам в рамках сабволюма вот такими кусками: 4×128M+2×128M

Нарисовать кейсы отказов, которые переживает gluster этой конфигурации:
В данной конфигурации мы можем пережить падение 2 нод или 2 любых дисков в рамках одного сабволюма.

Для тестов мы решили подсунуть получившуюся хранилку под наше облако и запускать fio с виртуальных машин.

Включаем последовательную запись с 15 ВМ и делаем следующее.

Ребут 1-й ноды:
17:09
Выглядит некритично (~5 секунд недоступности по параметру ping.timeout).

17:19
Запустил heal full.
Количество heal entries только растёт, вероятно, из-за большого уровня записи на кластер.

17:32
Решено выключить запись с ВМ.
Количество heal entries начало уменьшаться.

17:50
heal выполнен.

Ребут 2 нод:

Наблюдаются такие же результаты, что и с 1-й нодой.

Ребут 3 нод:
Точка монтирования выдала Transport endpoint is not connected, ВМ получили ioerror.
После включения нод Гластер восстановился сам, без вмешательства с нашей стороны, и начался процесс лечения.

Но 4 из 15 ВМ не смогли подняться. Увидел ошибки на гипервизоре:

2018.04.27 13:21:32.719 ( volumes.py:0029): I: Attaching volume vol-BA3A1BE1 (/GLU/volumes/33/33e3bc8c-b53e-4230-b9be-b120079c0ce1) with attach type generic...
 2018.04.27 13:21:32.721 ( qmp.py:0166): D: Querying QEMU: __com.redhat_drive_add({'file': u'/GLU/volumes/33/33e3bc8c-b53e-4230-b9be-b120079c0ce1', 'iops_rd': 400, 'media': 'disk', 'format': 'qcow2', 'cache': 'none', 'detect-zeroes': 'unmap', 'id': 'qdev_1k7EzY85TIWm6-gTBorE3Q', 'iops_wr': 400, 'discard': 'unmap'})...
 2018.04.27 13:21:32.784 ( instance.py:0298): E: Failed to attach volume vol-BA3A1BE1 to the instance: Device 'qdev_1k7EzY85TIWm6-gTBorE3Q' could not be initialized
 Traceback (most recent call last):
 File "/usr/lib64/python2.7/site-packages/ic/instance.py", line 292, in emulation_started
 c2.qemu.volumes.attach(controller.qemu(), device)
 File "/usr/lib64/python2.7/site-packages/c2/qemu/volumes.py", line 36, in attach
 c2.qemu.query(qemu, drive_meth, drive_args)
 File "/usr/lib64/python2.7/site-packages/c2/qemu/_init_.py", line 247, in query
 return c2.qemu.qmp.query(qemu.pending_messages, qemu.qmp_socket, command, args, suppress_logging)
 File "/usr/lib64/python2.7/site-packages/c2/qemu/qmp.py", line 194, in query
 message["error"].get("desc", "Unknown error")
 QmpError: Device 'qdev_1k7EzY85TIWm6-gTBorE3Q' could not be initialized

qemu-img: Could not open '/GLU/volumes/33/33e3bc8c-b53e-4230-b9be-b120079c0ce1': Could not read image for determining its format: Input/output error


Жестко погасить 3 ноды с выключенным шардингом

Transport endpoint is not connected (107)
/GLU/volumes/e0/e0bf9a42-8915-48f7-b509-2f6dd3f17549: ERROR: cannot read (Input/output error)


Тоже теряем данные, восстановить не получается.

Мягко погасить 3 ноды c шардингом, будет ли data corruption?
Есть, но значительно меньше (совпадение?), потерял 3 диска из 30.

Выводы:

  1. Heal этих файлов висит бесконечно, rebalance не помогает. Приходим к выводу, что файлы, на которые в момент выключения 3-й ноды шла активная запись, потеряны безвозвратно.
  2. Никогда не перезагружайте больше 2 нод в конфигурации 4 + 2 в продуктиве!
  3. Как не потерять данные, если очень хочется ребутнуть 3 + ноды? П рекратить запись в точку монтирования и/или остановить volume.
  4. Замену ноды или брика надо производить как можно скорее. Для этого крайне желательно иметь, например, по 1–2 а-ля hot-spare брика в каждой ноде для быстрой замены. И ещё одну запасную ноду с бриками на случай отвала ноды.


-mrjtikde2imxdydeka4w-hvjjk.png

Также очень важно протестировать случаи замены дисков

Вылеты бриков (дисков):
17:20

Выбиваем брик:

/dev/sdh 1.9T  598G  1.3T  33% /export/brick6


17:22

gluster volume replace-brick freezer sl051s:/export/brick_spare_1/freezer sl051s:/export/brick2/freezer commit force


Видно вот такую просадку в момент замены брика (запись с 1 источника):

jk96ns2kzhy0radczfpxlfpodso.png

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

Регулируемые параметры для лечения:

gluster volume set cluster.background-self-heal-count 20
# Default Value: 8
# Description: This specifies the number of per client self-heal jobs that can perform parallel heals in the background.

gluster volume set cluster.heal-timeout 500
# Default Value: 600
# Description: time interval for checking the need to self-heal in self-heal-daemon

gluster volume set cluster.self-heal-window-size 2
# Default Value: 1
# Description: Maximum number blocks per file for which self-heal process would be applied simultaneously.

gluster volume set cluster.data-self-heal-algorithm diff
# Default Value: (null)
# Description: Select between "full", "diff". The "full" algorithm copies the entire file from source to 
# sink. The "diff" algorithm copies to sink only those blocks whose checksums don't match with those of 
# source. If no option is configured the option is chosen dynamically as follows: If the file does not exist 
# on one of the sinks or empty file exists or if the source file size is about the same as page size the 
# entire file will be read and written i.e "full" algo, otherwise "diff" algo is chosen.

gluster volume set cluster.self-heal-readdir-size 2KB
# Default Value: 1KB
# Description: readdirp size for performing entry self-heal


Option: disperse.background-heals
Default Value: 8
Description: This option can be used to control number of parallel heals

Option: disperse.heal-wait-qlength
Default Value: 128
Description: This option can be used to control number of heals that can wait

Option: disperse.shd-max-threads
Default Value: 1
Description: Maximum number of parallel heals SHD can do per local brick. This can substantially lower heal times, but can also crush your bricks if you don’t have the storage hardware to support this.

Option: disperse.shd-wait-qlength
Default Value: 1024
Description: This option can be used to control number of heals that can wait in SHD per subvolume

Option: disperse.cpu-extensions
Default Value: auto
Description: force the cpu extensions to be used to accelerate the galois field computations.

Option: disperse.self-heal-window-size
Default Value: 1
Description: Maximum number blocks (128KB) per file for which self-heal process would be applied simultaneously.

Выстаил:

disperse.shd-max-threads: 6
disperse.self-heal-window-size: 4
cluster.self-heal-readdir-size: 2KB
cluster.data-self-heal-algorithm: diff
cluster.self-heal-window-size: 2
cluster.heal-timeout: 500
cluster.background-self-heal-count: 20
cluster.disperse-self-heal-daemon: enable
disperse.background-heals: 18


С новыми параметрами 1 Тб данных отребилдилось за 8 часов (в 3 раза быстрее!)

Неприятный момент, что в результате получается брик большего размера, чем был

был:

Filesystem  Size  Used Avail Use% Mounted on
/dev/sdd 1.9T  645G  1.2T  35% /export/brick2


стал:

Filesystem  Size  Used Avail Use% Mounted on
/dev/sdj  1.9T 1019G  843G  55% /export/hot_spare_brick_0


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

Ребалансировка:
After expanding or shrinking (without migrating data) a volume (using the add-brick and remove-brick commands respectively), you need to rebalance the data among the servers. In a non-replicated volume, all bricks should be up to perform replace brick operation (start option). In a replicated volume, at least one of the brick in the replica should be up.

Шейпинг ребалансировки:

Option: cluster.rebal-throttle
Default Value: normal
Description: Sets the maximum number of parallel file migrations allowed on a node during the rebalance operation. The default value is normal and allows a max of [($(processing units) — 4) / 2), 2] files to b
e migrated at a time. Lazy will allow only one file to be migrated at a time and aggressive will allow max of [($(processing units) — 4) / 2), 4]

Option: cluster.lock-migration
Default Value: off
Description: If enabled this feature will migrate the posix locks associated with a file during rebalance

Option: cluster.weighted-rebalance
Default Value: on
Description: When enabled, files will be allocated to bricks with a probability proportional to their size. Otherwise, all bricks will have the same probability (legacy behavior).

Сравнение записи, а потом чтения теми же параметрами fio (более подробные результаты performance тестов — в личку):

fio --fallocate=keep --ioengine=libaio --direct=1 --buffered=0 --iodepth=1 --bs=64k --name=test --rw=write/read --filename=/dev/vdb --runtime=6000


fwupj0gj9m6vn25bepslaaox01e.png

xiyfpdsecqbfc52fudoz4nwklty.png

nhxpestbf-gfjkcwogumbbqabc4.jpeg

Если интересно — сравнение скорости rsync к трафику на ноды Гластера:

6iczoxword1qaauuhkwm3vfkk-q.png

42kaeqkdbcuzhq8rwdrqybgkc5u.png

Видно, что примерно 170 МБ/сек/ трафика к 110 МБ/сек/ полезных данных. Выходит, это 33% дополнительного трафика, как и ⅓ избыточности Erasure Coding.

Потребление памяти на серверной стороне с нагрузкой и почти без неё не меняется:

nzszzcu0eqrck4gpmhvazhm8x7i.png
nzszzcu0eqrck4gpmhvazhm8x7i.png

Нагрузка на хосты кластера при максимальной нагрузке на вольюм:

vbu33cgi-rmgjn7c2w1guz5ps3c.png

© Habrahabr.ru