Key-value для хранения метаданных в СХД. Тестируем встраиваемые базы данных
7-8 ноября 2017 на конференции Highload++ исследователи лаборатории «Рэйдикс» представили доклад «Метаданные для кластера: гонка key-value-героев».
В этой статье мы представили основной материал доклада, касающийся тестирования баз данных key-value. «Зачем их тестировать производителю СХД?», — спрóсите вы. Задача возникла в связи с проблемой хранения метаданных. Такие «фичи», как дедупликация, тиринг, тонкое выделение ресурсов (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 не представляется возможным (либо это экономически нецелесообразно). В связи с этим возникает вопрос хранения метаданных, причем с максимальной производительностью доступа.
Варианты хранения метаданных
- Key-value БД. lba — ключ, metadata — значение
- Прямая адресация. Не храним lba = метаданных N*Y, а не N*(8+Y)Б
Рис. 2. Принцип работы прямой адресации
На рисунке 2 pba1 — это физический сектор (512Б) накопителя, где мы храним метаданные, а lba1. lba32 (их 32, т.к. 512Б/16Б = 32) — адреса тех страниц, которым эти метаданные соответствуют, и эти адреса нам хранить не надо.
Анализ рабочей нагрузки в СХД
Исходя из нашего опыта рабочих нагрузок, определимся, какие требования по задержкам и пропускной способности нам нужны.
Рабочая нагрузка в медиаиндустрии:
- NLE (нелинейный монтаж) — чтение и запись нескольких больших файлов параллельно.
- VOD (видео по запросу) — чтение множества потоков, иногда с перескоками. Возможна параллельная запись в несколько потоков.
- Транскодинг — 16–128 KB random R/W 50/50.
- 8–64 KB IO.
- Random read/write примерно 50/50.
- Периодически Seq Read и Write в сотни потоков (Boot, Virus Scan).
- 16/32 KB IO.
- Чередование read/write в сотнях и тысячах потоков.
- Показывать: • Задержки (latency) 1–2мс (перцентиль 99.99%) для flash. • От 20ГБ/с, от 300–500k IOPS.
- На следующих нагрузках: • Случайные R/W. • Соотношение 50/50. • Размер блоков 8–64K.
Какие сложности возникают на старте?
- Придётся выбрать всего несколько БД для тестов — все протестировать не получится. Как выбрать эти несколько? Только субъективно.
- В идеале нужно проводить тщательную настройку каждой БД перед тестированием, на что может понадобиться огромное количество времени. Поэтому мы решили сначала посмотреть на то, какие цифры можно получить в стандартной конфигурации, а потом принять решение, насколько перспективно тестирование.
- Сделать тесты полностью объективными и создать одинаковые условия для каждой из БД довольно сложно ввиду принципиальных различий между базами.
- Мало бенчмарков или их сложно найти. Нужны именно унифицированные бенчмарки, которыми можно протестировать любую (или почти любую) key-value БД, или хотя бы несколько самых интересных. При тестировании разных БД разными бенчмарками страдает объективность.
Типы key-value БД
Существует два типа key-value баз данных:
- Встраиваемые — ещё их называют «движки». По сути, это библиотека, которую можно подключить у себя в коде и пользоваться её функциями.
- Выделенные — ещё можно назвать их «сервер БД», «NoSQL БД». Это отдельные процессы, к котором чаще всего можно обращаться по сокетам. Обычно имеют больше функций, чем встраиваемые. Например, репликация.
Чем тестировать?
Первый вариант, который можно найти, — YCSB.
- Этот бенчмарк является неким отраслевым стандартом, ему доверяют.
- Workload'ы можно легко настраивать в файлах конфигурации.
- Написан на Java. В данном случае это минус, т.к. Java не слишком быстродействена, и это может вносить в результаты тестирования искажения. К тому же, движки, в основном, написаны на C/C++. Это затрудняет написание драйвера YCSB <-> движок.
- Написан на 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 Сбрасываем кэши! Иначе тесты будут нечестными: БД обычно пишут данные поверх файловой системы, поэтому срабатывает кэш операционной системы. Важно сбрасывать его перед каждым тестом.
- Тесты на 32 потока — прогоняем workload'ы 2.1 Random Read • Сбрасываем кэши! 2.2 Mix70/30 • Сбрасываем кэши! 2.3 Mix50/50 • Сбрасываем кэши! 2.4 Mix30/70 • Сбрасываем кэши!
- Тесты на 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 — не осуществляем сброс данных на диск и просто пишем их в буфер с тем, чтобы когда-нибудь (не принципиально когда) скинуть буфер на диск. В этом режиме могут быть большие потери в случае сбоя.
- Sync = 10k IOPS
- Lazy = 40k IOPS
- Nosync = 300k IOPS
В итоге для тестов был выбран режим lazy как наиболее сбалансированный. Об исключениях будет сказано отдельно.
Тестируем встраиваемые key-value БД
Для тестирования «движков» мы проводили два варианта тестирования: на 1 млрд ключей и на 17 млрд ключей.
-
— про него все знают, это БД от Facebook. LSM-индекс. — движок MongoDB. LSM-индекс. Читать тут. — у этого движка свой кастомный индекс, имеющий что-то общее с LSM-деревьями, B-деревьями. Почитать можно тут. — форк 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
Такова ситуация со встраиваемыми движками. В следующей статье мы расскажем о показателях выделенных баз данных key-value и сделаем выводы о бенчмарках.