Ceph: memory optimization

В этой заметке я расскажу о нескольких параметрах ядра которые очень критичны для нормальной работы Ceph OSD и RGW.

Отключение THP

THPTransparent Huge Pages. Тут я хотел дать ссылку на описание THP, но к сожалению не смог найти понравившееся мне описание. Напишу пару слов о том, что это такое. Архитектура x86 позволяет оперировать страницами памяти размером в 4Kb, 2Mb, 1Gb где 4Kb — стандартные страницы по умолчанию а последние два размера это так называемые Hugepages (HP). Если разбить 64 Gb на страницы разного размера то получится 32,768 x 2Mb и 16,777,216 x 4k страницы соответственно. Получается существенная разница. При этом управлять меньшем числом страниц гораздо легче, на это уходит сильно меньше ресурсов. По этой причине настоящие HP это благо особенно когда у вас сотни Гб памяти. Но использовать HP не так просто. Они специальным образом выделяются из системы в отдельный пул и приложение должно уметь работать с этим пулом. Например мы используем HP везде где запускаем qemu-kvm т.к. она умеет их использовать. Когда-то перейдя на HP мы сильно понизили утилизацию CPU в kernel space на очень больших серверах. В свою очередь THP это попытка RedHat быть красивой и умной. Это дополнительный уровень абстракции который призван прозрачно для приложений мапить стандартные страницы 4k на страницы 2 Mb. Это должно привести к снижению накладных расходов на обслуживание памяти обычных приложений не умеющих в HP. Еще важно, что THP поддерживаются только для анонимной памяти кучи и стека и не применимы для страничного кеша который так активно использует Ceph. Для многих приложений не рекомендуется использовать THP и Ceph в том числе: https://github.com/ceph/ceph-ansible/blob/824ec6d256fc23794d69dd82f789fb05ef5c7bb6/roles/ceph-osd/templates/tmpfiles_hugepage.j2

Отключение THP в runtime:

echo never > /sys/kernel/mm/transparent_hugepage/enabled

Что бы отключить THP перманентно необходимо добавить к параметрам ядра: transparent_hugepage=never

От себя добавлю, что использовать THP или даже настоящие HP при небольших объемах памяти (16, 32 Gb) — практически бесполезно а иногда даже накладно так как конкретно THP не бесплатен и кушает CPU. В общем на таком маленьком объеме можно смело отключать этот механизм.

NUMA и vm.zone_reclaim_mode

Еще один параметр ядра который призван повысить эффективность использования памяти. Если у вас в сервере несколько соккетов и соответственно NUMA-узлов то этот механизм скорее всего включен (его то включают то выключают, зависит от ядра). Особенности NUMA в общих чертах описаны тут.

Собственно vm.zone_reclaim_mode=1призван помочь выделять память процессу в той NUMA в которой он работает в настоящий момент. При этом, если в нужной NUMA памяти недостаточно то ядро может пытаться высвободить нужный объем путем сброса страничного кеша, например. В результате, процесс запрашивающий память может подвисать на мгновения или даже долше. Лично я видел это не один раз на серверах где по различным причинам не был отключен vm.zone_reclaim_mode. Например RGW инстанас может работать совершенно нормально только очень медленно. Обрабатывать не 10-20 реквестов в секунду а 2-3 и с огромными задержками. И такое же было с OSD, они переодически не отвечали на heartbeat и мониторы отмечали их как DOWN. Я долго не мог понять в чем дело.

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

For file servers or workloads
that benefit from having their data cached, zone_reclaim_mode should be
left disabled as the caching effect is likely to be more important than
data locality.

То есть, если приложение активно использует страничный кеш то общая производительность может быть лучше даже если кеш будет размазан по разным NUMA. Ceph активно использует страничный кеш, особенно с filestore и по это причине рекомендуется отключать этот механизм (если включен в вашем дистрибутиве):

sysctl vm.zone_reclaim_mode=0

Резервирование памяти. Ошибки page allocation failure

Я уже писал про эту ошибку ранее.
Тогда мы увеличили резерв до 512 Mb для серверов с 32 Gb и 12 OSD и эти ошибки перестали нас беспокоить. Но в последние несколько месяцев они снова дают о себе знать в моменты высокой нагрузки. Сейчас мы резервируем 2 GB:

sysctl vm.min_free_kbytes = 2097152

Посмотрите логи ядра, если вы периодически видите такие ошибки то и вам нужно увеличивать объем резерва. Мы увеличивали его с шагом в 64 Mb пока не избавлялись от ошибок.

Кстати есть еще один теоретический способ избавиться от этих ошибок путем повышения значения: vm.vfs_cache_pressure=100. Если повышать значение, то в теории страничный кеш будет высвобождаться агрессивнее и проблемы с недостатком памяти и ошибок page allocation failure быть не должно. Но лично мне не удалось подобрать правильное значение. Даже при vm.vfs_cache_pressure=2000ошибки продолжали напоминать о себе и в итоге ситуация разрешилась путем увеличения резерва.

Проблема фрагментации памяти

Опять же, ошибка page allocation failure частично возникает не из-за недостатка памяти а из-за ее сильной фрагментации. Это особенно характерно для нагруженных OSD-серверов.
Оценить уровень фрагментации:

echo m > /proc/sysrq-trigger
dimesq
…
[24051513.973433] Node 0 Normal: 83071*4kB (UEM) 14267*8kB (UEM) 9106*16kB (UEM) 9590*32kB (UEM) 5157*64kB (UEM) 2328*128kB (UEM) 351*256kB (UEM) 3*512kB (M) 0*1024kB 1*2048kB (R) 0*4096kB = 1620468kB
[24051513.973445] Node 1 DMA: 1*4kB (U) 0*8kB 1*16kB (U) 1*32kB (U) 1*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (R) 3*4096kB (M) = 15860kB
[24051513.973456] Node 1 DMA32: 79899*4kB (UEM) 3468*8kB (UEM) 40*16kB (UM) 0*32kB 0*64kB 0*128kB 0*256kB 1*512kB (R) 0*1024kB 0*2048kB 0*4096kB = 348492kB
[24051513.973466] Node 1 Normal: 313224*4kB (UEM) 11087*8kB (UEM) 96*16kB (UEM) 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1343128kB

….

Если у вас много страниц в первых нескольких колонках а в остальных не густо то у вас тоже есть проблема фрагментации памяти. С помощью следующей команды по cron можно немного исправлять ситуацию:

crontab -l
…
*/10 * * * * echo 1 > /proc/sys/vm/compact_memory

Я не знаю как это работает, честно. Но после выполнения этой команды память немного перераспределяется и появляются в том числе и большие линейные куски памяти. Распределение памяти из dmesq приведенное выше как раз получено на системе с такой задачей в cron.

Заключение

Параметры и настройки ядра относящиеся к подсистеме памяти помогающие Ceph работать стабильно и с оптимальной производительностью:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
sysctl vm.zone_reclaim_mode=0
sysctl vm.min_free_kbytes = 2097152
crontab -l
…
*/10 * * * * echo 1 > /proc/sys/vm/compact_memory

Опробованы так сказать нами в бою)