digdag0.9.21のretryは_retryの定義位置によって挙動が異なる

digdagretryは大変便利です。
例えば、それなりの確率で500statusをレスポンスしてくるAPI 1 にrequestを送る処理を含むscriptを実行するtaskを作るようなケースでも、実際に動作させるcodeにリトライを仕込まなくても自動でtask単位でretryさせることができます(もちろんoperatorの処理結果がErorrにならないとダメですが)。

ですが、最近いろいろと検証していたところ、_retry を定義する階層の違いによって、retry時の挙動に違いがあることに気づいたので、検証した結果をまとめてみます。

動作検証した環境

$ digdag --version
0.9.21
$ java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)

パターン別retryの挙動まとめ

なお、全てdigdag serverにpushしたworkflowをdigdag startで実行する方法で動作検証を行いました。

忙しい人用まとめ

  • _retry を定義している階層と同じ階層に複数のtaskがあると、Errorになったtaskの後続taskもretry時に実行されるという挙動をするので注意が必要
  • workflow(.digファイル)の一番上の階層で定義した _retry はそのworkflowを直接実行した時しか適用されない。call operatorで他のworkflowから実行される場合は無視されるので注意が必要。
  • retryする対象を確実に限定するためには、operatorのみが定義されているtaskの中に _retry を定義して使うのが無難

1. workflowが1つのdigファイルのみで構成されている場合

1.1 実行するworkflowのtopに _retry を定義した場合

retry_top.dig
_retry: 3

+success1:
    echo>: 'success1'

+success2:
    echo>: 'success2'

+fail:
    sh>: exit 1;

+success3:
    echo>: 'success3'

当初の想定では、+success3は実行されずに、 +success1 -> +success2 -> +fail というフローを4回(1回実行した後に3回最初からretryで計4回)繰り返してSessionがFailureで終わると予想していました。

しかし、一周目は想定通りでしたが、一度retryしてからの2週目以降では、+success3のtaskが実行されています。もし+success3 が、その前のタスクに依存していると正しく動作しなくなる問題が発生します。

toplevel.png

1.2 taskの中に_retryを定義した場合

retry_in_task.dig
+success1:
    echo>: 'success1'

+success2:
    echo>: 'success2'

+fail:
    _retry: 3
    sh>: exit 1;

+success3:
    echo>: 'success3'

+failでのみretryが発生し、後続の +success3 は実行されずに終了しました。

in_task.png

1.3 taskの中に_retryを定義した上で、かつそのtask内のnestしたtaskがErorrになる場合

今度はnestしたtaskがErrorになった場合を試してみます。

nested_retry_task.dig
+success1:
    echo>: 'success1'

+success2:
    echo>: 'success2'

+fail:
    _retry: 3

    +nested_success1:
        echo>: 'nested_success1'

    +nested_success2:
        echo>: 'nested_success2'

    +nested_fail:
        sh>: exit 1;

    +nested_success3:
        echo>: 'nested_success3'

+success3:
    echo>: 'success3'

1.1と1.2の組み合わせのような挙動をしました。

同じ階層にある +nested_success1 , +nested_success2 , +nested_fail, +nested_success3は、初回は +nested_success3 がblockされますが、その後は実行されています。これは1.1と同じ挙動です。
nested.png

また、 +success3 はblockedで実行されませんでした。これは1.2と同じ挙動です。
nested2.png

2. workflowが複数のdigファイルで構成されていてcall operatorで別のworkflowを実行している場合

2.1 呼び出し側のtaskに _retry を定義した場合

parent.dig
+caller:
    _retry: 3
    call>: child.dig

+nyanko:
    echo>: 'nya-n'
child.dig
+success1:
    echo>: 'success1'

+success2:
    echo>: 'success2'

+fail:
    _retry: 3
    sh>: exit 1;

+success3:
    echo>: 'success3'

child.dig+failがErrorとなっていますが、その後続taskである +success3 は毎回Blockedとなり実行されていません。

2_1_1.png

2_1_2.png

また、parent.dig側の一番最後のtaskである +nyanko もBlockedとなり実行されていません。

2_1_3.png

2.2 呼び出される側のworkflowのtopに _retry を定義した場合

1.1と同じようなことが起きるかどうかも試してみます。

parent2
+caller:
    call>: child2.dig

+nyanko:
    echo>: 'nya-n'
child2
_retry: 3

+success1:
    echo>: 'success1'

+success2:
    echo>: 'success2'

+fail:
    sh>: exit 1;

+success3:
    echo>: 'success3'

digdag-uiの画面上には Retry State Paramsretry_count: 3 と表示されていますが、実際は一度もretryされずにworkflowが終了しました。どうやら、一番上の階層で定義された _retry は、そのworkflowを直接実行した時しか適用されないようです。

nested_2_2.png

考察

  • _retry は複数のtaskを定義している階層と同じ階層に定義すると、retryによる実行時には、Errorになったtask以降のtaskも実行してしまう挙動をするようです。
    • これが仕様なのかバグなのかはまだ調査しきれていませんが、あとでissueを調べてみて、なければ出してみようと思います。
  • digdag 0.9.21においては、以下の配慮が必要なように考えられます。(というか実際筆者はそうしています)
    • retryする対象を分かりやすく指定するために、retryしたいtask単位で _retry を定義する
    • 複数のtaskをまとめてretryしたい場合は、 _parallel: true を併用している依存関係のないtaskのみの場合に限定する

  1. 肌感ですが、BigQueryやGCSのAPIはそれなりにInternal Server Errorをレスポンスしてくる印象があるので、api clientでrequest投げる部分はretry処理込みで実装しないと危険という印象があります… 

1473684104
Software Engineer