Чем плохо «дождались всех»
Веер задач разослан, результаты собираются, дальше их надо свести и запустить следующий круг. Кажется, что закрытие — это ожидание последнего исполнителя. Отказы, которые из такого закрытия следуют, все молчаливые:
- Наполовину сведённая пачка неотличима от сведённой. Сведение — это несколько записей подряд; падение между ними оставляет хранилище в состоянии, где часть результатов учтена, часть нет, и ни одного признака этого нет.
- Поздний результат въезжает в закрытую пачку. Медленный исполнитель публикует свой результат уже после сведения; повтор операции его подхватывает, и та же пачка при двух прогонах даёт разный состав.
- Остановка по таймеру убивает оплаченную работу. Жёсткое завершение по времени обрывает исполнителей, которые уже почти закончили, — деньги потрачены, результата нет.
- Работа, не доехавшая до публикации, влияет невидимо. Исполнитель оставил файлы в рабочей области, но не опубликовал результат; следующий круг эти файлы находит и считает их чем-то значащим.
Заморозка допуска — не то же самое, что остановка
Первый шаг закрытия — перестать принимать новое, не трогая начатое. Это два разных сигнала, и путать их дорого:
- заморозка допуска блокирует постановку новых задач, запуск процессов и любые дорогие операции, но оставляет разрешёнными чтение результатов, публикацию найденного и обновление памяти — то есть ровно то, чем исполнитель завершает уже сделанное;
- остановка приходит позже и после ограниченной паузы, за которую начатое успевает опубликоваться.
Отдельное правило внутри: активную защищённую работу не убивают. Что считать защищённым, решает задача — обычно это прогон, результат которого и есть цель, в отличие от вспомогательных вычислений.
Маркер — единственный факт завершения
Наличие результатов, обновлённой сводки или отчёта завершением не является: это следы, которые остаются и от прерванного сведения. Факт завершения — отдельная запись, которую ставят последней и по которой все остальные читатели узнают, что пачка закрыта.
Отсюда правило возобновления: результаты без маркера — это незавершённая работа по сведению, а не готовая пачка, и возобновление обязано её доделать, а не пропустить. Тот же принцип с другой стороны разобран в Unknown Not A Value: побочный след — не факт, и вывод команды не сообщает о её завершении.
Отсечка по времени делает повтор детерминированным
Вместе с маркером записывают момент отсечки: что было видно до него — попадает в пачку, что появилось после — не попадает, даже если операцию сведения повторить.
Без отсечки повтор недетерминирован по построению: он видит хранилище в момент повтора, а не в момент закрытия. С отсечкой поздний результат не пропадает — он остаётся видимым как сигнал для следующего круга, но не переписывает уже закрытое.
Порядок сведения фиксирован, а маркер ставится последним
Сведение — не «слить всё», а последовательность с одним направлением зависимости:
- собрать опубликованное исполнителями;
- привести к каноническому виду идемпотентно — повтор шага не должен менять результат (Idempotency For Agents);
- обновить каноническое состояние: то, что следующий круг считает истиной;
- обновить производное: сводки, рейтинги, память исполнителей;
- спланировать следующий круг по уже закоммиченному, а не по тому, что пришло в этот момент;
- поставить маркер.
Шаг 5 стоит после 3 намеренно: планирование, читающее незакоммиченное, делает следующий круг зависящим от порядка прихода результатов.
Что не доехало до публикации, не влияет
Полезное правило распространения: следующий круг видит только опубликованное. Артефакт, оставшийся в рабочей области без публикации, не участвует в планировании, даже если он лежит на видном месте.
Смысл в разделении «сделано» и «сообщено». Файл в рабочей области — побочный эффект работы, а не её результат; результатом он становится тогда, когда исполнитель его опубликовал вместе с оговорками и указанием, как его читать. Иначе следующий круг наследует чужие черновики, приняв их за выводы.
Закрывать по достаточности, а не по числу закончивших
Последнее решение — когда вообще начинать закрытие. «Все закончили» плохой критерий: одна застрявшая ветка держит пачку неограниченно, а быстрое большинство ничего не решает.
Работающая форма — доля годного к закрытию: пачка закрывается, когда набралась заданная доля результатов, доросших до нужной зрелости. Дальше три случая, и все три предусматриваются заранее:
- доля набрана — заморозить допуск, дать доиграть, свести, закрыть;
- доля не набрана к моменту оценки — перестать принимать обычную работу, но оставить допуск тем задачам, которые ещё успевают дать годный результат к сроку;
- упёрлись в предохранитель по времени — закрыться, записав, что зрелости не хватило. Это важнее, чем кажется: закрытие с недобором обязано оставлять след, иначе следующий круг унаследует слабую границу как нормальную.
Связано с
- Multi Agent Patterns — веер задач и координация через общее состояние; здесь его вторая половина, сведение
- Cohort Diversity — что запускать в пачку, чтобы она не оказалась одной задачей в N копиях
- Durable Execution — возобновление после падения; маркер и отсечка делают его детерминированным
- Idempotency For Agents — почему шаг приведения к каноническому виду обязан быть повторяемым
- Unknown Not A Value — побочный след не является фактом завершения
- Agent Audit Log — маркер и отсечка как то, по чему потом восстанавливают, что вошло в решение
- Parallel Agent Dispatch — цена ожидания и то, почему остановка по таймеру дороже дренажа