TDB Documentation portal logo
Помощь
Обновлена 22 октября 2026 г. в 11:37

Охлаждение и нагрев данных в 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 последовательно обращается к трём хранилищам. Каждый следующий шаг выполняется только в том случае, если кортеж не был найден на предыдущем:

  1. LRU-кэш в памяти (vinyl.cache) — оперативная память, в которой хранятся недавно прочитанные кортежи. Если кортеж недавно читали, он возвращается из кэша без обращения к диску (быстро). Если кортежа в кэше нет, поиск продолжается.
  2. In-memory уровень L0 (vinyl.memory) — первый уровень LSM-дерева, находится в оперативной памяти.
    Все новые кортежи сначала попадают сюда. Если кортежа нет в кэше, но данные ещё не выгружены на диск, чтение
    происходит из L0 без обращения к диску (быстро).
    Если кортежа на этом уровне нет, начинается чтение с диска.
  3. Диск (run-файлы) — постоянное хранилище. Если данных нет ни в кэше, ни в L0, они читаются с диска (медленно).

Устройство движка vinyl

LSM-дерево

Движок 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 выполняйте действия в следующем порядке:

  1. Определить тип носителя — HDD или SSD.

  2. Определить профиль нагрузки — OLTP, витрины или архивация данных.

  3. Задать необходимые значения параметров vinyl, в частности:

    • выбрать стартовое значение vinyl.memory, оно составляет 3–6 ГБ для большинства сценариев;
    • настроить параметр page_size в зависимости от профиля чтения.

    Рекомендованные значения параметров для типовых сценариев работы с vinyl (OLTP, витрины и архивация данных) описаны в разделе Типовые сценарии.

  4. Выполнить тестовую запись и чтение на репрезентативных данных.

  5. Дождаться завершения фоновых задач vinyl — выгрузки данных и слияния уровней.

  6. Измерить фактические метрики:

    • disk.data_compacted, disk.index_size, disk.bloom_size;
    • run_avg, run_count, rate_limit;
    • задержка p50, p99, p99.9.
  7. Скорректировать параметры с учётом замеров и целевой задержки, которая меньше или равна 50 мс.

Настройка параметров vinyl

В разделе перечислены параметры, влияющие на охлаждение и нагрев данных в 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 для каждого профиля нагрузки:

OLTP: точечные чтения и запись

Много операций 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 (количество потоков чтения с диска): можно увеличить для ускорения больших выборок.

Фильтры Блума в этом сценарии менее эффективны, поскольку точечные чтения выполняются редко.

Архивация данных через cooler

Автоматический перенос устаревших кортежей из 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.memory 128 МБ деградация началась с 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 отключён).

HDD

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 мс

SSD

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 ограничен двумя факторами:

  1. Пропускная способность фоновой выгрузки на диск.
  2. Стоимость слияний при росте дерева и типе нагрузки. Когда дерево становится глубже, количество и стоимость слияний возрастают. При вставке уникальных данных слияние идёт быстрее, а при большом количестве операций над одними и теми же ключами требует больше ресурсов.

При превышении допустимого потока записи 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 ГБ

7.33 ТБ без заметного снижения

Операции с точечным чтением

Операции insert, update и delete перед записью выполняют точечное чтение для проверки уникальности ключа. В отличие от replace и upsert, которые пишут сразу в уровень L0, эти операции дополнительно нагружают подсистему чтения. При их интенсивном использовании может потребоваться увеличить значение параметра read_threads или уменьшить значение коэффициента bloom_fpr.

Мониторинг работы движка vinyl

Для своевременного обнаружения деградации производительности рекомендуется отслеживать значение приведенных ниже метрик.

Метрики состояния:

  • 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.
Была ли статья полезна?