Как я могу версировать рабочие процессы Cadence?

Рабочие процессы Cadence должны быть детерминированными, что означает, что рабочий процесс должен давать точно такие же результаты, если он выполняется с одинаковыми входными параметрами.

Когда я узнал о вышеуказанном требовании как новый пользователь Cadence, я задался вопросом, как я могу поддерживать рабочие процессы в долгосрочной перспективе, когда требуются изменения, нарушающие детерминизм.

В качестве примера сценария у вас есть рабочий процесс, который последовательно выполняет Activity1 и Activity2, а затем вам нужно изменить порядок этих действий, чтобы рабочий процесс выполнял Activity2 до Activtiy1. Есть много других способов внести такие изменения, нарушающие детерминизм, и я хотел понять, как с этими изменениями обращаться.

Это особенно важно в тех случаях, когда рабочие процессы могут выполняться в течение длительного времени, например, дней, недель или даже месяцев!


person Emrah Seker    schedule 19.04.2020    source источник


Ответы (1)


По-видимому, это, наверное, один из самых частых вопросов, которые задает новый разработчик Cadence. Рабочие процессы Cadence должны быть детерминированными алгоритмами. Если алгоритм рабочего процесса не является детерминированным, работники Cadence будут рисковать столкнуться с недетерминированными ошибками рабочего процесса при попытке воспроизвести историю (т. Е. Во время восстановления работника после сбоя).

Есть два способа решить эту проблему:

  • Создание совершенно нового рабочего процесса. Это наиболее наивный подход к управлению версиями рабочих процессов. Подход так же прост, как кажется: каждый раз, когда вам нужно внести изменения в алгоритм рабочего процесса, вы делаете копию исходного рабочего процесса и редактируете его так, как хотите, даете ему новое имя, например MyWorkflow_V2, и начинаете использовать для всех новых экземпляры, идущие вперед. Если ваш рабочий процесс недолговечен, ваши существующие рабочие процессы в какой-то момент «истощатся», и вы сможете полностью удалить старую версию. С другой стороны, по понятным причинам такой подход может очень быстро превратиться в кошмар обслуживания.
  • Использование API GetVersion () для разветвления логики рабочего процесса: в клиенте Cadence есть функция GetVersion, которая сообщает, какая версия рабочего процесса выполняется в данный момент. Вы можете использовать информацию, возвращаемую этой функцией, чтобы решить, какую версию вашего алгоритма рабочего процесса необходимо использовать. Другими словами, в вашем рабочем процессе есть как старые, так и новые алгоритмы, работающие параллельно, и вы можете выбрать правильную версию для своих экземпляров рабочего процесса, чтобы гарантировать их детерминированное выполнение.

Ниже приведен пример подхода на основе GetVersion (). Предположим, вы хотите изменить следующую строку в рабочем процессе:

err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)

to

err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)

Это критическое изменение, поскольку оно запускает действие bar вместо foo. Если вы просто внесете это изменение, не беспокоясь о детерминировании, ваши рабочие процессы не смогут воспроизвести, если потребуется, и они застрянут с ошибкой недетерминированного рабочего процесса. Правильный способ внести это изменение должным образом - обновить рабочий процесс следующим образом:

v :=  GetVersion(ctx, "fooChange", DefaultVersion, 1)
if v  == DefaultVersion {
   err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
} else {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
}

Функция GetVersion принимает 4 параметра:

  • ctx - стандартный объект контекста.
  • «fooChange» - это читаемый человеком ChangeID или семантическое изменение, которое вы вносите в свой алгоритм рабочего процесса, которое нарушает детерминизм.
  • DefaultVersion - это константа, которая просто означает версию 0. Другими словами, самую первую версию. Он передается как параметр minSupportedVersion функции GetVersion.
  • 1 - это maxSupportedVersion, который может обрабатываться вашим текущим кодом рабочего процесса. В этом случае наш алгоритм может поддерживать версии рабочего процесса от DefaultVersion до версии 1 (включительно).

Когда новый экземпляр этого рабочего процесса впервые достигает указанного выше вызова GetVersion (), функция возвращает параметр maxSupportedVersion, чтобы вы могли запустить последнюю версию алгоритма рабочего процесса. Тем временем он также запишет этот номер версии в историю рабочего процесса (внутреннее название - Событие-маркер), чтобы его запомнили в будущем. При повторном воспроизведении этого рабочего процесса позже клиент Cadence будет возвращать тот же номер версии, даже если вы передадите другой параметр maxSupportedVersion (т. Е. Если ваш рабочий процесс имеет еще больше версий).

Если вызов GetVersion встречается во время воспроизведения истории и в истории нет события маркера, которое было зарегистрировано ранее, функция вернет DefaultVersion с предположением, что «fooChange» < / strong> никогда не существовало в контексте этого экземпляра рабочего процесса.

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

v :=  GetVersion(ctx, "fooChange", DefaultVersion, 2) // Note the new max version
if v  == DefaultVersion {
   err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
} else if v == 1 {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
} else { // This is the Version 2 logic
   err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
}

Когда вы откажетесь от поддержки версии 0, вы измените приведенный выше код следующим образом:

v :=  GetVersion(ctx, "fooChange", 1, 2) // DefaultVersion is no longer supported
if v == 1 {
   err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
} else { 
   err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
}

После этого изменения, если ваш код рабочего процесса выполняется для старого экземпляра рабочего процесса с версией DefaultVersion, клиент Cadence выдаст ошибку и остановит выполнение.

В конце концов, вы, вероятно, захотите избавиться от всех предыдущих версий и поддерживать только последнюю версию. Один из вариантов - просто полностью избавиться от вызова GetVersion и оператора if и просто иметь одну строку кода, которая делает правильные вещи. Однако на самом деле лучше оставить вызов GetVersion () там по двум причинам:

  1. GetVersion () дает вам лучшее представление о том, что пошло не так, если ваш рабочий пытается воспроизвести историю старого экземпляра рабочего процесса. Вместо того чтобы исследовать основную причину загадочной недетерминированной ошибки рабочего процесса, вы будете знать, что сбой вызван управлением версиями рабочего процесса в этом месте.
  2. Если вам нужно внести больше критических изменений в один и тот же шаг алгоритма рабочего процесса, вы сможете повторно использовать тот же идентификатор изменения и продолжить работу по той же схеме, что и выше.

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

GetVersion(ctx, "fooChange", 2, 2) // This acts like an assertion to give you a proper error
err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil) 
person Emrah Seker    schedule 19.04.2020