diff --git a/docs/clustering.md b/docs/clustering.md
index c618ec265b39e8941494f1f3f954ebdf274d4533..04d6675951c157e6548f3de9a50a0257a78533e3 100644
--- a/docs/clustering.md
+++ b/docs/clustering.md
@@ -1,6 +1,7 @@
-# Инициализация кластера. Высокоуровнево.
+# Общая схема инициализации кластера
+Данный документ описывает высокоуровневый процесс инициализации кластера Picodata на основе нескольких отдельно запущенных экземпляров Picodata (инстансов).
 
-Админ запускает пачку инстансов:
+Администратор запускает несколько инстансов, передавая в качестве аргументов необходимые параметры:
 
 ```sh
 picodata run --instance-id i1 --listen i1 --peer i1,i2
@@ -10,85 +11,70 @@ picodata run --instance-id i3 --listen i3 --peer i1,i2
 picodata run --instance-id iN --listen iN --peer i1,i2
 ```
 
-Сколько бы инстансов ни было, в опции `--peer` у каждого следует указать один и тот же набор из нескольких "первых". На них возлагается особая миссия по инициализации кластера (дискавери).
+Независимо от количества запускаемых инстансов, в опции `--peer` у каждого из них следует указать один и тот же набор из нескольких инстансов - обычно первых двух. Именно на их основе будет произведена инициализация кластера и поиск всех работающих инстансов для их включения в состав кластера (discovery).
 
-Подробный алгоритм дискавери в этой истории роли не играет, но описан в соседнем файле `discovery.md`. Пока об алгоритме дискавери достаточно знать лишь то, что, следуя этому алгоритму, один и только один из этих пиров возьмет на себя смелость создать рафт группу. Иначе рафт групп получилось бы неколько.
+Подробности алгоритма discovery приведены в отдельном [документе](discover.md). В контексте сборки кластера важно лишь понимать, что этот алгоритм позволяет лишь одному инстансу/peer'у создать Raft-группу, т.е. стать инстансом с raft_id=1. Если таких инстансов будет несколько, то и Raft-групп, а следовательно и кластеров Picodata получится несколько.
 
-Всё управление топологией рафт группы по сути возлагается на сам алгоритм рафт. И на его конкретную имплементацию - крейт `raft-rs`.
+Топологией Raft-группы управляет алгоритм Raft, реализованный в виде крейта `raft-rs`.
 
-# Инициализация кластера. Подробнее.
+# Этапы инициализации кластера
+На схеме ниже показаны этапы жизненного цикла инстанса в контексте его присоединения ко кластеру Picodata.
 
-[https://yuml.me/edit/15c7c2d0]
+![main.rs](clustering_curves.svg "main.rs control flow")
 
-![main.rs](main_run.svg "main.rs control flow")
-
-Эта устрашающая схема максимально точно изображает логику кода в `main.rs`. Ниже объясняется подробнее, что происходит на каждом этапе.
+Красным показан родительский процесс, который запущен на всём протяжении жизненного цикла инстанса. Вся логика поиска лидера Raft-группы и присоединения к ней происходит в дочернем процессе (голубой цвет). При сбросе состояния инстанса и rebootstrap происходит повторная инициализация форка (сиреневый цвет).
+ 
+Данная схема наиболее полно отражает логику кода в файле `main.rs`. Ниже описаны детали выполнения каждого этапа и соответствующей программной функции.
 
 ### fn main()
 
-Сначала процесс пикодаты форкается. Родитель (supervisor) ждет по механизму IPC сообщения от дочернего процесса и при необходимости рестартит его. Опционально дочерний процесс может попросить родителя дропнуть все файлы БД. Это будет нужно для т.н. ребутстрапа.
+На этом этапе происходит ветвление (форк) процесса `picodata`. Родительский процесс (supervisor) ожидает от дочернего процесса сообщения по механизму IPC и при необходимости перезапускает дочерний процесс. При необходимости дочерний процесс может попросить родителя удалить все файлы БД, т.е. вызвать функцию `drop_db`. Это может понадобиться для повторной инициализации кластера когда, например, у инстанса изначально имеется устаревший или некорректный `replicaset_id`.
 
 ### fn start_discover()
 
-Дочерний процесс начинает своё существование с вызова `box.cfg()` и вызова функции `start_discover`.
-Если вдруг из спейсов обнаруживается, что нода уже была забутстрапленна, то никакой алгоритм дискавери делаь и не надо, и инстанс сразу переходит на этап `postjoin()`. В противном случае, если это первый запуск, из алгоритма дискавери мы получаем флаг `its_me` и адрес лидера. Сам лидер (единственный `its_me == true`) выполняет `start_boot`, после чего выполняет `postjoin()`. Остальные инстансы (не только проигравшие пиры, но и все будущие) ребутстрапятся и идут делать `start_join`.
+Дочерний процесс начинает своё существование с запуска модуля `box.cfg()` и вызова функции `start_discover`. Возможно, что при этом из постоянно хранимых данных будет ясно, что bootstrap данного инстанса уже был произведён ранее и что Raft уже знает о вхождении этого инстанса в кластер - в таком случае никакого discovery не будет, инстанс сразу перейдёт к этапу `postjoin()`. Однако, если это новый инстанс, то алгоритм discovery выдаст ему флаг `i_am_bootstrap_leader == false` и сообщит адрес лидера Raft-группы. Сам лидер (единственный с `i_am_bootstrap_leader == true`) выполняет функцию `start_boot` и затем переходит к функции `postjoin()`. Остальные инстансы (уже запущенные и все будущие) сбрасывают своё состояние (этап rebootstrap) и переходят к функции `start_join`.
 
 
 ### fn start_boot()
 
-В функции `start_boot` происходит инициализация рафт группы - лидер генерирует и персистит первую запись в журнале. В этой записи будет лежать операция добавления ноды, что позволит всем остальным инстансам инициализироваться с пустой рафт группой.
+В функции `start_boot` происходит инициализация Raft-группы - лидер генерирует и сохраняет в БД первую запись в журнале. В этой записи будет лежать операция добавления Raft-узла; все остальные инстансы будут инициализированы с пустой Raft-группой.
 
-Саму рафт ноду инстанс на этом этапе не создаёт. Это произойдет позже, на стадии `postjoin()`.
+Сам Raft-узел на данном этапе ещё не создаётся. Это произойдет позже, на стадии `postjoin()`.
 
 ### fn start_join()
 
-Вызову `start_join` всегда предшествует ребутстрап (удаление БД и рестарт процесса), поэтому ни бокса, ни спейсов на этом этапе снова нет. Сама функция достаточно примитивная.
+Вызову функции `start_join` всегда предшествует rebootstrap (удаление БД и перезапуск процесса), поэтому на данном этапе в БД нет ни модуля box, ни пространства хранения. Функция start_join() имеет простое устройство:
 
-Инстанс отправляет запрос `join` на лидера (лидер известен после дискавери). Лидер шушукается с группой, и если всё хорошо, в ответ присылает необходимую информацию:
-- `raft_id` и `raft_group` - для инициализации рафт ноды;
-- `insance_uuid`, `replicaset_uuid`, `replication`, `read_only` - для `box.cfg`.
+Инстанс-клиент отправляет запрос `join` лидеру Raft-группы (он известен после discovery). После достижения консенсуса в Raft-группе лидер присылает в ответе необходимую информацию:
+- Идентификатор `raft_id` и данные таблицы `raft_group` - для инициализации Raft-узла;
+- Идентификаторы  `instance_uuid`, `replicaset_uuid` и параметры `replication`, `read_only` для `box.cfg`.
 
-Получив все настройки, инстанс засовывает их в `box.cfg()`, и после этого персистит `raft_group` с актуальными адресами других инстансов. Без этого инстанс не сможет отвечать на рафт сообщения. А чтобы записи в `raft_group` не были потёрты менее актуальными из рафт лога, каждая маркируется значением `commit_index`.
+Получив все настройки, инстанс использует их в `box.cfg()`, и затем создает в БД группу `raft_group` с актуальными адресами других инстансов. Без этого инстанс не сможет отвечать на  сообщения от Raft. Для того чтобы записи в `raft_group` не были затем заменены на менее актуальные из журнала Raft, каждая запись маркируется значением `commit_index`.
 
-После всех этих манипуляций, также идёт `postjoin()`.
+По завершении этих манипуляций инстанс также переходит к этапу `postjoin()`.
 
 ### fn postjoin()
 
-Логика `postjoin()` для всех инстансов одинакова. К этому моменту на инстансе уже инициализированы правильные спейсы и возможно даже существует предыстория рафт журнала. Инстанс инициализирует рафт ноду и получает read barrier (это позволяет убедиться, что рафт лог актуален). Из рафт лога становятся известны параметры репликации, и инстанс синхронизируется с репликами.
+Логика функции `postjoin()` одинакова для всех инстансов. К этому моменту для инстанса уже инициализированы корректные пространства хранения в БД и могут быть накоплены записи в журнале Raft. Инстанс инициализирует узел Raft и проверяет, что данные синхронизированы (`read barrier` получен) и журнал Raft актуален. Из журнала становятся известны параметры репликации, и инстанс начинает синхронизацию данных уровне репликационных групп Tarantool.
 
-Остаётся один маленький штришок - проверить свой статус voter / learner, при необходимости кинуть запрос на промоут до воутера (всё тот же `join`, лидер известен после получения read barrier), и дождаться его применения.
+На данном этапе инстансу остаётся проверить свой статус voter / learner, при необходимости запросить повышение до статуса voter (повторение функции `join`, лидер известен после получения `read barrier`), и дождаться применения статуса.
 
-Всё, нода готова к использованию.
+Теперь узел Raft готов к использованию.
 
 # Обработка запросов
 
 ### extern "C" fn join()
 
-Львиная доля всей логики по управлению топологией кроется в хранимке `join`. Её назначение достаточно простое - закоммитить ConfChange, но за этими словами кроется несколько нюансов.
-
-Во-первых, если этот `instance_id` уже есть в группе (закоммиченый) и никакая информация не обновилась (например флаг voter / learner), то можно сразу отвечать клиенту не задействуя рафт.
-
-Если это первое появление инстанса в группе, то он всегда добавляется в роли learner. В роли voter его добавлять нельзя, иначе появится проблема курицы и яйца. Чтобы ConfChange с воутером закоммитился, этот voter должен участвовать в кворуме. А он не может - он ещё ждёт ответа на запрос `join`.
-
-Во-вторых, рафт не позволяет делать ConfChange, если предыдущий ConfChange не был закоммичен. Поэтому запросы `join` на лидере придётся обрабатывать батчами в отдельном файбере. Пачку запросов накопили - обработали - каждому послали индивидуальный ответ.
-
-В-третьих, прежде чем отвечать клиенту, надо дождаться, пока ConfChange закоммитится. Или, более формально, пока лидер не выйдет из т.н. joint state (см. рафт диссер §4.3). После этого можно отвечать клиенту `raft_id`, `raft_group`, `insance_uuid`, `replicaset__uuid`, `replication`, `read_only`. В будущем состав ответа может дополняться новыми параметрами по мере необходимости.
-
-- `raft_id` генерит лидер, и делает это строго последовательно и атомарно на весь батч.
-- `raft_group` представляет собой дамп всего спейса с топологией кластера. Он понадобится новому инстансу чтобы знать адреса соседей и нормально с ними общаться.
-
-И, наконец, где-то здесь же надо будет убедиться, что нода является лидером, когда генерит `raft_id`. У остальных нет на это права.
+Значительная часть всей логики по управлению топологией находится в хранимой процедуре `join`. Её назначение состоит в обработке запросов на добавление нового инстанса (клиента) в Raft-группу с учётом следующих обстоятельств:
 
-# TODO
+Во-первых, если такой `instance_id` уже имеется в кластере (и его данные сохранены в БД) и никакая другая информация не обновлялась (например флаг `voter` / `learner`), то можно сразу отвечать клиенту, не задействуя Raft.
 
-Q: провести эксперимент, может ли ConfChange пролезть в MsgPropose?
-A: Может
+Если это первое появление инстанса в группе, то он всегда добавляется в роли `learner`. В роли `voter` его добавлять нельзя, так как для достижения консенсуса в Raft-группе требуется участие добавляемого инстанса, но он не сможет участвовать, так как сам ещё ждёт ответа на запрос `join`.
 
-Q: провести эксперимент, можно ли параллельно отравлять simple_conf_change, или это только v2 касается?
-A: Пофиг, simple у нас только один в логе - самый первый.
+Во-вторых, Raft не позволяет изменять конфигурацию, пока предыдущее изменение не было применено. Поэтому запросы `join` на лидере Raft-группы необходимо группировать и обрабатывать за один приём в отдельном потоке. Сначала накапливается пакет запросов, затем он обрабатывается, и после этого каждому инстансу возвращается индивидуальный ответ.
 
-Q: правда ли, что пропоуз сразу после коммита не потеряется, и не задублируется?
-A: нет, коммит на фоловерах может прийти, а пропоуз потеряется. тогда у нового лидера не будет подходящего момента, чтобы восполнить эту утрату.  Именно поэтому за промоутом до воутера следит сам фоловер.
+В-третьих, прежде чем отвечать инстансу-клиенту, надо дождаться применения изменений конфигурации. Это произойдёт после того как лидер Raft-группы выйдет из состояния `joint state` ([подробнее](https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf), §4.3). Только после этого лидер сможет вернуть клиенту данные `raft_id`, `raft_group`, `insance_uuid`, `replicaset_uuid`, `replication`, `read_only`.
 
-Q: может ли фоловер (не лернер) слать другому фоловеру MgsAppend закомиченных энтрей?
-A: ???
+- Значение `raft_id` генерируется лидером Raft-группы, причём строго последовательно и атомарно в рамках всего пакета запросов.
+- Данные `raft_group` представляют собой копию таблицы с топологией кластера. Они понадобятся новому инстансу чтобы знать адреса соседей и нормально с ними общаться.
+- Генерировать значения `raft_id` может только лидер Raft-группы. У любого другого узла Raft нет на это права.
diff --git a/docs/clustering.svg b/docs/clustering.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fa205799db6c702f765b44815876c5b18c514c26
Binary files /dev/null and b/docs/clustering.svg differ
diff --git a/docs/clustering_curves.svg b/docs/clustering_curves.svg
new file mode 100644
index 0000000000000000000000000000000000000000..107a7add67fb907180a549fc03ca141531fa3842
Binary files /dev/null and b/docs/clustering_curves.svg differ
diff --git a/docs/main.uml b/docs/main.uml
deleted file mode 100644
index cbcba63173be7dbc6e08c5dc59cbb86cdb5e2241..0000000000000000000000000000000000000000
--- a/docs/main.uml
+++ /dev/null
@@ -1,12 +0,0 @@
-(start)-|fork|
-
-|fork|[child]->(start_discover)->[summary\n{leader}]-><c>
-<c>-[its me]>(start_boot)->(postjoin)
-<c>->(drop_db)[rebootstrap]->|reboot|->(start_join)
-
-(start_join)->(postjoin)
-(start_discover)->(postjoin)
-(postjoin)->|b|
-
-|fork|-[supervisor]>(wait_pid)->|b|
-|b|->(end)
diff --git a/docs/main_run.svg b/docs/main_run.svg
deleted file mode 100644
index 47e51df30ea39928505621ce467bba120a28fcda..0000000000000000000000000000000000000000
Binary files a/docs/main_run.svg and /dev/null differ