---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.6
alternate:
  - https://ytsaurus.tech/docs/en/user-guide/dynamic-tables/compaction.md
  - https://ytsaurus.tech/docs/ru/user-guide/dynamic-tables/compaction.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ytsaurus.tech/docs/ru/llms.txt

<!-- source: ru/_includes/user-guide/dynamic-tables/compaction.md -->
# Фоновая компактификация

Таблет сортированной динамической таблицы представляет из себя [LSM-дерево](https://en.wikipedia.org/wiki/Log-structured_merge-tree). При записи строка сначала попадает в хранилище в памяти — dynamic store. По мере наполнения данные из dynamic store сбрасываются на диск в чанки. Фоновый процесс компактификации периодически объединяет несколько чанков. Это необходимо для:
- применения [delete tombstones](sorted-dynamic-tables#udalenie-stroki) и физического удаления строк;
- удаления старых версий;
- уменьшения overlapping store count — числа чанков, в которых может потребоваться искать некоторый фиксированный ключ.

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

## Глоссарий { #glossary }
- **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](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) по одному ключу может храниться много значений, каждое со своим timestamp-ом; отдельное значение называется версией. Вообще говоря, записи в каждую колонку могут версионироваться независимо, поэтому имеет смысл говорить о версиях *значений*, а не *строки* целиком, однако с точки зрения настройки компакшна обычно это не имеет значения.

## Список атрибутов { #attributes }

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

{% note warning "Предупреждение" %}

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

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

{% endnote %}

{% note info "Замечание" %}

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

CLI
```bash
yt set //path/to/table/@mount_config/merge_rows_on_flush %true
yt remount-table //path/to/table
```

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

{% endnote %}

### Flush { #flush_attributes }

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

| Имя | Тип | По умолчанию | Описание |
|--|--|--|--|
| 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 { #compaction_attributes }

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

| Имя | Тип | По умолчанию | Описание |
|--|--|--|--|
| auto_compaction_period | int*| - | Интервал времени в мс для регулярной компактификации. Компактификация затронет каждый чанк таблицы по меньшей мере раз в auto_compaction_period. Подробнее можно прочитать в разделе [Регулярная компактификация](#periodic_compaction). В большинстве случаев не рекомендуется к использованию. |
| auto_compaction_period_splay_ratio | double | 0.3 | Случайный сдвиг для периода, чтобы избежать синхронизации. Компактификация будет наступать спустя `period * (1 + random(0, splay_ratio))`. |
| periodic_compaction_mode | `store`, `partition` | `store` | Подробнее можно прочитать в разделе [Регулярная компактификация](#periodic_compaction). |
| forced_compaction_revision | - | - | Подробнее можно прочитать в разделе [Форсированная компактификация](#forced_compaction). |
| max_overlapping_store_count | int | 30 | Максимальное допустимое число чанков, потенциально содержащих один ключ. При достижении порога запись в таблицу будет заблокирована, пока компактификация не оптимизирует структуру. |
| critical_overlapping_store_count | int* | - | Устанавливает порог, при достижении которого в компактификации перестают учитываться [ограничения](#compaction_size_policy) на размеры чанков в одной порции. Установка этой опции в небольшие значения (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 |  |

### Удаление старых данных { #ttl_attributes }
Подробно эти атрибуты описаны в разделе [Удаление старых данных](https://ytsaurus.tech/docs/ru/user-guide/dynamic-tables/sorted-dynamic-tables.md#remove_old_data).

| Имя | Тип | По умолчанию |
|--|--|--|
| 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 }
Для принудительного запуска компактификации всех чанков таблицы установите значение атрибута `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`.

{% note info "Примечание" %}

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

{% endnote %}

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

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

Использовать регулярную компактификацию имеет смысл, если нужно удалять данные по TTL или применять удаления, вызванные delete-rows, а штатной компактификации оказывается недостаточно.

{% note warning "Предупреждение" %}

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

Не следует использовать регулярную компактификацию для экономии числа чанков. Нормальный размер чанков динамической таблицы составляет порядка 40-60 Mb (в терминах compressed size). Если чанки вашей таблицы подходят под этот размер, нет необходимости пытаться их укрупнять.

{% endnote %}

Для включения регулярной компактификации используется атрибут `auto_compaction_period`. Он задаёт время, в течение которого каждый чанк будет подвергнут компактификации. После выставления атрибута необходимо выполнить команду `remount-table` для применения настроек.

#### Режимы { #periodic_compaction_mode }

Имеется два режима периодической компактификации. Режим регулируется атрибутом `periodic_compaction_mode`.

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

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

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

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

#### Выбор периода { #periodic_compaction_period }
Рекомендуется выбирать период, соизмеримый с TTL таблицы. К примеру, если TTL составляет 7 дней, можно выставить `auto_compaction_period` в 3 дня. Это будет гарантировать, что строки гарантированно удалятся через 10 дней.

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

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

{% note warning "Предупреждение" %}

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

{% endnote %}

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

## Сценарии { #scenarios }

#### Удаление данных по TTL { #scenario_ttl }
**Сценарий**: на таблице установлен TTL, но размер увеличивается, как будто TTL не применяется.

**Решение**: используйте [периодическую компактификацию](#periodic_compaction).

#### Удаление короткоживущих ключей { #write_delete }
**Сценарий**: строка записывается и через некоторое время удаляется. Требуется, чтобы delete tombstones не копились и место очищалось.

**Решение**: используйте [периодическую компактификацию](#periodic_compaction) в режиме `partition`.

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

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

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

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

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


### Устройство { #tablet_description }

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

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

### Dynamic stores и сброс данных на диск { #flush }
В таблете всегда есть как минимум один 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 минут), а по одному ключу происходит много записей.


### Партицирование { #partitioning }

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

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

### Компактификация { #compaction }

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

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

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

В обычном режиме система выбирает чанки с учетом 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) компактификациях. На практике эта оценка неточна не только из-за удалений, но также из-за того, что при достижении порогового размера партиция разделяется на две.

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

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

Рассмотрим таблицу с 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`.
<!-- endsource: ru/_includes/user-guide/dynamic-tables/compaction.md -->