From c05cb21aa7ded868a82c8dcbcd582dbc516af0cd Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov <yaroslav.dynnikov@gmail.com> Date: Mon, 7 Aug 2023 13:33:35 +0300 Subject: [PATCH] doc: remove unnecessary docs Most of them are already moved to the site repo: https://git.picodata.io/picodata/picodata/docs/-/tree/97c3e35ec/docs - `clustering.md` -> https://docs.picodata.io/picodata/clustering/ - `glossary.md` -> https://docs.picodata.io/picodata/glossary/ - `cli.md` -> https://docs.picodata.io/picodata/cli/ - `discovery.md` -> https://docs.picodata.io/picodata/discovery/ - `sharding.md` -> /dev/null - `topology.md` -> https://docs.picodata.io/picodata/deploy/ --- docs/cli.md | 97 -------- docs/cluster_group.svg | Bin 139926 -> 0 bytes docs/clustering.md | 457 ------------------------------------- docs/clustering.svg | Bin 34416 -> 0 bytes docs/clustering_curves.svg | Bin 112921 -> 0 bytes docs/discovery.md | 92 -------- docs/fsm.svg | Bin 39823 -> 0 bytes docs/glossary.md | 248 -------------------- docs/sharding.md | 68 ------ docs/topology.md | 247 -------------------- 10 files changed, 1209 deletions(-) delete mode 100644 docs/cli.md delete mode 100644 docs/cluster_group.svg delete mode 100644 docs/clustering.md delete mode 100644 docs/clustering.svg delete mode 100644 docs/clustering_curves.svg delete mode 100644 docs/discovery.md delete mode 100644 docs/fsm.svg delete mode 100644 docs/glossary.md delete mode 100644 docs/sharding.md delete mode 100644 docs/topology.md diff --git a/docs/cli.md b/docs/cli.md deleted file mode 100644 index 542a761311..0000000000 --- a/docs/cli.md +++ /dev/null @@ -1,97 +0,0 @@ -# ОпиÑание вÑтроенных команд Picodata - -## Общие ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ - -ИÑполнÑемый файл `picodata` может не только запуÑкать инÑтанÑÑ‹, но и -выÑтупать в роли конÑольной утилиты. Ð”Ð°Ð½Ð½Ð°Ñ ÑƒÑ‚Ð¸Ð»Ð¸Ñ‚Ð° позволÑет запуÑкать -дополнительные и Ñлужебные команды, не учаÑтвующие в клаÑтерном -взаимодейÑтвии. - -Общий формат запуÑка: - -```bash -picodata <command> [<params>] -``` -Ðиже опиÑанные доÑтупные команды `picodata`. - -## picodata tarantool - -Открывает конÑоль c Lua-интерпретатором, в котором можно -взаимодейÑтвовать Ñ Ð¡Ð£Ð‘Ð” аналогично тому как Ñто проиÑходит в обычной -конÑоли Tarantool. ÐÐ¸ÐºÐ°ÐºÐ°Ñ Ð»Ð¾Ð³Ð¸ÐºÐ° Picodata поверх Tarantool не -выполнÑетÑÑ, ÑоответÑтвенно, клаÑтер не инициализируетÑÑ Ð¸ подключение к -клаÑтеру не производитÑÑ. ЗапуÑкаетÑÑ ÐºÐ¾Ð½Ñоль Tarantool, вÑтроенного в -Picodata (но не уÑтановленного обычного Tarantool, еÑли такой еÑÑ‚ÑŒ в -ÑиÑтеме). - -## picodata expel - -ИÑключает инÑÑ‚Ð°Ð½Ñ Ð¸Ð· клаÑтера. ПрименÑетÑÑ Ñ‡Ñ‚Ð¾Ð±Ñ‹ указать клаÑтеру, что -инÑÑ‚Ð°Ð½Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ не учаÑтвует в кворуме Raft. - -Полный формат: - -```bash -picodata expel --instance-id <instance-id> [--cluster-id <cluster-id>] [--peer <peer>] -``` - -Команда подключаетÑÑ Ðº `peer` через протокол _netbox_ и отдаёт ему -внутреннюю команду на иÑключение `instance-id` из клаÑтера. Команда -отправлÑетÑÑ Ð² raft-лог, из которого затем будет применена к таблице -инÑтанÑов и уÑтановит значение `target_grade=Expelled` Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ -инÑтанÑа. Затем через какое-то Ð²Ñ€ÐµÐ¼Ñ _governor_ возьмёт в обработку Ñтот -`target_grade`, выполнит необходимые работы по отключению инÑтанÑа и -уÑтановит ему значение `current_grade=Expelled`. Сам инÑÑ‚Ð°Ð½Ñ Ð¿Ð¾Ñле Ñтого -оÑтановитÑÑ, его процеÑÑ Ð·Ð°Ð²ÐµÑ€ÑˆÐ¸Ñ‚ÑÑ. Ð’ дальнейшем клаÑтер не будет -ожидать от Ñтого инÑтанÑа учаÑÑ‚Ð¸Ñ Ð² кворуме. ИÑключённый из клаÑтера -инÑÑ‚Ð°Ð½Ñ Ð¿Ñ€Ð¸ попытке перезапуÑтитьÑÑ Ð±ÑƒÐ´ÐµÑ‚ автоматичеÑки завершатьÑÑ. - -Параметр `cluster-id` проверÑетÑÑ Ð¿ÐµÑ€ÐµÐ´ добавлением команды в raft-лог. - -Параметр `peer` — Ñто Ð°Ð´Ñ€ÐµÑ Ð»ÑŽÐ±Ð¾Ð³Ð¾ инÑтанÑа клаÑтера. Формат: -`[host]:port`. Может Ñовпадать Ñ Ð°Ð´Ñ€ÐµÑом иÑключаемого инÑтанÑа. - -ЕÑли иÑключаемый инÑÑ‚Ð°Ð½Ñ ÑвлÑетÑÑ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ð¼ raft-лидером, то лидерÑтво -переходит другому инÑтанÑу. - -Обратите внимание, что иÑключённый инÑÑ‚Ð°Ð½Ñ Ð½ÑƒÐ¶Ð½Ð¾ ÑнÑÑ‚ÑŒ из-под ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ -Ñупервизора. - -Значение `instance-id` иÑключённого инÑтанÑа может быть иÑпользовано -повторно. Ð”Ð»Ñ Ñтого доÑтаточно запуÑтить новый инÑÑ‚Ð°Ð½Ñ Ñ Ñ‚ÐµÐ¼ же -`instance-id`. - -### Примеры иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ picodata expel - -Ðиже приведены типовые Ñитуации и подходÑщие Ð´Ð»Ñ Ñтого команды. - -1. Ðа хоÑте Ñ Ð¸Ð½ÑтанÑом `i4` вышел из ÑÑ‚Ñ€Ð¾Ñ Ð¶Ñ‘Ñткий диÑк, данные - инÑтанÑа утрачены, Ñам инÑÑ‚Ð°Ð½Ñ Ð½ÐµÑ€Ð°Ð±Ð¾Ñ‚Ð¾ÑпоÑобен. Какой-то из - оÑтавшихÑÑ Ð¸Ð½ÑтанÑов доÑтупен по адреÑу `192.168.104.55:3301`. - - ```bash - picodata expel --instance-id i4 --peer 192.168.104.9:3301 - ``` - -2. Ð’ клаÑтере `mycluster` из 3-Ñ… инÑтанÑов, где каждый работает на Ñвоём - физичеÑком Ñервере, проиÑходит замена одного Ñервера. Выключать - инÑÑ‚Ð°Ð½Ñ Ð½ÐµÐ»ÑŒÐ·Ñ, так как оÑтавшиеÑÑ 2 узла клаÑтера не Ñмогут Ñоздать - Ñтабильный кворум. ПоÑтому Ñначала в Ñеть добавлÑетÑÑ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ð¹ Ñервер: - - ```bash - picodata run --instance-id i4 --peer 192.168.104.1 --cluster-id mycluster - ``` - - Далее, еÑли на Ñервере Ñ Ð¸Ð½ÑтанÑом i3 наÑтроен автоматичеÑкий - перезапуÑк Picodata в Systemd или как-либо иначе, то его нужно - предварительно отключить. ПоÑле Ñтого c любого из уже работающих - Ñерверов клаÑтера иÑключаетÑÑ Ð¸Ð½ÑÑ‚Ð°Ð½Ñ i3: - - ```bash - picodata expel --instance-id i3 --cluster-id mycluster - ``` - - Ð£ÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° подключитÑÑ Ðº `127.0.0.1:3301`, который - ÑамоÑтоÑтельно найдёт лидера клаÑтера и отправит ему команду на - иÑключение инÑтанÑа `i3`. Когда процеÑÑ `picodata` на i3 завершитÑÑ - — Ñервер можно выключать. diff --git a/docs/cluster_group.svg b/docs/cluster_group.svg deleted file mode 100644 index 00260c54cbcbeaf872454104e59a5c74008f1fe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139926 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≺LcEr;Sgx*YSs6%+j}~zR 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 Y645CZ%8-;*{`33KK7RMjAAa}$03wUu-~a#s diff --git a/docs/clustering.md b/docs/clustering.md deleted file mode 100644 index a0949f8463..0000000000 --- a/docs/clustering.md +++ /dev/null @@ -1,457 +0,0 @@ -# ÐžÐ±Ñ‰Ð°Ñ Ñхема инициализации клаÑтера -Данный раздел Ñодержит опиÑание архитектуры Picodata, а именно — -выÑокоуровневый процеÑÑ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ клаÑтера на оÑнове неÑкольких -отдельно запущенных ÑкземплÑров (инÑтанÑов) Picodata. - -ÐдминиÑтратор запуÑкает неÑколько инÑтанÑов, Ð¿ÐµÑ€ÐµÐ´Ð°Ð²Ð°Ñ Ð² качеÑтве -аргументов необходимые параметры: - -```sh -picodata run --instance-id i1 --listen i1 --peer i1,i2,i3 -picodata run --instance-id i2 --listen i2 --peer i1,i2,i3 -picodata run --instance-id i3 --listen i3 --peer i1,i2,i3 -# ... -picodata run --instance-id iN --listen iN --peer i1 -``` - -ÐезавиÑимо от количеÑтва запуÑкаемых инÑтанÑов, в опции `--peer` у -каждого из них Ñледует указать один и тот же набор из неÑкольких -инÑтанÑов — одного обычно доÑтаточно, но Ð´Ð»Ñ Ð¿Ð¾Ð´Ñтраховки можно взÑÑ‚ÑŒ -три. Именно на их оÑнове будет произведена Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтера и -поиÑк вÑех работающих инÑтанÑов Ð´Ð»Ñ Ð¸Ñ… Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð² ÑоÑтав клаÑтера -(discovery). - -ПодробноÑти алгоритма discovery приведены в отдельном -[документе](discovery.md). Ð’ контекÑте Ñборки клаÑтера важно лишь -понимать, что Ñтот алгоритм позволÑет не более чем одному инÑтанÑу -(peer'у) Ñоздать Raft-группу, Ñ‚.е. Ñтать инÑтанÑом Ñ raft_id=1. ЕÑли -таких инÑтанÑов будет неÑколько, то и Raft-групп, а Ñледовательно и -клаÑтеров Picodata получитÑÑ Ð½ÐµÑколько. - -Топологией Raft-группы управлÑет алгоритм Raft, реализованный в виде -крейта `raft-rs`. - -## Ðтапы инициализации клаÑтера -Ðа Ñхеме ниже показаны Ñтапы жизненного цикла инÑтанÑа в контекÑте его -приÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ðº клаÑтеру Picodata. - - - -Ð’ контекÑте операционных ÑиÑтем каждый инÑÑ‚Ð°Ð½Ñ ÑоответÑтвует группе из -двух процеÑÑов — родительÑого (supervisor) и дочернего (именно он -выполнÑет tarantool runtime). - -КраÑным показан родительÑкий процеÑÑ, который запущен на вÑем протÑжении -жизненного цикла инÑтанÑа. Ð’ÑÑ Ð»Ð¾Ð³Ð¸ÐºÐ°, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ Ð¿Ñ€Ð¸ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ðº -клаÑтеру, и Ð·Ð°ÐºÐ°Ð½Ñ‡Ð¸Ð²Ð°Ñ Ð¾Ð±Ñлуживанием клиентÑких запроÑов, проиÑходит в -дочернем процеÑÑе (голубой цвет). ЕдинÑтвенное предназначение -родительÑкого процеÑÑа — выполнÑÑ‚ÑŒ [ребутÑтрап](#РебутÑтрап) и -инициализировать дочерний Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¾ (Ñиреневый цвет). - -Ð”Ð°Ð½Ð½Ð°Ñ Ñхема наиболее полно отражает логику кода в файле `main.rs`. Ðиже -опиÑаны детали Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ Ñтапа и ÑоответÑтвующей программной -функции. - -### fn main() - -Ðа Ñтом Ñтапе проиÑходит ветвление (форк) процеÑÑа `picodata`. -РодительÑкий процеÑÑ (supervisor) ожидает от дочернего процеÑÑа -ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ механизму IPC и при необходимоÑти перезапуÑкает дочерний -процеÑÑ. - -Выполнение дочернего процеÑÑа начинаетÑÑ Ñ Ð²Ñ‹Ð·Ð¾Ð²Ð° функции -[`start_discover()`](#fn-start_discover) и далее Ñледует алгоритму. При -необходимоÑти дочерний процеÑÑ Ð¼Ð¾Ð¶ÐµÑ‚ попроÑить Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ вÑе -файлы БД (Ñм. раздел ["РебутÑтрап"](#РебутÑтрап)). Ðто иÑпользуетÑÑ Ð´Ð»Ñ -повторной инициализации инÑтанÑа Ñ Ð½Ð¾Ñ€Ð¼Ð°Ð»ÑŒÐ½Ñ‹Ð¼ `replicaset_uuid` вмеÑто -рандомного. - -### РебутÑтрап - -У тарантула еÑÑ‚ÑŒ две оÑобенноÑти, из-за которых процеÑÑ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ -выглÑдит так как выглÑдит: - -1. ПринадлежноÑÑ‚ÑŒ инÑтанÑа тому или иному репликаÑету определÑетÑÑ Ð² - момент первого вызова `box.cfg()` когда ÑоздаетÑÑ Ð¿ÐµÑ€Ð²Ñ‹Ð¹ Ñнапшот. - ВпоÑледÑтии изменить принадлежноÑÑ‚ÑŒ репликаÑету невозможно. -2. Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ iproto Ñервера, реализующего бинарный Ñетевой протокол - тарантула, выполнÑетÑÑ Ñ‚Ð¾Ð¹ же функцией `box.cfg()`. - -Ð’ ÑовокупноÑти Ñти две оÑобенноÑти Ñоздают проблему курицы и Ñйца: - -- ИнÑÑ‚Ð°Ð½Ñ Ð½Ðµ может общатьÑÑ Ð¿Ð¾ Ñети, пока не узнает принадлежноÑÑ‚ÑŒ - репликаÑету. -- ПринадлежноÑÑ‚ÑŒ репликаÑету невозможно узнать без Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñети. - -Чтобы Ñту проблему решить, Picodata инициализируетÑÑ Ñо Ñлучайно -Ñгенерированными идентификаторами, а позже перезапуÑкает процеÑÑ, -попутно Ð¾Ñ‡Ð¸Ñ‰Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‡ÑƒÑŽ директорию. - -### fn start_discover() - -Дочерний процеÑÑ Ð½Ð°Ñ‡Ð¸Ð½Ð°ÐµÑ‚ Ñвое ÑущеÑтвование Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ -[`init_common()`](#fn-init_common), в рамках которой в Ñ‚.ч. -инициализируетÑÑ Ð¼Ð¾Ð´ÑƒÐ»ÑŒ `box`. Возможно, что при Ñтом из БД будет ÑÑно, -что bootstrap данного инÑтанÑа уже был произведен ранее и что Raft уже -знает о вхождении Ñтого инÑтанÑа в клаÑтер — в таком Ñлучае никакого -discovery не будет, инÑÑ‚Ð°Ð½Ñ Ñразу перейдет к Ñтапу `postjoin()`. Ð’ -противном Ñлучае, еÑли меÑто инÑтанÑа в клаÑтере еще не извеÑтно, -алгоритм discovery определÑет значение флага `i_am_bootstrap_leader` и -Ð°Ð´Ñ€ÐµÑ Ð»Ð¸Ð´ÐµÑ€Ð° Raft-группы. Далее инÑÑ‚Ð°Ð½Ñ ÑбраÑывает Ñвое ÑоÑтоÑние (Ñм. -["РебутÑтрап"](#РебутÑтрап)), чтобы повторно провеÑти инициализацию -`box.cfg()`, теперь уже Ñ Ð¸Ð·Ð²ÐµÑтными параметрами. Сам лидер -(единÑтвенный Ñ `i_am_bootstrap_leader == true`) выполнÑет функцию -`start_boot()`. ОÑтальные инÑтанÑÑ‹ переходÑÑ‚ к функции `start_join()`. - -### fn start_boot() - -Ð’ функции `start_boot` проиÑходит Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Raft-группы — лидер -генерирует и ÑохранÑет в БД первые запиÑи в журнале. Ðти запиÑи -опиÑывают добавление первого инÑтанÑа в пуÑтую Raft-группу и Ñоздание -начальной clusterwide-конфигурации. Таким образом доÑтигаетÑÑ -однообразие кода, обрабатывающего Ñти запиÑи. - -Сам Raft-узел на данном Ñтапе еще не ÑоздаетÑÑ. Ðто произойдет позже, на -Ñтадии `postjoin()`. - -### fn start_join() - -Вызову функции `start_join()` вÑегда предшеÑтвует -[ребутÑтрап](#РебутÑтрап) (удаление вÑех данных и перезапуÑк процеÑÑа), -поÑтому на данном Ñтапе в БД нет ни Ð¼Ð¾Ð´ÑƒÐ»Ñ `box`, ни проÑтранÑтва -хранениÑ. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ `start_join()` имеет проÑтое уÑтройÑтво: - -ИнÑÑ‚Ð°Ð½Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»Ñет Ð·Ð°Ð¿Ñ€Ð¾Ñ [`rpc::join`](#rpcjoin) лидеру Raft-группы (он -извеÑтен поÑле discovery), который в ответе приÑылает вÑÑŽ необходимую -Ð´Ð»Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ð¸Ð¸ информацию: - -Ð”Ð»Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ Raft-узла: -- идентификатор `raft_id`, -- данные таблицы `_picodata_peer_address`. - -Ð”Ð»Ñ Ð¿ÐµÑ€Ð²Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ вызова `box.cfg()`: -- идентификаторы `instance_uuid`, `replicaset_uuid`, -- `box.cfg.replication` — ÑпиÑок урлов Ð´Ð»Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸. - -Получив вÑе наÑтройки, инÑÑ‚Ð°Ð½Ñ Ð¸Ñпользует их в `box.cfg()` (Ñм. -[`init_common()`](#fn-init_common)), и затем Ñоздает в БД группу -`_picodata_peer_address` Ñ Ð°ÐºÑ‚ÑƒÐ°Ð»ÑŒÐ½Ñ‹Ð¼Ð¸ адреÑами других инÑтанÑов. Без -Ñтого инÑÑ‚Ð°Ð½Ñ Ð½Ðµ Ñможет отвечать на ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚ других членов -Raft-группы. - -По завершении Ñтих манипулÑций инÑÑ‚Ð°Ð½Ñ Ñ‚Ð°ÐºÐ¶Ðµ переходит к Ñтапу -`postjoin()`. - -### fn postjoin() - -Логика функции `postjoin()` одинакова Ð´Ð»Ñ Ð²Ñех инÑтанÑов. К Ñтому -моменту Ð´Ð»Ñ Ð¸Ð½ÑтанÑа уже инициализированы корректные проÑтранÑтва -Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² БД и могут быть накоплены запиÑи в журнале Raft. - -Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ `postjoin()` выполнÑет Ñледующие дейÑтвиÑ: - -- Инициализирует HTTP-Ñервер в ÑоответÑтвии Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ `--http-listen`. - -- ЗапуÑкает Lua-Ñкрипт, указанный в аргументе `--script`. - -- Инициализирует узел Raft, который начинает взаимодейÑтвовать Ñ - Raft-группой. - -- Ð’ Ñлучае, еÑли других кандидатов нет, инÑÑ‚Ð°Ð½Ñ Ñ‚ÑƒÑ‚ же - избирает ÑÐµÐ±Ñ Ð»Ð¸Ð´ÐµÑ€Ð¾Ð¼ группы. - -- УÑтанавливает `on_shutdown` триггер, который обеÑпечит - [корректное завершение работы инÑтанÑа](#Graceful-shutdown). - -ПоÑледним шагом инÑÑ‚Ð°Ð½Ñ Ð¾Ð¿Ð¾Ð²ÐµÑ‰Ð°ÐµÑ‚ клаÑтер о том, что он готов проходить -наÑтройку необходимых подÑиÑтем (репликации, шардинга, и Ñ‚.д.). Ð”Ð»Ñ -Ñтого лидеру отправлÑетÑÑ Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° обновление `target_grade` текущего -инÑтанÑа до ÑƒÑ€Ð¾Ð²Ð½Ñ `Online`, поÑле чего за дальнейшие дейÑÑ‚Ð²Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ -отвечать Ñпециальный поток ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ [topology governor](#Topology-governor). - -Как только запиÑÑŒ Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ‹Ð¼ грейдом будет зафикÑирована в Raft, узел -готов к иÑпользованию. - -### fn init_common() - -Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ `init_common` обобщает дейÑтвиÑ, необходимые Ð´Ð»Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ -инÑтанÑа во вÑех трех вышеопиÑанных ÑценариÑÑ… — `start_discover`, -`start_boot`, `start_join`. - -Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸Ð½ÑтанÑа ÑводитÑÑ Ðº Ñледующим шагам: - -- Ñоздание `data_dir`, -- первичный вызов `box.cfg`, -- Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ `package.preload.vshard`, -- Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð¼Ð¾Ðº (`box.schema.func.create`), -- Ñоздание ÑиÑтемных ÑпейÑов (`_picodata_raft_log` и Ñ‚.д). - -Параметры первичного вызова `box.cfg` завиÑÑÑ‚ от конкретного ÑценариÑ: - -| param | `start_discover` | `start_boot` | `start_join` | -|-------------|------------------|--------------|-------------------------------| -| listen | None | None | _from args_ | -| read_only | false | false | from `rpc::join` response | -| uuids | _random_ | _given_ | from `rpc::join` response | -| replication | None | None | from `rpc::join` response | -| data_dir | _from args_ | ... | ... | -| log_level | _from args_ | ... | ... | - -## Обработка запроÑов - -### rpc::join - -Ð—Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ вÑей логики по управлению топологией ÑодержитÑÑ Ð² -обработчике запроÑа `rpc::join`. - -Ðргументом Ð´Ð»Ñ Ð½ÐµÐµ ÑвлÑетÑÑ ÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтруктура: - -```rust -struct rpc::join::Request { - cluster_id: String, - instance_id: Option<String>, - replicaset_id: Option<String>, - advertise_address: String, - failure_domain: FailureDomain, -} -``` - -Ответом Ñлужит такаÑ: - -```rust -struct rpc::join::OkResponse { - /// Добавленный инÑÑ‚Ð°Ð½Ñ (чтобы знать вÑе ID) - instance: Instance, - /// ГолоÑующие узлы (чтобы добавлÑемый инÑÑ‚Ð°Ð½Ñ Ð¼Ð¾Ð³ наладить контакт) - peer_addresses: Vec<PeerAddress>, - /// ÐаÑтройки репликации (чтобы инициализировать репликацию) - box_replication: Vec<String>, -} - -struct Instance { - // вÑевозможные идентификаторы - raft_id: RaftId, - instance_id: String, - instance_uuid: String, - replicaset_id: String, - replicaset_uuid: String, - - // текущее меÑтоположение, виртуальное и физичеÑкое - peer_address: String, - failure_domain: FailureDomain, - - // текущий и целевой грейды - current_grade: CurrentGrade, - target_grade: TargetGrade, -} -``` - -Цель такого запроÑа ÑводитÑÑ Ðº добавлению нового инÑтанÑа в Raft-группу. -Ð”Ð»Ñ Ñтого алгоритма Ñправедливы Ñледующие тезиÑÑ‹: - -- Ð—Ð°Ð¿Ñ€Ð¾Ñ `rpc::join` вÑегда делает инÑÑ‚Ð°Ð½Ñ Ð±ÐµÐ· Ñнапшотов. -- Ð’ процеÑÑе обработки запроÑа в Raft-журнал добавлÑетÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ - `op::PersistPeer { peer }`, при Ñтом `current_grade: Offline`, - `target_grade: Offline` (подробнее о них в разделе [topology - governor](#Topology-governor)). -- Ð’ ответ выдаетÑÑ Ð²Ñегда новый `raft_id`, никому другому ранее не - принадлежавший. -- Помимо идентификаторов нового инÑтанÑа, ответ Ñодержит ÑпиÑок - голоÑующих членов Raft-группы. Они необходимы новому инÑтанÑу Ð´Ð»Ñ - того чтобы отвечать на запроÑÑ‹ от Raft-лидера. -- Также ответ Ñодержит параметр `box_replication`, который требуетÑÑ Ð´Ð»Ñ - правильной наÑтройки репликации. - -## Graceful shutdown - -Чтобы выключение прошло штатно и не имело негативных поÑледÑтвий, -необходимо Ñледить за Ñоблюдением Ñледующих уÑловий: - -- ИнÑÑ‚Ð°Ð½Ñ Ð½Ðµ должен оÑтаватьÑÑ Ð³Ð¾Ð»Ð¾Ñующим, пока еÑÑ‚ÑŒ другие кандидаты в - ÑоÑтоÑнии `Online`. -- ИнÑÑ‚Ð°Ð½Ñ Ð½Ðµ должен оÑтаватьÑÑ Ð»Ð¸Ð´ÐµÑ€Ð¾Ð¼. - -Чтобы Ñтого добитьÑÑ, каждый инÑÑ‚Ð°Ð½Ñ Ð¿Ñ€Ð¸ Ñрабатывании триггера -`on_shutdown` отправлÑет лидеру Ð·Ð°Ð¿Ñ€Ð¾Ñ `UpdatePeerRequest { -target_grade: Offline }`, обработкой которого займетÑÑ Ð²Ñ‹ÑˆÐµÑƒÐ¿Ð¾Ð¼Ñнутый -`governor_loop`. ПоÑле Ñтого инÑÑ‚Ð°Ð½Ñ Ð¿Ñ‹Ñ‚Ð°ÐµÑ‚ÑÑ Ð´Ð¾Ð¶Ð´Ð°Ñ‚ÑŒÑÑ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ -запиÑи о Ñмене Ñвоего `current_grade` на `Offline` (о том, почему так -произойдет Ñм. ниже). - - -## ОпиÑание уровней (grades) клаÑтера -По некоторым причинам коммит запиÑи может не уÑпеть дойти до инÑтанÑа в -Ñрок, отведенный на выполнение триггера `on_shutdown` триггера (например -в клаÑтере может быть потерÑн кворум). Ð’ таком Ñлучае корректное -завершение работы инÑтанÑа (`graceful shutdown`) невозможно. - -## Topology governor - -Ð’ отличие от других клаÑтерных решений (например, того же Tarantool -Cartridge) Picodata не иÑпользует понÑтие "ÑоÑтоÑниÑ" Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ -отдельных инÑтанÑов. ВмеÑто Ñтого теперь применÑетÑÑ Ð½Ð¾Ð²Ð¾Ðµ понÑтие -«грейд» (grade). Данный термин отражает не ÑоÑтоÑние Ñамого инÑтанÑа, а -конфигурацию оÑтальных учаÑтников клаÑтера по отношению к нему. -СущеÑтвуют две разновидноÑти грейдов: текущий (`current_grade`) и -целевой (`target_grade`). Инициировать изменение `current_grade` может -только лидер при поддержке кворума, что гарантирует конÑиÑтентноÑÑ‚ÑŒ -принÑтого Ñ€ÐµÑˆÐµÐ½Ð¸Ñ (и поддерживает доверие к ÑиÑтеме в плане -отказоуÑтойчивоÑти). - -Инициировать изменение `target_grade` может кто угодно — Ñто может быть -Ñам инÑÑ‚Ð°Ð½Ñ (при его добавлении), или админиÑтратор клаÑтера командой -`picodata expel` либо нажатием Ctrl+C на клавиатуре. `target_grade` — -Ñто желаемое ÑоÑтоÑние инÑтанÑа, в которое тот должен прийти. - -Приведением дейÑтвительного к желаемому занимаетÑÑ Ñпециальный файбер на -лидере — `governor_loop`. Он управлÑет вÑеми инÑтанÑами Ñразу. - -С грейдом (как Ñ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ð¼, так и Ñ Ñ†ÐµÐ»ÐµÐ²Ñ‹Ð¼) также вÑегда аÑÑоциирована -Ð¸Ð½ÐºÐ°Ñ€Ð½Ð°Ñ†Ð¸Ñ (`incarnation`) — порÑдковое чиÑло, отражающее чиÑло попыток -обработать данный инÑÑ‚Ð°Ð½Ñ Ñо Ñтороны файбера `governor_loop`. Ðто -позволÑет реагировать на Ñитуации, когда инÑтанÑÑ‹ выходÑÑ‚ из ÑÑ‚Ñ€Ð¾Ñ Ð½Ð° -какой-то период времени, поÑле чего их необходимо Ñнова привеÑти в -актуальное ÑоÑтоÑние. - - -Ðа оÑнове ÑовокупноÑти грейдов и их инкарнаций `governor_loop` на каждой -итерации беÑконечного цикла генерирует активноÑти (activity) и пытаетÑÑ -их организовать. Пока не организует, никаких других изменений в текущих -грейдах не произойдет (но могут изменитьÑÑ Ñ†ÐµÐ»ÐµÐ²Ñ‹Ðµ). ЕÑли активноÑти -завершатÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹, то на Ñледующей итерации они будут перевычиÑлены Ñ -учетом новых целей. - -Инкарнации грейдов вычиÑлÑÑŽÑ‚ÑÑ Ð¿Ð¾ Ñледующему принципу. -- Каждый раз когда `target_grade` инÑтанÑа получает значение `Online`, - его Ð¸Ð½ÐºÐ°Ñ€Ð½Ð°Ñ†Ð¸Ñ ÑƒÐ²ÐµÐ»Ð¸Ñ‡Ð¸Ð²Ð°ÐµÑ‚ÑÑ Ð½Ð° 1. -- Ð’Ñе оÑтальные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð³Ñ€ÐµÐ¹Ð´Ð¾Ð² копируют инкарнацию Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾Ð¿Ð¾Ð»Ð¾Ð¶Ð½Ð¾Ð³Ð¾ - грейда, то еÑÑ‚ÑŒ при изменении `target_grade` Ð¸Ð½ÐºÐ°Ñ€Ð½Ð°Ñ†Ð¸Ñ ÐºÐ¾Ð¿Ð¸Ñ€ÑƒÐµÑ‚ÑÑ Ñ - `current_grade`, при изменении `current_grade` — Ñ `target_grade`. - -Дальше перечиÑлены активноÑти, которыми занимаетÑÑ `governor_loop`, в -том же порÑдке, в котором он к ним приÑтупает. - - - - -Ðиже перечиÑлены ÑущеÑтвующие варианты активноÑтей, которые Ñоздает -`topology_governor`. - -### 1. Обновить ÑоÑтав голоÑующих / неголоÑующих инÑтанÑов - -Сначала нужно проверить необходимоÑÑ‚ÑŒ менÑÑ‚ÑŒ конфигурацию Raft-группы, а -именно — ÑоÑтав голоÑующих / неголоÑующих узлов (`voters` и `learners`). - -Правила выбора новой конфигурации опиÑаны в -`picodata::governor::cc::raft_conf_change` и заключаютÑÑ Ð² Ñледующем: -- Любые инÑтанÑÑ‹, переходÑщие в грейд `Expelled`, удалÑÑŽÑ‚ÑÑ Ð¸Ð· - Raft-группы; -- ГолоÑущие инÑтанÑÑ‹, переходÑщие в грейд `Offline`, переÑтают быть - голоÑующими (ÑтановÑÑ‚ÑÑ `learners`) и Ð´Ð»Ñ Ð½Ð¸Ñ… находитÑÑ Ð·Ð°Ð¼ÐµÐ½Ð°; -- Среди Ñвежедобавленных инÑтанÑов Ñ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ð¼ грейдом `Online` - подбираетÑÑ Ð½ÐµÐ¾Ð±Ñ…Ð¾Ð´Ð¸Ð¼Ð¾Ðµ количеÑтво голоÑующих инÑтанÑов (`voters`), - оÑтальные добавлÑÑŽÑ‚ÑÑ ÐºÐ°Ðº `learners`; - -<!-- [TODO](#ПредÑтоит Ñделать) Ðовые воутеры должны выбиратьÑÑ Ñ ÑƒÑ‡ÐµÑ‚Ð¾Ð¼ failure -domain'ов. --> - -По Ñтим правилам ÑоздаетÑÑ `ConfChangeV2`, и, еÑли он не пуÑÑ‚, -отправлÑетÑÑ Ð² Raft. Далее нужно дождатьÑÑ ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ `TopologyChanged`, -которое будет поÑлано в ответ на уÑпешное применение новой конфигурации. - -### 2. target_grade Offline / Expelled. - -Ðиже раÑÑмотрены два варианта вывода инÑтанÑа из ÑтроÑ: временный -(`target_grade = Offline`) и поÑтоÑнный (`target_grade = Expelled`). -Перед тем как выключить инÑтанÑ, нужно убедитьÑÑ, что клаÑтер Ñможет -продолжить функционировать без него. - -ЕÑли уходит лидер Raft-группы, то еÑÑ‚ÑŒ инÑтанÑ, на котором в данный -момент выполнÑетÑÑ `governor_loop`, то он Ñнимает Ñ ÑÐµÐ±Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð¼Ð¾Ñ‡Ð¸Ñ -(делает `transfer_leadership`) и ждет Ñмены Raft-ÑтатуÑа, дальше -дейÑтвовать будет кто-то другой. - -ЕÑли уходит лидер Ñвоего репликаÑета, то проиÑходÑÑ‚ новые выборы такого -лидера, поÑле чего нужно дождатьÑÑ ÑоответÑтвующей запиÑи в ÑÐ¿ÐµÐ¹Ñ Ñ -репликаÑетами. - -Далее Ñледует обновить конфигурацию ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (`vshard`) на вÑех -инÑтанÑах Ñ Ñ€Ð¾Ð»Ñми Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… (`storage`) и маршрутизации -(`routers`), чтобы оповеÑтить их об изменениÑÑ… в топологии. ЕÑли Ñто -поÑледний узел Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² репликаÑете, ему будет выÑтавлен Ð²ÐµÑ 0. - -<!-- [TODO](#Пока не Ñделано) - Также, еÑли Ñто поÑледний узел Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² репликаÑете, ему надо выÑтавить Ð²ÐµÑ Ð² 0. ДожидатьÑÑ Ñ€ÐµÐ±Ð°Ð»Ð°Ð½Ñировки на Ñтом шаге не требуетÑÑ (да и не получитÑÑ â€” Ñлишком Ð´Ð¾Ð»Ð³Ð°Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ°), Ð´Ð»Ñ Ñтого еÑÑ‚ÑŒ отдельный пункт. --> - -Ðаконец, инÑтанÑу приÑваиваетÑÑ `current_grade`, ÑоответÑтвующей его -целевому уровню. - -### 3. target_grade: Online, current_grade: * -> RaftSynced - -Дальше начинаетÑÑ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ° инÑтанÑов, которых нужно привеÑти в -актуальное ÑоÑтоÑние. Ðто либо Ñвежедобавленные инÑтанÑÑ‹, либо инÑтанÑÑ‹, -которые были какое-то Ð²Ñ€ÐµÐ¼Ñ Ð½ÐµÐ°ÐºÑ‚Ð¸Ð²Ð½Ñ‹. - -Выбираем инÑтанÑ, либо имеющий `current_grade: Offline`, либо имеющий -инкарнацию текущего грейда меньше, чем инкарнацию целевого. - -Ðа Ñтом Ñтапе мы Ñинхронизируем Raft-журнал выбранных инÑтанÑов. Берем -текущий `commit_index` лидера и дожидаемÑÑ, пока `commit_index` пира его -не догонит. ПоÑле Ñтого приÑваиваем инÑтанÑу `current_grade = -RaftSynced`. - -<!-- [TODO](#Пока не Ñделано) Ðтот шаг можно раÑпараллелить, отправив Ð·Ð°Ð¿Ñ€Ð¾Ñ Ñразу -неÑкольким подходÑщим пирам. --> - -### 4. target_grade: Online, current_grade: RaftSynced -> Replicated - -Ðтот Ñтап отвечает за наÑтройку репликации внутри одного репликаÑета, к -которому отноÑитÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ñ‹Ð¹ инÑтанÑ. - -Первым делом мы Ñообщаем вÑем инÑтанÑам репликаÑета, что необходимо -применить новую конфигурацию репликации через `box.cfg { replication = -... }`. Однако, так как ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÐºÐ»Ð°Ñтера (в том чиÑле и -ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñетов) раÑпроÑтранÑетÑÑ Ð¼ÐµÐ¶Ð´Ñƒ инÑтанÑами через -Raft-журнал, необходимо убедитьÑÑ Ñ‡Ñ‚Ð¾ журнал у вÑех Ñвежий. Ð”Ð»Ñ Ñтого в -запроÑе также передаем `commit_index`, которого пиры должны дождатьÑÑ -прежде чем выполнÑÑ‚ÑŒ Ñам запроÑ. - -ПоÑле Ñтого инÑтанÑу, инициировавшему активноÑÑ‚ÑŒ, приÑваиваетÑÑ -`current_grade: Replicated`. - -<!-- [TODO](#Пока не Ñделано) Можно обновлÑÑ‚ÑŒ грейд Ñразу вÑем инÑтанÑам в -репликаÑете, которым Ñто нужно. --> - -Ðа Ñтом же Ñтапе добавлÑем запиÑÑŒ в ÑÐ¿ÐµÐ¹Ñ Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñетами, еÑли ее там -еще нет. При Ñтом Ð²ÐµÑ ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÑƒÑтанавливаетÑÑ Ð² 0, еÑли только Ñто -не первый репликаÑет в клаÑтере. - -ПоÑледнее что нужно Ñделать на Ñтом Ñтапе, Ñто обновить значение -`box.cfg { read_only }` в конфигурации лидера затронутого репликаÑета. -<!-- ([TODO](#Пока не Ñделано) Ñто не обÑзательно делать каждый раз). --> - -### 5. target_grade: Online, current_grade: Replicated -> ShardingInitialized - -Ðа данном Ñтапе наÑтраиваетÑÑ ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ðµ вÑего клаÑтера, поÑтому -запроÑÑ‹ отправлÑÑŽÑ‚ÑÑ Ñразу вÑем инÑтанÑам. - -РаÑÑылаем вÑем Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° обновление конфигурации ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ -(`vshard.router.cfg()` и `vshard.storage.cfg()`) опÑÑ‚ÑŒ вмеÑте Ñ -`commit_index`, чтобы инÑтанÑÑ‹ получили поÑледние данные. - -Ðа Ñтом Ñтапе первый репликаÑет, наполненный до фактора репликации, -запуÑкает начальное раÑпределение бакетов (`vshard.router.bootstrap`) -<!-- ([TODO](#Пока не Ñделано) пока что Ñто делает первый инÑÑ‚Ð°Ð½Ñ Ð² клаÑтере). --> - -Ð’ конце Ñтого Ñтапа подÑиÑтема ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… (`vshard`) на вÑех -инÑтанÑах знает о топологии вÑего клаÑтера, но на некоторых репликаÑетах -Ð²ÐµÑ Ð²Ñе еще проÑтавлен Ð²ÐµÑ 0, поÑтому данные на них ребаланÑироватьÑÑ -еще не будут. - -### 6. target_grade: Online, current_grade: ShardingInitialized -> Online - -Ðтот Ñтап нужен Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾ чтобы запуÑтить ребаланÑировку данных на новые -репликаÑеты. Ð”Ð»Ñ Ñтого проверÑем, еÑÑ‚ÑŒ ли у Ð½Ð°Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñеты Ñ Ð²ÐµÑом 0 и -доÑтигнутым фактором репликации. ЕÑли еÑÑ‚ÑŒ, то обновлÑем их Ð²ÐµÑ Ð¸ -повторно обновлÑем конфигурацию ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ð° вÑем клаÑтере, чтобы -данные начали ребаланÑироватьÑÑ. diff --git a/docs/clustering.svg b/docs/clustering.svg deleted file mode 100644 index cdf2e9e9da954cfaa2c876431218e7c3bcabfb95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34416 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 QOS@bYe|5Tk%MaE4KSqrEw*UYD diff --git a/docs/clustering_curves.svg b/docs/clustering_curves.svg deleted file mode 100644 index b08865da3cc48d3934961b57bcea16a354c83e39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112921 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 diff --git a/docs/discovery.md b/docs/discovery.md deleted file mode 100644 index f74db79135..0000000000 --- a/docs/discovery.md +++ /dev/null @@ -1,92 +0,0 @@ -# Discovery algorithm - -## Входные данные: - -- `N` узлов, пока не ÑвÑзанных друг Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¼ по Ñети. СвÑзать их предÑтоит алгоритму. -- Тем не менее, каждый узел обладает на Ñтарте информацией о некоторых его ÑоÑедÑÑ… - маÑÑиве `initial_peers`, Ñодержащем Ñетевые адреÑа узлов (не менее одного). - -Ðлгоритм налагает некоторые ограничение на входные данные, в противном Ñлучае результат работы алгоритма может оказатьÑÑ Ð½ÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼. Чтобы обеÑпечить выполнение результата, у любой пары узлов должен ÑущеÑтвовать как минимум один общий Ñлемент `initial_peers`. - -ЗдеÑÑŒ Ñтоит Ñказать пару Ñлов о том, как именно мы будем моделировать Ñеть. Ðлгоритм Ñпециально ÑоÑтавлен таким образом, чтобы его легко было адаптировать под конкретный транÑпортный протокол, хоть TCP, хоть UDP. - -Чтобы не проÑлыть инфантильными, мы также Ñразу допуÑкаем, что ÑвÑзноÑÑ‚ÑŒ Ñети может Ñпонтанно нарушатьÑÑ. Любое Ñообщение может: -- быть доÑтавлено получателю ÑпуÑÑ‚Ñ Ð½ÐµÐ¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ‘Ð½Ð½Ð¾Ðµ времÑ, -- возможно беÑконечно малое, -- а возможно и беÑконечно никогда. - -Мы хотим, чтобы алгоритм был полезен на практике. И такое предположение, неÑомненно, благоприÑтно ÑкажетÑÑ Ð½Ð° ÑпоÑобноÑÑ‚ÑÑ… алгоритма не разбитьÑÑ Ð¾ Ñуровую реальноÑÑ‚ÑŒ, где Ñервера иногда ~~проÑтужаютÑÑ~~ перегреваютÑÑ Ð¸ выгорают целыми датацентрами. - -Ð’ тоже Ð²Ñ€ÐµÐ¼Ñ Ð°Ð»Ð³Ð¾Ñ€Ð¸Ñ‚Ð¼ Ñовершенно не подготовлен к решению задачи о византийÑких генералах. Ðкцент в первую очередь делаетÑÑ Ð½Ð° отказоуÑтойчивоÑти к ошибкам пользователÑ, а не злонамеренноÑти генералов. Ðто призвано упроÑтить процеÑÑ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ рафт группы (который в худшем Ñлучае можно и перезапуÑтить на Ñтапе подготовки к ÑкÑплуатации, в отличие от генералов). ВизантийÑÐºÐ°Ñ Ð¾Ñ‚ÐºÐ°Ð·Ð¾ÑƒÑтойчивоÑÑ‚ÑŒ, еÑли Ñто необходимо, должна обеÑпечиватьÑÑ Ð´Ñ€ÑƒÐ³Ð¸Ð¼ протоколом. - -## Результат: - -Результатом работы Ñтого раÑпределенного алгоритма ÑвлÑетÑÑ ÐµÐ´Ð¸Ð½Ñтвенное булево значение `i_am_bootstrap_leader`. Мы ожидаем (и заверÑем ваÑ), что, пока входные параметры не нарушают наложенных ограничений, не более одного узла будущего клаÑтера приÑвоÑÑ‚ Ñебе Ñту медальку. Я бы хотел, чтобы "не меньше" тоже было равно одному, но, увы, в уÑловиÑÑ… полной Ñетевой изолÑции ответ будет ноль, и никакой онлайн-вечеринки не ÑоÑтоитÑÑ. - -И опÑÑ‚ÑŒ, в угоду пользовательÑкого опыта, алгоритм не пытаетÑÑ Ð·Ð°Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ÑŒ больше информации о будущем клаÑтере. Ð’ противном Ñлучае Ñто потребовало бы делать дополнительные Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾ Ñетевой ÑвÑзноÑти, и Ñто могло негативно ÑказатьÑÑ Ð½Ð° удобÑтве ÑкÑплуатации решениÑ. Данный алгоритм надеетÑÑ Ð½Ð° лучшее (один бутÑтрап лидер), оÑтаваÑÑÑŒ готовым к худшему (ноль бутÑтрап лидеров). - -## СобÑтвенно Ñам алгоритм - -### **Шаг 0.1** - -Как уже упоминалоÑÑŒ раньше, каждый узел _i_ инициализирует маÑÑив извеÑтных адреÑов `known_peers[i] = initial_peers[i]` (Ñо значениÑми, предоÑтавленными пользователем). - -### **Шаг 0.2** - -Каждый узел _i_ генерирует Ñлучайный идентификатор (`guid[i]`), вероÑтноÑÑ‚ÑŒ коллизии которых мы предполагаем равной нулю. Ðто требование ÑовÑем не Ñложно выполнить на практике. - -### **Шаг 1** - -Каждый узел _i_ проводит раунд запроÑов номер `r` - отправлÑет по вÑем извеÑтным адреÑам `known_peers[i][m]` Ñообщение `("discovery_req", m, known_peers[i])`. Параметр `m` предÑтавлÑет Ñобой вÑего лишь Ð¸Ð½Ð´ÐµÐºÑ Ð°Ð´Ñ€ÐµÑата в маÑÑиве `known_peers[i]`. ЕдинÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ ÐµÐ³Ð¾ роль в алгоритме - быть возвращённым в ответе, чтобы можно было ÑопоÑтавить ответ Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ‚Ð½Ñ‹Ð¼ адреÑом. Впрочем, его можно вообще опуÑтить, еÑли Ñту функциональноÑÑ‚ÑŒ предоÑтавлÑет иÑпользуемый транÑпортный протокол. - -### _Меж двух шагов_ - -Получив Ñообщение `("discovery_req", m, known_peers[i])`, узел (_j_) проверÑет Ñвоё ÑоÑтоÑние. - -ЕÑли на данный момент лидер уже выбран и извеÑтен, то отправлÑет в ответ Ñообщение `("discovery_finished")`. - -Ð’ противном Ñлучае обновлÑет Ñвой маÑÑив `known_peers[j] = known_peers[i] \/ known_peers[j]`. Ответ Ñодержит `("discovery_resp", m, known_peers[j], guid[j])`. - -ЗдеÑÑŒ делаетÑÑ ÐµÑ‰Ñ‘ одно допущение о том, что иÑпользуемый транÑпортный протокол обладает функцией "отправки ответа". Оба TCP и UDP такой функциональноÑтью обладают. - -### **Шаг 2** (ВозвращаÑÑÑŒ к узлу _i_ - отправителю запроÑа) - -Yaroslav Dynnikov, [08/04/2022 22:18] - - -Получив ответ `("discovery_finished")` алгоритм завершает Ñвою работу - задача выполнена. `i_am_bootstrap_leader[i] = false`. - -Получив ответ `("discovery_resp", m, known_peers[j], guid[j])`, узел (_i_), как и в Ñлучае обработки `discovery_req`, обновлÑет Ñвой маÑÑив `known_peers[i] = known_peers[i] \/ known_peers[j]`. Далее полученный ответ ÑопоÑтавлÑетÑÑ Ñ Ð°Ð´Ñ€ÐµÑатом `known_peers[i][m]`. ЕÑли в маÑÑиве `known_peers[i]` оÑталиÑÑŒ ещё не ответившие адреÑаты, алгоритм приоÑтанавливаетÑÑ Ð´Ð¾ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñледующего ÑообщениÑ. - -ЕÑли ответ Ñодержит ошибку протокола транÑпортного уровнÑ, Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑетÑÑ Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼ интервалом до тех пор, пока не будет получен внÑтный ответ. - -ЕÑли к концу Ñтого шага были обнаружены новые адреÑаты, алгоритм начинает новый раунд запроÑов `r+1` и возвращаетÑÑ Ðº шагу 1. - -### **Шаг 3** - -ЕÑли вдруг лидер до Ñих пор не извеÑтен, то к Ñтому моменту каждому узлу _i_ ÑтановитÑÑ Ð¸Ð·Ð²ÐµÑтна Ð¿Ð¾Ð»Ð½Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° `known_peers[i][m] -> guid[m]`. - -ЕÑли вдруг `guid[i] == min(guid)`, то узел понимает, что `i_am_bootstrap_leader[i] = true`, и Ñ Ñтих пор переÑтаёт изменÑÑ‚ÑŒ внутреннее ÑоÑтоÑние, и отвечает на вÑе запроÑÑ‹ `("discovery_finished")`. - -ЕÑли же, напротив, `guid[i] != min(guid)`, то `i_am_bootstrap_leader[i] = false`. - -### ДоказательÑтво - -КорректноÑÑ‚ÑŒ алгоритма проще вÑего доказать от противного. Предположим, что в какой-то момент узел _i_ решил, что `i_am_bootstrap_leader[i] == true`, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº уже ÑущеÑтвовал `i_am_bootstrap_leader[j] == true` (и при Ñтом `i != j`). - -Так как на третьем шаге оба узла вычиÑлÑÑŽÑ‚ `min(guid)` идентичным оразом, то отличатьÑÑ Ð´Ð¾Ð»Ð¶Ð½Ñ‹ были Ñами таблицы `guid`. - -При Ñтом маÑÑив `known_peers[j]` заведомо не Ñодержал Ð°Ð´Ñ€ÐµÑ _i_ в момент принÑÑ‚Ð¸Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ, но Ñодержал Ñвой Ð°Ð´Ñ€ÐµÑ _j_, иначе бы не выполнилоÑÑŒ уÑловие `guid[j] == min(guid)`. Ðналогично, `known_peers[i]` точно Ñодержал _i_. - -Ð’ то же Ð²Ñ€ÐµÐ¼Ñ `known_peers[i]` не мог Ñодержать _j_, иначе _i_ не Ñмог бы получить от _j_ ответ `("discovery_resp")` и не “выдать†ÑебÑ. - -Таким образом _i_ и _j_ не должны были в момент принÑÑ‚Ð¸Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð¸Ñ‡ÐµÐ³Ð¾ знать друг о друге. - -Ðо так как у _i_ и _j_ по уÑловию должен быть как минимум один общий "ÑоÑед", назовём его _z_, он также должен был обоим _i_ и _j_ дать ответы `("discovery_resp")`. Противоречие заключаетÑÑ Ð² том, что _z_ пришлоÑÑŒ бы одному из двух узлов в ответе `("discovery_resp")` Ñообщить о ÑущеÑтвовании второго, покуда обработка запроÑов выполнÑетÑÑ Ð°Ñ‚Ð¾Ð¼Ð°Ñ€Ð½Ð¾. - -### Возможные оптимизации - -ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ алгоритма изложена так, чтобы быть макÑимально проÑтой. Тем не менее, Ñто не иÑключает Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð½ÐµÑкольких полезных оптимизаций, которые ниÑколько не влиÑÑŽÑ‚ на результат, Ñ…Ð¾Ñ‚Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»ÑÑŽÑ‚ Ñделать пользовательÑкий опыт приÑтнее. - -Ðачинать новый раунд на втором шаге не возбранÑетÑÑ Ñразу поÑле Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ адреÑата. Ðе обÑзательно Ð´Ð»Ñ Ñтого дожидатьÑÑ Ð²Ñех ожидаемых ответов. - -ПоÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ алгоритма, Ñ ÑообщениÑми `("discovery_finished")` можно раÑÑылать информацию об адреÑе лидера, о `known_peers`, или любую другую необходимую, которую хотелоÑÑŒ бы Ñинхронизировать. Так как единÑтвенноÑÑ‚ÑŒ лидера доказана, оÑтальные узлы могут ему верить, еÑли конечно никто больше не возьмётÑÑ ÐµÑ‘ модифицировать, а мы договорилиÑÑŒ не раÑÑматривать византийÑкие Ñценарии. diff --git a/docs/fsm.svg b/docs/fsm.svg deleted file mode 100644 index 62e5cb9a10d497c3ea87bfadcddf886b3fd61b6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39823 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 diff --git a/docs/glossary.md b/docs/glossary.md deleted file mode 100644 index f26b56d36f..0000000000 --- a/docs/glossary.md +++ /dev/null @@ -1,248 +0,0 @@ -# ГлоÑÑарий -## Общие ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ -Данный раздел Ñодержит опиÑание оÑновных терминов, иÑпользуемых в Picodata. - -## ПодÑиÑтемы -### Raft -**Raft** ÑвлÑетÑÑ Ð°Ð»Ð³Ð¾Ñ€Ð¸Ñ‚Ð¼Ð¾Ð¼ раÑпределенного конÑенÑуÑа, который нужен, чтобы неÑколько учаÑтников могли ÑовмеÑтно решить, произошло ли Ñобытие или нет, и что за чем Ñледовало. Raft иÑпользуетÑÑ Ð² Picodata Ð´Ð»Ñ ÑоглаÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ узлов и Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ ÐºÐ¾Ð½ÑиÑтентноÑти в клаÑтере. ÐšÐ¾Ð½Ñ†ÐµÐ¿Ñ†Ð¸Ñ Ñ€Ð°Ñпределенного конÑенÑуÑа предполагает, что в клаÑтере вÑегда еÑÑ‚ÑŒ только один лидер и некоторое количеÑтво голоÑующих узлов. Ðти узлы в нормальном ÑоÑтоÑнии подтверждают легитимноÑÑ‚ÑŒ лидера, а при отказе текущего лидера организуют выборы нового. -См. [подробнее](https://raft.github.io/raft.pdf). - -### Терм (term) -Период между выборами лидера в raft-группе называетÑÑ **термом** (term). Каждый терм начинаетÑÑ Ð² момент объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð¾Ð² нового лидера. Обычно Ñто проиÑходит поÑле потери ÑвÑзи Ñ Ð¿Ñ€ÐµÐ¶Ð½Ð¸Ð¼ лидером. Терм ÑоÑтоит из двух чаÑтей: выборов и периода нормальной работы raft-группы. ИÑключением Ñлужат термы, в течение которых не удалоÑÑŒ выбрать лидера группы: у таких термов еÑÑ‚ÑŒ только Ð¿ÐµÑ€Ð²Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ (выборы). -Важно помнить, что в одном терме не может ÑущеÑтвовать более одного лидера raft-группы. - -### СоÑтоÑÐ½Ð¸Ñ ÑƒÐ·Ð»Ð¾Ð² в Raft-группе <a name="raft-group"></a> -Ð’ raft-группе любой узел может быть в одном из трех ÑоÑтоÑний: - -**ПаÑÑивный узел (follower)** — ÑоÑтоÑние по умолчанию Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ инÑтанÑа поÑле запуÑка. Follower — обычный голоÑующий узел, который лишь отвечает на запроÑÑ‹, но не генерирует их. - -**Кандидат в лидеры (candidate)** — ÑоÑтоÑние инÑтанÑа во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð¾Ð² лидера. Когда начинаетÑÑ Ð½Ð¾Ð²Ñ‹Ð¹ терм, инÑтанÑÑ‹ в ÑтатуÑе follower увеличивают значение терма и переходÑÑ‚ в ÑÑ‚Ð°Ñ‚ÑƒÑ ÐºÐ°Ð½Ð´Ð¸Ð´Ð°Ñ‚Ð¾Ð², голоÑуют Ñами за ÑÐµÐ±Ñ Ð¸ затем ждут результатов выбора. Выходов из Ñтого ÑоÑтоÑÐ½Ð¸Ñ Ñ‚Ñ€Ð¸: - -- Кандидат побеждает в выборах и ÑтановитÑÑ Ð»Ð¸Ð´ÐµÑ€Ð¾Ð¼. -- Кандидат возвращаетÑÑ Ð² ÑÑ‚Ð°Ñ‚ÑƒÑ follower, так как другой инÑÑ‚Ð°Ð½Ñ ÑтановитÑÑ Ð»Ð¸Ð´ÐµÑ€Ð¾Ð¼. -- Кандидат оÑтаетÑÑ ÐºÐ°Ð½Ð´Ð¸Ð´Ð°Ñ‚Ð¾Ð¼, так как не удаетÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ лидера. Raft увеличивает терм еще раз и организует повторные выборы. - -**Лидер raft-группы (leader)** — избранный узел, который отвечает за обработку запроÑов и репликацию raft-журнала. - -### Raft-лидер -**Лидер в raft-группе** — Ñто один из узлов, который неÑет ответÑтвенноÑÑ‚ÑŒ за репликацию raft-журнала. Лидер избираетÑÑ Ð½Ð° голоÑовании и признаетÑÑ Ñ‚Ð°ÐºÐ¾Ð²Ñ‹Ð¼ вÑеми учаÑтниками голоÑованиÑ. Задача лидера ÑоÑтоит в приеме Ñообщений от клиентов клаÑтера и отправке Ñообщений на узлы клаÑтера таким образом, чтобы в любой момент времени вÑе узлы имели конÑиÑтентную, непротиворечивую верÑию raft-журнала. ЛидерÑтво в Raft предполагает, что вÑе оÑтальные узлы признают приоритет верÑии журнала, предлагаемую лидером. - -ЕÑли лидер ÑтановитÑÑ Ð½ÐµÐ´Ð¾Ñтупным, то алгоритм Raft организует выборы нового лидера Ñреди оÑтавшихÑÑ ÑƒÑ‡Ð°Ñтников. ЕÑли поÑле Ñтих выборов прежний лидер Ñнова приÑоединитÑÑ Ðº клаÑтеру, он уже будет иметь права обычного голоÑующего узла (но не лидера). - -### Ð ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ raft-журнала -**Ð ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ raft-журнала** нужна Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы на каждом узле клаÑтера (raft-группы) была Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ð°Ñ Ð¸ÑÑ‚Ð¾Ñ€Ð¸Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´. Когда лидеру группы нужно добавить в журнал новую команду, он Ñначала отправлÑет ее вÑем узлам, ждет Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñи (commit), и лишь затем добавлÑет Ñту команду в ÑобÑтвенный журнал. Ð’ Ñлучае, еÑли журнал обычного узла отличаетÑÑ Ð¾Ñ‚ журнала лидера, то лидер наÑтаивает на приоритете Ñвоего журнала и перезапиÑывает журнал обычного узла, ÑÑ‡Ð¸Ñ‚Ð°Ñ ÐµÐ³Ð¾ уÑтаревшим. -Ð”Ð»Ñ ÑƒÐ¿Ñ€Ð¾Ñ‰ÐµÐ½Ð¸Ñ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸ алгоритм Raft придерживаетÑÑ Ð´Ð²ÑƒÑ… правил при Ñравнении журналов разных узлов: - -- ЕÑли запиÑи имеют одинаковые индекÑÑ‹ и термы, то они Ñодержат одинаковые команды. -- ЕÑли запиÑи имеют одинаковые индекÑÑ‹ и термы, то ÑчитаетÑÑ, что вÑе предыдущие запиÑи ÑоответÑтвующих журналов одинаковы. - -Так как а) запиÑи в журнале не могут менÑÑ‚ÑŒ порÑдок и б) каждой запиÑи ÑоответÑтвует только один Ð¸Ð½Ð´ÐµÐºÑ Ð¸ терм, то указанные выше правила гарантирует конÑиÑтентноÑÑ‚ÑŒ журнала. - - - -### Web UI -**Веб-конÑоль** — Ñто вариант графичеÑкого интерфейÑа к функциÑм Picodata. Ð”Ð°Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´ÑиÑтема находитÑÑ Ð² разработке и будет предÑтавлена в Ñледующем релизе Picodata. Веб-конÑоль в наглÑдном виде отображает и позволÑет менÑÑ‚ÑŒ конфигурацию и ÑоÑтав клаÑтера, параметры отдельных узлов, Ñхему данных и Ñ‚.д. Веб-конÑоль ÑвлÑетÑÑ ÑƒÐ´Ð¾Ð±Ð½Ñ‹Ð¼ инÑтрументов локального и удаленного админиÑÑ‚Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Picodata. - -### CLI (Command-line interface) -**CLI** — Ñто Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð¹ Ñтроки Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка и ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ðº отдельными инÑтанÑами, так и вÑем клаÑтером Picodata. - -### ДиÑкавери (discovery) -**Discovery** — алгоритм, по которому инÑтанÑÑ‹ обнаруживают друг друга. Ðтот шаг необходим на Ñтарте каждого инÑтанÑа Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы клаÑтера. - -### Vshard -**Vshard** — библиотека из ÑкоÑиÑтемы СУБД Tarantool, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ð² Picodata Ð´Ð»Ñ Ð³Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð¾Ð³Ð¾ маÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ â€” ÑÐµÐ³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… по неÑкольким узлам в клаÑтере. Ðто ÑтановитÑÑ Ð²Ð°Ð¶Ð½Ñ‹Ð¼ по мере ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐ¼Ð° хранимых данных, ввода в Ñтрой новых узлов — Ñ‚.е. роÑта клаÑтера. -Библиотека Vshard вÑтроена в Picodata и ÑвлÑетÑÑ Ð½ÐµÐ¾Ñ‚ÑŠÐµÐ¼Ð»ÐµÐ¼Ð¾Ð¹ ее чаÑтью. -Ð’ клиентÑких интерфейÑах Vshard желательно прÑтать за фаÑадом, но при оÑтрой необходимоÑти ничто не помешает им воÑпользоватьÑÑ. - -## СущноÑти -Ð’ начале идет общее обозначение термина, затем в Ñкобках указан предпочтительный вариант ÑƒÐ¿Ð¾Ñ‚Ñ€ÐµÐ±Ð»ÐµÐ½Ð¸Ñ Ð² коде (без пробелов в “змеином региÑтреâ€). - -### ИнÑÑ‚Ð°Ð½Ñ (instance) <a name="instance"></a> -**Обозначение единицы клаÑтера СУБД и Ñервера приложений** -При опиÑании клаÑтера мы различаем программный и логичеÑкий уровни. - -Ðа программном уровне единицей клаÑтера ÑвлÑетÑÑ ÑкземплÑÑ€ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Picodata, также на техничеÑком жаргоне называемый инÑтанÑом. Среда Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть как виртуальной, так и физичеÑкой. Ð’ разрезе операционных ÑиÑтем каждый инÑÑ‚Ð°Ð½Ñ Ð¿Ð¾Ñ€Ð¾Ð¶Ð´Ð°ÐµÑ‚ два процеÑÑа: ÑобÑтвенно ÑкземплÑÑ€ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ вÑпомогательный процеÑÑ (supervisor), управлÑющий жизненным циклом первого. - -Ðа логичеÑком уровне единицей клаÑтера ÑвлÑетÑÑ ÑƒÐ·ÐµÐ». Под узлом, в завиÑимоÑти от контекÑта, может пониматьÑÑ ÐºÐ°Ðº Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð°Ñ Ð²Ñ‹Ñ‡Ð¸ÑÐ»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ð°, Ð¾Ð±Ð»Ð°Ð´Ð°ÑŽÑ‰Ð°Ñ Ð¿ÑƒÐ»Ð¾Ð¼ реÑурÑов (физичеÑкий Ñервер, Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð°, контейнер), так и программный ÑкземплÑÑ€ Picodata, уже входÑщий в ÑоÑтав клаÑтера. - -Также инÑÑ‚Ð°Ð½Ñ ÑвлÑетÑÑ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ¾Ð¹ в ÑоÑтаве репликаÑета и может входить в указанную при его запуÑке группу инÑтанÑов. - -### КлаÑтер (cluster) <a name="cluster"></a> -**КлаÑтер** — набор логичеÑких и программных узлов, ÑоÑтавлÑющих отдельную централизованно управлÑемую группу Ñ Ð¾Ð±Ñ‰Ð¸Ð¼ проÑтранÑтвом хранениÑ. - -**КлаÑтер** ÑвлÑетÑÑ Ð½Ð°Ð¸Ð±Ð¾Ð»ÐµÐµ крупной ÑущноÑтью в ÑиÑтеме хранениÑ, в некотором ÑмыÑле он и еÑÑ‚ÑŒ ÑиÑтема хранениÑ. Внутри клаÑтера находÑÑ‚ÑÑ Ð¸Ð½ÑтанÑÑ‹, объединенные в репликаÑеты (Ñм. ниже). Дополнительно, в Picodata иÑпользуетÑÑ Ð¿Ð¾Ð½Ñтие групп инÑтанÑов — отдельного ÑпоÑоба более гибко управлÑÑ‚ÑŒ большим чиÑлом инÑтанÑов в завиÑимоÑти от их ролей, характера нагрузки и топологии Ñети. - -Схематичное предÑтавление клаÑтера, в ÑоÑтаве которого еÑÑ‚ÑŒ некоторое чиÑло инÑтанÑов, репликаÑетов и групп инÑтанÑов, показано ниже.<a name="cluster_group"></a> - - - -### РепликаÑет (replicaset) <a name="replicaset"></a> -**РепликаÑет** — буквально «набор реплик», ÑкземплÑров приложений, в которых хранитÑÑ Ð¾Ð´Ð¸Ð½ и тот же набор данных. Реплика в ÑоÑтаве репликаÑета может быть в одном из двух ÑоÑтоÑний: - -- Ð°ÐºÑ‚Ð¸Ð²Ð½Ð°Ñ (active) — доÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ð½Ð° запиÑÑŒ, иногда ее называют маÑтером или лидером репликаÑета. -- Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð°Ñ (standby) — доÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на чтение, read-only. -Ð’ нормальных уÑловиÑÑ… в репликаÑете активной ÑвлÑетÑÑ Ñ€Ð¾Ð²Ð½Ð¾ одна реплика, но в отдельных ÑлучаÑÑ… их может быть неÑколько или не быть вообще. - -### Лидер (leader) -<p name="leader">Ð’ Picodata еÑÑ‚ÑŒ две разновидноÑти лидеров:</p> - -- “raft-лидер†— лидер raft-группы, -- и “репликаÑет-лидер†— лидер репликаÑета. - -Термин _лидер_ чаÑто путают Ñ _маÑтером_ применительно к репликации Tarantool. Ð’ Picodata они хоть и близки, но вÑе же отличаютÑÑ Ð¿Ð¾ ÑмыÑлу. Под _маÑтером_ Ñледует понимать инÑтанÑ, который выполнÑет пользовательÑкие DML-операции (insert / update / delete). Ðа практике чаще вÑего такой инÑÑ‚Ð°Ð½Ñ Ð¾Ð´Ð¸Ð½, и в таком Ñлучае оба термина опиÑывают один и тот же инÑÑ‚Ð°Ð½Ñ (отÑюда и путаница), но в Tarantool архитектурно заложена возможноÑÑ‚ÑŒ веÑти запиÑÑŒ на неÑкольких узлах репликаÑета одновременно — Ñ‚.н. режим мультимаÑтера (multi-master). Даже в таком режиме операции DDL (`create_space` и Ñ‚.д.) должен выполнÑÑ‚ÑŒ лишь один инÑÑ‚Ð°Ð½Ñ Ð¸Ð· вÑех, и здеÑÑŒ ÑтановитÑÑ Ð²Ð°Ð¶Ð½Ð¾ отличать _лидера_ от _маÑтера_. ÐеÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° то, что в Picodata режим мультимаÑтера пока не реализован, Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð¸ код должны иÑпользовать Ñти термины корректно. - -### Группа инÑтанÑов (instance_group) -**Группа инÑтанÑов** — Ñто логичеÑкое объединение инÑтанÑов Ñ Ð¾Ð±Ñ‰Ð¸Ð¼ фактором репликации и ролÑми. ÐšÐ°Ð¶Ð´Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° шардируетÑÑ Ð¸Ð½Ð´Ð¸Ð²Ð¸Ð´ÑƒÐ°Ð»ÑŒÐ½Ð¾. - -ÐšÐ¾Ð½Ñ†ÐµÐ¿Ñ†Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿ ÑвлÑетÑÑ Ð°Ð½Ð°Ð»Ð¾Ð³Ð¾Ð¼ [vshard_group](https://www.tarantool.io/ru/doc/latest/book/cartridge/cartridge_cli/commands/replicasets/#list-vshard-groups) из Cartridge, но дополнительно позволÑет назначать роли группам (в Cartridge роли наÑтраивалиÑÑŒ индивидуально Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ репликаÑета). - -**Группа инÑтанÑов** ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ крупным образованием, чем репликаÑет. См поÑÑнительную [картинку](glossary.md#cluster_group). - -Каждый шардированный ÑÐ¿ÐµÐ¹Ñ Ð¿Ñ€Ð¸Ð½Ð°Ð´Ð»ÐµÐ¶Ð¸Ñ‚ одной конкретной инÑтанÑ-группе. Ðа инÑтанÑах из других групп ÑÐ¿ÐµÐ¹Ñ Ñ„Ð¸Ð·Ð¸Ñ‡ÐµÑки не ÑоздаетÑÑ. Ðо Ñ‚.к. Ñхема данных ÑвлÑетÑÑ Ð¾Ð±Ñ‰ÐµÐ¹ на веÑÑŒ клаÑтер, Ñоздавать два ÑпейÑа Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼ именем в разных группах не разрешаетÑÑ. ПринадлежноÑÑ‚ÑŒ инÑтанÑ-группе определÑетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ð´Ð»Ñ ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ñ‹Ñ… ÑпейÑов, глобальные ÑпейÑÑ‹ ÑоздаютÑÑ Ð¿Ð¾Ð²Ñюду. - -### Домен отказа (failure_domain) -**Домен отказа** ÑвлÑетÑÑ Ð¿Ñ€Ð¸Ð·Ð½Ð°ÐºÐ¾Ð¼ физичеÑкого раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñервера, на котором запущен инÑтанÑ. Указание домена отказа позволÑет обозначить наличие единой точки отказа у двух инÑтанÑов. СмыÑл данного параметра ÑоÑтоит в том, чтобы в один репликаÑет попадали инÑтанÑÑ‹ из разных физичеÑких локаций, что повышает отказоуÑтойчивоÑÑ‚ÑŒ клаÑтера. - -**Домен отказа** предÑтавлÑет Ñобой набор пар “ключ=значениеâ€, которые ÑоответÑтвуют отдельным зонам (географичеÑкий регион, датацентр, Ñтойка и Ñ‚.д.). Зоны задаютÑÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¼ иÑÑ…Ð¾Ð´Ñ Ð¸Ð· фактичеÑкой конфигурации оборудованиÑ, будь то виртуальные машины в облаке (`“region=euâ€`) или физичеÑкие Ñервера (`“dc=mskâ€`). Домен отказа может включать неÑколько зон (`“dc=msk,srv=msk-1â€`). - -Можно иÑпользовать любые ключи и значениÑ. Picodata не делает предположений об иерархии зон или их физичеÑком ÑмыÑле и проÑто Ñравнивает Ñтроки. Тем не менее, чтобы избежать человечеÑких ошибок, Picodata требует, чтобы набор зон (ключей) на вÑех инÑтанÑах был одинаковым. - -ЕÑли домены отказа двух инÑтанÑов имеют Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одну общую зону (и ключ, и значение), то допуÑкаетÑÑ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ÑÑ‚ÑŒ одновременной потери ÑвÑзи Ñ Ð¾Ð±Ð¾Ð¸Ð¼Ð¸. ПоÑтому инÑтанÑÑ‹, делÑщие общую зону, не будут объединены в репликаÑет. Picodata также ÑтремитÑÑ Ñ€Ð°Ñпределить голоÑующие raft-узлы таким образом, чтобы их домены отказа имели по минимуму общих зон. - -### Фактор репликации (replication_factor) -**Фактор репликации** — чиÑло инÑтанÑов в репликаÑете. ЗадаетÑÑ Ð¾Ð±Ñ‰Ð¸Ð¼ на группу инÑтанÑов (так же как и набор ролей). Отредактировать фактор репликации, Ñохраненный в конфигурации клаÑтера, можно командой `picodata set-replication-factor`. Редактирование конфигурации ÑказываетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на вновь добавлÑемых инÑтанÑах, но не затрагивает уже работающие. - - -### Бакет (bucket) <a name="bucket"></a> -**Bucket (бакет)** — Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð½ÐµÐ´ÐµÐ»Ð¸Ð¼Ð°Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ð° Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…, обеÑÐ¿ÐµÑ‡Ð¸Ð²Ð°ÑŽÑ‰Ð°Ñ Ð¸Ñ… локальноÑÑ‚ÑŒ (Ñ‚. е. нахождение на каком-то одном репликаÑете). - -### Ð¡Ð¿ÐµÐ¹Ñ (space) -**Space (ÑпейÑ)** — проÑтранÑтво Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…. Ð’ резидентных СУБД ÑÐ¿ÐµÐ¹Ñ ÑвлÑетÑÑ Ñинонимом таблицы из релÑционных СУБД. Ð’ Picodata еÑÑ‚ÑŒ Ñледующие виды ÑпейÑов: - -1. Глобальные (_global_) — их Ñодержимое реплицируетÑÑ Ð½Ð° веÑÑŒ клаÑтер. -2. Шардированные (_sharded_) — каждый репликаÑет хранит лишь чаÑÑ‚ÑŒ общего - набора данных. Данные реплицируютÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ репликаÑета. - -Метаданные вÑех ÑпейÑов Picodata приÑутÑтвуют на вÑех узлах клаÑтера. -ФактичеÑки Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ ÑущеÑтвует понÑÑ‚Ð¸Ñ â€œÐ½Ðµ клаÑтерных†-ÑпейÑов, он лишь выбирает между ÑтратегиÑми ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ репликации -тех ÑпейÑов, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ он работает. - -### Ð˜Ð½Ð´ÐµÐºÑ (index) -**ИндекÑ** — Ñто ÑÐ¿ÐµÑ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ñтруктура данных, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñ…Ñ€Ð°Ð½Ð¸Ñ‚ группу ключевых значений и указателей. Ð˜Ð½Ð´ÐµÐºÑ ÑтроитÑÑ Ð¿Ð¾ какому-либо одному Ñтолбцу таблицы и иÑпользуетÑÑ Ð´Ð»Ñ Ñффективного поиÑка значений Ñтого Ñтолбца. У каждого ÑпейÑа обÑзательно должен быть первичный Ð¸Ð½Ð´ÐµÐºÑ Ð¸ опционально некоторое чиÑло вторичных индекÑов. - -### LSN (log sequence number) -Термин **LSN** (log sequence number) отноÑитÑÑ Ðº архитектурным оÑобенноÑÑ‚Ñм Tarantool, в котором вÑе Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð‘Ð” фикÑируютÑÑ Ð² журнале упреждающей запиÑи (WAL, write-ahead log) в виде отдельных запиÑей. ÐšÐ°Ð¶Ð´Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ предÑтавлÑет Ñобой Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° изменение данных (`insert` | `update` | `delete`) и маркируетÑÑ Ð¼Ð¾Ð½Ð¾Ñ‚Ð¾Ð½Ð½Ð¾ возраÑтающим номером LSN. Ðаибольший номер обозначает номер наиболее Ñвежей запиÑи, находÑщейÑÑ Ð² конце журнала WAL. - -### Vclock (vector clock) -Ð ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð² Tarantool предполагает обмен запиÑÑми между репликами в репликаÑете. Ð‘Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ñтому обмену, на каждой отдельной реплике имеетÑÑ Ñпециальный набор запиÑей, полученный от разных реплик Ñ Ñ€Ð°Ð·Ð½Ñ‹Ð¼Ð¸ LSN — Ñто и еÑÑ‚ÑŒ **Vclock** (vector clock, векторные чаÑÑ‹). Vclock опиÑывает ÑоÑтоÑние БД Ð´Ð»Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ инÑтанÑа в репликаÑете. - -Vclock репликаÑет-лидера играет важную роль в поддержании конÑиÑтентноÑти при переключении лидера (consistent switchover). Ð’ Ñлучае аварийного Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ (фейловера, failover), когда конÑиÑтентноÑÑ‚ÑŒ Ñохранить невозможно и она жертвуетÑÑ Ð² угоду доÑтупноÑти, отÑлеживание vclock позволÑет Ñтроить метрики точек воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ (RPO, recovery point objective). - -### Сетевой Ð°Ð´Ñ€ÐµÑ (address) <a name="address"></a> -**Сетевой адреÑ** — Ñто ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ `host:port`, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ð´Ð»Ñ ÑвÑзи инÑтанÑов друг Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¼ по Ñети. Другие Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ ÑвÑзки `host:port` (например URL, URI) мы ÑтараемÑÑ Ð¸Ñкоренить. РаÑÑˆÐ¸Ñ€ÐµÐ½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ `user:pass@host:port` вÑе равно определÑетÑÑ Ñ‚ÐµÑ€Ð¼Ð¸Ð½Ð¾Ð¼ _адреÑ_. - -### Грейд (grade) -**Grade (грейд)** — Ñпецифичный Ð´Ð»Ñ Picodata ÑпоÑоб Ð¾Ð±Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ Ð¸Ð½ÑтанÑа. Грейд отражает то, как инÑÑ‚Ð°Ð½Ñ Ñконфигурирован его ÑоÑедÑми. СущеÑтвуют текущий (`current`) и целевой (`target`) типы грейдов. За приведение первого ко второму отвечает governor (губернатор). - -### Губернатор (governor) -**Governor (губернатор)** — внутреннÑÑ Ñ†ÐµÐ½Ñ‚Ñ€Ð°Ð»Ð¸Ð·Ð¾Ð²Ð°Ð½Ð½Ð°Ñ ÑущноÑÑ‚ÑŒ, управлÑÑŽÑ‰Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñми и жизненными циклами инÑтанÑов в ÑоответÑтвие Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñми их грейдов. "Губернатор" выполнÑетÑÑ Ð½Ð° лидере raft-группы. - -### Крейт (crate) -**Крейт** (буквально “Ñщикâ€) — Ð½Ð°Ð¸Ð¼ÐµÐ½ÑŒÑˆÐ°Ñ Ð»Ð¾Ð³Ð¸Ñ‡ÐµÑÐºÐ°Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ð° проекта, напиÑанного на Rust. С точки Ð·Ñ€ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¸Ð»Ñтора rustc, любой отдельный фрагмент кода ÑвлÑетÑÑ ÐºÑ€ÐµÐ¹Ñ‚Ð¾Ð¼. Из одного крейта может быть Ñкомпилирован бинарный иÑполнÑемый файл, либо разделÑÐµÐ¼Ð°Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñ‚ÐµÐºÐ°. ÐеÑколько крейтов могут вмеÑте ÑоÑтавлÑÑ‚ÑŒ пакет (package). Пакет может ÑоÑтоÑÑ‚ÑŒ также и из одного крейта. ЕÑли крейтов в пакете неÑколько, то из них только один может предоÑтавлÑÑ‚ÑŒ разделÑемую библиотеку. - -### Снапшот (snapshot) <a name="snapshot"></a> -**Снапшот**, в Ñамом широком ÑмыÑле Ñтого Ñлова — Ñто Ñнимок ÑоÑтоÑÐ½Ð¸Ñ Ñ€Ð°Ñпределенного конечного автомата. Ð’ контекÑте Picodata можно говорить о двух незавиÑимых (почти) раÑпределенных конечных автоматах, и, ÑоответÑтвенно, о двух видах Ñнапшотов — Tarantool и Raft. - -Picodata прилагает вÑе уÑилиÑ, чтобы Ñти ÑоÑтоÑÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð¸ одинаковыми на каждом узле клаÑтера, но Ñ‚.к. Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð° нодах проиÑходÑÑ‚ через применение команд из журнала (WAL или raft), то даже в штатном режиме ÑлучаютÑÑ Ð½Ðµ одновременно. - -Более детально, ÑоÑтоÑние инÑтанÑа включает в ÑÐµÐ±Ñ Ð´Ð²Ðµ чаÑти: - -- перÑиÑтентные данные, которые можно Ñериализовать и Ñохранить на диÑк; -- транзиторное (transient) ÑоÑтоÑние — вÑе Ñтруктуры, которые Tarantool Ñтроит в оперативной памÑти Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ DML-запроÑов. - -См. также: - -- [RFC — Storage schema — Raft snapshot](https://docs.google.com/document/d/1MEpGnpKKj6WezLKytvvonZzbpy1tlWtAK5ccxaeAOrE/edit#heading=h.687c3wywf9ub) -- [Tarantool — Persistence](https://www.tarantool.io/en/doc/latest/concepts/data_model/persistence/) - - -## ПроцеÑÑÑ‹ и алгоритмы -### ÐšÐ¾Ð¼Ð¿Ð°ÐºÑ‚Ð¸Ð·Ð°Ñ†Ð¸Ñ raft-журнала (raft log compaction) -**КомпактизациÑ** — процеÑÑ, не допуÑкающий беÑконтрольного роÑта журнала запиÑей Raft. ÐšÐ¾Ð¼Ð¿Ð°ÐºÑ‚Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð°ÐºÐ»ÑŽÑ‡Ð°ÐµÑ‚ÑÑ Ð² удалении чаÑти журнала, отноÑÑщейÑÑ Ðº Ñделанному ранее Ñнапшоту. - -### Создание Ñнапшотов (snapshotting) -**Создание Ñнапшотов** — процеÑÑ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¸Ñ‡ÐµÑкого ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ Ð¸Ð½ÑтанÑа на жеÑткий диÑк. Ðаличие Ñнапшотов (Ñ‚.е. Ñнимков ÑоÑтоÑниÑ) позволÑÑŽÑ‚ воÑÑтановить инÑÑ‚Ð°Ð½Ñ Ð² прежнем виде поÑле его перезапуÑка. - -### ФенÑинг (fencing) -**ФенÑинг** — Ñто подпиÑÑŒ вÑех запроÑов в клаÑтере номером Ñпохи или терма, и отказ обÑлуживать запроÑÑ‹ Ñ ÑƒÑтаревшей Ñпохой. Данный инÑтрумент иÑпользуетÑÑ Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы раÑпределенной блокировки, Ñ‚.е. Ñитуации, когда из неÑкольких узлов нужно гарантированно выбрать один Ð´Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа. - -### CaS (compare and swap) <a name="cas"></a> -**Compare and swap** — оÑобый алгоритм в ÑоÑтаве Picodata. Он обеÑпечивает уровень изолÑции транзакций [serializable](glossary.md#isolation), тем Ñамым не допуÑÐºÐ°Ñ Ñлучаев неÑоглаÑованноÑти данных в результате Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ð½ÐºÑƒÑ€Ð¸Ñ€ÑƒÑŽÑ‰Ð¸Ñ… запроÑов/транзакций. Таким Ñлучаем, например, может быть ÑитуациÑ, когда одна Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ð·Ð°Ñ‚Ð¸Ñ€Ð°ÐµÑ‚ результат дейÑÑ‚Ð²Ð¸Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹, выполнÑющейÑÑ Ð² тоже времÑ. _Compare and swap_ решает Ñту проблему Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ проверки предиката, Ñ‚.е. менÑет данные какого-либо параметра клаÑтера только в том Ñлучае, еÑли иÑходное ожидаемое значение Ñтого параметра ÑоответÑтвует иÑходному фактичеÑкому. -ТехничеÑки данный алгоритм реализован в виде хранимой процедуры `proc_cas()`. - -### БутÑтрап (bootstrap) <a name="bootstrap"></a> -**Bootstrap** — процеÑÑ Ð¿ÐµÑ€Ð²Ð¾Ð½Ð°Ñ‡Ð°Ð»ÑŒÐ½Ð¾Ð³Ð¾ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ñ€Ð¾Ð·Ð½ÐµÐ½Ð½Ñ‹Ñ… инÑтанÑов в единый клаÑтер. Ð’ контекÑте Picodata речь обычно идет о бутÑтрапе инÑтанÑа, когда инÑÑ‚Ð°Ð½Ñ Ð·Ð°Ð¿ÑƒÑкаетÑÑ Ð² чиÑтой директории без Ñнапшотов. При необходимоÑти можно уточнить, бутÑтрапитÑÑ _лидер реплиаÑета_ или _read-only-реплика_ — алгоритмы Ð´Ð»Ñ Ð½Ð¸Ñ… отличаютÑÑ. - -Случай, когда инÑÑ‚Ð°Ð½Ñ Ð·Ð°Ð¿ÑƒÑкаетÑÑ Ð½Ð° ÑущеÑтвующих Ñнапшотах, называетÑÑ _воÑÑтановлением из Ñнапшота_ (recovery from a snapshot). Ðтот процеÑÑ ÑопутÑтвует перезапуÑку инÑтанÑа. - -Другой ÑкÑплуатационный Ñценарий, когда при перезапуÑке удалÑÑŽÑ‚ÑÑ Ð²Ñе данные инÑтанÑа, называют _ребутÑтрапом_ (rebootstrap), Ñ‚.е. повторным запуÑком. РебутÑтрап вÑегда ÑопровождаетÑÑ Ñменой `raft_id`, Ñ…Ð¾Ñ‚Ñ `instance_id` может при Ñтом переиÑпользоватьÑÑ. - -_БутÑтрап клаÑтера_ — бутÑтрап первого инÑтанÑа + [приÑоединение](#joining) некоторого количеÑтва других. - -_БутÑтрап репликаÑета_ — бутÑтрап лидера репликаÑета + приÑоединение к нему реплик. - -### ПриÑоединение (joining) инÑтанÑа к клаÑтеру <a name="joining"></a> -_ПриÑоединение инÑтанÑа_ близко по ÑмыÑлу к [бутÑтрапу](#bootstrap), но делает акцент на процеÑÑах, проиÑходÑщих в Ñамом клаÑтере. Чтобы инÑÑ‚Ð°Ð½Ñ Ð¼Ð¾Ð³ приÑоединитьÑÑ, другие (уже ÑущеÑтвующие) члены клаÑтера должны Ñначала Ñохранить информацию о нем в raft-журнал. - -### Ð ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ (replication) <a name="replication"></a> -**РепликациÑ** — один из механизмов [актуализации](#actualization) данных между инÑтанÑами. Ð’ Picodata ÑущеÑтвует два вида репликации: Tarantool (внутри [репликаÑета](#replicaset)) и Raft (Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð½Ð° веÑÑŒ клаÑтер). Под термином Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ð¾ имеют в виду переÑылку запиÑей журналов (`wal` или `raft` в завиÑимоÑти от того, о каком виде репликации идет речь). - -### ÐÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ (catch-up) инÑтанÑа <a name="actualization"></a> -ÐÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ â€” Ñто по Ñути ÑÐ¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… между инÑтанÑами. СущеÑтвует два механизма актуализации: поÑредÑтвом [репликации](#replication) журнала (catch-up by log replication), и поÑредÑтвом Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ [Ñнапшота](#snapshot) (catch-up by snapshot). - -ÐÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñнапшотом поддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ð´Ð»Ñ Raft, но не Ð´Ð»Ñ Tarantool. Она требуетÑÑ, когда на raft-лидере отÑутÑтвуют нужные запиÑи в raft-журнале. ЕÑли Ð°Ð½Ð°Ð»Ð¾Ð³Ð¸Ñ‡Ð½Ð°Ñ ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ñходит Ñ WAL Tarantool, пользователю не оÑтаетÑÑ Ð²Ñ‹Ð±Ð¾Ñ€Ð° кроме как делать [ребутÑтрап](#bootstrap) инÑтанÑа. - -### Proc API - -**Proc API** — Stored Procedures API (хранимые процедуры). Хранимыми процедурам ÑвлÑÑŽÑ‚ÑÑ Rust-функции Ñ Ð°Ñ‚Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð¼ `#[tarantool::proc]`. Процедуры могут быть вызваны Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ любого тарантул-коннектора, в чаÑтноÑти, Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Lua-Ð¼Ð¾Ð´ÑƒÐ»Ñ `net.box` или Rust-Ð¼Ð¾Ð´ÑƒÐ»Ñ `tarantool::network::client`. Ð’ Picodata хранимые процедуры иÑпользуютÑÑ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ð¸ между инÑтанÑами. - -Примерами хранимых процедур в Picodata могут Ñлужить: -- `.proc_cas` (низкоуровневый вызов механизма [Compare and Swap](#cas) на выбранном инÑтанÑе); -- `.proc_read_index` (получение текущего Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ raft-индекÑа выбранного инÑтанÑа). - -## Общие концепции -### ОтказоуÑтойчивоÑÑ‚ÑŒ <a name="failsoft"></a> -**ОтказоуÑтойчивоÑÑ‚ÑŒ** — ÑвойÑтво клаÑтера ÑохранÑÑ‚ÑŒ наличие и доÑтупноÑÑ‚ÑŒ данных при выходе из ÑÑ‚Ñ€Ð¾Ñ Ñ‡Ð°Ñти узлов. Ð’ Picodata отказоуÑтойчивоÑÑ‚ÑŒ обеÑпечиваетÑÑ Ñ€ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸ÐµÐ¹ и грамотным проектированием алгоритмов. - -### Горизонтальное маÑштабирование <a name="sharding"></a> -**Горизонтальное маÑштабирование** (оно же Ñегментирование или шардирование, sharding) — подход, предполагающий разделение данных на Ñегменты (бакеты, buckets), которые могут хранитьÑÑ Ð½Ð° отдельных репликаÑетах клаÑтера. С точки Ð·Ñ€ÐµÐ½Ð¸Ñ Ð½Ð°Ð±Ð¾Ñ€Ð° хранимых данных, каждый репликаÑет называетÑÑ ÑˆÐ°Ñ€Ð´Ð¾Ð¼. Деление на шарды — Ñто еще один вариант логичеÑкого Ð´ÐµÐ»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ñтера, но без привÑзки к Ñерверам, на которых выполнÑÑŽÑ‚ÑÑ Ð¸Ð½ÑтанÑÑ‹. - -### ЛинеаризуемоÑÑ‚ÑŒ <a name="linearizability"></a> -Ð’ контекÑте раÑпределенных баз данных **линеаризуемоÑÑ‚ÑŒ** обозначает ÑвойÑтво хранилища обеÑпечивать целоÑтноÑÑ‚ÑŒ и ÑоглаÑованноÑÑ‚ÑŒ данных на разных репликах БД. ЛинеаризуемоÑÑ‚ÑŒ в Picodata обеÑпечиваетÑÑ Ð°Ð»Ð³Ð¾Ñ€Ð¸Ñ‚Ð¼Ð¾Ð¼ конÑенÑуÑа Raft, который обновлÑет данные на маÑтер-реплике только поÑле того, как они запиÑаны на резервные реплики. - - -Ð’ Ñхеме данных клаÑтера Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ шардированного ÑпейÑа (таблицы) задаетÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€ раÑÐ¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ â€” _ключ шардированиÑ_, ÑоÑтоÑщий из одной или неÑкольких колонок. Пример: - -``` -sharding_key: - - id - - name -``` -### Уровень изолÑции транзакций <a name="isolation"></a> -Уровень изолÑции транзакций ÑвлÑетÑÑ Ð¾Ð´Ð½Ð¸Ð¼ из компонентов ACID (_atomicity, consistency, isolation, durability_), который предÑтавлÑет Ñобой набор требований к СУБД ÑоглаÑно Ñтандарту [ANSI/ISO SQL](https://en.wikipedia.org/wiki/ISO/IEC_9075). Уровень изолÑции транзакций определÑет Ñтепень ÑтрогоÑти, которую СУБД применÑет к возможной неÑоглаÑованноÑти данных при иÑполнении неÑкольких транзакций одновременно. Различают Ñледующие уровни (по мере Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð¸Ñ ÑтрогоÑти и уÑÐ¸Ð»ÐµÐ½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²Ð¾Ðº): - -- **Read uncommitted**. Ðизший уровень изолÑции, допуÑкающий чтение незафикÑированных данных. Ð’ результат Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ попаÑÑ‚ÑŒ данные от других, еще не завершившихÑÑ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¹ запиÑи. -- **Read committed.** Чтение уже зафикÑированных данных. Данный уровень гарантирует, что в момент Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ðµ были ранее зафикÑированы, однако позволÑет изменÑÑ‚ÑŒ данные Ñразу поÑле Ñтого. Т.е. повторное чтение внутри транзакции уже не гарантирует получение такого же набора данных. -- **Repeatable read**. ПовторÑемое чтение данных, при которых Ð½ÐµÐ»ÑŒÐ·Ñ Ð¸Ð·Ð¼ÐµÐ½ÑÑ‚ÑŒ данные до тех пор, пока Ñ‡Ð¸Ñ‚Ð°ÑŽÑ‰Ð°Ñ Ð¸Ñ… Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ð½Ðµ завершилаÑÑŒ. -- **Serializable**. ÐаивыÑший уровень изолÑции, предполагающий полное упорÑдочивание (Ñериализацию) транзакций. Результат Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð½ÐµÑкольких параллельных транзакций должен быть таким, как еÑли бы они выполнÑлиÑÑŒ поÑледовательно. - -### Read phenomena (проблемы Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…) -Уровни изолÑции транзакций ÑущеÑтвуют Ð´Ð»Ñ ÑƒÑ‡ÐµÑ‚Ð° и Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñледующих проблем, возникающих при одновременном (параллельном) чтении данных: - -- **Lost update** (потерÑнное обновление). При одновременном изменении данных разными транзакциÑми терÑÑŽÑ‚ÑÑ Ð²Ñе изменениÑ, кроме поÑледнего. -- **Dirty read** (неточное чтение). Ð’ результат Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð¾Ð±Ð°Ð²ÑÑ‚ÑÑ Ð´Ð°Ð½Ð½Ñ‹Ðµ, привнеÑенные другой транзакцией, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð²Ð¿Ð¾ÑледÑтвие будет отменена (не получит ÑтатуÑа committed). -- **Non-repeatable read** (проблема однократного чтениÑ). ÐеÑколько поÑледовательных операций Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¾Ð´Ð½Ð¸Ñ… и тех же данных дают разный результат, Ñ‚.к. между ними вклинилаÑÑŒ ÑтороннÑÑ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñи. -- **Phantom read** (фантомное чтение). Проблема Ñхожа Ñ Ð¿Ñ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰ÐµÐ¹ и также каÑаетÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… поÑле начала операции чтениÑ. Однако, “фантомное чтение†предполагает изменение Ñамой выборки (Ð¿Ð°Ñ€Ð°Ð»Ð»ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñи добавлÑет/удалÑет Ñтроки). - - diff --git a/docs/sharding.md b/docs/sharding.md deleted file mode 100644 index 39061f1fd6..0000000000 --- a/docs/sharding.md +++ /dev/null @@ -1,68 +0,0 @@ -# Шардирование данных в клаÑтере - -Ð’ данном разделе раÑÑматриваетÑÑ Ð¿Ñ€Ð¸Ð¼ÐµÑ€ ÑˆÐ°Ñ€Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… в клаÑтере Picodata. - -1. ЗапуÑтим клаÑтер из двух ÑкземплÑров - - ```sh - picodata run --data-dir tmp/i1 --listen localhost:3301 --instance-id i1 --peer localhost:3301,localhost:3302 - picodata run --data-dir tmp/i2 --listen localhost:3302 --instance-id i2 --peer localhost:3301,localhost:3302 - ``` - -1. Создадим шардированную таблицу. Ðа любом инÑтанÑе выполним команды - - ```lua - pico.add_migration(1, 'create table t2(a int, "bucket_id" unsigned, primary key (a));') - pico.add_migration(2, 'create index "bucket_id" on t2 ("bucket_id");') - pico.migrate(2) - ``` - - Ключевым моментом ÑвлÑетÑÑ Ñоздание индекÑа `bucket_id` в ÑпейÑе - (его наличие помечает ÑÐ¿ÐµÐ¹Ñ ÐºÐ°Ðº шардированный). Важно не забыть - кавычки, Ñ‚.к. vshard ожидает увидеть его в нижнем региÑтре. - - Ðа текущий момент в Picodata иÑпользуетÑÑ Ð¿Ñ€ÐµÐ´Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ð¾Ðµ количеÑтво бакетов - 3000. - -1. Заполним шардированный ÑÐ¿ÐµÐ¹Ñ Ñ‚ÐµÑтовыми данными. Ðа любом инÑтанÑе - выполним команды. - - ```lua - for i=1,3000 do vshard.router.callrw(i, 'box.space.T2:insert', {{i, i}}) end - ``` - -1. Проверим, что данные поровну раÑпределилиÑÑŒ между ÑкземплÑрами. Ðа - каждом из них (`i1`, `i2`) выполним запроÑ: - - ```lua - box.space.T2:len() - ``` - - Ð’ результате мы получим по 1500 бакетов (и Ñтолько же таплов) на - каждом ÑкземплÑре: - - ```yaml - --- - - 1500 - ... - ``` - -1. Добавим еще один ÑкземплÑÑ€ - - ```sh - picodata run --data-dir tmp/i3 --listen localhost:3303 --instance-id i3 --peer localhost:3301,localhost:3302 - ``` - -1. Проверим, что данные перераÑпределилиÑÑŒ между Ñ‚Ñ€ÐµÐ¼Ñ ÑкземплÑрами. Ðа - каждом из них (`i1`, `i2` и `i3`) выполним запроÑ: - - ```lua - box.space.T2:len() - ``` - - Ð’ результате мы получим по 1000 бакетов на каждом ÑкземплÑре: - - ```yaml - --- - - 1000 - ... - ``` diff --git a/docs/topology.md b/docs/topology.md deleted file mode 100644 index 2764553623..0000000000 --- a/docs/topology.md +++ /dev/null @@ -1,247 +0,0 @@ -# Ð¢Ð¾Ð¿Ð¾Ð»Ð¾Ð³Ð¸Ñ ÐºÐ»Ð°Ñтера Picodata - -Ð’ данном документе раÑÑматриваютÑÑ Ñ€Ð°Ð·Ð»Ð¸Ñ‡Ð½Ñ‹Ðµ Ñценарии работы Ñ ÐºÐ»Ð°Ñтером. Ð’Ñе они оÑнованы на одном и том же принципе: запуÑке и объединении отдельных ÑкземплÑров Picodata в раÑпределенный клаÑтер. При Ñтом ÑложноÑÑ‚ÑŒ Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð¾ÑпоÑобноÑти клаÑтера завиÑит только от ÑложноÑти его топологии. - -[TOC] - ---- - -## Минимальный вариант клаÑтера - -Picodata может Ñоздать клаÑтер, ÑоÑтоÑщий вÑего из одного ÑкземплÑра/инÑтанÑа. ОбÑзательных параметров у него нет, что позволÑет ÑвеÑти запуÑк к выполнению вÑего одной проÑтой команды: - -``` -picodata run -``` - -Можно добавлÑÑ‚ÑŒ Ñколько угодно поÑледующих инcтанÑов — вÑе они будут подключатьÑÑ Ðº Ñтому клаÑтеру. Каждому интанÑу Ñледует задать отдельную рабочую директорию (параметр `--data-dir`), а также указать Ð°Ð´Ñ€ÐµÑ Ð¸ порт Ð´Ð»Ñ Ð¿Ñ€Ð¸ÐµÐ¼Ð° Ñоединений (параметр `--listen`) в формате `<HOST>:<PORT>`. Фактор репликации по умолчанию равен 1 — каждый инÑÑ‚Ð°Ð½Ñ Ð¾Ð±Ñ€Ð°Ð·ÑƒÐµÑ‚ отдельный репликаÑет. ЕÑли Ð´Ð»Ñ `--listen` указать только порт, то будет иÑпользован IP-Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾ умолчанию (127.0.0.1): - -``` -picodata run --data-dir i1 --listen :3301 -picodata run --data-dir i2 --listen :3302 -picodata run --data-dir i3 --listen :3303 -``` - -## КлаÑтер на неÑкольких Ñерверах - -Выше был показан запуÑк Picodata на одном Ñервере, что удобно Ð´Ð»Ñ Ñ‚ÐµÑÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ отладки, но не отражает Ñценариев полноценного иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ñтера. ПоÑтому пора запуÑтить Picodata на неÑкольких Ñерверах. Предположим, что их два: `192.168.0.1` и `192.168.0.2`. ПорÑдок запуÑка будет Ñледующим: - -Ðа `192.168.0.1`: -```shell -picodata run --listen 192.168.0.1:3301 -``` - -Ðа `192.168.0.2`: -```shell -picodata run --listen 192.168.0.2:3301 --peer 192.168.0.1:3301 -``` - -Ðа что нужно обратить внимание: - -Во-первых, Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° `--listen` вмеÑто Ñтандартного Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ `127.0.0.1` надо указать конкретный адреÑ. Формат адреÑа допуÑкает ÑƒÐ¿Ñ€Ð¾Ñ‰ÐµÐ½Ð¸Ñ â€” можно указать только хоÑÑ‚ `192.168.0.1` (порт по умолчанию `:3301`), или только порт, но Ð´Ð»Ñ Ð½Ð°Ð³Ð»ÑдноÑти лучше иÑпользовать полный формат `<HOST>:<PORT>`. - -Значение параметра `--listen` не хранитÑÑ Ð² клаÑтерной конфигурации и может менÑÑ‚ÑŒÑÑ Ð¿Ñ€Ð¸ перезапуÑке инÑтанÑа. - -Во-вторых, надо дать инÑтанÑам возможноÑÑ‚ÑŒ обнаружить друг друга Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾ чтобы механизм [discovery](discovery.md) правильно Ñобрал вÑе найденные ÑкземплÑры Picodata в один клаÑтер. Ð”Ð»Ñ Ñтого в параметре `--peer` нужно указать Ð°Ð´Ñ€ÐµÑ ÐºÐ°ÐºÐ¾Ð³Ð¾-либо ÑоÑеднего инÑтанÑа. По умолчанию значение параметра `--peer` уÑтановлено в `127.0.0.1:3301`. Параметр `--peer` не влиÑет больше ни на что, кроме механизма Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… инÑтанÑов. - -Параметр `--advertise` иÑпользуетÑÑ Ð´Ð»Ñ ÑƒÑтановки публичного IP-адреÑа и порта инÑтанÑа. Параметр Ñообщает, по какому адреÑу оÑтальные инÑтанÑÑ‹ должны обращатьÑÑ Ðº текущему. По умолчанию он равен `--listen`, поÑтому в примере выше не упоминаетÑÑ. Ðо, например, в Ñлучае `--listen 0.0.0.0` его придетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ Ñвно: - -```shell -picodata run --listen 0.0.0.0:3301 --advertise 192.168.0.1:3301 -``` - -Значение параметра `--advertise` анонÑируетÑÑ ÐºÐ»Ð°Ñтеру при запуÑке инÑтанÑа. Его можно поменÑÑ‚ÑŒ при перезапуÑке инÑтанÑа или в процеÑÑе его работы командой `picodata set-advertise`. - -## Питомцы против Ñтада - -Чтобы проще было отличать инÑтанÑÑ‹ друг от друга, им можно давать имена: - -``` -picodata run --instance-id barsik -``` - -ЕÑли Ð¸Ð¼Ñ Ð½Ðµ дать, то оно будет Ñгенерировано автоматичеÑки в момент Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² клаÑтер. Ð˜Ð¼Ñ Ð¸Ð½ÑтанÑа задаетÑÑ Ð¾Ð´Ð¸Ð½ раз и не может быть изменено в дальнейшем (например, оно поÑтоÑнно ÑохранÑетÑÑ Ð² Ñнапшотах инÑтанÑа). Ð’ клаÑтере Ð½ÐµÐ»ÑŒÐ·Ñ Ð¸Ð¼ÐµÑ‚ÑŒ два инÑтанÑа Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼ именем — второй инÑÑ‚Ð°Ð½Ñ Ñразу поÑле запуÑка получит ошибку при добавлении в клаÑтер. Тем не менее, Ð¸Ð¼Ñ Ð¼Ð¾Ð¶Ð½Ð¾ повторно иÑпользовать еÑли предварительно иÑключить первый инÑÑ‚Ð°Ð½Ñ Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем из клаÑтера. Ðто делаетÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾Ð¹ `picodata expel barsik`. - -## Группы и роли - -До Ñих пор раÑÑматриваемый клаÑтер был гомогенным. Ð’Ñе инÑтанÑÑ‹ были одинаковы по функциональноÑти — хранили данные, обрабатывали запроÑÑ‹. Ð’ промышленной ÑкÑплуатации Ñти роли почти вÑегда требуетÑÑ Ñ€Ð°Ð·Ð´ÐµÐ»ÑÑ‚ÑŒ, чтобы Ñффективнее иÑпользовать реÑурÑÑ‹ оборудованиÑ. Под хранение выделÑÑŽÑ‚ÑÑ Ñерверы Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð¼ объемом памÑти, Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ запроÑов Ñто не требуетÑÑ. - -Ð’ Picodata Ð´Ð»Ñ Ñтих целей Ñлужит понÑтие групп инÑтанÑов. ПринадлежноÑÑ‚ÑŒ инÑтанÑа той или иной группе задаетÑÑ Ð¿Ñ€Ð¸ добавлении в клаÑтер параметром `--group` и впоÑледÑтвии не может быть изменена. По умолчанию клаÑтер ÑоÑтоит из одной группы "common". - -ФункциональноÑÑ‚ÑŒ инÑтанÑов определÑетÑÑ Ð½Ð°Ð±Ð¾Ñ€Ð¾Ð¼ ролей. Ðа данный момент ÑущеÑтвует две роли: - -- storage — позволÑет хранить шардированные данные на инÑтанÑе. -- router — реализует логику доÑтупа к шардированным данным. - -По умолчанию инÑÑ‚Ð°Ð½Ñ Ð¸ÑполнÑет обе роли одновременно, но его можно ограничить Ñвным указанием одной из них: - -``` -picodata run --role storage -picodata run --role router -``` - -Важно то, что обе Ñти роли отноÑÑÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ к шардированию. Так, отключение роли storage ничем не мешает хранить данные локально. - -Также инÑÑ‚Ð°Ð½Ñ Ð¼Ð¾Ð¶Ð½Ð¾ запуÑтить без ролей вовÑе, в результате чего он будет функционировать иÑключительно как не-шардированное локальное хранилище: - -``` -picodata run --no-role -``` - -Ð’Ñе инÑтанÑÑ‹ в группе имеют одинаковый набор ролей и одинаковый фактор репликации. - -## Ð ÐµÐ¿Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¸ зоны доÑтупноÑти (failure domains) - -КоличеÑтво инÑтанÑов в репликаÑете определÑетÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸ÐµÐ¼ переменной `replication_factor`. Внутри группы инÑтанÑов иÑпользуетÑÑ Ð¾Ð´Ð¸Ð½ и тот же `replication_factor`. - -Ð”Ð»Ñ ÐµÐµ инициализации Ñлужит параметр `--init-replication-factor`. Ðтот параметр играет роль только в момент ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ (Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ инÑтанÑа). Ð’ Ñтот момент значение из аргументов командной Ñтроки запиÑываетÑÑ Ð² конфигурацию клаÑтера. Ð’ дальнейшем значение параметра `--init-replication-factor` игнорируетÑÑ. - -Отредактировать фактор репликации, Ñохраненный в конфигурации клаÑтера, можно командой `picodata set-replication-factor`. Редактирование конфигурации ÑказываетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на вновь добавлÑемых инÑтанÑах, но не затрагивает уже работающие. - -По мере уÑÐ»Ð¾Ð¶Ð½ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð¿Ð¾Ð»Ð¾Ð³Ð¸Ð¸ возникает еще один Ð²Ð¾Ð¿Ñ€Ð¾Ñ â€” как не допуÑтить Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð² репликаÑет инÑтанÑов из одного и того же датацентра. Ð”Ð»Ñ Ñтого введен параметр `--failure-domain` — _зона доÑтупноÑти_, Ð¾Ñ‚Ñ€Ð°Ð¶Ð°ÑŽÑ‰Ð°Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ðº физичеÑкого Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñервера, на котором выполнÑетÑÑ Ð¸Ð½ÑÑ‚Ð°Ð½Ñ Picodata. Ðто может быть как датацентр, так и какое-либо другое обозначение раÑположениÑ: регион (например, `eu-east`), Ñтойка, Ñервер, или ÑобÑтвенное обозначение (blue, green, yellow). Ðиже показан пример запуÑка инÑтанÑа Picodata Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸ÐµÐ¼ зоны доÑтупноÑти: - -``` -picodata run --init-replication-factor 2 --failure-domain region=us,zone=us-west-1 -``` - -Добавление инÑтанÑа в репликаÑет проиÑходит по Ñледующим правилам: - -- ЕÑли в каком-либо репликаÑете количеÑтво инÑтанÑов меньше необходимого фактора репликации, то новый инÑÑ‚Ð°Ð½Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÑетÑÑ Ð² него при уÑловии, что их параметры `--failure-domain` отличаютÑÑ (региÑÑ‚Ñ€ Ñимволов не учитываетÑÑ). -- ЕÑли подходÑщих репликаÑетов нет, то Picodata Ñоздает новый репликаÑет. - -Параметр `--failure-domain` играет роль только в момент Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð½ÑтанÑа в клаÑтер. **ПринадлежноÑÑ‚ÑŒ инÑтанÑа репликаÑету впоÑледÑтвии не менÑетÑÑ**. - -Как и параметр `--advertise`, значение параметра`--failure-domain` каждого инÑтанÑа можно редактировать: - -- Либо перезапуÑтив инÑÑ‚Ð°Ð½Ñ Ñ Ð½Ð¾Ð²Ñ‹Ð¼Ð¸ параметрами. -- Либо в процеÑÑе его работы командой `picodata set-failure-domain`. - -ДобавлÑемый инÑÑ‚Ð°Ð½Ñ Ð´Ð¾Ð»Ð¶ÐµÐ½ обладать тем же набором параметров, которые уже еÑÑ‚ÑŒ в клаÑтере. Ðапример, инÑÑ‚Ð°Ð½Ñ `dc=msk` не Ñможет приÑоединитьÑÑ Ðº клаÑтеру Ñ `--failure-domain region=eu/us` и вернет ошибку. - -Как было указано выше, Ñравнение зон доÑтупноÑти производитÑÑ Ð±ÐµÐ· учета -региÑтра Ñимволов, поÑтому, к примеру, два инÑтанÑа Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ð°Ð¼Ð¸ -`--failure-domain region=us` и `--failure-domain REGION=US` будут отноÑитьÑÑ -к одному региону и, Ñледовательно, не попадут в один репликаÑет (иÑÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ -опиÑаны ниже). - -## КейÑ: два датацентра по две реплики - -Picodata ÑтараетÑÑ Ð½Ðµ объединÑÑ‚ÑŒ в один репликаÑет инÑтанÑÑ‹, у которых Ñовпадает Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один домен. Ðо иногда Ñто вÑе же необходимо. Чтобы ограничить Picodata в беÑконечном Ñоздании репликаÑетов, можно воÑпользоватьÑÑ Ñ„Ð»Ð°Ð³Ð¾Ð¼ `--max-replicaset-count` (по умолчанию `inf`). - -Как и `--init-replication-factor`, параметр `--max-replicaset-count` может быть разным Ð´Ð»Ñ Ñ€Ð°Ð·Ð½Ñ‹Ñ… групп. - -Как и другие параметры, `--max-replicaset-count` редактируетÑÑ Ð² любой момент: - -- При добавлении нового инÑтанÑа -- Ð’ процеÑÑе его работы командой `picodata set-max-replicaset-count` - -Важно учитывать, что параметр `--max-replicaset-count` Ð½ÐµÐ»ÑŒÐ·Ñ Ñделать меньше ÑущеÑтвующего количеÑтва репликаÑетов. - -## Файлы конфигурации - -СущеÑтвует три ÑпоÑоба передать Picodata параметры конфигурации. Они приведены ниже в порÑдке возраÑÑ‚Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð¾Ñ€Ð¸Ñ‚ÐµÑ‚Ð°: - -1. Файл конфигурации (yaml / toml) -2. Переменные Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ `PICODATA_<PARAM>=<VALUE>` -3. Ðргументы командной Ñтроки `--param value` - -Мы перечиÑлили доÑтаточно много разнобразных параметров, некоторые из которых делают команду запуÑка доÑтаточно длинной. ВмеÑто отдельных команд можно иÑпользовать файл конифгурации. Пример: - -<h5 a><strong><code>storage.toml</code></strong></h5> - -```toml -group = "storages" -max-replicaset-count = 30 -replication-factor = 4 -roles = ["storage"] -``` - -<h5 a><strong><code>storage.toml</code></strong></h5> - -```toml -group = "routers" -roles = ["router"] -``` - -Пример запуÑка клаÑтера Picodata c иÑпользованием файла конфигурации: - -``` -picodata run --cfg storage.toml --listen :3301 -picodata run --cfg router.toml --listen :3302 -``` - -## ДинамичеÑкое переключение голоÑующих узлов в Raft (Raft voter failover) - -Ð’Ñе узлы Raft в клаÑтере делÑÑ‚ÑÑ Ð½Ð° два типа: голоÑующие (`voter`) и неголоÑующие (`learner`). За конÑиÑтентноÑÑ‚ÑŒ Raft-группы отвечают только узлы первого типа. Ð”Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð° каждой транзакции требуетÑÑ Ñобрать кворум из `N/2 + 1` из голоÑующих узлов. ÐеголоÑующие узлы в кворуме не учаÑтвуют. - -Чтобы Ñохранить Ð±Ð°Ð»Ð°Ð½Ñ Ð¼ÐµÐ¶Ð´Ñƒ надежноÑтью клаÑтера и удобÑтвом его ÑкÑплуатации, в Picodata предуÑмотрена ÑƒÐ´Ð¾Ð±Ð½Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ â€” динамичеÑкое переключение типа узлов. ЕÑли один из голоÑующих узлов ÑтановитÑÑ Ð½ÐµÐ´Ð¾Ñтупен или прекращает работу (что может нарушить кворум в Raft), то тип `voter` автоматичеÑки приÑваиваетÑÑ Ð¾Ð´Ð½Ð¾Ð¼Ñƒ из доÑтупных неголоÑующих узлов. Переключение проиÑходит незаметно Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ. - -КоличеÑтво голоÑующих узлов в клаÑтере не наÑтраиваетÑÑ Ð¸ завиÑит только от общего количеÑтва инÑтанÑов. ЕÑли инÑтанÑов 1 или 2, то голоÑующий узел один. ЕÑли инÑтанÑов 3 или 4, то таких узлов три. Ð”Ð»Ñ ÐºÐ»Ð°Ñтеров Ñ 5 или более инÑтанÑами — пÑÑ‚ÑŒ голоÑующих узлов. - -## Configuration reference - -> `--cfg <path>` -: Read configuration parameters from file (toml / yaml). -*env*: `PICODATA_CFG` -*default*: *none* - -`--data-dir` -: Here the instance persists all of its data. -*env*: `PICODATA_DATA_DIR` -*default*: `.` - -`--listen` -: Socket bind address. -*env*: `PICODATA_LISTEN` -*default*: `localhost:3301` - -`--peer <[host][:port],...>` -: Address of other instance(s). -*env*: `PICODATA_PEER` -*default*: `localhost:3301` - -`--advertise <[host][:port]>` -: Address the other instances should use to connect to this instance. -*env*: `PICODATA_ADVERTISE` -*default*: `%listen%` - -`--cluster-id <name>` -: Name of the cluster. The instance will refuse to join the cluster with a different name. -*env*: `PICODATA_CLUSTER_ID` -*default*: `demo` - -`--instance-id <name>` -: Name of the instance. -*env*: `PICODATA_INSTANCE_ID` -*default*: `i%raft_id%`, e.g. `i1`, `i42`, etc. - -`--failure-domain <key=value,...>` -: Comma-separated list describing physical location of the server. Each domain is a key-value pair. Until max replicaset count is reached, picodata will avoid putting two instances into the same replicaset if at least one key of their failure domains has the same value. Instead, new replicasets will be created. Replicasets will be populated with instances from different failure domains until the desired replication factor is reached. -*env*: `PICODATA_FAILURE_DOMAIN` -*default*: *none* - -`--group <name>` -: Name of the instance group. It's an error to run instance with a group changed. -*env*: `PICODATA_GROUP` -*default*: `default` - -`--role <name,...>` -: Valid roles are `"storage"` and `"router"`. -*env*: `PICODATA_ROLE` -*default*: `storage,router` - -`--init-replication-factor <number>` -: Total number of replicas (copies of data) for each replicaset in the current group. It's only accounted upon the group creation (adding the first instance in the group), and ignored aftwerwards. -*env*: `PICODATA_INIT_REPLICATION_FACTOR` -*default*: `1` - -`--init-storage-weight <number>` -: Proportional capacity of current instance. Common for each instance in the current group. Only valid for instances with "storage" role enabled. -*env*: `PICODATA_INIT_STORAGE_WEIGHT` -*default*: `1` - -`--max-replicaset-count` -: Maximum number of replicasets in the current group. -*env*: `PICODATA_MAX_REPLICASET_COUNT` -*default*: don't modify -:hammer_and_wrench: ЕÑли меньше текущего размера группы, то игнорируетÑÑ. ЕÑли в текущей группе нет меÑта, то ошибка. ЕÑли больше — наÑтройка применÑетÑÑ Ðº текущей группе репликаÑетов. -- GitLab