Skip to content
Snippets Groups Projects

adr: TRUNCATE sql command

Merged Emir Vildanov requested to merge vildanov/adr-truncate into master
1 file
+ 122
0
Compare changes
  • Side-by-side
  • Inline
+ 122
0
status: accepted
decision-makers: @kostja, @darthunix
consulted: @gmoshkin, @gerold103
--------------------------------
# Считать ли TRUNCATE DDL-запросом (добавлять ли соответствуюущую запись в raft-журнал)? Как должен выглядеть механизм его исполнения?
## Описание запроса.
`TRUNCATE t` -- это SQL-запрос, который очищает всё содержимое таблицы (по сути `DELETE FROM t`).
* Он не вызывает `on_replace` тригеры Тарантула (в отличие от INSERT, UPDATE и DELETE).
* Тарантул относит этот запрос к категории schema-change* (DDL), а не data-change (DML) (см.
[документацию SQL](https://www.tarantool.io/en/doc/latest/reference/reference_sql/sql_statements_and_clauses/#truncate)).
* При вызове на сторадже функции `box.space.truncate`, она исполняется в фоне, не блокируя последующие вызовы
(см. [документацию space-api](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/truncate/#lua-function.space_object.truncate))
*Хотя у меня исполнение запроса номер схемы не меняет. Это так, потому что в нашем форке мы сделали эту операцию
не schema-change?
## Разница между исполнением DDL и DML-запросов.
Исполнение DDL-запроса добавляет запись в raft-журнал и увеличивает номер схемы. Консистентно меняя состояние всего кластера.
Логика исполнения DML-запроса зависит от того, с какой таблицей мы работаем:
* Запрос над глобальными таблицами выполняется через raft-журнал в виде CaS DML-опкодов (так же консистентно меняя
состояние кластера, как в случае с DDL-запросами).
* Для шардированных таблиц выполняется vshard-функция replicaset::callrw. Сейчас идёт переход на новую, более консистентную
функцию `router_map_callrw`*, поэтому будем дальше ссылаться на неё. В эту функцию передаётся сгенерированный
ExecutionPlan, который на стороджах трансформируется в транзакционный вызов функций space-api либо в исполнение
локального SQL. DML-запросы над таблицей t с узла-координатора** на стораджи отправляются с указанием table_version,
которая была прочитана на координаторе на момент вызова `router_map_callrw`. Любой DDL-запрос, затрагивающий таблицу t,
после успешного исполнения увеличивает table_version этой таблицы (в системной таблице _pico_table на каждом инстансе).
В случае, если между отправкой DML-запроса с узла-координатора и исполнением этого запроса на стороджах над этой таблицей
был исполнен DDL-запрос (мы заметим, что отправленная через `router_map_callrw` версия таблицы отличается от той, что мы
прочитали локально), DML-запрос исполнен не будет и мы получим ошибку.
*Функция `router_map_callrw` до исполнения пишушего запроса на стораджах рефает (ref) все затрагиваемые стороджа. См.
[комментарии](https://github.com/tarantool/vshard/blob/60a7089eb4a4ddcb33d33895811d6662f8ace353/vshard/router/init.lua#L1119)
к функции. Делает она это для того, чтобы убедиться в отсутствии ребалансировки во время пишущего запроса.
Иначе мы бы могли столкнуться с **НЕ**применением пишущих запросов к данным таблиц переезжающих с одного стораджа на другой.
**Координатор -- инстанс, который принимает пользовательский SQL-запрос. Координатор необязательно является raft-лидером.
## Контекст и описание проблемы
[Gitlab issue](https://git.picodata.io/picodata/picodata/picodata/-/issues/927).
Для исполнения TRUNCATE-запроса на стороджах необходимо вызвать метод `box.space.truncate`.
Вопрос в том, как исполнить эту операцию наиболее консистентно распределённо. Хочется,
чтобы не приводило к аномалиям параллельное исполнение следующих процессов:
0. сам TRUNCATE-запрос
1. исполнение DDL-запросов (например, DROP/CREATE TABLE)
2. ребалансинг бакетов
Например, хочется избежать следующей ABA-проблемы:
1.1. Исполняется CREATE TABLE t
1.2. Таблица наполняется данными (через INSERT)
1.3. Исполнение TRUNCATE t начинается на узле-координаторе
1.4. Исполняется DROP TABLE t
1.5. Ещё раз исполняется CREATE TABLE t
1.6. Ещё раз таблица наполняется данными (через INSERT)
1.7. До вершин кластера доходит TRUNCATE t, которая исполняется на новой таблице
Хочется также избежать следующего аномального сценария с ребалансировкой:
2.1. Начинается vshard-ребалансировка бакетов. Данные с одних инстансов начинают переезжать на другие
(инстанс удаляет у себя данные таблицы и отправляет запрос, пересылающий эти данные на другой инстанс)
2.2. Исполнение TRUNCATE t начинается на узле-координаторе
2.3. До вершин кластера доходит TRUNCATE t, затирая все данные
2.4. До таблиц на инстансах доходят данные, переехавшие из-за ребалансинга
2.5. Получается, что TRUNCATE исполнился, а в таблицах всё равно остались данные
Отметим, что функция `router_map_callrw` и логика, сейчас реализованная при исполнении Dml-запросов на стороджах,
отгораживают нас от этих аномалий.
## Варианты решения
1. Считать TRUNCATE Dml-запросом и исполнять его так же, как сейчас исполняются все остальные Dml-запросы:
* Для шардированных таблиц -- через `router_map_callrw`
* Для глобальных -- через рафт-журнал с кастомным вызовом `box.truncate` на лидере и фоловерах при вызове `advance`
Аномальные сценарии выше нам не страшны:
* Для шаридрованных таблиц `router_map_callrw` обрабатывает аномалии как описано выше
* Все операции с глобальными таблицами идут через рафт-журнал в упорядоченном виде + для них не происходит ребалансировки
В нынешней архитектуре этот вариант считаю самым лёгким в реализации.
Все остальные варианты решения будут предполагать, что TRUCATE -- это DDL-запрос. Предлагаю рассматривать их с точки зрения "Чем они лучше варианта 1, какой профит мы получим и в чём проиграем?".
Для DDL-запросов общий сценарий работы с рафт-журналом и обработки опкода сейчас такой:
* Добавляется DdlPrepare-опкод в рафт-журнал на лидере
* Собирается кворум от фолловеров
* raft-main-loop (`advance` -> `handle_committed_entries` -> `handle_committed_normal_entry`) обрабатывает DdlPrepare.
Выполняет специфичную для опкода работу (например для CreateTable создаёт записи в _pico_table и _pico_index) и
создаёт запись в _pico_property с PendingSchemaChange (это значит, что операция начала применяться).
* Губернатор видит PendingSchemaChange и через `proc_apply_schema_change` посылает всем мастерам запрос на применение этого
изменения. Получив, положительный ответ, добавляет в raft-журнал DdlCommit-опкод (и DdlAbort, если ответ отрицательный).
* raft-main-loop обрабатывает DdlCommit. Удаляет PendingSchemaChange, зовёт `apply_schema_change`, выставляет флаг `is_operable`
в true (предполагается, что Dml-запросы к таблице с невыставленным флагом не будут обрабатываться).
2. В функциях `handle_committed_normal_entry` и `apply_schema_change` добавить логику для обработки нового TRUNCATE DDL-опкода.
При такой реализации мы,кажется, не избежим аномалий с ребалансировкой (см. [тикет](https://git.picodata.io/core/picodata/-/issues/1091)).
3. Конкретно для TRUNCATE* изменить логику таким образом, чтобы вместо `proc_apply_schema_change` губернатор звал
`router_map_callrw`. В случае, если этот запрос вернул ошибку, предлагается ретраить его до тех пор, пока он не удастся. Имеет
ли смысл решать аномалию с балансировкой только для TRUNCATE?
4. Для всех DDL-опкодов (в том числе для добавленного TRUNCATE) изменить вызов `proc_apply_schema_change` через наш кастомный
ConnectionPool на вызов `router_map_callrw`. Так мы привнесём больше Lua-соединений в наш код, хотя планируем от него отказаться.
5. Вариант 2 с дополнением. Можно перед исполнением любой DDL-операции вручную останавливать ребалансинг, дёргая
ручку `vshard.storage.rebalancer_disable` на каждом из мастеров. А при завершении обработки позвать
`vshard.storage.rebalancer_enable`.
6. Вариант 2 с дополнением. Переписать `router_map_callrw` на Rust, чтобы он ходил через наш ConnectionPool.
## Выбранное решение
В качестве решения был выбрал вариант ...TODO, потому что ...TODO.
### Последствия
У выбранного решения есть:
* Плюсы: ...TODO
* И минусы: ...TODO
### Результаты
После реализации данного ADR у пользователей появиться возможность исполнять TRUNCATE-запросы.
Для проверки отсутствия при исполнении описанных выше аномалий нужно будет написать на это тесты. ...TODO
\ No newline at end of file
Loading