Ханки и их использование в динамических таблицах

Вступление

Ханковые чанки (hunk chunks) - это механизм, который позволяет хранить крупные строковые значения отдельно от самих строк таблицы. Если строковое значение превышает определенный порог длины, то в чанке оно заменяется на легкую ссылку, ведущую в чанк нового типа - ханковый. В ханковых чанках, в отличие от обычных, хранятся сырые неструктурированные данные: для чтения достаточно знать только смещение относительно начала чанка и длину. Такая конструкция открывает дополнительные возможности для оптимизации использования динамических таблиц:

  1. Позволяет держать лишь малую часть таблицы в памяти, сохраняя низкую латентность лукапов.
  2. Уменьшает write amplification за счет того, что ханковые чанки компактифицируются реже обычных.
  3. Для таблицы, не лежащей в памяти, уменьшает поток чтений с диска.
  4. Позволяет использовать erasure кодирование с низкими накладными расходами на erasure repair ханковых значений на лету.

Более подробно про оптимизации см. раздел "Сценарии использования".

Как понять, что стоит использовать ханки

Ханки могут оказаться полезны, если таблица содержит много крупных строковых значений.
Тем не менее даже в этом случае существует несколько причин, почему ханки могут не подойти:

  1. Таблица лежит на hdd - чтение ханков с диска сопровождается большим количеством параллельных мелкогранулярных обращений к носителю, hdd для такого не подходит.
  2. У данных в таблице хорошая степень сжатия (низкий compression ratio). Сейчас блоки в ханковых чанках не сжимаются, чтобы была возможность гранулярно читать с диска ханки, поэтому при переезде на ханки disk space таблицы может заметно вырасти. Это частично компенсируется выставлением hunk erasure codec. Также сейчас в тестовом режиме можно попробовать словарное сжатие ханковых значений, что должно помочь полностью избавиться от данной проблемы.
  3. К таблице применяется versioned remote copy (или скрипт по добавлению реплик к реплицированной таблице). Сейчас для таблиц с ханковыми чанками не поддержана возможность делать versioned remote copy. Тем не менее, в ручном режиме это возможно, см. FAQ.

Если размер строкового значения не больше, чем max_inline_hunk_size, то такое значение называется inline value, иначе - ref value. Ханковые чанки состоят из ref values.
Чтобы примерно оценить, какую долю таблицы составляют ref values с учетом разного max_inline_hunk_size , можно воспользоваться скриптом yt/yt/tools/hunk_chunk_advisor/hunk_chunk_advisor compute_weight.

Пример использования

./hunk_chunk_advisor compute_weight --proxy zeno --table-path //home/akozhikhov/table --sampling-rate 0.1 --result-file result_file --computation-path //home/akozhikhov/hunk_chunk_advisor

Скрипт запускает на кластере операции, с помощью которых сначала создается статическая таблица из динамической с возможностью семплирования строк, а затем для разных max_inline_hunk_size будет посчитана доля данных, являющаяся ref values.

Параметр скрипта Описание
proxy Название YT кластера
table-path Путь в Кипарисе до таблицы
sampling-rate Доля строк в таблице, по которой будет посчитана статистика (нужно, если таблица слишком большая, чтобы запустить по ней операцию)
result-file По этому локальному пути будет создан png файл с детализацией статистики
computation-path Путь в Кипарисе до директории, в которой будут лежать таблицы с промежуточными вычислениями. Рекомендуется создать новую поддиректорию внутри директории проекта, чтобы с одной стороны не нарушить acl, а с другой стороны ничего не замусорить в рабочей директории.

Включение ханков

Чтобы включить ханки, необходимо изменить схему таблицы, выставив на интересующие строковые колонки атрибут max_inline_hunk_size. Изменение в схеме происходит через отмонтирование таблицы.

{#slim_format}
Также рекомендуется использовать альтернативный формат чанков. Формат чанков по умолчанию @optimize_for=lookup может быть неэффективен с точки зрения uncompressed data size, особенно для таблиц с ханками, потому что в них хранится множество hunk refs, которые физически представленны как короткие строчки. Стоит либо выставить slim chunk format: @optimize_for=lookup и @chunk_format=table_versioned_slim, либо scan формат: @optimize_for=scan и @chunk_format=table_versioned_columnar. При использовании scan формата также скорее всего стоит выставить на все колонки схемы одинаковую column group. Подробнее про группы колонок можно найти в документации.

Для включения ханков на таблице можно воспользоваться скриптом, который лежит в yt/yt/experiments/public/hunkifier.

Подробнее про hunkifier

Пример использования: ./hunkifier --proxy zeno --table-path //home/akozhikhov/replica_table --max-inline-hunk-size 128

При запуске скрипта таблица будет отмонтирована, к ней будет применен schema alter с добавлением нужного атрибута, выставлены дополнительные настройки, а затем она будет примонтирована обратно.

Основные параметры:

Параметр скрипта Описание
proxy Название YT кластера
table-path Пусть в Кипарисе до таблицы
max-inline-hunk-size max_inline_hunk_size

Дополнительные параметры:

Параметр скрипта Описание
primary-medium Медиум, на котором будут расположены чанки (обычные и ханковые)
enable-crp Использовать на таблице consistent replica placement
max-hunk-compaction-garbage-ratio Атрибут max_hunk_compaction_garbage_ratio
hunk-erasure-codec Erasure codec для ханковых чанков
fragment-read-hedging-delay Задержка перед отправкой хеджирующего запроса чтения фрагментов
enable-slim-format Использовать на таблице slim формат чанков
columns Список колонок, на которые будет выставлен атрибут max_inline_hunk_size. Если опция не указана, то атрибут будет выставлен на все строковые колонки таблицы.

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

Чтобы быстро изменить структуру всех чанков в таблице, можно выполнить forced compaction, учитывая все предостережения, описанные в документации. Также существуют опции forced_store_compaction_revision и forced_hunk_compaction_revision для форсированной компактификации только обычных или только ханковых чанков.
Примечание: forced_hunk_compaction_revision не вызывает компакшн ханковых чанков самих по себе: алгоритм компакшна будет безусловно компактить лишь те ханковые чанки, на которые есть ссылка от обычных чанков, которые попали под критерии компакшена.

Сценарии использования

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

  1. Таблица в памяти занимает много tablet static.
    Обычно в таком случае важную роль играет низкая латентность на чтение таблицы.
    Если положить таблицу с включенными ханками в память, то в памяти окажутся лишь неханковые чанки. Благодаря этому можно существенно сэкономить tablet static, не сильно потеряв в latency читающих запросов.
    При этом если пользователь читает строчку, в которой нет ref values, то чтение по-прежнему будет из памяти. Если же в строчке есть ref values, то будет дополнительный расходы на чтение таких значений (поход по сети до data node и чтение этих значений с диска).
    Соответственно, при уменьшении max_inline_hunk_size доля чтений из памяти будет уменьшаться, как и размер требуемой tablet static памяти.
    Примечание: В случае высоконагруженных таблиц могут потребоваться дополнительные усилия по оптимизации, поэтому рекомендуется ознакомиться с разделом "Дальнейшая оптимизация".
  2. При записи таблицы возникает высокий write amplification.
    Как известно, LSM-структура динамической таблицы подразумевает, что поток записи на диск выше, чем изначальный поток пользовательской записи. Высокий поток и write amplification могут влиять на необходимое для обслуживания таблицы число дисков (с учетом DWPD разрешенный поток записи на диск оказывается сравнительного небольшим).
    При компактификации обычных чанков, содержащих ссылки на ханковые, не обязательно читать и модифицировать ханковые чанки. Поэтому после компактификации ханковые чанки могут частично состоять из устаревших значений, если некоторые из ref values в них остаются без ссылок. Это допущение позволяет пропускать чтение и перезапись ref values, снижая поток чтения и записи при компактификации.
    {#max_hunk_compaction_garbage_ratio}
    Опция, которая регулирует максимальное допустимую долю мусора в ханковых чанках - это max_hunk_compaction_garbage_ratio. Чем выше это значение, тем меньше будет write amplification, но тем выше у таблицы будет disk space (за счет того, что в ханковых чанках будет лежать больше устаревших значений).
  3. При чтении таблицы возникает высокая нагрузка на диск.
    При чтении с диска обычных чанков приходится читать целые блоки, которые как правило занимают пару сотен килобайт. В случае большого потока диски могут перегружаться, а кеш блоков может использоваться неэффективно. Чтение ханков же происходит с гранулярностью 4KB.
  4. У таблицы слишком высокий disk space.{#hunk_erasure_codec}
    В случае перегрузки или недоступности некоторых партов обычных чанков, erasure-восстановление может быть неэффективным и ресурсоемким. За счет оптимизаций в слое чтения ханковых чанков, этот процесс оказывается не настолько болезненным. Для ханковых чанков можно включить erasure кодирование, указав на таблице атрибут @hunk_erasure_codec. Для включения восстановления партов на лету см. раздел "Дальнейшая оптимизация". После изменения erasure codec на таблице можно узнать для каждого из кодеков, сколько чанков записано в нем, используя атрибут @erasure_statistics таблицы.

Дальнейшие оптимизации

С точки зрения дата ноды чтения ref values превращаются в чтения фрагментов, поэтому в данном разделе используется именно этот термин.

  1. Хеджирование запросов чтения фрагментов или восстановление на лету в случае erasure.{#hunk_hedging}
    Если запрос чтения с дата ноды выполняется слишком долго, то его стоит хеджировать. Для этого существует опция @hunk_chunk_reader/fragment_read_hedging_delay (значение в миллисекундах). Эта же опция отвечает за время, после которого запускается erasure repair парта в случае неответа от дата ноды.
  2. Батчинг запросов фрагментов.
    По умолчанию для каждого чанка таблицы выбираются три случайные дата ноды, на которых хранятся его реплики. Даже лукап одного ключа, как правило, порождает чтения из нескольких чанков, нечего говорить про ситуацию, когда совершается лукап нескольких ключей. Т.к. чтение каждого ref value - это чтение с диска с удаленной дата ноды, то возникает высокий fan-out запросов к подсистеме хранения данных. Это может быть критично для производительности, если поток запросов к таблице достаточно высок.
    В этом случае можно выставить на таблицу булев атрибут @enable_consistent_chunk_replica_placement. Тогда реплики чанков одного таблета в best-effort режиме будут назначаться одним и тем же дата нодам. Запросы чтения фрагментов к дата нодам батчуются. А на дата нодах, в свою очередь, батчуются запросы к диску. За счет этого в некоторых случается удается кратно уменьшить fan-out.
    Примечание: Данная опция может заметно влиять на слой хранения за счет особой нагрузки по репликации чанков. Стоит выставлять ее только после консультации с командой YT.
  3. Более эффективное чтение с диска.
    Чтение фрагментов вызывает множество мелкогранулярных чтений с диска. Чтобы они оставались эффективными и низколатентными, стоит для хранения чанков таблицы использоваться медиум с io_uring. Также для обхода кеша ОС можно читать с direct io, для чего на таблицу выставить булев атрибут @hunk_chunk_reader/use_direct_io (на некоторых медиумах чтения с direct io глобально выключены и этот атрибут не возымеет никакого эффекта).

Диагностика

Чтобы понять, какая часть чанков ханковая, и каков их размер, можно выполнить запрос yt get <table_cypress_path>/@chunk_format_statistics. Для получения более детальной статистики по структуре ханковых чанков можно выполнить запрос yt get <table_cypress_path>/@hunk_statistics. Оба эти запроса являются тяжелыми с точки зрения мастера и подходят лишь для единоразового ручного применения.

Также есть некоторые полезные атрибуты у чанков.

У всех чанков: @consistent_replica_placement_hash, @consistent_replica_placement.

У обычных чанков: @hunk_chunk_refs.

У ханковых чанков: @hunk_count и @total_hunk_length.

Подробнее про рефы ханковых чанков можно посмотреть в орхидее ноды: tablet_cells/<cell_id>/tablets/<tablet_id>/hunk_chunks/<chunk_id>

FAQ

  1. Что делать, если после включения ханков сильно вырос disk space?
    Может помочь тюнинг атрибута таблицы @max_hunk_compaction_garbage_ratio - это допустимая доля мусора (устаревших данных) в ханковых чанках. При превышении этого порога к ханковому чанку будет применена компактификация. По умолчанию равен 0.5. На некоторых таблицах в зависимости от нагрузки может быть полезно выставить в 0.15, 0.3. Недостаток в том, что при уменьшении этого значения растет write amplification компактификации.
  2. Что делать, если после включения ханков сильно вырос chunk count?
    Много маленьких чанков может порождаться при флаше dynamic store на диск. Тут может помочь тюнинг атрибута таблицы @max_hunk_compaction_size. Ко всем ханковым чанкам, размер которых меньше этого значения, будет применена компактификация. По умолчанию равен 8MB. На некоторых таблицах в зависимости от нагрузки может быть полезно выставить в 16MB, 32MB. Недостаток в том, что при увеличении этого значения растет write amplification компактификации.
  3. Как узнать реальную долю disk space, которая занята мусором в ханковых чанках?
    Можно прочитать атрибут таблицы @hunk_statistics и сравнить значения в полях total_hunk_length и total_referenced_hunk_length. Внимание: запрос данного атрибута является достаточно тяжелым для вычисления, его можно читать нечасто и только в ручном режиме.