Изменения

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

718 байтов убрано, 14:49, 4 марта 2019
Нет описания правки
== Почему вообще Bluestore такой медленный? ==
Ведь старались-старались, уходили от двойной записи и журналирования журнала в filestore{{Note}} По итогам дополнительных изысканий хейтспич несколько подправлен. И опять то же самое…
Речь о random write iops. Вкратце: потому что писано всё кривым **ем.Ведь вроде старались-старались, уходили от двойной записи и «журналирования журнала» в filestore…
Все мы знаемдержим в уме, что 1 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. Как так?
Ну окОкей, как мы рассуждаем — нудальше нам говорят — эээ, типа, есть журнал, есть дискне. Значит типа вроде как синхронно записало в журнал120 random iops с 1 HDD — это без sync, потом асинхронно постепенно перенесло на дискслучайная запись с sync — примерно 50-60 iops. ЗначитОкей, берём 100говорим мы, умножаем на число дисков в кластерено во-первых у нас же глубина очереди 128, делим на фактор репликации (3)значит, делим на 1.5-2 (данные+журнал)параллелизм, мы же держим в умезначит, что наверняка там всё асинхронно операции должны пролезать между sync-ами и оптимизировано… Получаембыть быстрее — а во-вторых, скажем, 100 50* 9 дисков / 2 1.5/ 3 = 150 iops3 — это всё равно 100. Запускаем тест Ну хоть 100 иопс-то должно у нас быть на собранном кластере — ОЙ. запись? А у нас 30 iops. Как так-то?
А просто всё Дальше мы отчаиваемся и по советам знатоков прикручиваем туда SSD под wal+db. И думаем: ну, теперь-то у нас запись идёт на самом деле довольно криво устроеноSSD, SSD хорошая, с конденсаторами. Теперь суммарно у нас должно быть не меньше 300 иопс (N * скорость шпинделя / фактор репликации), да и в 1 поток должно быть столько же. Тестируем. В 1 поток получаем ну… 60 иопс. Во много — где-то 200. Опять медленно.
Журнала собственного нетВариант решения № 1: у наших HDD обнаруживается media cache, вместо него RocksDB. Она как бы keyvalue базамы его включаем, но она же LSM, она же так получаем нормальную производительность и работает — типа до лимита пишет в память+журнал, потом когда упирается в лимит — делает compaction по уровням. По сути как бы БДуспокаиваемся (хотя осадочек-журналто остался). Вот они её как журнал Вариант № 2: медиакэша у наших дисков нет и юзаютмы продолжаем ломать голову «почему ж так медленно-то».
Журнала у блюстора собственного нет, вместо него RocksDB. Она как бы keyvalue база, но она же LSM, она же так и работает — типа до лимита пишет в память+журнал, потом когда упирается в лимит — делает compaction по уровням. По сути как бы БД-журнал. Вот они её как журнал и юзают. В той же RocksDB метаданные, и их дохрена, ибо виртуальные клоны и всякое такое. Плюс чексуммы. Плюс идея, что надо побыстрее закоммитить запрос в минимальном виде и ответить «ок». Плюс два варианта записи — прямой и отложенный.
В итоге алгоритм записи примерно такой:Теорически в этом ничего плохого нет. Наоборот, хорошо то, что и метаданные, и данные записываются 1 транзакцией.
# Пришла транзакция — писнулась в журнал. Sync.# Если запись мелкая, она откладывается и Однако на время «откладывания» записывается в ту же rocksdb в отдельное место. Sync.# Упёрлись в лимит сброса отложенных транзакций практике (всего лишь 32 штпри просмотре strace). Прочитали порцию из rocksdbоказывается, смержили, отправили в дискчто при записи 4к-блоками OSD демонстрируют фактор Write Amplification от 3 до 5 плюс делают огромное количество commit-ов. С*ка опять sync, чтобы понимать, когда уже можно будет обновить метаданныеЗапись делается двумя системными вызовами — pwritev и io_submit.# Обновляем метаданные. Sync.# Если запись крупнаяCommit — тоже двумя, то она сразу отправляется на диск. Потом Sync.))). sync_file_range и потом тоже обновляем метаданные. Syncfdatasync.
И ещё всё это приправлено тредамиНапример, блокировками… Все мы знаемпо итогам простого теста с SSD-настройками получается, что наиболее оптимальный способ написания любых i/o приложений — nginxэта тварь на 6193 запроса записи делает 18585 записей (суммарно записывая 21674 4к-подобный — «one thread per core + zero-copy». А тут OSD при старте сразу создаёт ~50 потоковблока) и 24776 коммитов (больше, из которых при чем запросов записи постоянно активны как минимум 4)! Итого Write Amplification = 3.5.
То есть получается, что есть как бы журнал, есть данные Тот же тест на HDD: на 1183 операции 3020 запросов записи суммарным объёмом 4926 * 4 кб и есть метаданные3664 sync-а. И ещё если мелкие записи то есть очередь отложенной записи Итого WA = 4.16 (в той же бд, но отдельная4926/1183), её сначала надо писатьа sync-ов опять больше, а потом сбрасывать и очищать. В итоге получается на 1 входящую транзакцию штук 5 реальных транзакцийчем запросов записи.
Без отложенной записи (на SSD это дефолт) поэтому Причины найдено две:* Блюстор всё и легче — остаётся только журналвремя «едет на ручнике» из-за совершенно идиотской проблемы, данные и метаданные — это хотя бы 3 транзакции, а которую я не 5далее чем вчера зарепортил сюда https://tracker. А когда нет SSD, то казалось бы ceph.com/issues/38559 - при каждой записи в журнал хоть немного должен помогатьRocksDB происходит дополнительная «ненужная» транзакция записи в журнал BlueFS, авотх*й, потому что seekсводящаяся к обновлению размера лог-ов то меньше, но зато транзакций файла RocksDB. Теоретически это не 3нужно, а 5так как RocksDB настроена с wal_recovery_mode=kTolerateCorruptedTailRecords и recycle_log_number=4Смысл Но практически — видимо именно в отсутствии собственного журнала. Если бы он был и они работали как традиционная СУБД, где-то по идееесть баг и это нужно, они могли бы делать примерно так:как иначе при падении OSD его rocksdb ломается.* приняли операциюДля серверных SSD число коммитов не важно, но важен WA — чем больше WA, тем больше работы приходится выполнять. А откуда WA?* BEGIN* UPDATE метаданныеВо-первых, тот же коммит BlueFS добавляет 4кб на каждую операцию.* UPDATE данные* COMMIT = ОДНА синхронная запись в журнал* ответили клиенту «ок» Ну а потом журнал просто потихоньку чистимВо-вторых, min_alloc_size на SSD по умолчанию — 16кб. А у них так не получается — у них rocksdbВсё, данные и метаданные в логически разных местахчто меньше, а журналирование — фактически отдельная транзакциязаполняется нулями — нулевые блоки добавляются к WAВот простой эксперимент: «кластер» из 2 SSD** …а также min_alloc_size приводит к тому, по 4 OSD что на SSDна самом деле тоже работает отложенная запись. Всё, чуть подкрученные SSD-настройки (shards=1, threads=1, что меньше min_alloc_size=4096, bluestore_sync_submit_transaction=true). Делаю strace одной из 4-х osd на одной из двух SSD во время запуска теста fio сначала пишется в 1 поток RocksDB, как и на 60 секундHDD<pre>root@m2Отложенная запись порождает примерно 3x WA:~# ceph daemon osd.0 perf dump | jq '.osd.op блок+ метаданные первый раз + блок второй раз.osd.subop'6193root@m2:~# grep -P 'io_submit\(' osd0-trace.txt |wc -l12383root@m2:~# grep -P 'io_submit\(.*iov_len=4096' osd0-trace.txt |wc -l12382root@m2:~# grep -P 'pwritev\(' osd0-trace.txt |wc -l6203root@m2:~# grep -P 'sync.*\(' osd0-trace.txt |wc -l24776root@m2:~# grep -P 'pwritev.*= 4096$' osd0-trace.txt |wc -l3163root@m2:~# grep -P 'pwritev.*= 8192$' osd0-trace.txt |wc -l2991root@m2:~# grep -P 'pwritev.*= 12288$' osd0-trace.txt |wc -l49</pre> То есть эта тварь на 6193 запроса записи сделала 18585 записей (суммарно 21674*4к) и 24776 (!!!) синков! (sync_file_range и fdatasync) Итого Write Amplification с SSD-настройками = 3.5. О чём и шла речь: данные, Сами по себе метаданные, журналтолстоваты. Но fsyncExtent-ов просто море — больше, чем запросов записи :) Тот же тест на HDD (уже завёрнут в скрипт, отдельные команды ы хранятся не привожу): <tab sep="bar" head="top" class="wikitable">ops in | writes | writes/4kb | sync1183 | 3020 | 4926 | 3664</tab> Итого WA = 4.16 (4926/1183)отдельными ключами, а sync-ов опять больше, чем запросов записи. Теоретически 1 запись 4кб блока должна представляться как просто запись 4кб блока + обновление максимум одного сектора в журнале БД (обновление 1 блока в списке блоков объекта всяко не должно занимать больше 512 списками размерами по умолчанию до 1200 байт). Если бы так Правда, с учётом дефолтных min_alloc_size и было — WA было бы 1.125. Однако в Bluestore WA находится max_blob_size на уровне 3-5. Предположительные причины:* отсутствие собственного механизма журналирования* хранение блоков, занимаемых объектом, не в виде дерева extent-ов SSD (как это обычно делается во всех файловых системах64K), а в виде длинных списков extent-овна самом деле они меньше. Но это порождает обратную проблему — по идее, хранящихся целиком в одном-нескольких ключах 4 Мб на SSD запишутся как 64*64Кб + 64 записи в RocksDB.
== DPDK и SPDK ==