Skip to content

picodata nuke для удаления репликасета без ребалансировки

  • #1353 (closed) picodata expel должна работать только на инстансах в которых current grade = offline и которые не являются последними или единственными инстансами в репликасете.

  • #1353 (closed) Если инстанс онлайн, то удалить его можно с помощью picodata expel --force. при этом произойдёт ребаланс бакетов на другие инстансы.


picodata nuke

Если инстанс офлайн, и он последний в репликасете, т.е. его удаление приведёт к потере бакетов, т.к. перебалансировать их невозможно, то expel должна отказываться от удаления такого инстанса. удалить его можно только с помощью picodata nuke.

При этом picodata nuke не должна удалять инстансы, которые можно удалить с помощью expel - online инстансы, или инстансы не являющиеся последними в репликасете. picodata nuke это последняя мера и должна использоватьса администратором только в ситуцаии, когда данные потеряны и восстановить их, например из бэкапа, невозможно.


тесты на nuke

Перенесено из #1105 (closed):

При общих ограничениях указанных в #1100 expel/nuke должны работать при наличии оффлайн инстансов либо незавершённого DDL.

  • #1100 Сценарий:
  1. создать кластер из 5 узлов rf=1
  2. опустить 2 узла
  3. nuke на опущенные узлы, по одному
  • #1100 Сценарий 4:
  1. Создать кластер из 6 узлов, rf=2

  2. опустить 1 репликасет целиком.

  3. Удалить 1 из узлов с помощью expel

  4. Удалить второй узел с помощью nuke.

    Сценарий 5, 6, 7, 8: то же самое что 1, 2, 3, 4 но выполнить restart оставшихся узлов кластера перед шагом nuke/expel.


Более детально

Операция expel/nuke реализована как CLI команда, которая посылает RPC запрос в хранимку .proc_expel_redirect на один из инстансов кластера (не обязательно лидер), а она в свою очередь редиректит запрос на лидера в хранимку .proc_expel. Это делается, потому что сама CLI команда не имеет локального доступа к системным таблицам и рафт машинерии. Соответственно роль "клиента" в данном случае выполняет .proc_expel_redirect а "сервера" .proc_expel.

Проверка предусловий выполняется на сервере (proc_expel), т.к. на нём самая актуальная информация. Операция expel/nuke может длиться долго, поэтому необходимо ожидать её конца. Ожидание происходит на клиенте (proc_expel_redirect), чтобы не забивать iproto fiber-pool лидера.

Описание предусловий и критериев завершения разных версий операции приведено ниже. Детали реализации предстоит выяснить во время реализации, но ясно что логику выполнять будет governor_loop.

Обозначения:

is_master(instance) === replicaset.target_master_name = instance OR replicaset.current_master_name = instance

picodata expel [--soft]

  • [лидер] Если target_instance.target_state = Expelled, возвращаем Ок (идемпотентность)
  • [лидер] Если target_instance.state != Offline -> Offline, возвращаем ошибку (попытка удалить живой инстанс)
  • [лидер] Если is_master(target_instance), возвращаем ошибку (попытка удалить последнюю реплику в шарде)
  • [лидер] Иначе
    • SET target_instance.target_state TO Expelled
    • возвращаем Ok
  • [клиент] ожидает target_instance.current_state = Expelled

picodata expel --force

  • [лидер] Если target_instance.target_state = Expelled, возвращаем Ок (идемпотентность)
  • [лидер] Если target_instance.target_state = Offline AND is_master(target_instance), возвращаем ошибку (попытка удалить последнюю реплику в шарде, без возможности перекатить данные)
  • [лидер] Если target_instance.target_state != Offline AND is_master(target_instance), Ok ->
    • SET replicaset.state TO ToBeExpelled (дальше будет происходить ребалансировка данных)
    • AND SET target_instance.target_state TO Expelled
  • [лидер] Иначе
    • SET target_instance.target_state TO Expelled
    • возвращаем Ok
  • [клиент] ожидает target_instance.current_state = Expelled (т.к. смена стейтов репликасета и инстанса произойдут одновременно)

При этом в случае replicaset.state = ToBeExpelled в какой-то момент последняя реплика может уйти в Offline (например потеря связи). При этом ребалансировка заблокируется, пока инстанс не вернётся. picodata expel --force вернёт error timeout

picodata nuke

  • [лидер] Если replicaset.state = ToBeNuked | Expelled, возвращаем Ок (идемпотентность)
  • [лидер] Если target_instance.state = Offline -> Offline, возвращаем ошибку (попытка удалить лежачий инстанс, для этого подходит обычный picodata expel [--soft])
  • [лидер] Если NOT is_master(target_instance), возвращаем ошибку (попытка удалить не последнюю реплику, для этого подходит picodata expel --force)
  • [лидер] Иначе Ok ->
    • SET replicaset.state TO ToBeNuked (дальше будет происходить создание пустых бакетов на других шардах)
    • AND SET target_instance.target_state TO Expelled
  • [клиент] ожидает replicaset.state = Expelled (т.к. сначала будет удалён инстанс, а потом будут воссозданы бакеты)

governor_loop: nuke

Шаг выполняющий логику nuke выполняется после expel инстанса, т.к. мы хотим избежать ситуации, когда бакеты воссозданы, но после этого возвращается инстанс, которого мы хотели удалить.

Предпосылка этого шага -- наличие replicaset у которого стейт ToBeNuked.

Действия губернатора:

  • отключить ребалансировку на этом тире
  • узнать количество живых бакетов на оставшихся репликасетах
  • создать нужное количество бакетов (total_count - existing_count) на остальных репликасетах
  • в SET replicaset.state TO expelled

step 1 RPC stop rebalancer + get active bucket_ids

Предлагаю отключение ребалансировки и сбор количества бакетов выполнять в рамках одной хранимки условно .proc_bucket_restoration_step_1. Как говорилось выше, она исполняется на мастере репликасета.

  • Первым делом нужно остановить vshard.rebalancer. На данный момент мы не выбираем, где выполняется ребалансер, то есть он может быть не на мастере репликасета. Соответственно мастер сначала проверяет нет ли ребалансера на нём (vshard.storage.internal.rebalancer_fiber ~= nil), если нет, то посылает RPC на всех своих реплик, чтобы те выключили ребалансер если он у них. Остановка ребалансера делается вызовом vshard.storage.rebalancer_disable.
  • После этого мастер ждёт, пока все его бакеты (спейс _bucket) не будут иметь статус ACTIVE (то есть ребалансировка закончилась).
  • Возвращаем в ответ на RPC количество бакетов в нашем спейсе _bucket. (см. костыль в proc_wait_bucket_count).

Ответ на первый RPC губернатор получит только когда ребалансировка остановится. Если она заканчивается долго, на губернаторе случится timeout и он пойдёт на следующую итерацию цикла где скорее всего повторно попытается выполнить воссоздание бакетов.

step 2 RPC bucket_force_create + restart rebalancer

Когда количество бакетов везде известно, губерантор вычисляет количество бакетов которое нужно воссоздать и посылает на мастеров второй RPC условно .proc_bucket_restoration_step_2, в котором мастер вызывает vshard.storage.bucket_force_create.

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

1..100, 200..350, 400..410, 600..700, 703..707

Воссоздать эти бакеты получится минимум 5ю вызовами vshard.storage.bucket_force_create.

Также нужно иметь ввиду, что .proc_bucket_restoration_step_2 тоже должна быть идемпотентна, поэтому должна проверять не созданы ли уже те бакеты, которые требуется. Если уже все созданы, эту часть запрос пропускаем.

После того как бакеты воссозданы нужно опять запустить ребалансера, так же как останавливали, только наоборот. Включение остановленного ребалансера делается вызовом vshard.storage.rebalancer_enable.

step 3 CAS replicaset.state = Expelled

Получив положительный ответ на второй RPC губернатор наконец выполняет cas операцию по смене стейта репликасета.

Edited by Georgy Moshkin
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information