このページでは、タスクのリトライの設定方法について説明します。

デフォルトの動作

デフォルトでは、asynq はタスクを最大25回までリトライします。タスクがリトライされるたびに、指数関数的なバックオフ戦略を使用してリトライの遅延を計算します。タスクがすべてのリトライ試行を使い果たすと(デフォルトは25回)、タスクはデバッグや検査のために archived 状態に移動され、自動的にはリトライされません(CLI や WebUI を使用して手動でタスクを実行することはできます)。

以下のタスクのリトライプロパティはカスタマイズできます:

  • 各タスクの最大リトライ試行回数
  • 失敗したタスクを再試行する前の待ち時間(つまり、遅延)
  • タスクのリトライカウントを使用するかどうか
  • リトライをスキップしてタスクを直接アーカイブに送信するかどうか

このページの残りの部分では、上記で言及された各カスタムオプションについて説明します。

タスクの最大リトライ試行回数のカスタマイズ

asynq.MaxRetry オプションを使用して、タスクの最大リトライ試行回数を指定できます。

例:

client.Enqueue(task, asynq.MaxRetry(5))

これにより、task は最大5回までリトライされます。

また、特定のタスクの最大リトライ試行回数を設定したい場合は、それをタスクのデフォルトオプションとして設定できます。

task := asynq.NewTask("feed:import", nil, asynq.MaxRetry(5))
client.Enqueue(task) // MaxRetryが5に設定される

リトライ遅延のカスタマイズ

Config 構造体の RetryDelayFunc オプションを使用して、リトライ遅延の計算方法を指定できます。

RetryDelayFunc のシグネチャは以下の通りです:

// n はタスクのリトライが試みられた回数
// e はタスクハンドラが返したエラー
// t は関連するタスク
RetryDelayFunc func(n int, e error, t *asynq.Task) time.Duration

例:

srv := asynq.NewServer(redis, asynq.Config{
    Concurrency: 20,
    RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
        return 2 * time.Second
    },
})

これにより、すべての失敗したタスクは再処理されるまでに2秒待機します。

デフォルトの動作は指数バックオフであり、DefaultRetryDelayFunc で定義されています。次の例は、特定のタスクタイプのためにリトライ遅延をカスタマイズする方法を示しています:

srv := asynq.NewServer(redis, asynq.Config{
    // "foo" タスクの場合、常に2秒の遅延を使用し、他のタスクはデフォルトの動作を使用する。
    RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
        if t.Type() == "foo" {
            return 2 * time.Second 
        }
        return asynq.DefaultRetryDelayFunc(n, e, t) 
    },
})

非失敗エラー

ハンドラからエラーを返してタスクを後でリトライしたい場合がありますが、その際にタスクのリトライカウントを使用せずにスケジュールすることがあります。たとえば、作業単位にタスクを処理するのに十分なリソースがない場合などは後でリトライしたいと考えるかもしれません。 サーバの初期化中に、IsFailure(error) bool 関数用の Config を指定できます。この述語関数は、ハンドラからのエラーが失敗としてカウントされるかどうかを決定します。関数が false を返す場合(すなわち、非失敗エラーの場合)、サーバはタスクのリトライカウントを使用せずにそのタスクを単純に後でリトライのためにスケジュールします。

例:

var ErrResourceNotAvailable = errors.New("no resource is available")

func HandleResourceIntensiveTask(ctx context.Context, task *asynq.Task) error {
    if !IsResourceAvailable() {
        return ErrResourceNotAvailable
    }
    // ... 資源集約的なタスクの処理ロジック
}

// ...

srv := asynq.NewServer(redisConnOpt, asynq.Config{
    // ... 他の構成オプション
    IsFailure: func(err error) bool {
        return err != ErrResourceNotAvailable // リソースが利用できない場合は非失敗エラーとする
    },
})

リトライのスキップ

Handler.ProcessTaskSkipRetry エラーを返す場合、残りのリトライ回数に関係なくタスクはアーカイブされます。返されるエラーは SkipRetry であっても、SkipRetry エラーでラップされたエラーであっても構いません。

func ExampleHandler(ctx context.Context, task *asynq.Task) error {
    // タスクの処理ロジック
    // ハンドラがタスクをリトライすべきでないとわかる場合は、SkipRetry を返す
    return fmt.Errorf(": %w", asynq.SkipRetry)
}