Охлаждение и нагрев данных в 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.
Использование диапазонов снижает нагрузку при чтении данных: если данные запрашиваются по узкому набору "горячих" ключей,
затрагиваются только соответствующие диапазоны.
Информация о методе index:stat() приведена в документации платформы Tarantool.
Когда данные выгружаются на диск, они попадают в run-файлы, и со временем vinyl запускает слияние уровней (compaction) — объединение
нескольких run-файлов одного уровня в один файл, которое выполняется фоновыми задачами vinyl.
В отличие от memtx, LSM-дерево в vinyl записывает операции над ключом, а не данные.
Например, при замене кортежа добавляется запись о выполненной операции REPLACE c указанием ключа и данных.
При слиянии уровней операции над одним ключом объединяются: последовательные 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, тем менее затратно слияние уровней, но медленнее чтении.
Размер уровня N относительно vinyl.memory (M) можно оценить по формуле:
Размер уровня N ≈ M × run_size_ratio^(N-1)
Например, при значениях vinyl.memory = 6 ГБ и run_size_ratio = 3.5 получится следующий размер:
Уровень L1: ~6 × 3.5 × 2 ≈ 42 ГБУровень L2: ~6 × 3.5² × 2 ≈ 147 ГБУровень L3: ~6 × 3.5³ × 2 ≈ 515 ГБУровень L4: ~6 × 3.5⁴ × 2 ≈ 1.8 ТБУровень L5: ~6 × 3.5⁵ × 2 ≈ 6.3 ТБ
Фильтры Блума (Bloom filter) позволяют избежать лишнего чтения run-файлов при точечных запросах.
Для операций с точечным чтением фильтр Блума эффективно отсеивает файлы, в которых нет искомого ключа.
При использовании фильтра Блума возможны ложноположительные срабатывания, при которых файл читается без необходимости.
Коэффициент ложноположительного срабатывания задается параметром bloom_fpr (по умолчанию 0.05).
Чем ниже значение параметра, тем точнее фильтр и меньше выполняется лишних чтений, но выше потребление оперативной памяти.
Для диапазонных запросов (select, pairs) фильтры Блума неэффективны, поскольку при этом просматриваются все файлы независимо.
В каждом run-файле есть индекс страниц, который хранится в памяти.
Такой индекс позволяет сразу перейти к нужной странице без сканирования всего файла.
Память под индексы и фильтры Блума можно оценить по следующей формуле:
RAM_index = expected_disk_bytes × K_idx × 1.2K_idx = (disk.index_size + disk.bloom_size) / disk.bytes
Здесь disk.index_size и disk.bloom_size — статистика из index:stat().
Коэффициент K_idx можно использовать для оценки памяти под конкретный спейс, умножив на ожидаемый объём данных и добавив запас в 20 %.
Движок vinyl сжимает данные на диске постранично с помощью алгоритма zstd.
Размер страницы задаётся параметром vinyl.page_size.
Метрика box.stat.vinyl().disk.data показывает логический объём данных без сжатия.
Фактический размер на диске может быть значительно меньше.
Коэффициент сжатия сильно зависит от структуры данных и профиля нагрузки.
Чем больше повторяющихся данных в странице, тем лучше работает сжатие, и наоборот.
В тестах с полностью уникальными данными сжатие было близко к нулю.
При настройке охлаждения и нагрева данных в 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),
тем больше в среднем необходимо прочитать файлов с диска и соответственно дольше будет чтение.
Чем больше среднее количество 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;tnt_vinyl_regulator_dump_bandwidth— максимальная скорость записи на диск, которую может обеспечить vinyl (байты в секунду).
Метрики cooler (для сценария архивации):
cooler_on,cooler_full_scan_elapsed,cooler_inefficiency,cooler_mismatches,cooler_errors— описаны в разделе Метрики Tarantool DB.