7-8 ноября 2017 на конференции Highload++ исследователи лаборатории «Рэйдикс» представили доклад «Метаданные для кластера: гонка key-value-героев».

В этой статье мы представили основной материал доклада, касающийся тестирования баз данных key-value. «Зачем их тестировать производителю СХД?», — спрoсите вы. Задача возникла в связи с проблемой хранения метаданных. Такие «фичи», как дедупликация, тиринг, тонкое выделение ресурсов (thin provisioning), лог-структурированная запись, идут вразрез с механизмом прямой адресации – возникает необходимость хранить большое количество служебной информации.

Введение


Предположим, что всё пространство хранения поделено на страницы размером X байт. У каждой из них есть свой адрес — LBA (8 Байт). Для каждой такой страницы мы хотим хранить Y байт метаданных. Таким образом, получаем набор соответствий lba -> metadata. Сколько таких соответствий у нас будет? Всё зависит от того, сколько данных мы будем хранить.


Рис. 1. Метаданные

Например, при X=4KB, Y=16 байта. Получаем следующую таблицу:

Таблица 1. Соотношение объема хранения и метаданных
Объем данных на узел Количество ключей на узел Объем метаданных
512ТБ 137 млрд 3ТБ
64ТБ 17 млрд 384ГБ
4ТБ 1 млрд 22.3ГБ

Объём метаданных достаточно большой, поэтому держать метаданные в RAM не представляется возможным (либо это экономически нецелесообразно). В связи с этим возникает вопрос хранения метаданных, причем с максимальной производительностью доступа.

Варианты хранения метаданных


  1. Key-value БД. lba — ключ, metadata — значение
  2. Прямая адресация. Не храним lba = метаданных N*Y, а не N*(8+Y)Б

Что такое прямая адресация? Это когда мы просто размещаем на накопителе наши метаданные по порядку, начиная с самого первого сектора накопителя. При этом нам даже не надо записывать, какому lba соответствуют метаданные, так как всё лежит в порядке возрастания lba.


Рис. 2. Принцип работы прямой адресации

На рисунке 2 pba1 — это физический сектор (512Б) накопителя, где мы храним метаданные, а lba1...lba32 (их 32, т.к. 512Б/16Б = 32) — адреса тех страниц, которым эти метаданные соответствуют, и эти адреса нам хранить не надо.

Анализ рабочей нагрузки в СХД


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

Рабочая нагрузка в медиаиндустрии:

  • NLE (нелинейный монтаж) — чтение и запись нескольких больших файлов параллельно.
  • VOD (видео по запросу) — чтение множества потоков, иногда с перескоками. Возможна параллельная запись в несколько потоков.
  • Транскодинг — 16–128 KB random R/W 50/50.

Рабочая нагрузка в сегменте Enterprise:

  • 8–64 KB IO.
  • Random read/write примерно 50/50.
  • Периодически Seq Read и Write в сотни потоков (Boot, Virus Scan).

Рабочая нагрузка в высокопроизводительных вычислениях (High Performance Computing – HPC):

  • 16/32 KB IO.
  • Чередование read/write в сотнях и тысячах потоков.

Из представленных рабочих нагрузок в различных отраслях рынка сформируем итоговые требования к производительности All-Flash СХД:

  1. Показывать:
    • Задержки (latency) 1–2мс (перцентиль 99.99%) для flash.
    • От 20ГБ/с, от 300–500k IOPS.
  2. На следующих нагрузках:
    • Случайные R/W.
    • Соотношение 50/50.
    • Размер блоков 8–64K.

Чтобы выбрать, какая БД из десятков существующих нам подойдёт, надо провести тесты на избранных нагрузках. Так мы сможем понять, как та или иная БД справляется с определённым объёмом данных, какие обеспечивает задержки/пропускную способность.

Какие сложности возникают на старте?

  1. Придётся выбрать всего несколько БД для тестов — все протестировать не получится. Как выбрать эти несколько? Только субъективно.
  2. В идеале нужно проводить тщательную настройку каждой БД перед тестированием, на что может понадобиться огромное количество времени. Поэтому мы решили сначала посмотреть на то, какие цифры можно получить в стандартной конфигурации, а потом принять решение, насколько перспективно тестирование.
  3. Сделать тесты полностью объективными и создать одинаковые условия для каждой из БД довольно сложно ввиду принципиальных различий между базами.
  4. Мало бенчмарков или их сложно найти. Нужны именно унифицированные бенчмарки, которыми можно протестировать любую (или почти любую) key-value БД, или хотя бы несколько самых интересных. При тестировании разных БД разными бенчмарками страдает объективность.

Типы key-value БД


Существует два типа key-value баз данных:

  1. Встраиваемые — ещё их называют «движки». По сути, это библиотека, которую можно подключить у себя в коде и пользоваться её функциями.
  2. Выделенные — ещё можно назвать их «сервер БД», «NoSQL БД». Это отдельные процессы, к котором чаще всего можно обращаться по сокетам. Обычно имеют больше функций, чем встраиваемые. Например, репликация.

В этой статье мы рассмотрим тестирование встраиваемых key-value БД (далее по тексту будем называть их «движками»).

Чем тестировать?


Первый вариант, который можно найти, — YCSB.

Особенности YCSB:
  • Этот бенчмарк является неким отраслевым стандартом, ему доверяют.
  • Workload'ы можно легко настраивать в файлах конфигурации.
  • Написан на Java. В данном случае это минус, т.к. Java не слишком быстродействена, и это может вносить в результаты тестирования искажения. К тому же, движки, в основном, написаны на C/C++. Это затрудняет написание драйвера YCSB <-> движок.

Второй вариант — бенчмарк для движков ioarena.
  • Написан на C.
  • Мало workload'ов. Из тех, которые нам интересны, есть только Random Read. Пришлось дописывать в коде нужные нам workload'ы.



Рис. 3. Варианты тестирований

В итоге для движков выбираем ioarena, а для выделенных – YCSB.

Кроме workload'ов в ioarena нами была добавлена опция (-a), позволяющая указывать при запуске отдельно количество выполняемых операций на поток и отдельно количество ключей в БД.

Все изменения в коде ioarena можно найти на GitHub.

Параметры тестирования key-value БД


Основной workload, который нам интересен, – Mix50/50. Также мы решили посмотреть на RR, Mix70/30 и Mix30/70, чтобы понять, какие БД больше «любят» тот или иной workload.

Методика тестирования


Тестируем в 3 этапа:

  1. Заполнение БД — заполняем в 1 поток БД до необходимого количества ключей.
    1.1 Сбрасываем кэши! Иначе тесты будут нечестными: БД обычно пишут данные поверх файловой системы, поэтому срабатывает кэш операционной системы. Важно сбрасывать его перед каждым тестом.
  2. Тесты на 32 потока — прогоняем workload'ы
    2.1 Random Read
    • Сбрасываем кэши!
    2.2 Mix70/30
    • Сбрасываем кэши!
    2.3 Mix50/50
    • Сбрасываем кэши!
    2.4 Mix30/70
    • Сбрасываем кэши!
  3. Тесты на 256 потоков.
    3.1 То же самое, что и на 32 потока.

Что измеряем?


  • Пропускная способности/throughput (IOPS/RPS — кто какие обозначения больше любит).
  • Задержки/latency (msec):
    • Min.
    • Max.
    • Среднее квадратическое значение — более показательное значение, чем среднее арифметическое, т.к. учитывает квадратичное отклонение.
    • Перцентиль 99.99.

Тестовое окружение


Конфигурация:
CPU: 2x Intel Xeon E5-2620 v4 2.10GHz
RAM: 16GB
Disk: [2x] NVMe HGST SN100 1.5TB
OS: CentOS Linux 7.2 kernel 3.11
FS: EXT4

Здесь важно отметить, что такой малый объем RAM взят не случайно. Таким образом, база не сможет полностью поместиться в кэш на тестах с 1 млрд ключей.

Объем доступной RAM регулировался не физически, а программно — часть заполнялась искусственно скриптом на Python, а остаток был свободен для БД и кэшей.

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

NVMe в тестах использовался один.

Надёжность записи


Важный момент — режим надёжности записи данных на диск. От этого очень сильно зависит скорость записи и вероятность/объём потерь при сбоях.

В целом, можно выделить 3 режима:

  • Sync — честная запись на диск прежде, чем ответить пользователю «OK» на запрос записи. В случае сбоя всё остаётся на месте до последней зафиксированной транзакции.
  • Lazy — записываем данные в буфер, отвечаем пользователю «OK», а буфер через небольшой промежуток времени сбрасываем на диск. В случае сбоя можем потерять немного последних изменений.
  • Nosync — не осуществляем сброс данных на диск и просто пишем их в буфер с тем, чтобы когда-нибудь (не принципиально когда) скинуть буфер на диск. В этом режиме могут быть большие потери в случае сбоя.

По производительности разница примерно такая (на примере движка MDBX):

  • Sync = 10k IOPS
  • Lazy = 40k IOPS
  • Nosync = 300k IOPS

Цифры здесь ТОЛЬКО для примерного понимания разницы между режимами.

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

Тестируем встраиваемые key-value БД


Для тестирования «движков» мы проводили два варианта тестирования: на 1 млрд ключей и на 17 млрд ключей.

Выбранные «движки»:

  • RocksDB — про него все знают, это БД от Facebook. LSM-индекс.
  • WiredTiger — движок MongoDB. LSM-индекс. Читать тут.
  • Sophia — у этого движка свой кастомный индекс, имеющий что-то общее с LSM-деревьями, B-деревьями. Почитать можно тут.
  • MDBX — форк LMDB с улучшениями по надёжности и производительности. B+ дерево в качестве индекса.

Результаты тестов. 1 млрд ключей


Заполнение




Рис. 4.1. Зависимость текущей скорости (IOPS) от количества ключей (ось абсцисс — миллионы ключей).

Здесь все движки в стандартной конфигурации. Режим надёжности Lazy, кроме MDBX. У него слишком медленная запись в Lazy, поэтому для него был выбран режим Nosync, иначе заполнение продлится слишком долго. Однако видно, что с некоторого момента всё равно скорость записи падает примерно до уровня скорости Sync режима.

Что можно увидеть на этом графике?

Первое: с RocksDB что-то случается после 800 млн ключей. К сожалению, не были выяснены причины происходящего.


Рис. 4.2. Зависимость текущей скорости (IOPS) от количества ключей (ось абсцисс — миллионы ключей).

Второе: MDBX плохо перенёс момент, когда данных стало больше, чем доступно памяти.


Рис. 4.3. Зависимость текущей скорости (IOPS) от количества ключей (ось абсцисс — миллионы ключей).

Далее можно посмотреть на график максимальной задержки. Тут также видно, что у RocksDB начались вылеты после 800 млн ключей.


Рис. 5 Максимальная latency

Ниже находится график среднего квадратического значения задержки. Тут также видны те самые границы для RocksDB и MDBX.


Рис. 6.1. RMS Latency


Рис. 6.2. RMS Latency

Тесты


К сожалению, Sophia показала низкий результат во всех тестах. Скорее всего, она не «любит» много потоков (т.е. 32 и более).

WiredTiger сначала показывал очень низкую производительность — на уровне 30 IOPS. Оказалось, что у него есть важный параметр cache_size, который по умолчанию выставлен на 500МБ. После его установки на 8ГБ (или даже 4ГБ) всё становится намного лучше.

Ради интереса был проведён тест с тем же объёмом данных, но с объёмом доступной памяти > 100ГБ. В этом случае MDBX с отрывом уходит вперёд в тесте на чтение.


Рис. 7. 100% Read

При добавлении записи в workload получаем сильное падение MDBX (что ожидаемо, т.к. при заполнении скорость была низкой). WiredTiger подрос, а RocksDB снизил скорость.


Рис. 8. Mix 70%/30%

Тенденция сохранилась.


Рис. 9. Mix 50%/50%

Когда записи становится довольно много, WiredTiger начинает обгонять RocksDB на малом количестве потоков.


Рис. 10. Mix 30%/70%

Теперь можно посмотреть на графики задержек. Столбцы показывают минимальную и максимальную задержки, оранжевая полоска — средняя квадратическая задержка, а красная полоска — перцентиль 99.99.

Зелёная полоса — это примерно 2 мс. То есть мы хотим, чтобы перцентиль находился не выше зелёной полосы. В данном случае мы этого не получаем (шкала логарифмическая).


Рис. 11. Latency Read


Рис. 12. Latency 50%/50%

Результаты тестов. 17 млрд ключей


Заполнение


Тесты на 17млрд ключей были проведены только на RocksDB и WiredTiger, т.к. они были лидерами в тестах на 1 млрд ключей.

У WiredTiger начались странные выпады, но в целом он довольно неплохо себя показывает на заполнении, плюс не наблюдается деградации при увеличении объёма данных.

А вот RocksDB в итоге спустился ниже 100k IOPS. Таким образом, в тесте на 1 млрд ключей мы не видели всей картины, поэтому важно проводить тесты на объёмах, сравнимых с реальными!


Рис. 13. Производительность 17 млрд ключей

Пунктирной линией изображена средняя квадратическая задержка. Видно, что максимальная задержка у WiredTiger выше, а средняя квадратическая ниже, чем у RocksDB.


Рис. 14. Latency 17 млрд ключей

Тесты


С WiredTiger'ом случилась та же беда, что и в прошлый раз — он показывал около 30 IOPS на чтение, даже при cache_size=8GB. Было решено ещё увеличить значение параметра cache_size, но и это не помогло: даже при 96ГБ скорость не поднялась выше нескольких тысяч IOPS, хотя выделенная память даже не была заполнена.

При добавлении записи к workload'у WiredTiger традиционно поднимается.


Рис. 15. Производительность 100% Read


Рис. 16. Производительность 70%/30%


Рис. 17. Производительность 30%/70%


Рис. 18. Производительность 50%/50%


Рис. 19. Latency 100% Read


Рис. 20. Latency 50%/50%

Выводы


Из того, что сказано выше, можно сделать следующие выводы:

Для базы объемом в 1 млрд ключей:

  • Запись + мало потоков => WiredTiger
  • Запись + много потоков => RocksDB
  • Чтение + DATA > RAM => RocksDB
  • Чтение + DATA < RAM => MDBX

Из чего следует, что:

  • Mix50/50 + много потоков + DATA > RAM => RocksDB

Для базы объемом в 17 млрд ключей: однозначное лидерство у RocksDB.

Такова ситуация со встраиваемыми движками. В следующей статье мы расскажем о показателях выделенных баз данных key-value и сделаем выводы о бенчмарках.

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


  1. yleo
    21.12.2017 17:31

    Руслан, финально выскажусь чуть позже, когда закончится "голосование".


    Но в целом — прилетит, ибо в сухом остатке "rocks не всех победил" и конкуренты (показывающие лучшие показатели) почему-то не используют rocks. Вот и… будем разбиться отчего, зачем и почему )


    Пожалуйста, найдите время углубится в тему и матчасть, чтобы стоящее отвечать без "рука лицо".


    P.S.
    Жаль, что из-за проблем не смог присутствовать на вашем докладе.


    • Немного обижен не выполненным обещанием выслать слайды.

    Леонид (MDBX и libmdbx).