前提
先にこちらの記事の内容を理解しておくことをお勧めします。
https://qiita.com/shiozaki/items/b3aaff926b7b601b4f86
概要
requireオペレーターが書かれたワークフローが失敗した時に、リトライする手順によっては意図せずワークフローを二重に起動することになります。
再現手順
以下のワークフローを用意します。
+step1:
require>: workflow2
+step2:
sh>: exit 1 # this is a bug.
workflow1がworkflow2をrequireしています。
workflow2にはバグ( exit 1
)があり、shオペレーターが失敗します。
この状態で、workflow1を実行させてみます。
workflow1がworkflow2を呼び出しますが、workflow2のstep2で失敗し、連鎖的にworkflow1のstep1が失敗しています。
$ digdag server -m -O /tmp/digdag_log
$ digdag push sample
$ digdag start sample workflow1 --session '2019-01-01 00:00:00'
2019-03-24 19:16:03 +0900 [INFO] (XNIO-1 task-19): Starting a new session project id=1 workflow name=workflow1 session_time=2019-01-01T00:00:00+00:00
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow1+step1): require>: workflow2
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow1+step1): Starting a new session project id=1 workflow name=workflow2 session_time=2019-01-01T00:00:00+00:00
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow2+step2): sh>: exit 1
2019-03-24 19:16:04 +0900 [ERROR] (0044@[0:sample]+workflow2+step2): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:312)
at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:254)
at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
at io.digdag.core.agent.ExtractArchiveWorkspaceManager.withExtractedArchive(ExtractArchiveWorkspaceManager.java:77)
at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow2^failure-alert): type: notify
2019-03-24 19:16:05 +0900 [INFO] (0044@[0:sample]+workflow1+step1): require>: workflow2
2019-03-24 19:16:05 +0900 [ERROR] (0044@[0:sample]+workflow1+step1): Task +workflow1+step1 failed.
Dependent workflow failed. Session id: 2, attempt id: 2
2019-03-24 19:16:06 +0900 [INFO] (0044@[0:sample]+workflow1^failure-alert): type: notify
バグを修正し、digdag pushします。
+step2:
sh>: exit 0 # fixed the bug
digdag push sample
次にリトライをしますが、この時に、sessionを見ると2つのセッションがあることが分かります。
セッションが2つあるので、どちらから先にリトライするべきか悩みます。
リトライする順序によっては同じワークフローが二重に実行される恐れがあるので、注意が必要です。
このようなケースの場合は、 requireオペレーターが書かれている側のワークフローだけをリトライすると、2つのワークフローがそれぞれ1回だけ実行されます。
$ digdag sessions
2019-03-24 19:20:27 +0900: Digdag v0.9.33
Sessions:
session id: 1
attempt id: 1
uuid: 11164087-9f57-4ece-85fe-8530d853c9fb
project: sample
workflow: workflow1
session time: 2019-01-01 00:00:00 +0000
retry attempt name:
params: {}
created at: 2019-03-24 19:16:03 +0900
kill requested: false
status: error
session id: 2
attempt id: 2
uuid: afef1840-53ab-4181-9380-a13d62ed64f0
project: sample
workflow: workflow2
session time: 2019-01-01 00:00:00 +0000
retry attempt name:
params: {}
created at: 2019-03-24 19:16:04 +0900
kill requested: false
status: error
以下、workflow2が二重起動してしまう手順です。
workflow2側のセッションを先にリトライします。(1回目)
$ digdag retry 2 --latest-revision --resume
この時点でセッション一覧を見ると、workflow1側のセッションはfailuerのままです。
ですので、次にそちらのセッションもリトライします。
$ digdag retry 1 --latest-revision --resume
この時、workflow1側は既にsuccessなセッションがあるにもかかわらず、再度実行されてしまいます。(2回目)
なお、この挙動は --resume
オプションではなく --all
オプションを指定した場合や、web UIのRETRY ALL
ボタンや RETRY FAILED
ボタンを押した場合も同様です。
まとめ
失敗したrequireをリトライする時にはどちらのワークフローからリトライするかによって挙動が変わる。
requireオペレーターが書かれた側のワークフローをリトライする
→ 引数に指定されたワークフローも連鎖的に起動され、どちらのワークフローも1回ずつ実行される
requireオペレーターの引数に指定された側のワークフローをリトライし、その後にrequireオペレーターが書かれた側のワークフローをリトライする
→ requireオペレーターの引数に指定された側のワークフローは2回、requireオペレーターが書かれた側のワークフローは1回実行される
多くのケースでは前者の挙動が望ましいケースになるかと思います。