Фоновая компактификация

Таблет сортированной динамической таблицы представляет из себя LSM-дерево. При записи строка сначала попадает в хранилище в памяти — dynamic store. По мере наполнения данные из dynamic store сбрасываются на диск в чанки. Фоновый процесс компактификации периодически объединяет несколько чанков. Это необходимо для:

  • применения delete tombstones и физического удаления строк;
  • удаления старых версий;
  • уменьшения overlapping store count — числа чанков, в которых может потребоваться искать некоторый фиксированный ключ.

Компактификация во многом полагается на эвристики. Однако в зависимости от сценария записи в таблицу оптимальные подходы могут отличаться, например:

  • если на таблице установлен небольшой max_data_ttl, имеет смысл периодически компактифицировать старые чанки и освобождать место;
  • если строки регулярно удаляются, то при компактификации имеет смысл одновременно рассмотреть чанк, содержащий вставки и удаления, чтобы физически удалить строки;
  • если поток записи в таблицу большой, можно пожертвовать оптимальностью чтения и ослабить настройки overlapping store count в пользу уменьшения write amplification — отношения количества данных, обрабатываемых компактификацией, к количеству записанных данных, и наоборот.

Глоссарий

  • Dynamic store — структура для хранения свежезаписанных строк, находится в оперативной памяти. Аналог MemTable.
  • Chunk, чанк — иммутабельная структура для хранения строк, сброшенных на диск. Аналог SSTable.
  • Store, стор — общее название для чанков и dynamic stores. Формально говоря, в таблете хранятся не чанки, а chunk stores (и dynamic stores), но термины chunk и chunk store обычно взаимозаменимы.
  • Partition, партиция — часть подразбиения таблета. Подобно тому, как таблица разбита на таблеты, ограниченные pivot-ключами, таблет внутри разбит на партиции. Чанки, находящиеся внутри партиции, не пересекают её границ.
  • Eden, Эден — особая партиция, содержащая чанки, которые не могут быть помещены ни в одну из партиций, поскольку пересекают границы. Типично чанки живут в Эдене недолго, быстро подвергаясь партицированию.
  • Overlapping store count, OSC — максимальное наложение чанков; максимальное количество чанков, накрывающих тот или иной ключ. Ограничивает сверху fan-in, т.е. количество чанков, которые фактически придётся прочитать для того, чтобы получить актуальное значение по ключу.
  • Flush, флаш — процесс сброса данных на диск, из dynamic store в чанк.
  • Compaction, компакшн, компактификация — процесс слияния чанков, в результате которого отбрасываются старые версии данных, удаляются delete tombstones и объединяются чанки малого размера.
  • Partitioning, партицирование — процесс, схожий с компакшном, однако основной его целью является не объединение чанков, а разбиение чанков из Эдена по различным партициям.
  • Фоновые процессы — общее название для flush+compaction+partitioning.
  • Write amplification, WA — отношение объёма данных, обработанных фоновыми процессами (compaction/partitioning), к объёму данных, записанных в таблицу. Служит одним из важных показателей эффективности подобранных параметров.
  • Версия строки — в модели MVCC по одному ключу может храниться много значений, каждое со своим timestamp-ом; отдельное значение называется версией. Вообще говоря, записи в каждую колонку могут версионироваться независимо, поэтому имеет смысл говорить о версиях значений, а не строки целиком, однако с точки зрения настройки компакшна обычно это не имеет значения.

Список атрибутов

Все значения с типом Duration указаны в миллисекундах. Все значения, обозначающие объём данных, указаны в байтах. Атрибуты, для которых отсутствует описание, имеют сложную семантику, и их не рекомендуется изменять.

Предупреждение

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

Пожалуйста, используйте только те атрибуты, смысл которых вы понимаете.

Замечание

Установка атрибута на примонтированную таблицу не влечёт применения настроек. Чтобы применить настройки, используйте команду remount-table:

CLI

yt set //path/to/table/@auto_compaction_period 86400000
yt remount-table //path/to/table

Узнать актуальные настройки таблета можно по пути //sys/tablets/x-x-x-x/orchid/config.

Flush

Перечисленные атрибуты регулируют поведение флаша.

Имя Тип По умолчанию Описание
dynamic_store_auto_flush_period Duration* 900 000 (15 min) Интервал времени для принудительного флаша: даже если dynamic store не переполнился за это время, он будет сброшен на диск вне очереди.
dynamic_store_flush_period_splay Duration 60 000 (1 min) Случайный сдвиг для периода, чтобы избежать синхронизации разных таблетов. Реальный флаш будет наступать спустя period + random(0, splay).
merge_rows_on_flush bool false Разрешает слияние версий и удаление строк по TTL при флаше.
merge_deletions_on_flush bool false Разрешает слияние последовательных удалений в одно при флаше.
max_dynamic_store_row_count int 1 000 000 Максимальное количество строк в dynamic store.
max_dynamic_store_pool_size int 1 073 741 824 (1 GB) Максимальный размер dynamic store.
dynamic_store_overflow_threshold double 0.7 Доля заполнения dynamic store относительно max_dynamic_store_row_count и max_dynamic_store_pool_size, при которой начинается флаш.

Compaction

Основные опции

Имя Тип По умолчанию Описание
auto_compaction_period int* - Интервал времени в мс для регулярной компактификации. Компактификация затронет каждый чанк таблицы по меньшей мере раз в auto_compaction_period.
auto_compaction_period_splay_ratio double 0.3 Случайный сдвиг для периода, чтобы избежать синхронизации. Компактификация будет наступать спустя period * (1 + random(0, splay_ratio)).
periodic_compaction_mode store, partition store Подробнее можно прочитать в разделе Регулярная компактификация.
forced_compaction_revision - - Подробнее можно прочитать в разделе Форсированная компактификация.
max_overlapping_store_count int 30 Максимальное допустимое число чанков, потенциально содержащих один ключ. При достижении порога запись в таблицу будет заблокирована, пока компактификация не оптимизирует структуру.
critical_overlapping_store_count int* - Устанавливает порог, при достижении которого в компактификации перестают учитываться ограничения на размеры чанков в одной порции. Установка этой опции в небольшие значения (5-10) позволяет уменьшить OSC и скорость доступа к данным ценой заметного увеличения write amplication.
enable_compaction_and_partitioning bool true Полностью отключает компактификацию. Запись в таблицу в таком состоянии быстро приведёт к превышению overlapping store count. Если запись не предполагается, вместо этого атрибута используйте монтирование таблицы в frozen состоянии.

Размеры и константы

Имя Тип По умолчанию Описание
min_partition_data_size int 96 MB Минимальный, желаемый и максимальный размер партиции.
desired_partition_data_size int 256 MB
max_partition_data_size int 320 MB
min_partitioning_data_size int 64 MB Минимальный и максимальный размер данных для одной порции партицирования. Увеличение позволяет снизить write amplification ценой повышения числа чанков в Эдене, а значит, увеличения overlapping store count.
max_partitioning_data_size int 1 GB
min_partitioning_store_count int 1 Минимальное и максимальное число чанков для одной порции партицирования.
max_partitioning_store_count int 5
min_compaction_store_count int 3 Минимальное и максимальное число чанков для одной порции компактификации. Periodic и forced компактификации игнорируют нижнюю оценку, но учитывают верхнюю.
max_compaction_store_count int 5
compaction_data_size_base int 16 MB
compaction_data_size_ratio double 2.0

Удаление старых данных

Подробно эти атрибуты описаны в разделе Удаление старых данных.

Имя Тип По умолчанию
min_data_ttl int 1 800 000 (30 min)
max_data_ttl int 1 800 000 (30 min)
min_data_versions int 1
max_data_versions int 1

Форсированная компактификация

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

Если выполнить remount-table, настройка применится сразу ко всем таблетам таблицы. При работе с таблицами размером от терабайта это может создать всплеск нагрузки как на бандл таблицы, так и на кластер целиком. Поэтому рекомендуется выполнять remount разных таблетов в разное время. Для этого используйте команду yt remount-table --first-tablet-index X --last-tablet-index Y. Время, в течение которого стоит перемонтировать всю таблицу, можно приблизительно посчитать по формуле table_data_weight / bundle_node_count / (100 Mb/s).

Чтобы отменить форсированную компактификацию, удалите атрибут forced_compaction_revision с таблицы и выполните remount-table.

Примечание

Если размер таблицы больше 20 Tb или 100 000 чанков, перед запуском форсированной компактификации получите разрешение администратора.

Регулярная компактификация

Чтобы все чанки таблицы регулярно компактифицировались независимо от того, идёт запись в таблицу или нет, используйте атрибут auto_compaction_period. У него есть два режима, регулируемых атрибутом periodic_compaction_mode:

  • store (значение по умолчанию): решение о компактификации каждого чанка, созданного ранее, чем now - auto_compaction_period, принимается независимо;
  • partition: если в партиции есть хотя бы один чанк, созданный ранее, чем now - auto_compaction_period, компактифицируются все чанки партиции одновременно.

Чтобы избежать синхронизации и усреднить нагрузку, при вычислении времени компактификации каждого конкретного чанка к auto_compaction_period добавляется случайное отклонение, доля которого задаётся атрибутом auto_compaction_period_splay_ratio.

Установка только атрибута periodic_compaction_mode недостаточна для включения регулярной компактификации. Для этого необходимо явно установить атрибут auto_compaction_period.

Выбор режима

  • Нужно удалять данные TTL: режим store (по умолчанию).
  • Нужно очищать строки, удалённые через delete-rows: режим partition.

Режим partition лучше подходит для очистки строк, удалённых через delete-rows, поскольку в случае store запись по некоторому ключу и соответствующий delete tombstone могут попасть в разные чанки, которые будут независимо компактифицироваться по очереди, и tombstone никогда не удалится.

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

Выбор периода

Чем больше период, тем меньше нагрузка на бандл. Предельно каждая нода бандла способна компактифицировать порядка 100-200 Mb/s. Как правило, нагрузка от регулярной компактификации не превосходит единиц Mb/s. Часто период соизмерим с max_data_ttl или в несколько раз меньше него.

Например, есть таблица размером 500 Gb и периодом компактификации в сутки, в бандле две ноды. Тогда нагрузка на одну ноду составит 500 Gb / 86400 сек / 2 ≃ 3.1 Mb/s, что допустимо.

Предупреждение

Первоначальная установка auto_compaction_period на таблицу с большим количеством старых чанков может привести к резкому запуску компактификации всех чанков. В этом случае стоит следовать тем же рекомендациям, что и для форсированной компактификации.

Сценарии

Удаление данных по TTL

Сценарий: на таблице установлен TTL, но размер увеличивается, как будто TTL не применяется.

Решение: используйте периодическую компактификацию.

Удаление короткоживущих ключей

Сценарий: строка записывается и через некоторое время удаляется. Требуется, чтобы delete tombstones не копились и место очищалось.

Решение: используйте периодическую компактификацию в режиме partition.

Много записей по одному ключу

Сценарий: по одному ключу бывает больше нескольких тысяч записей.

Проблема: в системе есть ограничение на количество версий одного ключа. Оно составляет порядка 30 000. Если его превысить, фоновые процессы будут завершаться ошибкой «Too many write timestamps in a versioned row».

Решение: Установите значение True атрибута merge_rows_on_flush и уменьшите TTL через атрибут min_data_ttl так, чтобы количество версий в пределах TTL было не более нескольких тысяч. Если по одному ключу делается много удалений, используйте merge_deletions_on_flush.

Устройство таблета и фоновых процессов

В этом разделе описано строение таблета. Оно не обязательно для прочтения, но облегчит понимание тем, кто захочет самостоятельно менять какие-то настройки.

Устройство

Таблет — часть таблицы, отвечающая за данные, находящиеся между двумя pivot-ключами. Типичный размер таблета составляет от 100 Mb (в случае таблиц в памяти) до единиц гигабайт, иногда достигая десятков гигабайт для особенно больших таблиц. С точки зрения мастер-сервера, таблет является набором чанков. Все описанные далее операции происходят на таблетной ноде, непосредственно обслуживающей таблет.

Подобно тому, как таблица разбита на таблеты, ограниченные pivot-ключами, таблет внутри разбит на партиции. Размер одной партиции — порядка 200 Mb (compressed size). Также есть особая партиция — Эден (Eden), границы которой совпадают с границами таблета. Каждый чанк относится либо к Эдену, либо к одной из партиций. Если чанк попадает целиком между граничными ключами некоторой партиции, то он относится к ней, иначе к Эдену.

Dynamic stores и сброс данных на диск

В таблете всегда есть как минимум один dynamic store — активный. При записи данные сначала попадают в dynamic store. Когда dynamic store становится слишком большим (сотни мегабайт) или на ноде заканчивается память категории «tablet dynamic», происходит ротация стора — создаётся новый активный стор, а старый становится пассивным. Это активирует процесс флаша, и со временем пассивный стор сбрасывается на диск.

Если флаш по какой-то причине не работает, данные копятся в памяти, и после переполнения запись начинает падать с ошибкой «Node is out of tablet memory, all writes disabled».

По умолчанию во время флаша в чанк сохраняются все версии строки. Чтобы применять очистку по TTL, следует использовать атрибут merge_rows_on_flush. Это имеет смысл делать, когда TTL меньше типичного времени жизни dynamic store (порядка 15 минут), а по одному ключу происходит много записей.

Партицирование

Чанк, сброшенный на диск, оказывается в Эдене. Как правило, он содержит ключи из всего диапазона таблета, поэтому не может быть отнесён к конкретной партиции. Процесс партицирования берёт один или несколько чанков из Эдена, сливает их и разбивает данные по партициям, помещая в каждую партицию по одному чанку. Если в Эдене много чанков небольшого размера, то сначала в нём запустится компактификация и их объединит.

Маленькие чанки делают систему менее эффективной. Первая причина — накладные расходы. Вторая — рост write amplification. Предположим, нода обслуживает несколько таблетов большой таблицы. Поскольку таблетов много, а память общая, размер dynamic store в каждом таблете будет составлять десятки мегабайт, что довольно немного. Если в таблете 100 партиций, то после партицирования в каждой из них размер чанка будет составлять меньше мегабайта. Если увеличить минимально допустимый размер Эдена, при котором запускается партицирование, то в партиции будут сбрасываться чанки бо́льшего размера ценой возросшего overlapping store count.

Компактификация

Процесс компактификации читает порцию чанков в одной партиции и объединяет их в один (реже — несколько), физически удаляя строки и старые версии. Компактификация может запускаться по нескольким причинам:

  • forced: на таблицу установлен атрибут forced_compaction_revision. В этом случае компактификации будут подвергнуты все чанки таблета, размер порции будет ограничен только значением max_compaction_store_count.
  • periodic: на таблицу установлен атрибут auto_compaction_period. Будут компактифицированы чанки, созданные ранее now - auto_compaction_period.
  • regular: обычный режим, запускающийся без внешнего воздействия.

Выбор чанков исходя из размера

В обычном режиме система выбирает чанки с учетом write amplification. Если в партиции есть чанк размером 100 Mb и периодически появляется чанк размера 1 Mb, то если каждый раз компактифицировать их вместе, будет получена амплификацию в 100 раз. Используются следующие правила:

  • в порции должно быть от min_compaction_store_count до max_compaction_store_count чанков, но чем больше, тем лучше;
  • отсортируем чанки по размеру. Каждый следующий чанк должен быть не более чем в compaction_data_size_ratio раз больше, чем сумма размеров предыдущих;
  • предыдущее правило не применяется, пока суммарный размер чанков меньше compaction_data_size_base.

Например, ratio = 2, base = 16 Mb. Тогда:

  • набор чанков размерами 1 Kb, 1 Mb, 10 Mb допустим: сумма не больше 16 Mb;
  • набор чанков размерами 10 Mb, 20 Mb, 50 Mb, 150 Mb допустим: 50 < 2 × (10 + 20), 150 < 2 × (10 + 20 + 50);
  • набор чанков размерами 1 Mb, 10 Mb, 100 Mb не допустим: 100 > 2 × (10 + 1).

Уменьшение base и ratio позволяет улучшить WA ценой увеличения OSC. В предельном случае, когда base = 1, а данные не удаляются, можно показать, что WA логарифмически зависит от объёма данных. Каждый раз, когда некоторая строка участвует в компактификации, размер содержащего её чанка увеличивается хотя бы в (1 + 1 / ratio) раз. Следовательно, всего строка поучаствует не более чем в log(tablet_size, 1 + 1 / ratio) компактификациях. На практике эта оценка неточна не только из-за удалений, но также из-за того, что при достижении порогового размера партиция разделяется на две.

Механизм очистки старых версий

Когда компактификация сливает очередную порцию чанков, она может отбросить старые версии некоторых строк и применить удаления. Однако это можно делать не всегда, поскольку компактификация рассматривает не все чанки в партиции, а только часть.

Рассмотрим таблицу с min/max_data_ttl = 0, min/max_data_versions = 1 (по каждому ключу необходимо хранить только самую свежую версию). Например, по некоторому ключу было две записи: {value = 1; timestamp = 10}, {value = 2; timestamp = 20}, и удаление с timestamp = 30.
Пусть эти версии попали в три разных чанка. Если компактификация рассмотрит только первый и третий чанк, в обработку попадут следующие данные:

delete tombstone {timestamp = 30}
{value = 1; timestamp = 10}

Если применить удаления и полностью отбросить эту строку, не записывая её в новый чанк, это приведёт к некорректному чтению: дальнейшие чтения прочитают значение {value = 2; timestamp = 20}, хотя строка была удалена.

Во избежание этой проблемы вычисляется major timestamp: минимальный timestamp данных во всех чанках данной партиции (и Эдене), не попавший в текущую порцию компакшна. После этого компактификация имеет право удалять только версии с timestamp < major timestamp.

Данная логика может приводить к тому, что устаревшие версии на самом деле не удаляются. Во-первых, если в таблет или партицию не идёт запись, то множество чанков в партиции стабилизируется и компатификация перестаёт запускаться, не имея возможности узнать об устаревании некоторых строк. Во-вторых, если в партиции есть один большой чанк и запись идёт не очень интенсивно, то вновь появляющиеся маленькие чанки будут компактифицироваться между собой, не затрагивая большой. Большой чанк накладывает ограничение на major timestamp, поэтому даже если ttl = 0, повторные версии в свежих чанках удаляться не будут. Бороться с этим можно при помощи auto_compaction_period.