Производительность Ceph

Материал из YourcmcWiki
Перейти к: навигация, поиск

Бенчмаркинг

Основные направления тестирования:

  • Линейное чтение/запись (большими блоками)
  • Пиковая производительность высоко-параллельного случайного чтения/записи мелкими блоками
  • Задержка однопоточного случайного чтения мелкими блоками (4-8 Кб)
  • Задержка однопоточной транзакционной записи мелкими блоками (4-8 Кб) — обычно последовательной, как в журнал СУБД, но в один поток это обычно слабо отличается от случайной

Задержки обычно важнее простой пиковой производительности случайного чтения/записи, так как далеко не каждое приложение может загрузить диск при большом параллелизме / глубокой очереди (32-128 запросов).

Ceph — это SDS, его задержки всегда выше, чем у устройств при прямом доступе, и от этого никуда не денешься. В интернете есть доклад Nick Fisk «Low-Latency Ceph», в его исполнении Low latency это 0.7ms, то есть на лучший результат рассчитывать особенно не приходится. 0.7ms — это всего лишь примерно ~1500 iops в 1 поток (хорошая новость — другие SDS и просто SAN-ы тоже тормозят :)).

Тестирование дисков

Сначала прогоните fio на голом диске:

Warning icon.svg ВНИМАНИЕ! Для тех, кто в танке — fio-тест записи на диск ДЕСТРУКТИВНЫЙ. Не вздумайте запускать его на дисках/разделах, на которых есть нужные данные… например, журналы OSD (был прецедент).
  • Перед тестированием попробуйте отключить кэш записи диска: hdparm -W 0 /dev/sdX (SATA-диски через SATA или HBA), sdparm --set WCE=0 /dev/sdX (SAS-диски). Не совсем ясно, почему, но эта операция на серверных SSD может увеличить IOPS-ы на 2 порядка (а может НЕ увеличить, поэтому пробуйте оба варианта — и W0, и W1). Также см.ниже #Картина маслом «Тормозящий кэш».
  • Линейное чтение: 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. Нужно сделать следующее:
    1. fio -ioengine=rbd -direct=1 -name=test -bs=4M -iodepth=16 -rw=write -pool=rpool_hdd -runtime=60 -rbdname=testimg
    2. fio -ioengine=rbd -direct=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -pool=rpool_hdd -runtime=60 -rbdname=testimg
    3. 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:
    1. fio -ioengine=libaio -direct=1 -name=test -bs=4M -iodepth=16 -rw=write -runtime=60 -filename=/dev/rbdX
    2. fio -ioengine=libaio -direct=1 -sync=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -runtime=60 -filename=/dev/rbdX
    3. 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, что примерно то же самое. Родоначальник идеи — @socketpair Марк Коренберг (оригинал). Бенчилка тестирует отдельные 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

Примечания:

  • dd не использовать вообще никогда.
  • rados bench использовать тоже не надо, т.к. он создаёт для тестирования очень мало объектов (в 1 поток всего 2, в 128 — несколько сотен). "Случайная" запись в такое число объектов не очень-то и случайная.
  • rbd bench использовать можно, но fio лучше.
  • Не надо удивляться, что Ceph не может загрузить диски на 100 % при случайной записи. Он тормоз :)

О транзакционности записи

Warning icon.svg Плохая новость!

Важная особенность Ceph — вся запись, даже та, для которой никто этого явно не просит, ведётся транзакционно. То есть, никакая операция записи не завершается, пока она не записана в журналы всех OSD и не сделан fsync() диска. Так сделано, чтобы предотвращать #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 порядка меньше).

Без буфера записи, кстати, и 7200 rpm HDD — это не 120 иопс, а всего 40-50. Сорри, наврал.

В общем, чтобы понять, сколько у вас теоретически может быть IOPS-ов на запись в Ceph, диски под него нужно тестировать с опциями fio sync=1 iodepth=1. Это даст «журнальные» иопсы (производительность последовательного коммита операций по одной).

Другие почти идентичные варианты: fdatasync=1 (в файле поверх ФС) либо fsync=1 (на голом девайсе). Разница между опциями:

  • fsync=1 синхронизирует данные и метаданные тестируемого файла отдельным запросом после каждой операции записи. Так работает BlueStore.
  • fdatasync=1 синхронизирует только данные (но не метаданные) тестируемого файла после каждой операции записи. Соответственно, от fsync=1 это отличается, только если тестируется файл в ФС, а не блочное устройство.
    Note.svg fdatasync=1 надо использовать, когда на диске уже есть ФС, а прогнать тест хочется. Результаты будут достаточно корректными.
  • sync=1 использует O_SYNC и синхронный ввод/вывод, то есть, каждая операция начинается только после завершения предыдущей. Так работает FileStore.
    Но ещё нужна опция iodepth=1, иначе в очередь диска до синхронизации «пролезает» несколько операций и IOPS-ы растут, тест перестаёт быть тестом журнала.

Конденсаторы

Нас спасёт такое чудо инженерной мысли, как SSD с конденсаторами (точнее, обычно суперконденсаторами — ионисторами). Которые на M.2 SSD, кстати, прекрасно видны невооружённым глазом:

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 раза быстрее Filestore — двойную запись там честно ликвидировали, крупные блоки пишутся только 1 раз — сразу на устройство, а не 2 (в журнал и потом на устройство).

Но вот со случайной записью всё не так однозначно.

  • В HDD-only конфигурациях (по крайней мере, если «снять ручник» https://tracker.ceph.com/issues/38559) по random 4k write iops Bluestore тоже в честных 2 раза быстрее, чем Filestore.
    Фактически можно сказать, что для HDD и плохих SSD схема записи Bluestore по-настоящему оптимальна и выжимает максимум, который возможно выжать из диска в транзакционном режиме
  • В HDD+SSD кластерах производительность у Bluestore идеально стабильная, но сильно меньшая, чем у Filestore в пике. От блюстора невозможно добиться «сглаживания пиков» случайной записи через SSD-журнал. Он устроен так, что отказывается писать быстрее, чем в среднем может писать HDD. То есть даже с SSD-журналом вы получите лишь 100—200 iops с 1 диска — в то время, как в Filestore, пока в журнале есть место, можно иметь 1000—2000 iops.
    И проблема не только в том, что параметры по умолчанию — deferred_batch_ops и max_deferred_txc — задают частый сброс операций на медленный диск (раз в 64 операции). Проблема ещё в том, что в Bluestore отсутствуют механизмы фоновой очистки «журнала» (очереди отложенной записи). Поэтому, когда очередь забивается, производительность просто падает до HDD-шной до перезапуска OSD. Ну и сама очередь находится в RocksDB, поэтому сильно поднимать её размер, по идее, неполезно.
  • В All-Flash кластерах (то есть, на быстрых дисках) Bluestore латентнее Filestore, возможно, на 30-50 %. Однако эти 30-50 % относятся к latency именно самого блюстора, в абсолютном выражении составляют ~0.1 мс и на фоне общей задержки Ceph-а практически не заметны. Кроме того, latency ничего не говорит о параллельной пиковой пропускной способности, а она по крайней мере не хуже, чем в Filestore (обычно чуть лучше, +5..10 %). Жор CPU тоже чуть меньше.
  • Жор памяти у Bluestore ощутимо больше, потому что RocksDB и потому что кэш у него собственный вместо использования системного page cache-а, как в Filestore.

Как полечить медленный коммит на SSD?

  • Либо вместо журнала (или рядом с журналом) сделать на SSD bcache для HDD.
  • Либо использовать HDD с SSD Cache, Media Cache или аналогом (перманентным кэшем случайной записи на пластинах). Например, в старых дисках HGST это включается при отключении волатильного кэша командой `hdparm -W 0 /dev/sdXX`. В новых, похоже, включено всё время.

Также BlueStore делает огромное количество fsync-ов (что очень смешно — даже больше, чем запросов записи), из-за чего не терпит десктопных SSD под журналом. Но FileStore работает похоже, и кардинальных различий производительности это не вносит.

Ну и ещё FileStore отстаёт от BlueStore по функциональности:

  • Снапшоты там работают со скоростью LVM, то есть, при записи 4 кб после снятия снапшота копируется весь 4 Мб объект. То есть, после снятия снапшота RBD ВМ тормозят.
  • Плюс отсутствуют некоторые другие фичи BlueStore: нет частичной перезаписи в EC-пулах (соответственно, EC нельзя использовать под CephFS и RBD), нет сжатия (не всегда нужно, но вещь прикольная), нет контрольных сумм (и из-за их отсутствия пул с size=2 может не вылечиться при сбое сам).

Про размер block.db

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

Дефолтные настройки цефа:

  • 1 Гб WAL = 4x256 Мб
  • 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 Гб. Все промежуточные значения бессмысленны — место сверх предыдущего граничного значения использоваться не будет.

Контроллеры

  • 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 - если вкратце - человек, админивший 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…
  • …Но менять их бесполезно, поднять производительность таким образом не получается абсолютно, дефолтные значения (1x5 на HDD и 2x8 на SSD) оптимальны. kv_sync_thread-то всё равно только один.
  • Есть одна мера, которая помогает поднять производительность сразу раза в 2-3: отключение экономии энергии процессором:
    • cpupower idle-set -D 1 — отключает C-States (либо опции ядра processor.max_cstate=1 intel_idle.max_cstate=0)
    • for i in $(seq 0 $((`nproc`-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

Настройка виртуалок и ФС

  • С дефолтными опциями qemu подключает RBD, увы, криво.
  • Криво — это значит, что а) используется медленная эмуляция lsi-контроллера б) используется режим с кэшированием чтения, но без кэширования записи.
  • Кэш в qemu регулируется опцией, собственно, cache. Бывает <не указано>, writethrough, writeback, none, unsafe, directsync. С RBD эта опция регулирует работу rbd cache, то есть кэша на стороне клиентской библиотеки Ceph (librbd).
  • Но cache=unsafe с RBD не работает, операции записи всё равно ждут подтверждения. А writethrough, <не указано> и directsync, по сути, эквивалентны.
  • RBD cache сильно помогает на HDD, но на SSD-кластере наоборот вносит заметное торможение. Что-то там сделано с блокировками, что-то там однопоточное, всё это оптимизируют, но пока не оптимизировали.
  • Есть следующие способы эмуляции дисков: lsi (самый медленный), virtio-scsi (достаточно быстрый), virtio (самый быстрый, но до QEMU 4.0 не умел TRIM). Вообще virtio-scsi умеет multiqueue и поэтому на быстром хранилище (например, при прямом доступе к локальной NVMe) должен быть быстрее virtio — но в случае с Ceph multiqueue, по-видимому, значения не имеет.
  • А ещё тормозит файловая система! Конкретно, если у вас не включена опция lazytime, то при каждой мелкой записи ФС обновляет mtime, то есть время модификации inode-а. Так как это метаданные, а ФС журналируемые — это изменение журналируется. Из-за этого при тесте fio -sync=1 -iodepth=1 -direct=1 поверх ФС без lazytime iops-ы уменьшаются в 3-4 раза.
  • А если у вас (не дай бог) внутри Oracle, то ему надо обязательно поставить опцию FILESYSTEMIO_OPTIONS=SETALL.
  • Производительность случайной записи в CephFS почти не отличается от RBD
  • Производительность случайной записи в CephFS через mount -t cephfs и через ceph-fuse… при iodepth=1 почти не отличается. А вот при iodepth=128 ядерный клиент ведёт себя нормально, а ceph-fuse выдаёт столько же, сколько при iodepth=1 (то есть на порядок/порядки меньше, чем ядерный клиент).

Поэтому:

  • Для HDD / SSD+HDD крайне желательно на уровне виртуалок включать опцию qemu cache=writeback. Этот режим безопасный, так как fsync-и от гостевых ВМ заставляют qemu сбрасывать кэш в Ceph. То есть, транзакционно записываемые данные не теряются.
  • Для SSD-only лучше выключать кэш вообще (cache=none), максимальные параллельные iops на запись от этой процедуры растут с примерно 11000 до ~25000.
  • Лучше всего использовать драйвер virtio. Как всё это настраивается в вашем GUI виртуалок (Proxmox, Opennebula) — отдельная песня :) в опеннебуле сделано довольно-таки через жопу.
  • Обязательно используйте опцию монтирования lazytime. Для неё нужны свежие ядра: с ext4 — хотя бы 4.0, с XFS — хотя бы 4.17. Также нужны соответствующие (свежие) util-linux (подсказка: в протухшей 7-й центоси их нет, поставить можно только из исходников).

Оценка производительности кластера

Оценка производительности кластера просто по спецификациям входящих в него дисков — абсолютно неправильна.

Речь о Bluestore. Под iops понимаются iops случайного чтения/записи блоками по 4 кб.

1 HDD (обычный, 7200 rpm, не SMR, без ssd-кэша) — это:

  • ~100-120 iops при QD=128
  • ~66 iops при QD=1
  • ~40 MB/s линейного чтения/записи
  • При недостатке памяти показатели будут хуже, так как будут cache miss по метаданным

1 быстрый SSD или NVMe SSD (с конденсаторами, write iops >= 25000):

  • ~1000 iops на запись при QD=1, в зависимости от частоты CPU и настроек может быть от 300 до, в самом идеальном случае, ~2500 iops.
  • Максимум до ~10000-20000 iops на запись при QD=128 на 1 OSD.
  • На чтение показатели примерно в 2-2.5 раза лучше: QD=1 ~2000 iops (максимум до ~4000), QD=128 ~20000, максимум до 50000.
  • Естественно, показатель QD=128 не может быть больше, чем показатель самого диска :). Но так как хорошие SSD в параллельном режиме обычно быстрые — этого не замечаешь.
  • Создав несколько OSD на одном диске, можно умножить число QD=128 iops на число OSD (пока диск позволяет). Естественно, ценой аналогичного увеличения жора CPU.
  • Скорость линейного чтения/записи примерно равна скорости линейного чтения/записи самого диска.
  • Кардинальной разницы по iops между SATA SSD и даже NVMe, если у обоих есть конденсаторы — нет. Это не отменяет того, что NVMe лучше, но на случайном доступе это заметить трудно.
  • На актуальных SSD чтение QD=1 часто бывает медленнее записи QD=1, так как писать можно в быстрый кэш, а читать можно только из реальной памяти. Разница бывает примерно такая: на чтение при QD=1 8000 iops, а на запись 40000 iops.

Итоговые показатели:

  • Линейное чтение из кластера = Число OSD * MB/s одного OSD
  • Линейная запись в реплицированный пул = Число OSD / Репликация * MB/s одного OSD
  • Линейная запись в EC-пул = Число OSD / (K+M) * K * MB/s одного OSD
  • С iops всё сложнее, так как репликация вносит нелинейный эффект в масштабирование. Но, условно говоря:
    • iops QD=1 — усредняются по всем OSD, а iops QD=128 — суммируются
    • При этом на 1 RBD клиента можно получить лишь до ~30000 iops на чтение и ~15000 iops на запись
  • Естественно, если что-то упирается в сеть — соответствующая цифра обрезается показателем сети

Картина маслом «Тормозящий кэш»

O_SYNC vs fsync vs hdparm -W 0

У SATA и SCSI дисков есть два способа сброса кэша: команда FLUSH CACHE и флаг FUA (Force Unit Access) на команде записи. Первый — это явный сброс кэша, второй — это указание записать операцию на диск, минуя кэш. Точнее, у SCSI оно есть, а с SATA ситуация точно не ясна: в спецификации NCQ бит FUA есть, но факту FUA большинством дисков вроде как не поддерживается и, соответственно, эмулируется ядром/контроллером (если контроллер не кривой).

По всей видимости, fsync() отправляет диску команду FLUSH CACHE, а открытие файла с O_SYNC устанавливает бит FUA на все команды записи.

Есть ли разница? Обычно нет. Но на некоторых контроллерах и/или с некоторыми настройками различия по неустановленным причинам встречаются. И тогда fio -sync=1 и fio -fsync=1 начинают давать разные результаты — возможно, даже на порядки разные результаты.

Кроме того, у дисков есть команда отключения кэша. Когда он отключен, запросы сброса (fsync) Linux диску не отправляет. Казалось бы, такой режим тоже должен быть эквивалентен выполнению fsync и/или O_SYNC после каждой команды. Но и это не всегда так! На SSD с конденсаторами (то есть серверных моделях с Advanced Power Loss Protection) при отключении кэша iops-ы случайной записи часто вырастают на порядок (например, с 5000 до 40000). Но не всегда, так как это, опять-таки, зависит от контроллера.

Почему? По-видимому, потому, что команда FLUSH CACHE трактуется диском как «сбрось все кэши» (включая энергонезависимый), а отключение кэша — как «отключи энергозависимый кэш» (а энергонезависимый можешь оставить включенным). Соответственно, запись со сбросом кэша становится медленнее, чем просто отключённый кэш.

А что с NVMe? В NVMe разнообразие чуть меньше — возможность отключить кэш в спецификации не предусмотрена вообще, но точно так же есть команды FLUSH CACHE и бит FUA. При этом по личным наблюдениям FUA часто игнорируется то ли диском, то ли Linux-ом, и fio -sync=1 выдаёт с NVMe такие же результаты, как и без sync вообще. -fsync=1 при этом ведёт себя как надо и приземляет производительность туда, где ей самое место (на десктопных NVMe — до тех же 1000—2000 iops).

P.S: Bluestore использует fsync. Filestore использует O_SYNC.

Серверные SSD

Disabling cache is not a joke!

fio -ioengine=libaio -name=test -filename=/dev/sdb -(sync|fsync)=1 -direct=1 -bs=(4k|4M) -iodepth=(1|32|128) -rw=(write|randwrite)

Micron 5100 Eco 960GB

Запись
sync или fsync bs iodepth rw hdparm -W 1 hdparm -W 0
sync 4k 1 write 612 iops 22200 iops
sync 4k 1 randwrite 612 iops 22200 iops
sync 4k 32 randwrite 6430 iops 59100 iops
sync 4k 128 randwrite 6503 iops 59100 iops
sync 4M 32 write 469 MB/s 485 MB/s
fsync 4k 1 write 659 iops 25100 iops
fsync 4k 1 randwrite 671 iops 25100 iops
fsync 4k 32 randwrite 695 iops 59100 iops
fsync 4k 128 randwrite 701 iops 59100 iops
fsync 4M 32 write 384 MB/s 486 MB/s
Чтение
bs iodepth rw результат
4k 1 randread 6000 iops
4k 4 randread 15900 iops
4k 8 randread 18500 iops
4k 16 randread 24800 iops
4k 32 randread 37400 iops
4M 1 read 460 MB/s
4M 16 read 514 MB/s

Результаты по чтению не отличаются на hdparm -W 0 и 1.

Seagate Nytro 1351 XA3840LE10063

Диск заполнен почти полностью, на 90-100 %.

Запись
sync или fsync bs iodepth rw hdparm -W 1 hdparm -W 0
sync 4k 1 randwrite 18700 iops 18700 iops
sync 4k 4 randwrite 49400 iops 54700 iops
sync 4k 32 randwrite 51800 iops 65700 iops
sync 4M 32 write 516 MB/s 516 MB/s
fsync=1 4k 1 randwrite 288 iops 18100 iops
fsync=1 4k 4 randwrite 288 iops 52800 iops
fsync=4 4k 4 randwrite 1124 iops 53500 iops
fsync=1 4k 32 randwrite 288 iops 65700 iops
fsync=32 4k 32 randwrite 7802 iops 65700 iops
fsync=1 4M 32 write 336 MB/s 516 MB/s
Чтение
bs iodepth rw результат
4k 1 randread 8600 iops
4k 4 randread 21900 iops
4k 8 randread 30500 iops
4k 16 randread 39200 iops
4k 32 randread 50000 iops
4M 1 read 508 MB/s
4M 16 read 536 MB/s

Если не хотите 288 иопс — отключайте кэш.

Ceph HDD+SSD

Дано: 3 компа с 4x 7200rpm SATA HDD, с 1 SSD (десктопным) под систему и ceph-mon и с 1 SSD (старым, но серверным, 25000 iops) под журналы. Не самая быстрая 10-гигабитная сеть — флуд пингом средний RTT (задержка) 0.098ms. Развёрнут Ceph + OpenNebula с KVM. Диски под Ceph отформатированы в Bluestore утилитой ceph-volume (то есть используется LVM). Диски виртуалок лежат в обычном реплицированном ceph pool с size=3.

Создаём Debian-виртуалку (настройки диска kvm по умолчанию — bus=virtio, cache=none), ставим fio, запускаем в ней тест на задержку транзакционной случайной записи: fio -ioengine=libaio -size=10G -sync=1 -direct=1 -name=test -bs=4k -iodepth=1 -rw=randwrite -runtime=60 -filename=./testfile (или можно не случайной, тогда rw=write, но результат идентичный).

  1. Настройки по умолчанию — все кэши дисков включены (везде hdparm -W 1, в /sys/block/*/queue/write_cache везде write back) — Ж О П А, iops=59, avg lat = 16.88ms
  2. Отключаю кэш записи SSD с журналами: hdparm -W 0 /dev/sdb — остаётся Ж О П А, iops=58, avg lat = 16.99ms
  3. Всем LVM-девайсам отключаю кэш записи: for i in /sys/block/dm-*; do echo write through > $i/queue/write_cache; done` — А Ф И Г Е Т Ь, iops=584, avg lat = 1.7ms
  4. Обратно включаю кэш SSDшке с журналами: hdparm -W 1 /dev/sdb — остаётся iops=582, avg lat = 1.7ms
  5. Откручиваю все отключения кэшей LVM: for i in /sys/block/dm-*; do echo write back > $i/queue/write_cache; done — обратно жопа, 57 iops, avg lat = 17.2ms
  6. Опять отключаю кэш журнальным LVM-девайсам: for i in `ls /dev/ceph-journals/lvol*`; do j=readlink $i; echo write through > /sys/block/${j##../}/queue/write_cache; done — никакого улучшения, всё та же жопа (но с ними это точно безопасно, так как они с конденсаторами :))
  7. Отключаю кэш HDD LVM-разделам (for i in `ls /dev/ceph-*/osd-block*`; do j=readlink $i; echo write through > /sys/block/${j##../}/queue/write_cache; done) — бинго, iops=603, avg lat = 1.65ms
  8. Ага. Простите. Обнаруживаю, что просто писать куда-то write through небезопасно без hdparm -W 0 /dev/sd*, так как https://www.kernel.org/doc/Documentation/block/queue-sysfs.txt - Writing to this file can change the kernels view of the device, but it doesn’t alter the device state. ок, добавляю for i in /dev/sd?; do hdparm -W 0 $i; done (отключаю все кэши) — результат похуже, iops=405, avg lat = 2.47ms — но это всё равно лучше, чем изначальная жопа.

Виртуалку, в которой тестировал — даже не перезапускал между тестами.

Note.svg Мораль: отключайте кэш записи всем дискам!

Частичная разгадка:

  1. В жёстких дисках HGST есть Media Cache (энергонезависимый кэш случайной записи прямо на пластинах) — во-первых, там не HGST, во-вторых, в HGST медиакэш включён всегда и не зависит от hdparm -W 0.
  2. В жёстких дисках Seagate Enterprise Capacity ST8000NM0055 (коих из общего числа 3) есть встроенный SSD-кэш. И вот он, видимо, действительно включается только при hdparm -W 0.
  3. Блюстор блокирует запись в журнал записью на HDD. Когда включен спец.кэш, HDD начинает рандомно писать сильно быстрее, и блокировки при сбросе уходят. Действует кэш, естественно, временно — когда он кончится, производительность случайной записи опять упадёт. Однако плюс в том, что в Ceph-е этого, скорее всего, не произойдёт, так как скорость случайной записи ограничивается, собственно, самим Ceph-ом и распределяется по всем дискам кластера :).
  4. Однако, для обычных дисков без SSD-кэша отключение кэша тоже даёт выигрыш… есть гипотеза, что из-за того же тормоза с bluefs (проверю).

Данные тестов

Более свежие тесты бенчилкой ceph-gobench на том же самом стенде. Версия Ceph Mimic 13.2.2, Bluestore, журналы на SSD.

osd.1 и osd.3 без выноса журналов на SSD оба показывали 75-80 iops вне зависимости от отключения кэша.

В таблице показаны однопоточные IOPS с глубиной очереди Q=1 без репликации.

Номер OSD Патч + кэш Кэш выкл Кэш вкл Модель диска Номер модели
osd.0 365 308 226 Seagate Constellation ES.2 ST32000645NS
osd.1 322 325 234 Hitachi Ultrastar 7K3000 HUA723020ALA640
osd.2 1278 1392 230 Seagate Enterprise Capacity ST8000NM0055
osd.4 198 244 140 Hitachi Ultrastar A7K2000 HUA722020ALA330
osd.5 251 185 154 Hitachi Ultrastar A7K2000 HUA722020ALA330
osd.7 1351 1129 253 Seagate Enterprise Capacity ST8000NM0055
osd.9 399 315 184 Hitachi Ultrastar 7K3000 HUA723020ALA640
osd.13 226 212 142 Hitachi Ultrastar A7K2000 HUA722020ALA330
osd.3 393 386 241 Hitachi Ultrastar 7K3000 HUA723020ALA640
osd.8 340 323 156 Hitachi Ultrastar A7K2000 HUA722020ALA330
osd.10 1461 1319 273 Seagate Enterprise Capacity ST8000NM0055
osd.14 302 229 135 Hitachi Ultrastar A7K2000 HUA722020ALA330

Почему вообще Bluestore такой медленный?

Note.svg Третья редакция hatespeech’а.

Вкратце ситуация такова: После того, как хранилище Ceph переписали с упоротой архитектуры с хранением объектов в ФС XFS и дополнительным журналом, в которой, по идее, был дикий оверхед — быстрее (в смысле random write iops) оно практически не стало. Логичный ответ на вопрос «какого х#ра» такой: то ли XFS на самом деле написана очень хорошо, то ли Bluestore на самом деле написан очень плохо. Сразу скажу: ответ правильный.

Все мы держим в уме, что 1x 7200rpm HDD может выдать примерно 100—120 iops. Дальше нам говорят — ну, там типа журналирование. Ну ок, как мы рассуждаем — ну, типа, есть журнал, есть диск. Значит типа вроде как синхронно записало в журнал, потом асинхронно постепенно перенесло на диск. Значит, берём 100, умножаем на число дисков в кластере, делим на фактор репликации (3), делим на 1.5-2 (данные+журнал), мы же держим в уме, что наверняка там всё асинхронно и оптимизировано… Получаем, скажем, 100 * 9 дисков / 1.5-2 / 3 = 150—200 iops. Запускаем fio iodepth=128 на собранном кластере — ОЙ, 30 iops. Как так?

Отчаиваемся и по советам знатоков прикручиваем туда SSD под wal+db. И думаем: ну, теперь у нас быстрая SSD с конденсаторами под журналом, латенси записи 50 микросекунд, значит должно быть много иопсов — ну, в устоявшемся режиме хотя бы 300 (9 * 100 / 3). Тестируем. В 1 поток получаем ну… 60 иопс. Во много — где-то 200. Опять плохо.

Смысл в том, что собственной реализации журнала у блюстора нет, есть очередь отложенной записи, живущая прямо в RocksDB. RocksDB — это LSM keyvalue база, по сути база-журнал. В принципе, это достаточно разумно, так как всё равно нужно журналировать изменения метаданных, которые там держит блюстор. Когда очередь отложенной записи засунута туда же, изменение можно коммитить одной транзакцией (соответственно, одним fsync-ом).

И в этой схеме есть одно большое отличие от filestore — оно заключается в том, что в filestore журнал работал как буфер для временного повышения нагрузки на запись. Пока в журнале было место, случайная запись была очень быстрой, а журналы обычно делали размеров в несколько гигабайт. В bluestore же «очередь отложенной записи» очень маленькая и сбрасывается через каждые 64 запроса. То есть, bluestore не пишет быстрее, чем в среднем может медленное устройство (HDD).

Плюс к этому на практике (при просмотре strace) оказывается, что fsync-ов на каждую операцию записи делается не 1, а 2. Второй fsync — это лишняя транзакция записи в журнал BlueFS, сводящаяся к обновлению размера лог-файла RocksDB. Это нафиг не нужно, так как в опциях RocksDB в цефе по умолчанию стоит wal_recovery_mode=kTolerateCorruptedTailRecords и recycle_log_number=4, но это так, потому что без этого у них из-за другого бага корраптятся данные при падении OSD. Что на самом деле исправляется легко, я им даже отправил фикс — https://tracker.ceph.com/issues/38559 https://github.com/ceph/ceph/pull/26909 - и они даже вроде как обещают его влить. С фиксом на HDD ускорение случайной записи при глубине очереди 1 — двукратное (с 33 % до 66 % возможностей самого HDD, обычно как раз с 33 до 66 иопс). При глубине очереди 128 — почти нулевое.

ОК, ладно. В конце концов мы решаем — гулять так гулять и собираем кластер на серверных SSD (или вообще NVMe). Думаем — ну теперь-то?!… Бенчим в 1 поток. 300 иопс. Охреневаем окончательно и идём гуглить эту статью :)

Здесь смысл в том, что в голове у всех сидит мысль «а, ну да, оно медленное, потому что слишком много пишет на диск — диск же относительно медленный, а софт быстрый». А вот хрен. :) оказывается, Ceph довольно сложно разогнать до latency < 1 ms, и виной тому не диски, а сам Ceph. То есть да, Ceph при записи мелкими блоками порождает WA (Write Amplification) 3..5 на каждой OSD — это легко посчитать через тот же strace. Но на хороших SSD это практически не занимает времени, одна операция 4кб записи занимает условно 20 микросекунд. Проблема именно в С++ коде Ceph.

Причём даже не до конца понятно, что конкретно там тормозит — такое ощущение, что всё целиком. Выявить какие-то «горячие точки» при профилировании трудно, просто при записи выполняется много всякой C++ной мелочи, которая суммарно отъедает достаточно много времени. Одно горячее место — вычисление цифровых подписей пакетов (включено по умолчанию, можно отключить), другое — сериализация/десериализация (код обрабатывает каждое поле пакета, чуть ли не каждый байт, отдельным вызовом функции). Дальше идут уже malloc-и, которых тоже происходят тонны. Причём всё это происходит в несколько потоков. На это ещё навёрнута какая-то странная смесь буферизованного и прямого I/O.

RocksDB не виновата — её я пробовал бенчить, она быстрая, ~8000 транзакций в секунду на NVMe в 1 поток она даёт и даже масштабируется до ~120000 tps в 256 потоков. А Ceph OSD даже с максимальным параллелизмом на той же NVMe даёт только 10-20 тысяч iops.

Сеть тоже не виновата — её я пробовал бенчить с помощью nbd (network block device). При прямом доступе диск выдаёт 50000 iops, при пробросе диска с одного сервера на другой через nbd — 8000 iops. То есть, добавленная latency сети — примерно 0.1ms. Это не много.

И даже Bluestore не совсем виноват. На NVMe Bluestore с некоторыми тюнами всё-таки осиливает завершить запись примерно за 0.3мс. И в то же время код самого Ceph сжирает ещё 0.4мс.

По сути, нужно было бы добиться ситуации, в которой Ceph бы отъедал 0.1ms и Bluestore ещё 0.1ms. Сеть съест ещё 0.1ms и тогда на хорошей NVMe получится latency ~ 0.33ms, что в 1 поток соответствует 3000 операциям записи в секунду. До локальной SSD это всё равно не дотянуло бы, но, тем не менее, было бы уже очень-очень неплохо. Тогда, скажем, можно будет бороться за CPU: поставив самолётные CPU по 20000$ каждый, настроив ядро и снизив время Ceph+Bluestore ещё в 2 раза, вы получите уже 0.23ms = ~4350 iops. А допилив поддержку Infiniband, может, получите и все 6000 iops. Пока же код не оптимизирован… всё это — мёртвому припарки.

Авторы сейчас пытаются пилить новую реализацию OSD на асинхронном фреймворке Seastar (Crimson OSD), так что у нас есть теоретические шансы увидеть Ceph, который будет в несколько раз быстрее. Хотя, конечно, там вопрос далеко не только в многопоточности и блокировках — по идее, много съедает именно программная логика. Но так как её они фактически тоже частично переписывают с нуля — она тоже может улучшиться.

DPDK и SPDK

  • DPDK = Data Plane Developer Kit, быстрая библиотека от Intel для работы с (в основном) сетевыми устройствами в userspace, без задействования драйверов ядра
  • SPDK = Storage Performance Developer Kit, основанная на DPDK библиотека для работы с NVMe SSD, тоже в пространстве пользователя. Ещё есть форк libnvme — SPDK, отвязанный от DPDK
  • DPDK включается через ms_type=async+dpdk
  • SPDK включается для NVMe-шек передачей в качестве пути девайса spdk:<серийный номер pcie устройства> и ручным созданием OSD по инструкции Manual Deployment
  • Это в теории — на практике (проверялся Mimic 13.2.x) НИ ХРЕНА не работает, ни DPDK, ни SPDK
    • С DPDK Ceph «из коробки» даже не собирается — это в общем-то довольно легко исправить, но даже когда добиваешься сборки и запуска — OSD падают после обработки ~50 пакетов
    • С SPDK Ceph собирается и даже собран по умолчанию — но оно опять-таки не работает — вскоре после запуска OSD просто виснет в пространстве
    • Code is there, так что, вероятно, всё это можно исправить, если подебажить подольше
    • Есть сообщения, что SPDK всё-таки работает из коробки, просто не даёт никакого выигрыша производительности. Но мне пока завести его не удалось
  • Однако, похоже, в силу неоптимальной реализации самого сетевого кода Ceph ни от DPDK, ни от RDMA ожидать ускорения не приходится — потому что один чувак недавно отрезал код AsyncMessenger-а от всего остального цефа и попробовал побенчить его отдельно: https://www.spinics.net/lists/ceph-devel/msg43555.html - и получил всего лишь ~80000 iops.
  • В перспективе SPDK будет на хрен не нужен, так как в ядро приняли штуку под названием io_uring: https://lore.kernel.org/linux-block/20190116175003.17880-1-axboe@kernel.dk/ - с ней обычный код прокачивает через Optane-ы практически столько же iops, сколько и SPDK, при заметно меньшем объёме геморроя на поддержку работы с SPDK/DPDK

RAID WRITE HOLE

В RAID-е есть один интересный момент: при отказе диска и одновременном отключении питания RAID 5 может кораптить данные.

Суть такая: допустим, есть три диска в рейд5. Есть какая-то пара блоков данных A и B. Соответственно на дисках хранятся: A, B, A xor B.

Теперь представим, что мы пишем в блок B данные B2. Для этого нам надо обновить данные на двух дисках: B -> B2, A+B -> A+B2. Теперь представим, что один из них успел записать, а второй не успел. Тут вырубилось питание и одновременно сдох диск A (или диск сдох от умирания питания, или контроллер повис и ядро в панику упало...). Что мы имеем на дисках?

?, B2, A+B либо ?, B, A+B2.

Теперь при попытке восстановить A мы получим A+B+B2 => опа! Покорраптились данные, которые даже не записывались!

Из-за этого raid всегда делает полный resync после нештатного вырубания питания. И, собственно, такая же потеря данных возможна при отказе диска до завершения resync. mdadm RAID5 в таких ситуациях (когда одновременно потерян диск и массив помечен как грязный) просто отказывается стартовать.

И именно чтоб этого избежать, в цефе сделано полное журналирование всех данных на уровне отдельных дисков (т.е. OSD). Потому что других способов борьбы с этой проблемой НЕТ, а при работе по сети отказы гораздо более вероятны, чем при работе RAID-массива на локальных дисках. Даже write intent bitmap может только сказать вам, потеряли вы какие-то данные или нет, но не может помочь их восстановить, если они потеряны.

Так что Ceph надёжнее RAID-а. :) медленнее (на SSD). Но надёжнее и не требует resync-а.

Краткий экскурс в устройство SSD и флеш-памяти

Особенность NAND флеш-памяти заключается в том, что пишется она мелкими блоками, а стирается большими. Актуальное соотношение для Micron 3D NAND — страница (page) 16 КБ, блок стирания (block) — 16 или 24 МБ (1024 или 1536 страниц, MLC/TLC соответственно). Случайное чтение быстрое. Запись тоже, но писать можно только в предварительно стёртую область. А вот стирание медленное, да ещё и число стираний каждого блока ограничено — после нескольких тысяч (типичное значение для MLC) блок физически выходит из строя. В более дешёвых и плотных (MLC, TLC, QLC — 2-4 бита на ячейку) чипах лимит стираний меньше, в более дорогих и менее плотных (SLC, один бит на ячейку) — больше. Соответственно, при «тупом» подходе — если при записи каждого блока его просто стирать и перезаписывать — случайная запись во флеш-память, во-первых, будет очень медленной, а во-вторых, она будет быстро выводить её из строя.

Но почему тогда SSD быстрые? А потому, что внутри SSD на самом деле есть очень мощный и умный контроллер (1-2 гигагерца, типично 4 ядра или больше, примерно как процессоры мобильников), и на нём выполняется нечто, называемое Flash Translation Layer — прошивка, которая переназначает каждый мелкий логический сектор в произвольное место диска. FTL всё время поддерживает некоторое количество свободных стёртых блоков и направляет каждую мелкую случайную запись в новое место диска, в заранее стёртую область. Поэтому запись быстрая. Одновременно FTL делает дефрагментацию свободного места и Wear Leveling (распределение износа), направляя запись и перемещая данные так, чтобы все блоки диска стирались примерно одинаковое количество раз. Кроме того, во всех SSD некоторый % реального места зарезервирован под Wear Leveling («overprovision»), а в хороших серверных SSD этот процент весьма большой — например, в Micron 5100 Max это +60 % ёмкости.

Именно из наличия FTL вытекает и проблема с энергонезависимостью и «power loss protection»-ом. Карты отображения секторов — это метаданные, которые при сбросе кэша тоже нужно сбрасывать в постоянную память, и именно этот сброс и вносит торможение в работу настольных SSD с fsync.

Дополнение: когда я попытался кого-то в списке рассылки полечить на тему, что «все SSD делают fsync», мне в ответ кинули статью: https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf. В общем, суть статьи в том, что в 2013 году нормой было то, что SSD вообще не сбрасывали метаданные на диск при fsync, и при отключении питания это приводило к разным весёлым вещам вплоть до (!!!) полного отказа SSD.

Есть экземпляры старых SSD без конденсаторов (OCZ Vector/Vertex), которые при этом выдают большие iops на запись с fsync. Как это возможно? Неизвестно, но есть предположение, что суть как раз в небезопасности записи. Принцип работы флеш-памяти за последние годы вроде как не менялся — в SSD как раньше был FTL, так и сейчас FTL. Как достигнуть быстрой записи, если постоянно сбрасывать на диск карты трансляции — хз… наверное, если только сделать некое подобие лог-структурированной ФС внутри — писать всё время вперемешку метаданные и данные. Но при этом, по идее, при старте всё это «добро» придётся сканировать и старт/монтирование станет долгим. А в SSD долгого монтирования вроде как нет.

Ну и, собственно, «power loss protection», видимо, бывает простой, а бывает advanced. Простой означает просто «мы корректно делаем fsync и не сдохнем при отключении питания», а advanced означает наличие конденсаторов и быструю безопасную запись с fsync. Сейчас, в 2018—2019 годах, «обычный» PLP, похоже, всё-таки стал нормой и при отключении питания большая часть SSD терять данные и умирать уже не должна.

Бонус: USB-флешки

А почему тогда USB-флешки такие медленные? Случайная запись на флешку 512-байтными (или 4 Кб) блоками обычно идёт со скоростью 2-3 iops. А флеш-память там примерно та же, что в SSD — ну, более дешёвые и мелкие чипы, но принцип тот же и разница в скорости не на порядки. Ответ кроется в том, что на флешках тоже есть FTL (и даже Wear Leveling), но по сравнению с SSD-шным он маленький и тупой. У него слабый процессор и мало памяти. Из-за малого объёма RAM контроллеру флешки, в отличие от контроллера SSD, негде хранить полную таблицу сопоставления виртуальных и реальных секторов — поэтому отображаются не сектора, а крупные блоки где-то по мегабайту или больше, а при записи есть лимит на количество «открытых» блоков. Как это происходит:

  • Допустим, вы пишете в сектор X.
  • Контроллер отображает блок, которому принадлежит этот сектор, на реальный блок, и «открывает» его — выделяет пустой блок, запоминает, что он «дочерний» для открытого и записывает туда один изменённый вами сектор.
  • Таким макаром можно открыть максимум N разных блоков; число N обычно очень маленькое — от 3 до 6.
  • Дальше если вы пишете следующий сектор из уже открытого блока — он просто записывается в его дочерний блок (что быстро).
  • Если же следующий записываемый сектор принадлежит другому блоку — какой-то из открытых блоков приходится закрывать и «сливать» содержимое дочернего блока с оригинальным.

Для копирования больших файлов на флешку, отформатированную в любую из стандартных файловых систем, двух блоков достаточно: в один открытый блок пишутся данные, во второй — метаданные записываемого файла. Запись последовательная, всё быстро. А вот при случайной записи вы перестаёте попадать в уже «открытые» блоки и каждая операция записи превращается в полное стирание. Тут-то и начинаются тормоза…

Пример теста от Micron

Пример самолётного сетапа от Micron с процами по полляма (2x Xeon Platinum 8168), 2x100-гбит сетью (точнее 2x2x100, так как 2 карты по 2 порта) и 10x топовыми NVMe (с конденсаторами, ага) в каждом узле, 4 узла, репликация 2x: https://www.micron.com/resource-details/30c00464-e089-479c-8469-5ecb02cfe06f

Всего 367011 iops на запись в пике на весь кластер, при 100 % загрузке CPU. Казалось бы, довольно много, но если поделить 367000/40 osd — получится 9175 иопс на 1 osd. С учётом репликации на диски нагрузка двойная, выходит, 18350 иопс. Ок, журналы тоже удваивают нагрузку, итого — 36700 iops на запись смог выжать ceph из одной NVMe… которая сама по спеке может 260000 иопс в одиночку. Вот такой вот overhead.

Данных по задержкам в 1 поток нет (а было бы интересно узнать).

Примечание: PDF-ка по ссылке обновлена, новый результат с теми же тюнами, что ниже, составил 479882 iops на запись и 2277453 iops на чтение. Старая тут. Что и подтверждает, что разница между Micron 9200 и 9300 для Ceph незаметна.

Апдейт

https://www.micron.com/-/media/client/global/documents/products/other-documents/micron_9300_and_red_hat_ceph_reference_architecture.pdf

NVMe обновились до Micron 9300 (максимальной ёмкости 12.8 ТБ). iops-ов такие диски дают даже не 260 тыс., а 310 тыс. Всё остальное осталось прежним. Итого 100 клиентами на запись сняли 477029 iops. Однако надо понимать, что каждому клиенту при этом досталось лишь 4770 iops. 10 клиентами сняли 294000 iops — то есть на 1 клиента 29400 иопс, что всё-таки поприличней.

Почему стало лучше? Предположительно, благодаря тюнингу. По сравнению с прошлым тестом они:

  • отключили чексуммы мессенджера (ms_crc_data=false) и чексуммы блюстора (bluestore_csum_type=none)
  • затюнили rocksdb: bluestore_rocksdb_options = compression=kNoCompression,max_write_buffer_number=64,min_write_buffer_number_to_merge=32,recycle_log_file_num=64,compaction_style=kCompactionStyleLevel,
    write_buffer_size=4MB,target_file_size_base=4MB,max_background_compactions=64,level0_file_num_compaction_trigger=64,level0_slowdown_writes_trigger=128,
    level0_stop_writes_trigger=256,max_bytes_for_level_base=6GB,compaction_threads=32,flusher_threads=8,compaction_readahead_size=2MB
    • 64x32x4 MB memtable (number x merge x size) вместо стандартных 4x1x256 MB. Скорее всего, это и сыграло основную роль. Эффект не совсем очевиден, но, вероятно, это снижает нагрузку на CPU, потому что поиск в 64 маленьких memtable быстрее, чем поиск в 1 (или 4) больших.
    • сильно изменён max_bytes_for_level_base — с 256 мб он поднят до 6 гб!
    • добавлены потоки compaction-а.
  • выдали 14 гб RAM каждому OSD
  • osd_max_pg_log_entries=osd_min_pg_log_entries=osd_pg_log_dups_tracked=osd_pg_log_trim_min = 10 (хз, по-моему, ничего не даёт)

Также надо отметить, что:

  • cephx у них уже был отключён. В этот раз зачем-то добавили и отключение подписей — видимо, читали мою статью. Но это нафиг не надо, при отключенном cephx подписи можно уже не отключать.
  • debug objecter = 0/0 и вообще отключенные дебаги у них тоже уже были
  • с prefer_deferred_size и min_alloc_size они, видимо, не игрались (а зря)
  • обновлённые диски не играют никакой роли гарантированно. 260000 или 310000 iops — для цефа никакой разницы нет.

Модели

  • Micron серий 5100/5200
  • HGST SN260
  • Intel P4500

https://docs.google.com/spreadsheets/d/1E9-eXjzsKboiCCX-0u0r5fAjjufLKayaut_FOPxYZjc

Резюме

Резюмируя вышесказанное, для random write iops:

  • Использовать только SSD с конденсаторами. NVMe это тоже касается. Подсказка: 99 % desktop SSD и NVMe конденсаторов не имеют.
  • …и надо отключать этим SSD кэш (hdparm -W 0), если они SATA!
  • Отключить powersave: governor performance, cpupower idle-set -D 0
  • Отключить электронные подписи:
    cephx_require_signatures = false
    cephx_cluster_require_signatures = false
    cephx_sign_messages = false

    (и монтировать rbd map / cephfs ядерным драйвером с опциями -o nocephx_require_signatures,nocephx_sign_messages)
  • Для хороших SSD или NVMe: min_alloc_size=4096, prefer_deferred_size_ssd=0 (ДО создания OSD)
  • Актуально для версий до Nautilus включительно — [global] debug objecter = 0/0 (там большой тормоз на клиентской стороне)
  • Если нужны только random write iops, то в userspace драйвере (например, QEMU) — отключить rbd cache
  • Для HDD-only или Bad-SSD-Only и версий до Nautilus включительно — снять ручник https://github.com/ceph/ceph/pull/26909
  • mount -o lazytime

Примечание

Написанное в статье актуально для версий Ceph, доступных на момент последней правки (см. «История» вверху страницы). Конкретно — для 12 luminous, 13 mimic, да и 14 nautilus тоже — всё пока что актуально. Если вдруг в будущем что-то пофиксят и всё вдруг станет чудесно быстрым — сам побегу обновляться первым и поправлю статью :).

См. также