====== Производительность Ceph ======
Ceph — это SDS (по-русски — программная СХД), которая по некоторым параметрам является уникальной в своём роде, и в целом, умеет очень многое — S3, диски виртуалок, кластерную FS + огромный багаж дополнительных фич.
И всё было бы хорошо — бери, ставь, запускай своё облако и руби бабло — если бы не один маленький нюанс: ПРОИЗВОДИТЕЛЬНОСТЬ. Терять 95 % производительности в Production-е разумным людям обычно жалко. «Облакам» типа AWS, GCP, Яндекса, по-видимому, не жалко — у них тоже собственные крафтовые SDS и они тоже тормозят примерно так же :-) но этот вопрос оставим — кто мы такие, чтобы их судить.
В данной статье описано, каких показателей производительности можно добиться от цефа и как. Но сразу предупрежу: локальный SSD вы не догоните. Локальные SSD сейчас ОЧЕНЬ быстрые (особенно NVMe), порядок их задержки — 0.05ms. Догнать эту цифру SDS-ке крайне трудно (одна только сеть сожрёт те же 0.05ms), перегнать — наверное, вообще невозможно.
**UPDATE: Догнать можно. Я это сделал в проекте — Vitastor: [[https://vitastor.io/|https://vitastor.io]] :-) это блочная SDS с архитектурой, похожей на Ceph, но при этом БЫСТРАЯ — в тесте на SATA SSD кластере задержка и чтения, и записи составила 0.14 мс. На том же кластере задержка записи у Ceph была 1 мс, а чтения — 0.57 мс. Детали есть в [[https://yourcmc.ru/git/vitalif/vitastor/src/branch/master/README.md|README]] — смотрите по ссылке.**
**Ceph была 1 мс, а чтения — 0.57 мс. Детали есть в [[https://yourcmc.ru/git/vitalif/vitastor/src/branch/master/README.md|README]] — смотрите по ссылке.**
==== ====
{{ https://fatalex.cifro.net/lib/plugins/ckgedit/fckeditor/userfiles/image/ceph/ceph-funnel.jpg?nolink&500x565|ceph-funnel.jpg}}
==== Бенчмаркинг ====
Основные направления тестирования:
* Линейное чтение/запись (большими блоками)
* Пиковая производительность высоко-параллельного случайного чтения/записи мелкими блоками
* Задержка однопоточного случайного чтения мелкими блоками (4-8 Кб)
* Задержка однопоточной транзакционной записи мелкими блоками (4-8 Кб) — обычно последовательной, как в журнал СУБД, но в один поток это обычно слабо отличается от случайной
Задержки обычно важнее простой пиковой производительности случайного чтения/записи, так как далеко не каждое приложение может загрузить диск при большом параллелизме / глубокой очереди (32-128 запросов).
Примерным ориентиром наилучшей возможной задержки Ceph служит доклад Nick Fisk «Low-Latency Ceph», в его исполнении Low latency это 0.7ms. Это и есть ориентир, на лучший результат рассчитывать особенно не приходится. 0.7ms — это всего лишь примерно ~1500 iops в 1 поток.
==== Тестирование дисков ====
[[https://docs.google.com/spreadsheets/d/1E9-eXjzsKboiCCX-0u0r5fAjjufLKayaut_FOPxYZjc|SSD Bench Google Docs]]
Сначала прогоните fio на голом диске:
**ВНИМАНИЕ!** Для тех, кто в танке — fio-тест записи на диск ДЕСТРУКТИВНЫЙ. Не вздумайте запускать его на дисках/разделах, на которых есть нужные данные… например, журналы OSD (был прецедент).
* Перед тестированием попробуйте отключить кэш записи диска: hdparm -W 0 /dev/sdX(SATA-диски через SATA или HBA), sdparm –set WCE=0 /dev/sdX(SAS-диски). Не совсем ясно, почему, но эта операция на серверных SSD может увеличить IOPS-ы на 2 порядка (а может НЕ увеличить, поэтому пробуйте оба варианта — и W0, и W1). Также см.ниже [[https://yourcmc.ru/wiki/Производительность_Ceph#.D0.9A.D0.B0.D1.80.D1.82.D0.B8.D0.BD.D0.B0_.D0.BC.D0.B0.D1.81.D0.BB.D0.BE.D0.BC_.C2.AB.D0.A2.D0.BE.D1.80.D0.BC.D0.BE.D0.B7.D1.8F.D1.89.D0.B8.D0.B9_.D0.BA.D1.8D.D1.88.C2.BB|#Картина маслом «Тормозящий кэш»]].
* Линейное чтение: fio -ioengine=libaio -direct=1 -invalidate=1 -name=test -bs=4M -iodepth=32 -rw=read -runtime=60 -filename=/dev/sdX
* Линейная запись: fio -ioengine=libaio -direct=1 -invalidate=1 -name=test -bs=4M -iodepth=32 -rw=write -runtime=60 -filename=/dev/sdX
* Пиковые IOPS случайного чтения: fio -ioengine=libaio -direct=1 -invalidate=1 -name=test -bs=4k -iodepth=128 -rw=randread -runtime=60 -filename=/dev/sdX
* Задержка случайного чтения: fio -ioengine=libaio -sync=1 -direct=1 -invalidate=1 -name=test -bs=4k -iodepth=1 -rw=randread -runtime=60 -filename=/dev/sdX
* Пиковые IOPS случайной записи: fio -ioengine=libaio -direct=1 -invalidate=1 -name=test -bs=4k -iodepth=128 -rw=randwrite -runtime=60 -filename=/dev/sdX
* Задержка записи в журнал: fio -ioengine=libaio -sync=1 -direct=1 -invalidate=1 -name=test -bs=4k -iodepth=1 -rw=write -runtime=60 -filename=/dev/sdX— также стоит повторить тот же тест с -fsync=1вместо -sync=1и принять худший результат, так как иногда бывает, что одним из методов sync игнорируется (зависит от контроллера).
* Задержка случайной записи: fio -ioengine=libaio -sync=1 -direct=1 -invalidate=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -runtime=60 -filename=/dev/sdX
«А почему так мало…» — см.ниже.
Warning icon.svgКогда разворачиваете Ceph OSD на SSD — очень разумно не отдавать её под Ceph целиком, а оставить небольшой раздел (10-20 гб) пустым для будущего использования под бенчмаркинг. Ибо SSD имеют свойство со временем (или при забивании данными под 80%) начинать тормозить. Очень удобно иметь возможность гонять fio на пустом никем не используемом разделе.
==== Тестирование кластера Ceph ====
Как тестировать Ceph после сборки:
* fio -ioengine=rbd. Нужно сделать следующее:
- fio -ioengine=rbd -direct=1 -name=test -bs=4M -iodepth=16 -rw=write -pool=rpool_hdd -runtime=60 -rbdname=testimg
- fio -ioengine=rbd -direct=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -pool=rpool_hdd -runtime=60 -rbdname=testimg
- fio -ioengine=rbd -direct=1 -name=test -bs=4k -iodepth=128 -rw=randwrite -pool=rpool_hdd -runtime=60 -rbdname=testimg
И потом то же самое для read/randread. Смысл в том, чтобы протестировать а) задержку в идеальных условиях б) линейную пропускную способность в) случайные iops-ы.
Перед тестами чтения образ сначала нужно заполнить линейной записью, так как чтение из пустого образа очень быстрое :)
Запускать оттуда, где будут реальные пользователи RBD. В целом, с другого узла результаты обычно лучше.
* Всё то же самое изнутри виртуалки или через krbd:
- fio -ioengine=libaio -direct=1 -name=test -bs=4M -iodepth=16 -rw=write -runtime=60 -filename=/dev/rbdX
- fio -ioengine=libaio -direct=1 -sync=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -runtime=60 -filename=/dev/rbdX
- fio -ioengine=libaio -direct=1 -name=test -bs=4k -iodepth=128 -rw=randwrite -runtime=60 -filename=/dev/rbdX
Заметьте, что при тестировании задержки добавилась опция -sync=1. Это не случайно, а соответствует режиму работы СУБД (транзакционная запись в 1 поток). В ioengine=rbd понятие sync отсутствует, там всё всегда «sync».
* ceph-gobench
Или [[https://github.com/vitalif/ceph-bench|https://github.com/vitalif/ceph-bench]], что примерно то же самое. Родоначальник идеи — @socketpair Марк Коренберг ([[https://github.com/socketpair/ceph-bench|оригинал]]). Бенчилка тестирует //отдельные OSD//, что очень помогает понять, кто же из них тупит-то.
Перед запуском надо создать пул без репликации ceph osd pool create bench 128 replicated; ceph osd pool set bench size 1; ceph osd pool set bench min_size 1и с числом PG, достаточным, чтобы при случайном выборе туда попали все OSD (ну или прибить их вручную к каждому OSD upmap-ами)
* CephFS
* Нормальных инструментов для тестирования ФС, сцуко, нет!!!
* «Нормальный» инструмент — это такой инструмент, который вёл бы себя, как файловый сервер: случайно открывал, создавал/писал/читал и закрывал маленькие файлы среди большого общего количества, разбитые по набору каталогов
* Всё, что есть, какое-то кривожопое: bonnie++, например, зачем-то тестирует запись по 1 байту. iometer, fs_mark не обновлялись лет по 10, но и паттерн файл сервера не умеют. Лучшее, что умеют — это тест создания файлов.
* Пришлось написать свой ioengine для fio: [[https://github.com/vitalif/libfio_fileserver|https://github.com/vitalif/libfio_fileserver]] :)
* S3 (rgw):
* [[https://github.com/intel-cloud/cosbench|cosbench]] — очень толстый, Java с Web-интерфейсом, XML-настройки
* [[https://github.com/vitalif/hsbench|hsbench]] — ссылка дана на исправленную версию (!). Максимально простой, консольное Golang приложение. Оригинальная версия пока что имеет 2 неприятных бага: во-первых, вместо чтения объектов целиком читает только первые 64 КБ, во-вторых, производит последовательное, а не случайное, чтение. Что, например, с minio приводит к слишком оптимистичным результатам тестов.
* [[https://github.com/minio/warp|minio warp]] — тестов чуть больше, чем в hsbench, но зато тестирует только 1 бакет и при каждом тесте загружает данные заново
Примечания:
* dd и hdparm для бенчмаркинга не использовать вообще никогда!!!
* rados bench использовать тоже не надо, так как он создаёт для тестирования очень мало объектов (в 1 поток всего 2, в 128 — несколько сотен). «Случайная» запись в такое число объектов не очень-то и случайная.
* rbd bench использовать можно, но fio лучше.
* Не надо удивляться, что Ceph не может загрузить диски на 100 % при случайной записи. Он тормоз :)
==== Тестирование сети ====
sockperf. На одной ноде запускаем сервер: sockperf sr -i IP –tcp. На другой клиент в режиме ping-pong: sockperf pp -i SERVER_IP –tcp -m 4096. ВНИМАНИЕ: В выводе фигурирует **половина** задержки (задержка в одну сторону). Таким образом, для получения RTT её стоит умножить на 2. Нормальный средний RTT - в районе 30-50 микросекунд (0.05ms).
Также qperf. На одной ноде просто qperf. На второй qperf -vvs -m 4096 SERVER_IP tcp_lat.
qperf написан криво: 1) всегда использует для tcp_lat размер сообщения 1 байт!!! 2) не использует TCP_NODELAY. Так что его юзать, только если возьмёте его с моим патчем отсюда: [[https://yourcmc.ru/wiki/Мой_Debian_репозиторий|Мой Debian репозиторий]].
Warning icon.svgВнимание: в Ubuntu на сетевую задержку негативно влияет AppArmor, его лучше отключить. Картина примерно такая (Intel X520-DA2):
* centos 3.10: rtt min/avg/max/mdev = 0.039/0.053/0.132/0.012 ms
* ubuntu 4.x + apparmor: rtt min/avg/max/mdev = 0.068/0.163/0.230/0.029 ms
* ubuntu 4.x: rtt min/avg/max/mdev = 0.037/0.071/0.157/0.018 ms
===== О транзакционности записи =====
Плохая новость!
Важная особенность Ceph — //вся запись, даже та, для которой никто этого явно не просит, ведётся транзакционно//. То есть, никакая операция записи не завершается, пока она не записана в журналы всех OSD и не сделан fsync() диска. Так сделано, чтобы предотвращать [[https://yourcmc.ru/wiki/Производительность_Ceph#RAID_WRITE_HOLE|#RAID WRITE HOLE]]-подобные ситуации рассинхронизации данных между репликами при отключении питания, потере сети и т.п…
Если конкретизировать сильнее, это означает, что Ceph не использует никакие буферы записи дисков (наоборот, он делает всё, чтобы эти буферы всё время очищать). Это не значит, что буферизации записи нет вообще — она есть на уровне клиентов (page cache в linux, кэш RBD устройства на уровне драйвера librbd qemu…). Но именно внутренние дисковые буферы не используются.
Это приводит к тому, что типичная настольная SSD под журналом в Ceph выдаёт **неприлично низкие IOPS-ы** — обычно от 500 до 2000. И это при том, что при обычном тестировании почти любая SSD выдаёт > 20000 iops. Даже самый паршивый китайский noname выдаёт не менее 10000 iops. NVMe легко выжимает 150000 и больше. Но стоит начать использовать fsync… и та же NVMe выдаёт 600 iops (на 2.5 порядка меньше).
В общем, чтобы понять, сколько у вас теоретически может быть IOPS-ов на запись в Ceph, диски под него нужно тестировать с опциями fio **sync=1 iodepth=1**. Это даст «журнальные» иопсы (производительность последовательного коммита операций по одной).
Другие почти идентичные варианты: fdatasync=1 (в файле поверх ФС) либо fsync=1 (на голом девайсе). Разница между опциями:
* fsync=1 синхронизирует данные и метаданные тестируемого файла отдельным запросом после каждой операции записи. Так работает BlueStore.
* fdatasync=1 синхронизирует только данные (но не метаданные) тестируемого файла после каждой операции записи. Соответственно, от fsync=1 это отличается, только если тестируется **файл в ФС**, а не блочное устройство. \\ Note.svgfdatasync=1 надо использовать, когда на диске уже есть ФС, а прогнать тест хочется. Результаты будут достаточно корректными.
* sync=1 использует O_SYNC и синхронный ввод/вывод, то есть, каждая операция начинается только после завершения предыдущей. Так работает FileStore.
Но ещё нужна опция iodepth=1, иначе в очередь диска до синхронизации «пролезает» несколько операций и IOPS-ы растут, тест перестаёт быть тестом журнала.
===== Конденсаторы =====
Нас спасёт такое чудо инженерной мысли, как **SSD с конденсаторами** (точнее, обычно суперконденсаторами — ионисторами). Которые на M.2 SSD, кстати, прекрасно видны невооружённым глазом:
[[https://yourcmc.ru/wiki/Файл:Micron_5100_sata_m2.jpg|{{https://yourcmc.ru/wiki/images/2/2d/Micron_5100_sata_m2.jpg?nolink&650x216|Micron 5100 sata m2.jpg}}]]
Конденсаторы работают фактически как встроенный в SSD ИБП и позволяют SSD успеть сбросить кэш во флеш-память при потере питания. Таким образом кэш становится «энергонезависимым» — и таким образом SSD может просто игнорировать запросы fsync, так как точно знает, что данные из кэша в любом случае доедут до постоянной памяти.
При этом **IOPS-ы транзакционной записи становятся равны IOPS-ам нетранзакционной**.
Конденсаторы в официальных описаниях SSD-шек обычно называются «enhanced/advanced power loss protection». Этой характеристикой обладают, как правило, только «серверные» SSD, да и то не все. Например, в Intel DC S3100 конденсаторов нет, а в Intel DC S4600 есть.
Note.svgЭто и является главным отличием серверных SSD от настольных. Обычному пользователю транзакции нужны редко — а вот на серверах живут СУБД, которым транзакции как раз нужны позарез.
То есть, под Ceph следует закупать **только** SSD с конденсаторами. Даже если рассматривать NVMe — NVMe без конденсаторов хуже, чем SATA с оными.
И ещё один вариант — Intel Optane. Это тоже SSD, но они основаны не на Flash памяти (не NAND и не NOR), а на Phase-Change-Memory «3D XPoint». По спецификации заявляются 550000 iops при полном отсутствии необходимости в стирании блоков, кэше и конденсаторах. Но если даже задержка такого диска и равна 0.005мс (она действительно равна), то задержка Ceph всё равно 0.5-1мс, соответственно, с Ceph оптаны использовать чуть менее, чем бессмысленно — за большие деньги (1500$ за 960 гб, 500$ за 240 гб) вы получите не сильно лучший результат.
===== Bluestore vs Filestore =====
Блюстор — «новое» хранилище. От «нового» хранилища честно ожидаешь лучшей или хотя бы не худшей производительности во всех сценариях. Однако это, увы, не совсем так.
Что лучше в Bluestore?
* Ликвидирована двойная запись при линейной записи. Линейная запись быстрее в честных 2 раза практически в любых конфигурациях.
* Отложенная запись, эффективная для HDD (и, частично, для очень плохих SSD). iops случайной записи в HDD-only конфигурациях почти в 2 раза больше, чем в Filestore. Правда, речь об iops на HDD не идёт в принципе, поэтому вся разница — 33 или 66 iops на 1 HDD.
* Возможность использования EC под CephFS и RBD благодаря реализованной частичной перезаписи объектов в EC-пулах.
* Эффективные снапшоты (благодаря «виртуальным клонам»): после снятия снапшота iops практически не падают, в отличие от Filestore, в котором они падают до считанных сотен даже на NVMe, так как при перезаписи даже 4 КБ после снятия снапшота в Filestore копируется целый 4 МБ объект.
Warning icon.svgУвы, это относится только к **rbd snapshot**, но не к **rbd clone**. Клоны неэффективны в Bluestore точно так же, как и в Filestore. Запись 4 КБ в клон точно так же выливается в копирование 4 МБ.
* Это не так критично, но в Bluestore есть поддержка сжатия и контрольных сумм данных.
А что хуже? В целом, претензии сводятся к производительности:
* Гораздо хуже производительность случайной записи на SSD+HDD, SSD-раздел не работает как буфер для быстрой записи. Bluestore не пишет быстрее, чем может в среднем сам HDD. То есть, с SSD-журналом и Filestore будет 1000—2000 иопс случайной записи, а с Bluestore (и без bcache) — 200—300. 1000—2000, конечно, упадёт до 100 или даже ниже, когда у Filestore забьётся журнал и его придётся сбрасывать — но тем не менее, «буфер» для сглаживания пиков Filestore предоставляет. А Bluestore — нет.
И проблема не только в том, что параметры по умолчанию — deferred_batch_ops и max_deferred_txc — задают частый сброс операций на медленный диск (раз в 64 операции). Проблема ещё в том, что в Bluestore отсутствуют механизмы фоновой очистки «журнала» (очереди отложенной записи). Поэтому, когда очередь забивается, производительность просто падает до HDD-шной до перезапуска OSD. Ну и сама очередь находится в RocksDB, поэтому сильно поднимать её размер, по идее, неполезно.
* До 1.5-2 раз хуже latency случайной записи на SSD/NVMe (All-Flash), ибо накладных расходов на каждую операцию записи у Bluestore больше.
* Жор памяти больше. Да, у Filestore много занимал pagecache, но Bluestore меньше 2 ГБ памяти не жрёт вообще никогда. Причиной тому — RocksDB (одни только memtable-ы с дефолтными настройками съедают 1 ГБ памяти) и собственный кэш метаданных и данных (Bluestore не может использовать pagecache).
* Фрагментация приводит к снижению скорости чтения.
Также BlueStore делает огромное количество fsync-ов (что очень смешно — даже больше, чем запросов записи), из-за чего не терпит десктопных SSD под журналом. Но FileStore работает похоже, и кардинальных различий производительности это не вносит.
Как полечить высокие задержки на SSD+HDD?
* Либо вместо журнала (или рядом с журналом) сделать на SSD bcache для HDD.
* Либо использовать HDD с SSD Cache, Media Cache или аналогом (перманентным кэшем случайной записи на пластинах). Например, в старых дисках HGST это включается при отключении волатильного кэша командой `hdparm -W 0 /dev/sdXX`. В новых, похоже, включено всё время.
==== Тест на 1 NVMe ====
Threadripper 2920X, NVMe Intel P4500, localhost. 1 OSD без репликации, 8 PG, чтобы не упираться в блокировки, 1 маленький RBD образ 10 Гб.
Журнал Filestore 1 GB, чтобы в тестах успевал начинаться сброс. Bluestore 4k — это min_alloc_size и prefer_deferred_size = 4096 (4k запись идёт через redirect-write), Bluestore 16k — 16384 (4k запись идёт через deferred).
^ ^Filestore^Bluestore 16k^Bluestore 4k^ |
^bs=4M iodepth=16 rw=write|950 MB/s|1700 MB/s|1700 MB/s| |
^bs=4M iodepth=16 rw=read|1250 MB/s|1300 MB/s|1300 MB/s|После полной линейной перезаписи + drop_caches|
^bs=4M iodepth=16 rw=read|1250 MB/s|450 MB/s|320 MB/s|После 33 % случайной перезаписи блоком min_alloc_size|
^bs=4k iodepth=1 rw=randwrite|3900 iops|3200 iops|2500 iops| |
^bs=4k iodepth=128 rw=randwrite|19100 iops|19500 iops|25500 iops| |
^bs=4k iodepth=1 rw=randwrite|180 iops|2800 iops|2500 iops|Сразу после снятия snapshot-а RBD|
^bs=4k iodepth=128 rw=randwrite|180 iops|8800 iops|15600 iops|Сразу после снятия snapshot-а RBD|
^bs=4k iodepth=1 rw=randread|3900 iops|4500 iops|4500 iops|После drop_caches / перезапуска OSD|
^bs=4k iodepth=1 rw=randread|6300 iops|4500 iops|4500 iops|Прогретый кэш|
^bs=4k iodepth=128 rw=randread|33000 iops|32000 iops|33000 iops| |
^RAM|270 MB|2 GB +| |Filestore также использует произвольный объём page cache|
^CPU randwrite Q=128|600 %|550 %| | |
==== Про размер block.db ====
Кто задолбался со спилловерами? Все задолбались со спилловерами! :)
Спилловер — это когда вы собрали Bluestore на SSD+HDD, выделив SSD под базу (block.db), но при этом эта самая база постоянно частично утекает на HDD. При этом она, вроде бы, даже влезает в SSD с запасом — но всё равно утекает. Начиная, кажется, с Ceph 14 Nautilus, о спилловерах предупреждает ceph -s, а с Ceph 15 Octopus авторы попытались победить spillover-ы через дополнительные «allocation hint»-ы RocksDB (надо потестировать: коммит 5f72c376deb64562e5e88be2f22339135ac7372b, добавили опцию bluestore_volume_selection_policy).
Когда случается спилловер в SSD+HDD конфигурациях, работа кластера замедляется — в большей или меньшей степени, в зависимости от размеров RocksDB и паттерна нагрузки, так как когда метаданных не очень много, они влезают в кэш OSD — либо onode cache, либо rocksdb cache, либо, если включено bluefs buffered io — то ещё и в системный page cache. Если кэш-промахов достаточно много, или если OSD упирается в compaction RocksDB, могут даже появляться slow ops-ы.
Так в чём же дело и как это победить? А дело в том, что с выбором раздела для очередного файла БД (RocksDB организована в виде набора файлов) «есть нюанс», точнее, даже два.
**Нюанс № 1:** RocksDB кладёт файл на быстрый диск только когда считает, что на быстром диске хватит места под все файлы этого же уровня (для тех, кто ещё не в курсе — RocksDB это [[https://github.com/facebook/rocksdb/wiki/Leveled-Compaction|LSM база]]).
Дефолтные настройки цефа:
* 1 Гб WAL = 4×256 Мб
* max_bytes_for_level_base и max_bytes_for_level_multiplier не изменены, поэтому равны 256 Мб и 10 соответственно
* соответственно, L1 = 256 Мб
* L2 = 2560 Мб
* L3 = 25600 Мб и т. д.
…Соответственно!
Rocksdb положит L2 на block.db, только если раздел имеет размер хотя бы 2560+256+1000 Мб — округлим вверх до **4 ГБ**. А L3 она положит на block.db, только если block.db размером хотя бы 25600+2560+256+1000 МБ = около **30 ГБ**. А L4, соответственно, если ещё +256 ГБ, то есть итого **286 ГБ**.
Иными словами, имеют смысл только размеры раздела block.db 4 ГБ, 30 ГБ, 286 ГБ. Все промежуточные значения бессмысленны — место сверх предыдущего граничного значения использоваться не будет. Например, если БД занимает 10 ГБ, а раздел SSD — 20 ГБ, то фактически на SSD ляжет только WAL (1 ГБ), L1 и L2 (256 МБ + 2.56 ГБ). L3, составляющий бОльшую часть базы, уедет на HDD и будет тормозить работу.
При этом 4 ГБ — слишком мало, 286 ГБ — слишком много. Так что, по сути, правильно делать block.db размером 30 ГБ для OSD любого размера.
Кстати, из этого же следует то, что официальная рекомендация — выделять под block.db то ли 2 %, то ли 4 % от размера устройства данных — полный отстой.
Но что делать, если у вас разделы другого размера? Например, 80 ГБ, и вы по каким-то причинам не хотите делать bcache, но хотите использовать эти 80 ГБ по максимуму. В этом случае можно поменять базовый размер уровня RocksDB (max_bytes_for_level_base). multiplier менять не будем, оставим по умолчанию 10 — его значение влияет на итоговое количество уровней RocksDB, а это уже более тонкая материя. Теоретически, меньшее число уровней снижает read и space amplification, но замедляет compaction и из-за этого может сильно повысить итоговый write amplification. Также есть тема с уменьшением размера отдельных memtable и кратным увеличением общего их числа, то есть, например, установки 32*32 МБ вместо дефолтных 4*256 МБ и min_write_buffer_to_merge=8, но эффект от этого тоже не совсем понятен (возможно, немного экономится CPU при compaction-е), так что это тоже пока лучше не трогать.
Так как каждый уровень отличается от предыдущего в 10 раз, общий размер раздела БД должен быть равен k*X, где k — коэффициенты из ряда: 1, 11, 111, 1111 и т. п. (по числу уровней RocksDB). Значит, мы можем взять размер нашего block.db, вычесть из него 1 ГБ WAL (лучше даже вычесть с запасом 2 ГБ) и делить его последовательно на каждую из цифр до тех пор, пока не получим значение, близкое к 256 МБ … 1 ГБ. Это значение округлить вниз, принять за базовый размер уровня RocksDB и прописать в конфиг как max_bytes_for_level_base. База компактится по 256 МБ за раз, так что меньше 256 МБ размер первого уровня ставить точно смысла нет. Например, для 80 ГБ раздела это будет 719 МБ, только не забываем считать всё в двоичных мегабайтах — MiB. Остаётся прописать это значение в конфигурацию (bluestore_rocksdb_options = …,max_bytes_for_level_base=719MB), перезапустить OSD и сделать ручной compaction (можно дважды).
**Нюанс № 2:** При ручном compaction-е RocksDB переписывает уровни целиком. Если при этом на SSD нет запаса места в размере этого уровня, то уровень, опять-таки, утечёт на HDD и так там и останется, ибо перемещать после compaction-а его обратно она не умеет. Теоретически, если после этого сделать compaction ещё раз, то уровень должен вернуться на SSD (поэтому выше дана рекомендация делать ручной compaction дважды). Однако по сведениям из чата якобы бывает так, что один-два файла *.sst на SSD не возвращается. Чтобы это побороть на 100 %, можно предусмотреть на SSD-разделе ещё и запас в размере первого + последнего уровня БД. В этом случае коэффициенты вместо 1-11-111-1111 превращаются в 2-22-212-2112 и т. п.
===== RGW vs Minio =====
Вопрос частый, так как Ceph и Minio — две наиболее распространённые реализации S3.
Сравнение, как всегда, не совсем честное, так как в Minio «бесконечного масштабирования» и произвольных схем избыточности нет. Есть только erasure коды, которые оперируют группами дисков, кратными по количеству 4 или 16 дискам. Расширения кластера в Minio раньше не было вообще, потом в каком-то смысле появилось через создание дополнительных зон.
Таких же гарантий целостности, как в Ceph, в Minio тоже нет. Minio работает поверх обычных ФС, даже не делая fsync данных. На практике ext4, правда, делает sync автоматически раз в 5 секунд, да и Minio пишет с O_DIRECT, так что не совсем всё плохо — но тем не менее, потенциально небольшие потери при отключении питания возможны.
Особенно классный перл был в баге [[https://github.com/minio/minio/issues/3478|https://github.com/minio/minio/issues/3478]]:
Minio in this case is working as intended, minio cannot be expanded or shrinkable in this manner. Minio is different by design. It is designed to solve all the needs of a single tenant. Spinning minio per tenant is the job of external orchestration layer. Any addition and removal means one has to rebalance the nodes. When Minio does it internally, it behaves like blackbox. It also adds significant complexity to Minio. Minio is designed to be deployed once and forgotten. We dont even want users to be replacing failed drives and nodes. Erasure code has enough redundancy built it. By the time half the nodes or drives are gone, it is time to refresh all the hardware. If the user still requires rebalancing, one can always start a new minio server on the same system on a different port and simply migrate the data over. It is essentially what minio would do internally. Doing it externally means more control and visibility. Minio is meant to be deployed in static units per tenant.Короче, всё работает как надо, в минио нет возможности расширения, если у вас будут ломаться диски — не меняйте, просто дождитесь, пока из строя выйдет половина дисков и пересоздайте кластер. На самом деле всё не так печально, можно заменить диск и запустить heal, но, конечно, без той же прозрачности, что в Ceph — оно будет просто сканировать все объекты и проверять отсутствующие. Если дисков много, это очень накладно. Ещё Minio хранит объекты в виде обычных файлов, даже не шардируя каталоги (соответствующие бакетам) по подкаталогам, плюс на каждый объект ещё создаёт директорию с парой файлов метаданных. Ну а директории в ФС по миллиону файлов — это, естественно, удовольствие ниже среднего. Хотя просто для раздачи оно, благодаря всяким dir_index-ам, работает. Для представления о производительности проведём простой тест Ceph (bluestore) vs Minio (ext4) на 1 HDD. Да, я знаю, что это тупо и нужно ещё хотя бы посравнивать их на SSD. Но всё-таки результаты довольно показательны. Да и объектное хранилище чаще холодное/прохладное и строится на HDD, а не на SSD. Тест делался через [[https://github.com/vitalif/hsbench|hsbench]]. Заключался в заливке примерно 1.1 миллиона объектов в 1 бакет, потом сброса кэшей и перезапуска Ceph/Minio, и потом — их раздачи в случайном порядке, а также проверки скорости выполнения операций листингов. Результаты: * Заливка в 32 потока: Minio — 305 объектов в секунду, RGW — 135 объектов в секунду. RGW indexless — 288 объектов в секунду. * Раздача в 32 потока: Minio — 45 объектов в секунду, RGW — 78 объектов в секунду * Листинги в 32 потока: Minio — после сброса кэша 35 сек, после прогрева — 2.9 сек с разбросом от 0.5 до 16 сек. RGW — стабильно — 0.4 сек Да, заливка в Minio быстрее. Но, во-первых, меньшая скорость заливки — это цена, во-первых, консистентности (fsync), а во-вторых, bucket index-а и bucket index log-а, которые позволяют RGW, например, делать геосинхронизацию (multisite), чего в Minio нет. Кроме того, индексы в RGW можно положить на отдельные SSD (как обычно все и делают), а если же вам совсем не нужны листинги, синхронизация и прочее, в Ceph бакеты можно сделать безиндексными (indexless), и тогда оверхед bucket index-а вообще исчезает, как и возможные проблемы с его шардированием. ===== Снапшоты ===== В реализации снапшотов в Ceph есть целый ворох проблем из-за количества параллельных реализаций и общего накопленного архитектурного бардака: * На уровне RADOS снапшоты свои и их уже 2 вида: снапшоты пулов и снапшоты объектов. * На уровне RBD тоже свои снапшоты, причём они отличаются от RADOS-снапшотов, хоть внутри и реализованы частично через них. RBD снапшоты, можно сказать, не юзабельны ни для чего, кроме быстрого снятия бэкапов, за которым тут же следует удаление снапшота. Откат к RBD снапшотам очень медленный и реализован не просто ужасно, а отвратительно — копированием содержимого снапшота поверх образа, //даже неизменённых частей//. Кроме того, при откате цепочка последующих снапшотов уничтожается. Кроме того, есть прикол с жором места — см. на 2 пункта ниже. * В Bluestore снапшоты эффективны — после снятия снапшота случайная запись практически не замедляется, в отличие от Filestore, где принцип работы снапшотов схож со старым LVM и при записи даже 4 КБ после снятия снапшота соответствующий 4 МБ объект копируется целиком. * Но при этом эта оптимизация снапшотов — «виртуальные клоны» — реализована именно на уровне Bluestore, ни клиент, ни OSD о ней ничего не знают и поэтому она… ломается при ребалансе. Выглядит это так: ты добавляешь в кластер диск, он заполняется, а доступного места больше не становится. Почему? Потому что изначально, при записи, Bluestore получил запрос «клонировать 4 МБ объект в новую версию» и сделал это внутри себя, реально не копируя данные, а потом перезаписал 4 КБ. А при ребалансе эта связь порвалась и объект стал занимать все 4 МБ… Fail. * Также на уровне RBD есть клоны, которые, с одной стороны, более юзабельны — реализованы они через ссылку на родительский образ, соответственно, откат к такому «снапшоту» быстрый — достаточно просто создать новый клон. С другой стороны, для клонов не работает эта самая блюсторовская оптимизация, поэтому они опять-таки копируют объекты целиком при любой записи… что выливается в 40 иопс на запись (QD=1) в свежий клон даже в NVMe кластере. * В CephFS ещё одна реализация снапшотов, и там отката нет вообще. В общем, наговнокодили — мама не горюй… ===== EC ===== EC ещё больше снижает iops-ы, так как добавляется цикл Read-Modify-Write. [[https://yourcmc.ru/wiki/Производительность_Ceph#RAID_WRITE_HOLE|#RAID WRITE HOLE]] в цефе закрыт, поэтому при записи все OSD сначала делают вторые копии объекта (по-видимому, виртуальные, через тот же механизм virtual clone bluestore), а потом удаляют старые. В моём примере на NVMe-шках — write iops с репликацией Q=1 примерно 1500, а с EC — примерно 500. Q=128 — примерно 25000 с репликацией и 10000 с EC. ==== Про вероятность потери данных ==== Смотрите мой калькулятор вероятности потери данных в кластере: [[https://yourcmc.ru/afr-calc/|https://yourcmc.ru/afr-calc/]] ===== Контроллеры ===== * SATA — это нормально, SAS не обязателен от слова «совсем». SATA за счёт того, что «не умничает», достаточно быстрая и точно лучше, чем старые RAID контроллеры. * Разница в IOPS между RAID и HBA/SATA может быть колоссальна. В производительность не самого нового RAID контроллера легко упереться. Плохо даже не то, что на 1 диск вы получите 48000 iops вместо 60000, хуже то, что при подключении 8 дисков вы получите 6000 iops на каждый диск вместо 60000, так как 48000 поделятся на всех. Также в RAID режиме увеличивается задержка в 1 поток. * Так что свой RAID контроллер либо переключите в режим passthrough (если он умеет), либо перепрошейте, чтобы умел, либо выкиньте в помойку и купите HBA («RAID без RAID-функционала», например, LSI 9300-8i). Это актуально для всех видов программных хранилок — Ceph, ZFS и т. п. * Если не выкинули RAID — отключайте все кэши контроллера, чтобы уменьшить влияние прослойки и не страдать при разряде батарейки / перемещении диска в другой сервер… и молитесь :). Наверное, в теории можно выжить и с включенным кэшем, но это стрельба себе в ногу. * Даже если у вас HBA — имейте в виду, что некоторые HBA (в частности, Adaptec) могут всё равно не сбросить кэш корректно и устроить вам Cloudmouse при отключении питания. Но по крайней мере точно известно, что LSI ведут себя нормально. * Хороший пост про RAID-кэши в списке рассылки: [[http://lists.ceph.com/pipermail/ceph-users-ceph.com/2019-July/036237.html|http://lists.ceph.com/pipermail/ceph-users-ceph.com/2019-July/036237.html]] - если вкратце - человек, админивший 6000 OSD, пишет, что никогда больше не свяжется с RAID0-режимами. * У HBA тоже есть предел IOPS. К примеру, у LSI 9211-8i это ~280000 iops на весь контроллер. * При подключении через SATA или HBA контроллер не забывайте для **серверных** SATA дисков сделать hdparm -W 0 /dev/sdX, для SAS — sdparm –set WCE=0 /dev/sdX. * Для SAS и NVMe включайте blk-mq (ну или юзайте свежие ядра, в районе 4.18 оно включается по умолчанию). Но для SATA blk-mq обычно бесполезен или почти бесполезен. * Фактическая глубина очереди, используемая Ceph OSD при случайной записи, редко больше 10 (посмотреть можно при работе утилитой iostat -xmt 1). ===== Процессоры ===== * На SSD Ceph ОЧЕНЬ СИЛЬНО упирается в процессор. Можно сказать, что процессор — основной bottleneck. * Как сказано в презентации Ника Фиска — Ceph is a Software-Defined Storage **and every piece of Ceph «Software»** will run faster with every GHz of CPU frequency. * Кроме частоты, на серверных процессорах часто наличествует NUMA (Non-Uniform Memory Access). То есть, часть памяти и оборудования доступна процессору напрямую, а часть — только через другой процессор. * Для максимизации производительности конфигураций с NUMA лучше избегать, а процессорам с бОльшим числом ядер и меньшей частотой лучше предпочитать бОльшую частоту и меньшее число ядер… * …но в пределах разумного, так как даже один OSD на серверном SSD под нагрузкой может спокойно выжрать на 100 % ядер 6. * Под частотой подразумевается номинальная частота, а не Turbo Boost, так как оный актуален только для однопоточных нагрузок. * Рекомендации по привязке OSD к отдельным CPU (taskset), можно сказать, неактуальны, так как Ceph OSD сильно многопоточные — при записи постоянно активно как минимум 4 потока, и ограничение их несколькими ядрами сильно урезает производительность. * Есть два параметра, которые регулируют число рабочих потоков OSD — osd_op_num_shards и osd_op_num_threads_per_shard… * …Но менять их бесполезно, поднять производительность таким образом не получается абсолютно, дефолтные значения (1×5 на HDD и 2×8 на SSD) оптимальны. kv_sync_thread-то всё равно только один. * Есть одна мера, которая помогает поднять производительность сразу раза в 2-3: отключение экономии энергии процессором: * cpupower idle-set -D 0— отключает C-States (либо опции ядра processor.max_cstate=1 intel_idle.max_cstate=0) * cpupower frequency-set -g performanceили (старое) for i in $(seq 0 $((1))) ) ) ); do cpufreq-set -c $i -g performance; done— отключает снижение частоты через множитель * После этих двух команд процессор начинает греться как ПЕЧ, но iops-ы увеличиваются сразу раза в 2 (а то и 3) * Также жор CPU — одна из причин НЕ делать из Ceph «гиперконвергентное облако» (в котором совмещены узлы хранения и запуска виртуальных машин) * Ещё можно отключить все mitigation-ы аппаратных уязвимостей: noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier ===== Сеть ===== * Разумеется, 10 Гбит/с или быстрее * MTU 9000: ip l set enp3s0f0 mtu 9000 * Отключить оффлоады: ethtool -K enp3s0f0 gro off gso off tso off lro off sg off * Отключить объединение прерываний: ethtool -C enp3s0f0 rx-usecs 0 * Самый дешёвый 10G свитч с Ebay: Quanta LB6M / Brocade TurboIron 24X Если совсем задолбала латенси, как отключить ВСЕ оффлоады?
for i in rx tx tso ufo gso gro lro tx nocache copy sg txvlan rxvlan; do
/sbin/ethtool -K eth3 $i off 2>&1> /dev/null;
done
===== Настройка виртуалок и ФС =====
С дефолтными опциями qemu подключает RBD, увы, криво.
Криво — это значит, что:
а) используется медленная эмуляция lsi-контроллера
б) неправильно настроен кэш
==== Драйвер виртуального диска и ФС ====
В qemu есть следующие способы эмуляции дисков:
* lsi — самый медленный
* virtio — самый быстрый, но до QEMU 4.0 не умел TRIM. Юзать надо его.
* virtio-scsi — достаточно быстрый. На самом деле, умеет multiqueue и поэтому на быстром хранилище (например, при прямом доступе к локальной NVMe) должен быть быстрее virtio — но в случае с Ceph это не так, так как цеф не настолько быстрый, чтобы multiqueue играл роль
==== cache=writeback ====
Кэш дисков в qemu регулируется опцией, собственно, cache. Бывает <не указано>, writethrough, writeback, none, unsafe, directsync.
С Ceph RBD эта опция регулирует работу rbd cache, то есть кэша на стороне клиентской библиотеки Ceph (librbd). RBD cache маленький (32 МБ) и является аналогом буфера записи на HDD, то есть, предназначен исключительно для группировки операций записи перед, собственно, отправкой их в хранилище.
Режимы writethrough, <не указано> и directsync с RBD, по сути, эквивалентны и означают отсутствие кэширования записи (каждая операция отправляется сразу в Ceph).
Режим writeback означает «честное» (безопасное) кэширование записи. То есть, операции записи без fsync ложатся в кэш и отправляются оттуда либо раз в rbd_cache_max_dirty_age (по умолчанию 1 сек), либо когда ВМ просит fsync.
Режим unsafe означает «нечестное» (чреватое потерей данных) кэширование записи. Операции записи также ложатся в кэш, но fsync от ВМ игнорируются. Транзакционность в виртуалке отсутствует, но и производительность повышается. Использовать этот режим можно только для виртуалок, не содержащих ценные данные (например, для каких-нибудь тестов или CI).
При среднем применении правильный режим — **cache=writeback**. Грубо говоря, cache=writeback увеличивает результат следующего теста: