Упорядоченные динамические таблицы
В данном разделе рассмотрено устройство упорядоченных динамических таблиц и описаны действия, которые можно выполнять с данным видом таблиц.
Модель данных
Упорядоченные динамические таблицы — это разновидность таблиц в YTsaurus, представляющая собой простую упорядоченную последовательность строк. Каждая строка упорядоченной таблицы состоит из колонок и подчинена схеме таблицы, указываемой при создании. Ключевые колонки в таких таблицах отсутствуют.
Упорядоченные динамические таблицы поддерживают добавление новых строк в конец в рамках транзакции, а также чтение строк по их индексам без транзакционной изоляции.
Ближайшим аналогом упорядоченных динамических таблиц является Apache Kafka.
Как и в случае сортированных динамических таблиц, пространство ключей упорядоченной динамической таблицы делится на таблеты.
Разделение данных на таблеты произвольно. Каждый таблет содержит упорядоченную последовательность строк таблицы. При записи строк в таблет они попадают в конец этой последовательности. Тем самым упорядоченность гарантируется лишь в пределах таблета.
Упорядоченная динамическая таблица исходно состоит из одного таблета. Множество таблетов можно менять с помощью команды reshard_table
. Она позволяет изменить структуру для набора таблетов, идущих подряд. В упорядоченных динамических таблицах при перешардировании указывается новое количество таблетов, на которые следует заменить исходные. Указание pivot_keys
не требуется. Существующие данные перераспределяются между новыми таблетами неспецифицированным образом.
Поддерживаемые операции
Создание
Для создания динамической упорядоченной таблицы необходимо выполнить команду create table
, указав в атрибутах схему и настройку dynamic=True
. Схема должна соответствовать схеме упорядоченной таблицы, в частности, среди её колонок не должно быть ключевых.
yt create table //path/to/table --attributes \
'{dynamic=%true;schema=[{name=first_name;type=string};{name=last_name;type=string}]}'
Запись строк
Для записи в упорядоченную динамическую таблицу служит команда insert_rows
. По умолчанию данные записываются в случайный замонтированный таблет. Для того чтобы управлять распределением данных в ручном режиме, можно в записываемых строках использовать специальную системную колонку $tablet_index
, имеющую тип int64
. Значения в этой колонке должны представлять собой числа от 0 до N - 1, где N — число таблетов в таблице. Соответствующие строки будут записаны строго в указанный таблет. Строки, для которых такая аннотация отсутствует, будут записаны в случайный примонтированный таблет.
Запись, произведённая в рамках транзакции, транзакционна: если транзакция завершается успешно, то строки появляются в соответствующих таблицах, если неудачно, то не появляются. При этом в рамках одной транзакции можно работать как с сортированными, так и с упорядоченными таблицами.
Чтение строк
Читать данные из упорядоченных таблиц можно с помощью SQL-подобного языка запросов и команды select_rows
. При этом каждая упорядоченная динамическая таблица предстаёт как сортированная с системными ключевыми колонками ($tablet_index, $row_index)
(оба типа int64
), а также всеми указанными в схеме таблицы колонками данных.
Например, так выглядит запрос, читающий диапазон строк из фиксированного таблета упорядоченной таблицы:
* from [//path/to/table] where [$tablet_index] = 10 and [$row_index] between 100 and 200
Изменение схемы и типа таблицы
Изменить схему уже существующей динамической таблицы можно с помощью команды alter-table
. Для успешного выполнения команды таблица должна быть отмонтирована, а новая схема должна быть совместима со старой. При этом никаких изменений в записанных на диск данных не происходит, поскольку старые данные удовлетворяют новой схеме.
С помощью alter-table
можно превратить упорядоченную динамическую таблицу в статическую и обратно. Подробности можно узнать в разделе MapReduce по динамическим таблицам.
Trim
Внимание
Использование reshard вместе с trim запрещено, так как может приводить к непредвиденным последствиям.
В общем случае из упорядоченной динамической таблицы нельзя удалить данные. Но существует исключение: в каждом таблете можно удалить начальный отрезок строк. Для этого необходимо использовать команду trim_rows
. В качестве аргументов ей передаётся путь к таблице, номер таблета, а также аргумент trimmed_row_count
, показывающий сколько строк в таблице будет удалено после выполнения данной команды. При таком удалении нумерация строк сохраняется. Например, при первом вызове для trimmed_row_count = 10
будут удалены строки с номерами с 0 по 9 включительно. Затем при вызове с trimmed_row_count = 30
— строки с 10 по 29 включительно и т. д. trimmed_row_count
имеет не относительный смысл, а абсолютный и указывает не количество строк, которые будут дополнительно удалены при очередном вызове, а какие именно начальные строки будут удалены после вызова.
Команда trim_rows
выполняется вне транзакций. После того как она отработала, удалённые данные уже нельзя прочитать командой select_rows
. Как только оказывается, что в таблете удалено столько строк, что они образуют целый начальный чанк, узел кластера, обслуживающий таблет, посылает сигнал мастер-серверу, и данный чанк удаляется целиком. Именно в этот момент освобождается дисковое пространство.
Количество удалённых строк в любом таблете можно узнать из атрибута trimmed_row_count
таблета. Данный параметр обновляется асинхронно, то есть между выполнением trim_rows
и его изменением может пройти некоторое время.
При отмонтировании и последующем монтировании таблета число начальных удалённых строк сохраняется, что гарантирует неизменность нумерации.
Важно
При превращении динамической таблицы в статическую с помощью команды alter_table
информация о том, какие строки были удалены, теряется. Также теряется и порядок следования строк в таблице. Порядок строк внутри чанков остаётся неизменным, но на это сложно опираться. Действительно, у динамической таблицы удалёнными могут быть помечены некоторые начальные строки некоторых чанков, и эта информация не может быть сохранена при превращении таблицы в статическую. В итоге получается, что при конвертации динамической таблицы в статическую возможно появление в таблице некоторых из ранее удалённых строк.
Решардирование
Если при шардировании число таблетов увеличивается, то существующие таблеты не меняются, а новые таблеты создаются пустыми. Если же при решардировании требуется уменьшить число таблетов, таблеты в конце диапазона склеиваются с последним из результирующего списка таблетов, который не входит в диапазон.
При этом система старается сохранить инвариант «то, что удалено, более не становится доступно». Поскольку при решардировании система добавляет чанки из удаляемых таблетов в конец последнего сохраняющегося, то при наличии удалённых строк в удаляемых таблетах решардирование затруднено. Для успешного завершения решардирования требуется, чтобы в удаляемых таблетах удалённые строки образовывали префикс по чанкам.
Практически данное ограничение сложно соблюдать, поэтому можно считать, что решардирование с уменьшением числа таблетов невозможно, если в удаляемых таблетах есть удалённые строки. Чтобы обойти указанное ограничение, можно воспользоваться двумя вызовами alter_table
, превратив таблицу сначала в статическую, а потом в динамическую. Но важно помнить, что при таком способе в таблице вновь станут видны некоторые из удалённых строк.
Автоматическое удаление старых строк (TTL)
К данным упорядоченных таблиц применяются те же настройки удаления старых данных, что и у сортированных таблиц. Подробнее можно прочитать в разделе Удаление старых данных. Существенное отличие состоит в том, что в упорядоченных таблицах у строки всегда есть только одна версия. Тогда настройки по очистке можно интерпретировать следующим образом:
- если
min_data_versions > 0
(по умолчанию значение 1), то автоматического удаления не происходит; - система YTsaurus не удаляет строки, записанные менее чем за
min_data_ttl
до текущего момента; - если
max_data_versions = 0
, можно удалять строки, записанные позднееmin_data_ttl
; - если
max_data_versions > 0
, можно удалять строки, записанные позднееmax_data_ttl
.
Примечание
Автоматическое удаление (trim) применяется сразу ко всему чанку. Пока в чанке есть строки, которые нельзя удалить, все строки из чанка будут доступны.
Колонка $timestamp
В схеме упорядоченной динамической таблицы можно указать специальную системную колонку $timestamp
типа uint64
. Значение в данной колонке формируется системой автоматически при записи, оно равно commit timestamp для транзакции, в которой строки оказались добавлены в таблицу.
Колонка $cumulative_data_weight
В схеме упорядоченной динамической таблицы можно указать специальную системную колонку $cumulative_data_weight
типа int64
. Значение в данной колонке формируется автоматически при записи. Оно равно суммарному логическому весу строк в байтах в таблете, считая от начальной строки с индексом ноль до текущей, включительно. Вес самой колонки $cumulative_data_weight
также учитывается в этом значении.
При добавлении этой колонки в схему уже существующей таблицы (через отмонтирование и запрос alter_table
), начальное значение $cumulative_data_weight
берётся из метаданных чанков таблицы.
Видимость изменений, strong/weak commit ordering
Уровень консистентности упорядоченных динамических таблиц принципиально ниже такового для сортированных: закоммиченные данные в общем случае могут быть видны не сразу после коммита, а также могут добавиться не в том порядке, в котором коммиты происходили.
При использовании распределённого коммита — когда его участниками оказываются несколько таблет-селлов — оказывается, что непосредственное появление данных в таблетах различных таблет-селлов происходит в различные моменты времени. Действительно, вторая фаза протокола (коммит) выполняется физически распределёнными участниками. Более того: один и тот же таблет-селл может быть участником нескольких распределённых транзакций, причём значения их commit-ts
и фактическая последовательность, в которой данный таблет-селл выполняет коммит, в общем случае не подчинены никаким условиям. Участник может выполнить коммит транзакции A
до коммита транзакции B
даже когда commit-ts(A) > commit-ts(B)
.
В случае динамических сортированных таблиц указанная деталь реализации скрыта от пользователя snapshot-изоляцией: несмотря на то что данные из транзакции A
уже добавлены в таблицу, прочитать их можно, лишь заказав ts
, не меньший commit-ts(A)
. Кроме того, система блокировок строк гарантирует, что подобная инверсия не будет наблюдаться на одном и том же ключе в случае, когда интервалы [start-ts,commit-ts]
для транзакций пересекаются.
Для динамических упорядоченных таблиц ситуация иная: обычно они не хранят commit timestamps, исключая случай поля $timestamp
, и не поддерживают snapshot-изоляцию. Поэтому порядок, в котором записи будут добавляться в конец упорядоченной динамической таблицы, не имеет ничего общего с порядком на commit timestamps.
В случае, когда упорядоченная динамическая таблица участвует в двухфазном коммите, подтверждение успешного коммита от координатора не значит, что все участники также произвели коммит и данные действительно добавлены в конец таблицы (двухфазный коммит — это коммит, который задевает более одного таблет-селла; таковы, например, все коммиты, в которых упорядоченная динамическая таблица является синхронной репликой). Попытка найти закоммиченные строки на участнике сразу после успешного окончания двухфазного коммита может закончиться неудачей: эти строки станут видны лишь через некоторое время.
Можно получить определённые гарантии монотонности для добавленных строк. У таблицы есть системный атрибут commit_ordering
, который управляет порядком занесения строк в таблицу:
weak
— режим по умолчанию, строки попадают в упорядоченную таблицу сразу в момент коммита участника, который запаздывает относительно координатора, порядок относительно commit timestamps не гарантируется;strong
— гарантируется, что строки попадают в таблицу в порядке commit timestamps.
В режиме strong
при наличии поля $timestamp
таблица оказывается упорядоченной по этому полю. Обратите внимание — это не делает данное поле ключевым.
Режим strong
реализован следующим образом: каждый таблет-селл отслеживает специальный barrier-ts
, который постоянно монотонно возрастает и про который известно, что ни одна транзакция не может получить commit-ts
меньше barrier-ts
. Строки, записанные в рамках транзакции в упорядоченную динамическую таблицу с режимом strong
, попадают в неё не сразу в момент коммита, когда barrier-ts
становится больше commit-ts
данной транзакции. Тем самым система сериализует все транзакции по commit-ts
, но только те, для которых commit-ts < barrier-ts
. Для транзакций с commit-ts > barrier-ts
система может определить относительный порядок, но не может гарантировать, что в будущем не появится новой транзакции, которая нарушит установленный порядок.
Для статических таблиц атрибут commit_ordering
также присутствует, но он всегда равен weak
.