Фоновая компактификация
Таблет сортированной динамической таблицы представляет из себя 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
.