«‎Не могу поверить, что они догадались до этого первыми». Именно такая мысль пронеслась у меня в голове в середине декабря после нескольких недель работы по 12 часов в день, когда я отлаживал причины медленной работы кластера. Это был, пожалуй, самый интенсивный анализ производительности, который я проводил со времен Inktank. 

В моем сознании промелькнули полузабытые суеверия из 90-х о том, как получить благосклонность богов SCSI. 90-х? Блин, я старею. Мы прошли примерно две трети пути, который позволил бы нам начать с самого начала. Кстати говоря, я начну с самого начала.

В 2023 (я почти написал «в начале года», пока не вспомнил, что уже 2024) году в компанию Clyso обратилась довольно популярная и передовая компания, которая хотела перевести свой Ceph-кластер с HDD на NVMe-диски объемом 10 петабайт. Они сразу же заинтересовали нас. У них не было особых потребностей в RBD, RGW или CephFS. У них было свое представление, о том какое оборудование необходимо, но, к моей радости, обратились к нам, прежде чем что-то покупать. 

У них были несколько необычные требования. Кластер должен был быть размещен в 17 стойках, в каждой из которых было всего 4U свободного места. При этом учитывались такие факторы, как мощность, охлаждение, плотность и предпочтения производителя. Новые узлы должны были быть перенесены в существующий кластер без даунтайма. 

Однако сеть уже была построена, и это был просто зверь. Это одна из самых быстрых Ethernet-конфигураций, которые я когда-либо видел. Я с самого начала знал, что хочу помочь им построить этот кластер. Я также знал, что нам нужно будет провести предварительное тестирование и что это будет идеальная возможность продемонстрировать, на что способен Ceph на такой системе. Далее следует рассказ о том, как мы создавали и тестировали этот кластер и как сильно мы смогли его раскачать.

Благодарности

Прежде всего я хотел бы поблагодарить нашего замечательного заказчика, благодаря которому все это стало возможным. С вами было очень приятно работать! Спасибо также, что позволили нам, Clyso, поделиться этим опытом с сообществом Ceph. Именно благодаря обмену знаниями мы делаем мир лучше. 

Спасибо IBM/Red Hat и Samsung за предоставленное сообществу Ceph оборудование, использованное для сравнительного тестирования. Возможность оценить цифры, которые мы получали, в сравнении с предыдущими тестами в лаборатории, была бесценной. Спасибо всем соавторам Ceph, которые неустанно работают над тем, чтобы сделать Ceph великим! Наконец, отдельное спасибо Энтони Д'Атри и Ли-Энн Пуллар за их потрясающие навыки копирайтинга!

Настройка кластера

Когда заказчик впервые обратился в Clyso, он предложил конфигурацию с использованием 34 двухсокетных 2U-серверов, распределенных по 17 стойкам. Мы предложили несколько альтернативных конфигураций от разных поставщиков, с прицелом на меньшую производительность отдельного сервера. В конечном итоге они остановились на архитектуре Dell, которую разработали мы, — она стоила примерно на 13% дешевле первоначальной конфигурации, несмотря на ряд ключевых преимуществ. 

Новая конфигурация имела меньше памяти на один OSD (по-прежнему комфортные 12 ГБ на каждый), но более высокую пропускную способность памяти. Она также обеспечивает больше совокупных ресурсов процессора, значительно большую совокупную пропускную способность сети, более простую односокетную компоновку, а также использует новейшее поколение процессоров AMD и оперативную память DDR5. Из-за использования меньших узлов, мы вдвое снизили влияние отказа узла на восстановление кластера.

Заказчик хотел ограничить дополнительное энергопотребление на стойку до 1000-1500 Вт. При 4 таких узлах на стойку суммарный TDP оценивался как минимум в 1120 Вт плюс базовое энергопотребление, пики перегрузки процессора и накладные расходы блока питания. Вполне вероятно, что под нагрузкой мы немного превысим этот показатель, но мы не ожидаем значительного отклонения за пределы приемлемых значений. В худшем случае, по нашим расчетам, мы могли бы сэкономить около 100 Вт на стойку за счет снижения cTDP процессора.

Технические характеристики системы:

Узлы

68 x Dell PowerEdge R6615

Процессор

1 x AMD EPYC 9454P 48C/96T

Память

192 Гб DDR5

Сеть

2 x 100GbE Mellanox ConnectX-6

NVMe-диски

10 x Dell 15.36TB Enterprise NVMe Read Intensive AG

Операционная система

Ubuntu 20.04.6 (Focal)

Ceph

Quincy v17.2.7 (официальные релизные deb-пакеты)

Дополнительное преимущество использования 1U серверов Dell в том, что они являются обновленной версией систем, которые Дэвид Гэллоуэй и я разработали для лаборатории производительности Ceph. Эти системы были протестированы в различных статьях за последние пару лет. Оказалось, что во время тестирования возникла серьезная проблема, влияющая на производительность, которая не коснулась предыдущего поколения оборудования в лаборатории производительности Ceph, но затронула новое оборудование. Мы поговорим об этом подробнее позже.

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

Настройка тестирования

Чтобы провести тестирование производительности, мы развернули эфемерные кластеры Ceph и запустили тесты FIO с помощью CBT. CBT был настроен на развертывание Ceph с несколькими измененными параметрами. Для OSD было назначено 8 Гб osd_memory_target. В продакшне более высокое значение osd_memory_target должно быть приемлемым. У заказчика не было необходимости тестировать нагрузку на блочные устройства или S3, поэтому можно предположить, что естественным выбором будет RADOS bench. 

По моему опыту, тестирование в больших масштабах с помощью RADOS bench — сложная задача. Трудно определить, сколько экземпляров необходимо для насыщения кластера при заданном количестве потоков. В прошлом я сталкивался с проблемами, когда для масштабирования производительности требовалось нагружать несколько пулов одновременно. Кроме того, у меня не было под рукой готовых стендовых тестов RADOS, с которыми можно было бы сравнивать. Вместо этого мы решили провести тестирование, используя то же самое тестирование FIO с поддержкой librbd, которое мы использовали в лаборатории производительности Ceph. Это позволило нам разделить кластер на более мелкие части и сравнить результаты с ранее опубликованными. 

FIO также очень хорошо известен и заслуживает доверия. Основным преимуществом механизма librbd в FIO (по сравнению с использованием FIO с RBD-драйвером ядра) является отсутствие проблем с зависшими точками монтирования, которые могут потребовать перезагрузки системы. У нас не было IPMI-доступа к этому кластеру, и мы были ограничены сроками завершения тестов. По этой причине мы пропустили тестирование RBD-драйвера ядра. Однако, основываясь на предыдущем тестировании, мы ожидали, что совокупная производительность будет примерно одинаковой при наличии достаточного количества клиентов. Мы смогли протестировать трехкратную репликацию и конфигурацию с EC 6+2. Мы также протестировали msgr V2 в незашифрованном и защищенном режиме, используя следующие опции Ceph:

ms_client_mode = secure
ms_cluster_mode = secure
ms_service_mode = secure
ms_mon_client_mode = secure
ms_mon_cluster_mode = secure
ms_mon_service_mode = secure

OSD могли использовать все ядра процессора на серверах. FIO был настроен на предварительное заполнение RBD большими блоками, после чего проводились тесты IO с блоком 4 МБ и 4 КБ в течение 300 секунд каждый (60 секунд во время отладочных прогонов). Некоторые фоновые процессы, такие как scrub, deep scrub, автомасштабирование и балансировка PG, были отключены.

Заметка о показателях PG

Позже в этой статье вы увидите некоторые впечатляющие показатели счетчиков PG во время тестов. Мы добавили их специально. Из предыдущих лабораторных испытаний мы знаем, что количество PG может сильно влиять на производительность. Отчасти это связано с неравномерностью в случайных распределениях при низком количестве выборок (PG). В теории, этот эффект можно смягчить с помощью дополнительной балансировки. 

Менее обсуждаемый вопрос — борьба за блокировку PG внутри OSD. Мы заметили, что на очень быстрых кластерах блокировка PG может играть значительную роль в общей производительности. К сожалению, эту проблему не так легко устранить без увеличения количества PG. Какое количество PG оказывает влияние?

При использовании всего 60 OSD производительность случайного чтения растет до 16384 PG в пуле RBD с 3-кратной репликацией. Запись достигает максимума гораздо раньше, но все равно выигрывает от количества PG < 2048.

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

Тем не менее, я хочу призвать сообщество задуматься о том, имеет ли смысл продолжать использовать общепринятое значение в 100 PG на OSD. Я бы хотел, чтобы мы переосмыслили, что нам нужно сделать, чтобы достичь большего количества PG на OSD, сохраняя при этом накладные расходы и использование памяти под контролем. Я мечтаю о будущем, в котором 1000 PG на OSD не будет чем-то необычным, журналы PG будут автоматически масштабироваться для каждого пула, а автомасштабирование PG станет гораздо более редкой операцией.

Тяжелый старт

Мы впервые смогли войти в систему на новом оборудовании спустя неделю после Дня благодарения в США (прим. пер. — четвертый четверг ноября). Планировалось, что в течение недели или двух мы проведем проверочные тесты, а затем добавим новое оборудование в существующий кластер. Мы надеялись завершить миграцию к Новому году, если все пойдет по плану. К сожалению, мы столкнулись с проблемами прямо в самом начале. 

Первоначальные низкоуровневые тесты производительности выглядели хорошо. Сетевое тестирование Iperf показало, что скорость передачи данных на каждом сервере составляет чуть менее 200 Гбит/с. Случайная выборка нескольких узлов показала приемлемую базовую производительность NVMe-накопителей. 

Одна из проблем, которую мы сразу же заметили, заключалась в том, что операционная система на всех 68 серверах была случайно установлена на 2 диска, предназначенных для OSD, вместо внутренних загрузочных дисков Dell BOSS m.2. Мы планировали сравнить результаты для конфигурации с 30 OSD (3 узла, 10 OSD на сервер) с результатами, полученными в лаборатории производительности Ceph (5 серверов, 6 OSD на узел). Вместо этого мы протестировали 8 NVMe-накопителей на сервер. Первые результаты тестирования оказались гораздо ниже тех, которые мы надеялись увидеть, даже с учетом уменьшения количества дисков OSD.

Единственный приемлемый результат был получен при случайном чтении, но и это было не очень хорошо. Очевидно, что-то было не так. Мы перестали проводить тесты на 3 серверах и начали рассматривать конфигурации с одним сервером и даже с одним OSD.

И вот тут-то все и начало становиться странным.

Пугающее поведение

Когда мы проводили различные комбинации тестов на 8-OSD и 1-OSD на отдельных узлах кластера, мы наблюдали совершенно разное поведение. Потребовалось несколько дней тестирования, чтобы понять закономерности того, что мы наблюдали. Системы, которые изначально хорошо работали в тестах с одним OSD, деградировали по производительности после тестов с несколькими OSD, а спустя несколько часов снова начинали работать хорошо. Тесты, где было 8 OSD, иногда показывали признаки хорошей производительности, но затем демонстрировали ужасные результаты во всех последующих тестах, пока система не перезагружалась. В конце концов нам удалось выявить закономерность при новой загрузке, которую мы могли примерно воспроизвести на разных серверах кластера:

Step

OSDS

4MB Randread (MB/s)

4MB Randwrite (MB/s)

Boot

1

1 OSD

5716

3998

2

8 OSDs

3190

2494

3

1 OSD

523

3794

4

8 OSDs

2319

2931

5

1 OSD

551

3796

ожидание 20-30 минут

6

1 OSD

637

3724

ожидание 20-30 минут

7

1 OSD

609

3860

ожидание 20-30 минут

8

1 OSD

362

3972

ожидание 20-30 минут

9

1 OSD

6581

3998

ожидание 20-30 минут

10

1 OSD

6350

3999

ожидание 20-30 минут

11

1 OSD

6536

4001

Первоначальный тест с одним накопителем выглядел просто фантастически при чтении и записи большими блоками и показал почти такую же пропускную способность, какую мы наблюдали при выполнении тестов FIO непосредственно с накопителями. Однако, как только мы запустили тест с 8 дисками, мы заметили резкое падение производительности. Последующие тесты с одним OSD продолжали демонстрировать низкие показатели до тех пор, пока через несколько часов они не восстановились. До тех пор, пока не запускался тест с несколькими OSD, производительность оставалась высокой.

Смущает еще и тот факт, что мы не смогли добиться такого же поведения при выполнении тестов FIO непосредственно на дисках. Не менее странно и то, что во время теста с 8 OSD один из OSD использовал значительно больше CPU, чем остальные:

Случайное чтение блоками по 4 МБ 

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND 
511067 root      20   0 9360000   7.2g  33792 S  1180   3.8  15:24.32 ceph-osd                                              
515664 root      20   0 9357488   7.2g  34560 S 523.6   3.8  13:43.86 ceph-osd                                              
513323 root      20   0 9145820   6.4g  34560 S 460.0   3.4  13:01.12 ceph-osd                                              
514147 root      20   0 9026592   6.6g  33792 S 378.7   3.5   9:56.59 ceph-osd                                              
516488 root      20   0 9188244   6.8g  34560 S 378.4   3.6  10:29.23 ceph-osd                                              
518236 root      20   0 9390772   6.9g  33792 S 361.0   3.7   9:45.85 ceph-osd                                              
511779 root      20   0 8329696   6.1g  33024 S 331.1   3.3  10:07.18 ceph-osd                                              
516974 root      20   0 8984584   6.7g  34560 S 301.6   3.6   9:26.60 ceph-osd 

Профилирование под нагрузкой показало значительное время, проведенное в io_submit. Обычно это мы наблюдаем, когда ядро начинает блокировать диск из-за того, что его очередь заполнена.

Пример tp_osd_tp с io_submit

+ 31.00% BlueStore::readv(boost::intrusive_ptr<ObjectStore::CollectionImpl>&, g...
 + 31.00% BlueStore::_do_readv(BlueStore::Collection*, boost::intrusive_ptr<Blu...
  + 24.00% KernelDevice::aio_submit(IOContext*)
  |+ 24.00% aio_queue_t::submit_batch(std::_List_iterator<aio_t>, std::_List_it...
  | + 24.00% io_submit
  |  + 24.00% syscall

Почему выполнение теста на 8 OSD может привести к тому, что ядро начнет блокировать io_submit во время последующих тестов с одним OSD? Это не имело особого смысла. Изначально мы подозревали троттлинг. Мы видели, что при стандартном профиле охлаждения в BIOS температура нескольких ядер процессора достигала 96 градусов Цельсия. Мы предположили, что, возможно, во время тестов на 8 OSD мы достигали тепловых пределов либо для процессора, либо для NVMe-накопителей. Возможно, из-за этого система находилась в деградированном состоянии в течение некоторого времени. К сожалению, эта теория не подтвердилась. AMD/Dell подтвердили, что даже при таких температурах троттлинга не должно быть, и мы смогли опровергнуть эту теорию, запустив системы с включенными на 100% кулерами и пониженным cTDP для процессора. Эти изменения позволили поддерживать температуру в районе 70 градусов Цельсия под нагрузкой, не решив проблему.

В течение недели мы перепроверили все: настройки BIOS, NVMe multipath, низкоуровневую отладку NVMe, смену версий ядра/Ubuntu, а также все возможные варианты ядра, ОС и Ceph. Ни одна из этих мер не помогла полностью решить проблему.

Мы даже провели анализ blktrace и iowatcher во время «хороших» и «плохих» тестов с одним OSD и смогли напрямую наблюдать медленное завершение ввода-вывода:

Вывод Blkparse: «хорошие» и «плохие» результаты

Timestamp (good)

Offset+Length (good)

Timestamp (bad)

Offset+Length (bad)

10.00002043

1067699792 + 256 [0]

10.0013855

1206277696 + 512 [0]

10.00002109

1153233168 + 136 [0]

10.00138801

1033429056 + 1896 [0]

10.00016955

984818880 + 8 [0]

10.00209283

1031056448 + 1536 [0]

10.00018827

1164427968 + 1936 [0]

10.00327372

1220466752 + 2048 [0]

10.0003024

1084064456 + 1928 [0]

10.00328869

1060912704 + 2048 [0]

10.00044238

1067699280 + 512 [0]

10.01285746

1003849920 + 2048 [0]

10.00046659

1040160848 + 128 [0]

10.0128617

1096765888 + 768 [0]

10.00053302

1153233312 + 1712 [0]

10.01286317

1060914752 + 720 [0]

10.00056482

1153229312 + 2000 [0]

10.01287147

1188736704 + 512 [0]

10.00058707

1067694160 + 64 [0]

10.01287216

1220468800 + 1152 [0]

10.00080624

1067698000 + 336 [0]

10.01287812

1188735936 + 128 [0]

10.00111046

1145660112 + 2048 [0]

10.01287894

1188735168 + 256 [0]

10.00118455

1067698344 + 424 [0]

10.0128807

1188737984 + 256 [0]

10.00121413

984815728 + 208 [0]

10.01288286

1217374144 + 1152 [0]

Три исправления

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

Первое исправление

Первое исправление было простым, но дало нам лишь скромный прирост производительности в 10-20%. Много лет назад было обнаружено (то ли Ником Фиском, то ли Стивеном Блиником, если мне не изменяет память), что Ceph невероятно чувствителен к задержкам, вносимым сменой состояний C-State в CPU. Быстрая проверка BIOS этих серверов показала, что они не работают в режиме максимальной производительности, который отключает C-State. Это была приятная победа, но недостаточная для получения нужных результатов.

Второе исправление

В момент, когда я копался в результатах blktrace, показанных выше, я был на 95% уверен, что мы наблюдаем либо проблему с NVMe-накопителями, либо что-то связанное с шиной PCIe, поскольку в этих системах нет PCIe свитчей. Я был занят тем, что копался в технических руководствах и пытался найти способы отладки и профилирования оборудования. 

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

В то время как я сосредоточился в основном на профилировании и теперь копался в попытках отладить железо, он хотел понять, происходит ли что-то интересное на стороне ядра (что, я сейчас понимаю, было бы очевидным следующим шагом!) Он запустил perf профиль во время неудачного запуска и сделал очень проницательное открытие:

 77.37%  tp_osd_tp        [kernel.kallsyms]             [k] native_queued_spin_lock_slowpath
            |
            ---native_queued_spin_lock_slowpath
               |          
                --77.36%--_raw_spin_lock_irqsave
                          |          
                          |--61.10%--alloc_iova
                          |          alloc_iova_fast
                          |          iommu_dma_alloc_iova.isra.0
                          |          iommu_dma_map_sg
                          |          __dma_map_sg_attrs
                          |          dma_map_sg_attrs
                          |          nvme_map_data
                          |          nvme_queue_rq
                          |          __blk_mq_try_issue_directly
                          |          blk_mq_request_issue_directly
                          |          blk_mq_try_issue_list_directly
                          |          blk_mq_sched_insert_requests
                          |          blk_mq_flush_plug_list
                          |          blk_flush_plug_list
                          |          |          
                          |          |--56.54%--blk_mq_submit_bio

Огромное количество времени тратится в ядре на борьбу за спин-блокировку при обновлении IOMMU-маппинга. Он отключил IOMMU в ядре и сразу же увидел огромный прирост производительности во время тестов с 8 серверами. Мы повторили эти тесты несколько раз и неоднократно видели гораздо лучшую производительность при чтении/записи на 4 МБ-блоках. Балл в пользу клиента. Однако проблема с произвольной записью на блоках 4 КБ все еще оставалась.

Третье исправление 

После того как заказчик справился с проблемой IOMMU, я был почти благодарен за то, что у нас появилась дополнительная проблема, которую нужно было решить. Производительность случайной записи блоками по 4 КБ улучшилась после первых двух исправлений, но все еще была значительно хуже, чем в лаборатории производительности Ceph (даже с учетом уменьшения количества узлов/дисков). Я также заметил, что compaction в RocksDB происходил гораздо медленнее, чем ожидалось. Ранее было два значительных случая, которые проявлялись схожим образом и казались релевантными:

  1. Ceph может быть очень медленным, если не скомпилирован должным образом с поддержкой TCMalloc.

  2. Ceph может быть очень медленным, если не скомпилировать его с правильными флагами cmake и оптимизациями компилятора.

Исторически этот клиент использовал официальные пакеты Ceph для Ubuntu, и мы все еще использовали их здесь (а не что-то скомпилированное самостоятельно или cephadm с контейнерами). Я проверил, что TCMalloc был скомпилирован. Это исключило первую проблему. Далее я откопал журналы сборки пакетов для 17.2.7 под Ubuntu. И тут я заметил, что на самом деле мы собирали RocksDB без правильных флагов компиляции. Неясно, как давно это происходит, но у нас были общие проблемы с производительностью сборок еще в 2018 году.

Оказалось, что Canonical исправила это для своих сборок, как и Gentoo, увидев заметку, которую я написал в do_cmake.sh более 6 лет назад. Довольно прискорбно, что наши официальные сборки deb-пакетов, страдают от этого так долго, однако, по крайней мере, похоже, что это не влияет на тех, кто использует cephadm в Debian/Ubuntu с официальными сборками образов контейнеров. Разобравшись в проблеме, мы собрали собственные пакеты для 17.2.7 с исправлением. Время compaction’а уменьшилось примерно в 3 раза, а производительность случайной записи блоками 4 КБ удвоилась (хотя на графике это немного сложно разобрать):

Производительность случайной записи блоками 4 КБ все еще была ниже, чем мне хотелось бы, но, по крайней мере, теперь мы находились примерно на правильном уровне, учитывая, что у нас было меньше OSD, всего 3/5 от количества узлов и меньше (хотя и более быстрых) ядер на OSD. 

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

Первая неделя 2024 года

Утром 2 января я зашел в Slack, и меня встретила сцена, которую я охарактеризую как умеренно контролируемый хаос. На совершенно другом кластере, за который мы отвечаем, произошел серьезный сбой. Не вдаваясь глубоко в подробности, скажу, что потребовалось 3 дня, чтобы вытащить этот кластер с края пропасти и привести его в стабильное и относительно здоровое состояние. Только в пятницу я смог вернуться к тестированию производительности. Мне удалось выкроить дополнительный день для тестирования в понедельник, но это означало, что у меня было очень мало времени, чтобы продемонстрировать, что кластер может нормально работать под нагрузкой, прежде чем мы начнем процесс миграции данных.

Судьба улыбается 

В пятницу я работал весь день, чтобы заново развернуть CBT и воспроизвести тесты, которые мы проводили ранее. На этот раз я смог использовать все 10 дисков в каждом узле. Я также увеличил количество клиентов, чтобы поддерживать в среднем около 1 клиента FIO с глубиной io_depth 128 на каждый OSD. Первый тест на 3 узлах выглядел неплохо. При 10 OSD на узел мы достигли примерно пропорциональной (т.е. более высокой) производительности по сравнению с предыдущими тестами. 

Я знал, что у меня не будет много времени для правильных тестов масштабируемости, поэтому я сразу же увеличил количество серверов с 3 до 10. Я также увеличил количество PG и использовал CBT для развертывания нового кластера. На 3 узлах я увидел 63 ГБ/с для случайных чтений блоком 4 МБ. На 10 узлах я увидел 213,5 ГБ/с. Это почти линейное масштабирование на уровне 98,4%. Именно в этот момент я понял, что ситуация наконец-то меняется к лучшему. 

Из 68 узлов этого кластера на тот момент работали только 63. Остальные были выключены на техническое обслуживание для устранения различных проблем. Я разделил кластер примерно пополам: в первой половине было 32 сервера (320 OSD), а в другой — 31 клиент которых выполнялось по 10 процессов FIO. Я наблюдал, как CBT собирает кластер в течение примерно 7-8 минут. Первоначальное предварительное заполнение выглядело очень хорошо. 

Мое сердце забилось. Мы читали данные со скоростью 635 ГБ/с. Мы превысили 15 миллионов IOPS блоками по 4К при случайном чтении. Хотя это не кажется впечатляющим по сравнению с отдельными NVMe-накопителями, это были самые высокие показатели, которые я когда-либо видел для Ceph-кластера с ~300 OSD.

Я также построил график средней и хвостовой задержки для тестов масштабирования. Оба выглядели согласованно. Скорее всего, это связано с масштабированием числа PG и числа клиентов FIO одновременно с OSD. Эти тесты очень требовательны к IO. У нас так много клиентского трафика, что мы, скорее всего, уже подошли к точке насыщения, когда производительность не увеличивается, а задержка продолжает расти по мере роста IO.

Я показал эти результаты своему коллеге Дэну ван дер Стеру, который ранее создавал инфраструктуру Ceph в CERN. Он поставил пиво (лучше хорошее, Дэн!), что я не смогу достичь скорости 1 ТБ/с. Я сказал ему, что это было моим планом с самого начала.

Частично действующая Звезда Смерти

У меня не было дополнительных клиентских узлов для тестирования кластера полного размера, поэтому единственным реальным вариантом было совместное размещение процессов FIO на тех же узлах, что и OSD. 

С одной стороны, это дает некоторое сетевое преимущество. Клиенты смогут общаться с локальными OSD 1/63 времени. С другой стороны, из предыдущего тестирования мы знаем, что совместное размещение клиентов FIO на узлах OSD не является бесплатным. Это часто приводит к снижению производительности, и мне было неясно, насколько сильно пострадает кластер такого масштаба.

Я создал новую конфигурацию CBT, ориентированную на 63 доступных мне узла. Развертывание кластера с помощью CBT заняло около 15 минут, чтобы запустить все 630 OSD и создать пул. Я ждал с затаенным дыханием, пока появятся результаты теста.

Около 950 ГБ/с. Что ж, очень-очень близко. В пятницу вечером было уже поздно, поэтому я завершил работу и лег спать. В субботу утром я вошел в систему и проверил несколько настроек кластера: Уменьшил количество шардов OSD и потоков асинхронного мессенджера, а также применил настройки RocksDB из Reef. 

Как вы можете видеть, мы немного навредили производительности чтения и одновременно повысили производительность записи. На самом деле, производительность случайной записи выросла почти на 20%. После дальнейшего тестирования стало похоже, что настройки из Reef оказались достаточно мягкими, хотя и помогли немного в тестах на запись. 

Больший эффект, похоже, был от изменений шардов и потоков. На этом этапе мне пришлось сделать перерыв, и я смог вернуться к работе над кластером только в воскресенье вечером. Я пытался лечь спать, но понимал, что остались последние 24 часа до того, как нам нужно будет все отдать заказчику. Около полуночи я сдался в попытке уснуть и вернулся к работе.

Я уже упоминал, что мы знаем, что количество PG может влиять на производительность. Я решил оставить прежнюю «оттюненную» конфигурацию, но удвоил количество PG. В первом прогоне тестов я уменьшил соотношение клиентов и OSD, учитывая, что мы размещали их на узлах OSD. Теперь я снова попробовал увеличить их количество. Производительность случайного чтения блоками по 4 МБ немного улучшилась с ростом числа клиентов, в то время как IOPS при случайном чтении малыми блоками снизились. Как только мы достигли 8 процессов FIO на узел (всего 504), производительность последовательной записи упала практически в ноль.

Чтобы понять, что произошло, я повторил тест записи и посмотрел вывод «ceph -s»:

services:
    mon: 3 daemons, quorum a,b,c (age 42m)
    mgr: a(active, since 42m)
    osd: 630 osds: 630 up (since 24m), 630 in (since 25m)
         flags noscrub,nodeep-scrub
 
  data:
    pools:   2 pools, 131073 pgs
    objects: 4.13M objects, 16 TiB
    usage:   48 TiB used, 8.2 PiB / 8.2 PiB avail
    pgs:     129422 active+clean
             1651   active+clean+laggy
 
  io:
    client:   0 B/s rd, 1.7 GiB/s wr, 1 op/s rd, 446 op/s wr

Как только я запустил в кластер 504 процесса FIO, выполняющих запись блоками по 4 МБ, некоторые из PG начали появляться со статусом active+clean+laggy. Производительность упала, и кластер не выходил из этого состояния до тех пор, пока рабочая нагрузка не была завершена. 

Что еще хуже, со временем все больше PG стали работать с задержками, хотя пропускная способность составляла лишь малую часть того, на что был способен кластер. С тех пор мы нашли в списке рассылки несколько сообщений о PG в статусе laggy, а также несколько предложений по их устранению. Неясно, помогли ли бы эти идеи в данном случае. 

Мы знаем, что IO временно приостанавливается, когда PG переходит в статус laggy, и что это происходит потому, что реплика вовремя не подтвердила новые лизы от мастера. Обсудив проблему с другими разработчиками Ceph, мы думаем, что это может быть связано с блокировкой в OSD или с тем, что сообщения о лизах конкурируют с работой в одних и тех же потоках async msgr.

Несмотря на то, что меня отвлекала проблема с задержкой PG, я хотел сосредоточиться на достижении 1,0 Тбайт/с. Недостаток сна наконец-то настиг меня. В какой-то момент я снова удвоил количество PG до 256 000, просто чтобы посмотреть, повлияет ли это на проблему лагающего PG. Это привело нас к верхней границе кривой, которую мы показывали ранее, хотя, честно говоря, я не думаю, что это имело большое значение. 

Я решил вернуться к стандартному количеству шардов OSD и продолжить тестирование с 504 клиентскими процессами FIO. Однако я увеличил количество потоков асинхронного мессенджера. Было два важных вывода:

  1. Уменьшение числа асинхронных мессенджеров до 1 позволило нам избежать задержек PG и достичь нормальной пропускной способности записи при 504 клиентах. Это также значительно ухудшило производительность при чтении блоками по 4 МБ. 

  2. Настройки Ceph по умолчанию были идеальны для чтения 4 мегабайтными блоками. С 8 шардами, 2 потоками на шард и 3 потоками msgr мы наконец-то пробили отметку в 1 Тбайт/с. Вот вид, который я имел около 4 часов утра в понедельник, когда выполнялся последний набор тестов в ту ночь:

  services:
    mon: 3 daemons, quorum a,b,c (age 30m)
    mgr: a(active, since 30m)
    osd: 630 osds: 630 up (since 12m), 630 in (since 12m)
         flags noscrub,nodeep-scrub
 
  data:
    pools:   2 pools, 262145 pgs
    objects: 4.13M objects, 16 TiB
    usage:   48 TiB used, 8.2 PiB / 8.2 PiB avail
    pgs:     262145 active+clean
 
  io:
    client:   1.0 TiB/s rd, 6.1 KiB/s wr, 266.15k op/s rd, 6 op/s wr

и графики результатов FIO:

Сон; Erasure Coding

Наконец-то увидев волшебную строчку «1,0 ТБ/с», которой я ждал несколько недель, я отправился спать. Тем не менее я встал через несколько часов. Предстояло еще поработать. Все тесты, которые мы проводили до сих пор, были связаны с трехкратной репликацией, но заказчик собирался перенести это оборудование в существующий кластер, развернутый с EC 6+2. Нам нужно было получить представление о том, на что способен этот кластер в той конфигурации, которую они будут использовать.

Я снова переконфигурировал кластер и провел новые тесты. Я выбрал значения количества PG/шардов/клиентов из предыдущих тестов, которые, как оказалось, работали хорошо. Производительность была хорошей, но я увидел, что потоки асинхронного мессенджера работают очень интенсивно. Я решил попробовать увеличить эти значения сверх установленных по умолчанию, чтобы посмотреть, помогут ли они, учитывая дополнительный сетевой трафик.

Мы смогли достичь более 500 Гбайт/с для чтения и почти 400 Гбайт/с для записи с 4-5 потоками асинхронного мессенджера. Но почему результаты чтения при использовании EC намного медленнее, чем при репликации? При репликации основной OSD для PG должен считывать только локальные данные и отправлять их клиенту. Сетевые накладные расходы по сути равны 1. 

При EC 6+2 primary OSD должен прочитать 5 из 6 фрагментов из реплик, прежде чем отправить воссозданный объект клиенту. Общие сетевые накладные расходы на запрос составляют примерно (1 + 5/6)X*. Вот почему при чтении мы видим производительность чуть лучше, чем половина производительности трехкратной репликации. 

Для записи ситуация обратная. При трехкратной репликации клиент отправляет объект на primary OSD, который затем отправляет копии по сети на две реплики. В результате суммарные сетевые накладные расходы составляют 3x. В случае с EC нам нужно отправлять на вторичные сервера только 7/8 фрагментов (почти, но не совсем так же, как в случае с чтением). Для больших записей производительность действительно выше.

* Изначально в этой статье говорилось, что для чтения необходимо извлекать 7/8 фрагментов. Правильное значение — 5/6 фрагментов, если только не включено быстрое чтение. В этом случае будет 7/6 фрагментов. Спасибо Джошуа Бергену за то, что заметил это!

Однако IOPS — это совсем другая история. Для очень маленьких блоков чтения и записи Ceph будет обращаться ко всем участвующим OSD в PG для этого объекта, даже если данные, которые они хранят, не имеют отношения к операции. Например, если вы выполняете чтение блоками по 4K, а интересующие вас данные хранятся в одном чанке на одном из OSD, Ceph все равно получит данные со всех OSD, участвующих в страйпе. 

Летом 2023 года Clyso воскресил PR от Xiaofei Cui, который реализует частичное чтение страйпа для EC, чтобы избежать этой дополнительной работы. Эффект впечатляет:

Пока неясно, удастся ли нам замержить это в Squid, хотя Радослав Заржински, ключевой руководитель проекта Ceph, предложил свою помощь в попытке довести это до конца.

Получилось и протестировать шифрование Msgr

Наконец, мы хотели дать заказчику примерное представление о том, насколько сильно шифрование на уровне msgr повлияет на его кластер, если он решит его использовать. Адреналин предыдущей ночи уже давно улетучился, и к этому моменту я был смертельно уставшим. Мне удалось провести тесты трехкратной репликации и EC 6+2 с включенным шифрованием msgr v2 и сравнить их с результатами наших предыдущих тестов.

Больше всего пострадало чтение большим блоком. Оно просело с ~1 Тбайт/с до примерно 750 Гбайт/с. Во всем остальном наблюдается чуть более скромное, хотя и стабильное падение. На этом этапе мне пришлось остановиться. Мне очень хотелось провести тесты масштабирования PG и даже тесты ядерного RBD-драйвера. Однако пора было возвращать системы заказчику для переналивки, а затем одному из моих замечательных коллег в Clyso — для интеграции.

Итоги

Что же произошло с этим кластером после окончания тестирования? Все аппаратное обеспечение было переналито, и были развернуты новые OSD в существующем кластере HDD заказчика. Для управления процессом миграции используется скрипт upmap-remapped Дэна, и мы перенесли около 80% существующих данных на NVMe-OSD. 

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

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

30 OSDs (3x)

100 OSDs (3x)

320 OSDs (3x)

630 OSDs (3x)

630 OSDs (EC62)

Co-Located Fio

Нет

Нет

Нет

Да

Да

4MB Read

63 GiB/s

214 GiB/s

635 GiB/s

1025 GiB/s

547 GiB/s

4MB Write

15 GiB/s

46 GiB/s

133 GiB/s

270 GiB/s

387 GiB/s

4KB Rand Read

1.9M IOPS

5.8M IOPS

16.6M IOPS

25.5M IOPS

3.4M IOPS

4KB Rand Write

248K IOPS

745K IOPS

2.4M IOPS

4.9M IOPS

936K IOPS

Что дальше? Нам нужно придумать, как исправить проблему с лагами PG при записи. Мы не можем допустить, чтобы Ceph развалился при увеличении нагрузки на запись. Кроме того, в ходе этого упражнения мы узнали, что Ceph вполне способен насытить 2 сетевые карточки 100GbE. Для дальнейшего увеличения пропускной способности нам понадобится 200GbE+ при использовании 10 NVMe-накопителей на узел или более. 

С IOPS больше нюансов. Мы знаем, что количество PG может оказывать большое влияние. Мы также знаем, что большую роль играет потоковая модель OSD. Мы постоянно сталкиваемся с проблемой на уровне около 400-600 тыс. IOPS при случайном чтении на узел, и мы наблюдали это во многих инсталляциях. Отчасти это может быть связано с тем, как асинхронный msgr взаимодействует с ядром, а отчасти с тем, как потоки OSD просыпаются, когда в очереди шардов появляется новая работа. 

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

Насколько мне известно, это самые быстрые результаты работы одного кластера Ceph из когда-либо опубликованных и первый случай, когда кластер Ceph достиг скорости 1 ТБ/с. Я думаю, что Ceph способен на большее. Если у кого-то тут есть более быстрый кластер, предлагаю опубликовать результаты своих тестов производительности. Спасибо за чтение и если есть какие-либо вопросы или вы хотите предметнее поговорить о производительности Ceph, не стесняйтесь писать.

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