🎱

LaravelのQueueにリクエスト量制限有りのAPIとの通信を組み込む

2023/06/13に公開

https://readouble.com/laravel/9.x/ja/queues.html

LaravelのQueueに関する内容。

外部APIを使用する際にTwitterAPIのようにリクエスト制限が設けられているケースもあり、その対策についてまとめます。

リトライの設定

Queue実行中に失敗したJobはfailed_jobsテーブルに突っ込まれる。

ここでJobクラスにリトライの回数を設定しておくと、設定した回数失敗するまでfaild_jobsテーブルに移動せずにQueueに残すことが可能。

※下記は自作した、twitterのuser_idを渡してユーザー情報を取得するJobから抜粋。

class GetUsersLookup implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
    public $tries = 50; //ここを設定

    public function __construct($user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        //省略
    }
}

時間差でリトライさせる方法

例えば、TwitterのAPIは時間ごとに受け付けられるリクエスト数に上限があり、1度リクエスト量が制限に達してしまうと制限が解除されるまではリトライしても失敗することになってしまう。

直ちにリトライさせず、一定の時間遅らせてリトライさせたい場合は下記のような記述を加える。

Twitterの場合は15分単位でリクエスト数の上限が定められているため、releaseメソッドを使って900秒遅らせてリトライを実施させるように設定する。

class GetUsersLookup implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
    public $tries = 50; //ここを設定

    public function __construct($user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        $twitterApi = new TwitterApiService;
        $res = $twitterApi->usersLookupById($this->user->id);

        if (is_int($res)) {
            if (count($http_response_header) > 0) {
                $status_code = explode(' ', $http_response_header[0]);
                switch ($status_code[1]) {
                    case 404:
                        return $this->batch()->cancel();
                    case 429:
                        return $this->release(900); //ここを設定
                    default:
                        echo "エラーが発生しました。";
                        return 500;
                }
            }
        }

        //エラーが発生しなかった場合の処理を続けて書く。
}

Twitterではリクエスト制限が発生した場合は429エラーが返されるため、エラーコードが429のときにreleaseメソッドを実行する形で書いた。

Jobごとにリトライ開始時刻をずらす

前項の状態では失敗したジョブについてほぼ全て同じ時間にリトライが開始されてしまい、1つ失敗すると後続も全て失敗する問題は解決されていない。

リトライ開始時間が重ならないようにする必要がある。

そこで最後に失敗したJobのリトライ時刻を参照し、そこに一定の時間差を加えることで対処する。

class GetUsersLookup implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
    public $tries = 50; //ここを設定

    public function __construct($user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        $twitterApi = new TwitterApiService;
        $res = $twitterApi->usersLookupById($this->user->id);

        if (is_int($res)) {
            if (count($http_response_header) > 0) {
                $status_code = explode(' ', $http_response_header[0]);
                switch ($status_code[1]) {
                    case 404:
                        return $this->batch()->cancel();
                    case 429:
                        $last_available = DB::table('jobs')->where('queue', 'GetUsersLookup')->orderBy('available_at', 'desc')->first();
                        $delay = !empty($last_available) ? $last_available->available_at - time() + 900 : 900;
                        return $this->release($delay);
                    default:
                        echo "エラーが発生しました。";
                        return 500;
                }
            }
        }

        //エラーが発生しなかった場合の処理を続けて書く。
}

php artisan queue:tableでjobsテーブルを作成した場合は、available_atにリトライ予定の時刻が挿入される。

このカラムをソートして最新の日時を参照し、さらに900秒加算した値をreleaseメソッドに渡す。

結構前になるが、参考にしたのは以下のスレッドにあった回答。
https://laracasts.com/discuss/channels/laravel/delay-between-processes-in-job-queue

以上

Discussion