Охлаждение и нагрев данных в vinyl
Tarantool DB поддерживает два подхода к работе с данными на диске: архивация данных
через модуль cooler и прямое хранение в дисковом движке vinyl.
В этом разделе приведены рекомендации для второго варианта — прямого хранения данных в vinyl.
При таком подходе большая часть данных хранится на диске, а часть в памяти в виде кэша и буфера на запись.
Рекомендации приведены для типовых сценариев нагрузки (OLTP, витрины данных, архивация данных через cooler) и основаны
на результатах нагрузочного тестирования vinyl на стендах с HDD и SSD.
Общие рекомендации по настройке движка vinyl приведены в разделе Методика настройки движка vinyl в Tarantool Database.
Часто запрашиваемые кортежи называют горячими данными, а данные, к которым обращаются редко — холодными данными. В сценарии прямого хранения в vinyl нагрев и охлаждение данных происходят автоматически через LRU-кэш в памяти:
- Нагрев — кортеж, прочитанный с диска, попадает в кэш. При повторных запросах он быстро возвращается из памяти. Чем чаще читается кортеж, тем дольше он остаётся в кэше.
- Охлаждение — кортеж, который долго не запрашивали, вытесняется из кэша. Последующие чтения выполняются с диска (медленно).
Скорость доступа к данным из памяти зависит от размера кэша (vinyl.cache) и рабочего набора данных.
Чтобы данные оставались горячими, достаточно их регулярно запрашивать — нагрев происходит автоматически при каждом чтении.
Размер кэша определяет, сколько горячих данных может одновременно находиться в памяти.
Никаких дополнительных действий для нагрева или охлаждения не требуется, всем управляют частота чтения и размер кэша.
Для хранения данных на диске vinyl использует LSM-дерево (Log-Structured Merge Tree).
В такой структуре данные сначала записываются в оперативную память на уровень L0.
Размер уровня L0 задается параметром vinyl.memory.
При заполнении уровня L0 происходит выгрузка уровня L0 на диск (dump).
Вызвать выгрузку на диск можно также вручную, используя box.snapshot.
Данные при сбросе из памяти vinyl записывает в run-файлы, которые организованы по уровням L1, L2, ..., LN.
При чтении данных vinyl последовательно обращается к трём хранилищам. Каждый следующий шаг выполняется только в том случае, если кортеж не был найден на предыдущем:
- LRU-кэш в памяти (
vinyl.cache) — оперативная память, в которой хранятся недавно прочитанные кортежи. Если кортеж недавно читали, он возвращается из кэша без обращения к диску (быстро). Если кортежа в кэше нет, поиск продолжается. - In-memory уровень L0 (
vinyl.memory) — первый уровень LSM-дерева, находится в оперативной памяти.
Все новые кортежи сначала попадают сюда. Если кортежа нет в кэше, но данные ещё не выгружены на диск, чтение
происходит из L0 без обращения к диску (быстро).
Если кортежа на этом уровне нет, начинается чтение с диска. - Диск (
run-файлы) — постоянное хранилище. Если данных нет ни в кэше, ни в L0, они читаются с диска (медленно).
Движок vinyl хранит данные на диске в виде LSM-дерева. Каждый индекс в спейсе имеет собственное LSM-дерево.
Ключевые элементы LSM-дерева:
- Уровень L0 (первый уровень) находится в оперативной памяти, сюда попадают сначала все новые кортежи.
Размер L0 определяется параметром
vinyl.memory. Когда память заполнена, данные выгружаются на диск. - Уровни L1, L2, ..., LN находятся на диске в виде
run-файлов. Каждый уровень иерархически больше предыдущего. Чем ниже уровень, тем более старые данные на нём находятся.
Индекс делится на диапазоны ключей (range), у каждого диапазона есть собственное поддерево и run-файлы.
Размер диапазона вычисляется автоматически.
На задержку чтения сильнее всего влияет количество run-файлов на диапазон.
Среднее количество run-файлов на диапазон можно узнать через index:stat().run_avg.
Диапазоны позволяют сделать нагрузку на запись равномерной: vinyl рандомизирует форму LSM-деревьев в разных диапазонах,
благодаря чему слияние уровней выполняется постоянно, без пиков нагрузки на диск во время значительного слияния.
Информация о методе index:stat() приведена в документации платформы Tarantool.
Когда данные выгружаются на диск, они попадают в run-файлы, и со временем vinyl запускает слияние уровней (compaction) — объединение
нескольких run-файлов одного уровня в один файл, которое vinyl выполняет в фоне.
В отличие от memtx, LSM-дерево в vinyl записывает операции над ключом, а не данные.
Например, при замене кортежа добавляется запись о выполненной операции REPLACE с указанием ключа и данных.
При слиянии уровней операции над одним ключом объединяются: последовательные REPLACE, UPDATE, UPSERT объединяются в одну операцию REPLACE.
Если после них было выполнено удаление, эти операции над ключом удаляются полностью.
Слияние уровней оптимизирует поиск значений, удаляя лишние операции и освобождая место на диске.
При этом слияние требует фоновой работы с диском: дополнительных операций чтения и записи.
Слияние уровней можно вызвать для индекса вручную через :compact.
Количество run-файлов на один диапазон ключей, которое может накопиться на уровне до запуска слияния, определяется параметром
run_count_per_level (по умолчанию 2).
Диапазонов при этом может быть много, и их количество может меняться.
Параметр run_size_ratio (по умолчанию 3.5) определяет, во сколько раз следующий уровень больше предыдущего.
На последнем уровне после слияния остаётся один run-файл.
Чем ниже run_count_per_level и выше run_size_ratio, тем быстрее будет выполняться чтение, поскольку при чтении понадобится просматривать меньше файлов.
В этом случае растут расходы на слияние уровней, поскольку при этом нужно выполнять больше операций чтения и записи.
И наоборот, чем выше run_count_per_level и ниже run_size_ratio, тем менее затратно слияние уровней, но медленнее чтение.
Размер одного run-файла на уровне N можно посчитать по следующей формуле:
Размер одного файла на уровне N ≈ M × run_size_ratioᴺ⁻¹
Суммарный размер данных на уровне N относительно vinyl.memory (M) можно оценить по формуле:
Суммарный размер данных уровня N ≈ M × run_size_ratioᴺ⁻¹ × run_count_per_level
Например, при значениях vinyl.memory = 6 ГБ, run_size_ratio = 3.5 и run_count_per_level = 2 получится следующий размер:
Уровень L1: ~6 × 3.5⁰ × 2 ≈ 12 ГБУровень L2: ~6 × 3.5¹ × 2 ≈ 42 ГБУровень L3: ~6 × 3.5² × 2 ≈ 147 ГБУровень L4: ~6 × 3.5³ × 2 ≈ 515 ГБУровень L5: ~6 × 3.5⁴ × 2 ≈ 1.8 ТБ
Фильтры Блума (Bloom filter) позволяют избежать лишнего чтения run-файлов при точечных запросах.
Для операций с точечным чтением фильтр Блума эффективно отсеивает файлы, в которых нет искомого ключа.
При использовании фильтра Блума возможны ложноположительные срабатывания, при которых файл читается без необходимости.
Коэффициент ложноположительного срабатывания задается параметром bloom_fpr (по умолчанию 0.05).
Чем ниже значение параметра, тем точнее фильтр и меньше выполняется лишних чтений, но выше потребление оперативной памяти.
Для диапазонных запросов (select, pairs) фильтры Блума неэффективны, поскольку при этом просматриваются все файлы независимо.
В каждом run-файле есть индекс страниц, который хранится в памяти.
Такой индекс позволяет сразу перейти к нужной странице без сканирования всего файла.
Текущее потребление памяти под индексы и фильтры Блума можно посмотреть в статистике индекса:
disk.index_size— память под индексы страниц;disk.bloom_size— память под фильтры Блума.
Для оценки будущего потребления памяти под спейс можно использовать формулу на основе текущей статистики:
RAM_index = expected_disk_bytes × K_idx × 1.2K_idx = (disk.index_size + disk.bloom_size) / disk.bytes
Здесь:
expected_disk_bytes— ожидаемый объём данных;disk.index_size,disk.bloom_sizeиdisk.bytes— текущая статистика изindex:stat();- множитель
1.2— запас в 20 %.
Движок vinyl сжимает данные на диске постранично с помощью алгоритма zstd.
Размер страницы задаётся параметром vinyl.page_size.
Метрика box.stat.vinyl().disk.data показывает логический объём данных без сжатия.
Фактический размер данных на диске с учётом сжатия показывает метрика box.stat.vinyl().disk.data_compacted.
Коэффициент сжатия сильно зависит от структуры данных и профиля нагрузки.
Чем больше повторяющихся данных в странице, тем лучше работает сжатие, и наоборот.
В тестах с полностью уникальными данными сжатие было близко к нулю.
При настройке охлаждения и нагрева данных в vinyl выполняйте действия в следующем порядке:
-
Определить тип носителя — HDD или SSD.
-
Определить профиль нагрузки — OLTP, витрины или архивация данных.
-
Задать необходимые значения параметров vinyl, в частности:
- выбрать стартовое значение
vinyl.memory, оно составляет 3–6 ГБ для большинства сценариев; - настроить параметр
page_sizeв зависимости от профиля чтения.
Рекомендованные значения параметров для типовых сценариев работы с vinyl (OLTP, витрины и архивация данных) описаны в разделе Типовые сценарии.
- выбрать стартовое значение
-
Выполнить тестовую запись и чтение на репрезентативных данных.
-
Дождаться завершения фоновых задач vinyl — выгрузки данных и слияния уровней.
-
disk.data_compacted,disk.index_size,disk.bloom_size;run_avg,run_count,rate_limit;- задержка p50, p99, p99.9.
-
Скорректировать параметры с учётом замеров и целевой задержки, которая меньше или равна 50 мс.
В разделе перечислены параметры, влияющие на охлаждение и нагрев данных в vinyl. Общие рекомендации по настройке всех параметров движка vinyl приведены в разделе Методика настройки движка vinyl в Tarantool Database. Рекомендованные значения параметров для типовых сценариев работы с vinyl приведены в разделе Типовые сценарии.
-
vinyl.memory— самый важный параметр, размер первого уровня LSM-дерева (L0) в оперативной памяти. Стартовое значение: 3–6 ГБ. Чем больше значениеvinyl.memory, тем реже происходит выгрузка данных на диск и тем выше объём данных, которые можно хранить без деградации. -
vinyl.page_size— размер страницы хранения данных на диске. Влияет на эффективность чтения и сжатия.- Для современных SSD рекомендуется увеличивать значение с 8 КБ до 64–128 КБ.
- Для OLTP-сценариев (много точечных запросов с одним кортежем) выбирайте меньшее значение
page_size. - Для витрин и отчётов (диапазонные выборки, агрегации) выбирайте большее значение
page_size.
-
vinyl.run_count_per_levelиvinyl.run_size_ratio— максимальное количествоrun-файлов, которое может накопиться на уровне до запуска слияния, и соотношение размеров соседних уровней соответственно. -
vinyl.bloom_fpr— коэффициент ложноположительного срабатывания фильтра Блума. Значение по умолчанию:0.05(5%). Снижение значения уменьшает количество лишних чтенийrun-файлов при точечных запросах, но увеличивает потребление памяти. На практике при стандартных объёмах данных изменение значения опции не дало заметного улучшения.
В подразделах ниже приведены рекомендованные значения параметров vinyl для каждого профиля нагрузки:
Много операций get, update, delete, insert по одному ключу.
Размер кортежа составляет 1–10 КБ.
Рекомендации:
vinyl.memory: 3–6 ГБ;page_size: 8–32 КБ, чтобы минимизировать избыточное чтение;bloom_fpr: 0.05 или ниже при высокой нагрузке на чтение;- Рекомендуемое количество индексов составляет не более 1–2, поскольку каждый вторичный индекс добавляет отдельное LSM-дерево.
Диапазонные выборки (select с большим лимитом, pairs), агрегации.
Объём запросов небольшой, но каждый запрос затрагивает много данных.
Рекомендации:
vinyl.memory: 3–6 ГБ;page_size: 64–128 КБ;read_threads(количество потоков чтения с диска): можно увеличить для ускорения больших выборок.
Фильтры Блума в этом сценарии менее эффективны, поскольку точечные чтения выполняются редко.
Автоматический перенос устаревших кортежей из memtx в vinyl. После переноса кортежи остаются в vinyl для долгосрочного хранения, нагрев не предполагается.
Рекомендации:
- Для обхода рекомендуется выбрать вторичный индекс, это снизит нагрузку сканирования;
- Для
full_scan_timeиtuples_per_iterationиспользуйте значения по умолчанию. Увеличивайте значения параметров при отставании архивации от роста данных; - Для
page_sizeрекомендуется значение в 128 КБ, поскольку архивные данные редко читаются точечно; - Данные в vinyl не удаляются автоматически. Рекомендуется предусмотреть процесс финального удаления устаревших кортежей из vinyl.
Подробная информация об архивации данных приведена в разделах Архивация данных по их времени жизни и Настройка и запуск архивации.
Движок vinyl при работе создаёт большое количество run-файлов.
Рекомендуется отслеживать текущее число run-файлов — их может быть много, поэтому увеличивайте лимит
на количество открытых файлов, иначе Tarantool станет недоступным на запись.
Проверить текущий лимит можно, используя команду ниже:
ulimit -n
Установить лимит можно тремя способами:
- в systemd-конфигурации сервиса Tarantool;
- через
/etc/security/limits.conf - при установке Tarantool DB через инсталлятор ATE, подробности указаны в документации ATE в разделе Предварительная настройка сервера под Tarantool (изменение Unix-лимитов для
пользователя
tarantool- max open files). Значения в 64000, рекомендованного в документации ATE, может быть недостаточно.
Индексы и фильтры Блума run-файлов хранятся в оперативной памяти.
Для оценки необходимого объема памяти выполните запись репрезентативных данных,
дождитесь завершения выгрузки и слияния, а затем снимите статистику.
Пример
-- Статистика по индексуlocal s = box.space.my_space.index[0]:stat()-- Память под индексы и фильтры Блумаlocal index_size = s.disk.index_sizelocal bloom_size = s.disk.bloom_size
Коэффициент K_idx = (index_size + bloom_size) / s.disk.bytes можно использовать для оценки:
RAM_index = Expected_disk_bytes × K_idx × 1.2 (с запасом в 20%).
Масштабирование нужно, если:
-
При чтении:
- стабильно растет значение
index:stat().run_avg; - задержка p99.9 приближается к значению в 50 мс;
- для задач модуля
coolerрастет значение метрикиcooler_full_scan_elapsed.
- стабильно растет значение
-
При записи:
- значение
box.stat.vinyl().regulator.rate_limitстановится ниже входящего потока записи.box.stat.vinyl().regulator.rate_limit— это максимальный лимит на запись на диск. При достижении этого лимита будет выполняться ограничение скорости записи на диск. Чтобы поднять этот лимит, используйте более быстрый диск или увеличьте значениеvinyl.memory; - в записях журнала появляются ошибки
too many open files— необходимо увеличить лимит количества открытыхrun-файлов; - появляются ошибки
waited for N bytes of vinyl memory quota for too long.
- значение
При превышении пороговых объёмов данных, приведённых в разделе Влияние объёма данных и потока записи на производительность, возможны следующие варианты масштабирования:
- Увеличить
vinyl.memory— это увеличивает порог деградации. На SSD при значенииvinyl.memory128 МБ деградация началась с 27 ГБ. При поднятииvinyl.memoryдо значения 6 ГБ деградации не было вплоть до 7 ТБ; - Шардировать данные — распределить нагрузку на несколько экземпляров. Уменьшает объём данных и поток записи на один узел;
- Увеличить
write_threads, если деградация связана с недостаточной скоростью слияния. - Использовать более быстрый диск — скорость записи на SSD выше, что увеличивает порог деградации.
При диапазонных запросах (например, select с большим лимитом) необходимо обойти все run-файлы, чтобы собрать ключи.
Чем глубже дерево и чем больше среднее количество run-файлов на один диапазон ключей (index:stat().run_avg),
тем больше в среднем необходимо прочитать файлов с диска и соответственно дольше будет чтение.
Ниже приведены результаты тестов для vinyl.memory = 6 ГБ.
Выполнялся select по 1000 записей со случайным UUID, размер кортежа ~10 КБ,
page_size = 128 КБ, cache_size = 0 (кэш кортежей vinyl отключён).
run_avg | data_compacted (логический объём) | p99 | p99.9 |
|---|---|---|---|
1 | 22.2 ГБ | 3.2 мс | 13.5 мс |
2 | 74.6 ГБ | 3.7 мс | 15.3 мс |
3 | 259.7 ГБ | 4.4 мс | 13.5 мс |
4 | 904.2 ГБ | 6.6 мс | 14.3 мс |
5 | 3.08 ТБ | 6.6 мс | 14.7 мс |
run_avg | data_compacted (логический объём) | p99 | p99.9 |
|---|---|---|---|
1 | 43.5 ГБ | 3.7 мс | 8.6 мс |
2 | 71.7 ГБ | 4.8 мс | 10.0 мс |
3 | 262.4 ГБ | 4.0 мс | 8.5 мс |
4 | 901.9 ГБ | 4.1 мс | 9.2 мс |
5 | 3.08 ТБ | 5.3 мс | 10.7 мс |
7 | 7.82 ТБ | 14.0 мс | 21.9 мс |
Поток записи в vinyl ограничен двумя факторами:
- Пропускная способность фоновой выгрузки на диск.
- Стоимость слияний при росте дерева и типе нагрузки. Когда дерево становится глубже, количество и стоимость слияний возрастают. При вставке уникальных данных слияние идёт быстрее, а при большом количестве операций над одними и теми же ключами требует больше ресурсов.
При превышении допустимого потока записи vinyl ограничивает скорость записи для пишущих файберов.
Текущее ограничение скорости отражает метрика box.stat.vinyl().regulator.rate_limit.
Если значение этого ограничения становится ниже входящего потока, начинается деградация задержки записи.
Ниже приведены объёмы данных, при которых в ходе тестирования начиналась деградация записи данных для разных значений vinyl.memory.
Поток записи в тесте составлял 64 МБ/с, выполнялась операция replace по уникальным ключам:
Носитель | vinyl_memory | Объём данных к началу деградации |
|---|---|---|
HDD | 128 МБ | 11 ГБ |
HDD | 1 ГБ | 128 ГБ |
HDD | 6 ГБ | 1.64 ТБ |
SSD | 128 МБ | 27 ГБ |
SSD | 6 ГБ |
|
Операции insert, update и delete перед записью выполняют точечное чтение для проверки уникальности ключа.
В отличие от replace и upsert, которые пишут сразу в уровень L0, эти операции дополнительно нагружают подсистему чтения.
При их интенсивном использовании может потребоваться увеличить значение параметра read_threads или уменьшить значение коэффициента bloom_fpr.
Для своевременного обнаружения деградации производительности рекомендуется отслеживать значение приведенных ниже метрик.
Метрики состояния:
index:stat().run_avg— среднее количествоrun-файлов на диапазон. Рост этого показателя — ранний признак возможной деградации чтения;index:stat().run_count— общее количествоrun-файлов;box.stat.vinyl().disk.data— логический объём данных без сжатия;box.stat.vinyl().disk.data_compacted— объём данных с учётом сжатия.
Метрики чтения:
index:stat().cache.get.rows— количество кортежей, обслуженных из кэша (cache hit);index:stat().get.rows— общее количество кортежей, возвращённых через этот индекс. Cache miss — количество запросов, при которых кортеж не был найден в кэше, данные пришлось читать с диска. Количество таких запросов рассчитывается по формулеmiss= get.rows - cache.get.rows.
Метрики записи:
box.stat.vinyl().regulator.rate_limit— текущее ограничение скорости записи, установленное регулятором vinyl;box.stat.vinyl().regulator.dump_bandwidth— максимальная скорость записи на диск, которую может обеспечить vinyl (байты в секунду).
Метрики cooler (для сценария архивации):
cooler_on,cooler_full_scan_elapsed,cooler_inefficiency,cooler_mismatches,cooler_errors— описаны в разделе Метрики Tarantool DB.