API呼び出しの回数制限にブチ当たった時のリトライ処理について

## はじめに

Google Sheets APIなどは、一定時間あたりのAPI実行回数に制限があって、
ちょっとした処理でもすぐにその制限を超えてしまうことがあります。
そんな場面で使えるRubyによる再処理についてお伝えしたいと思います。

この記事ではGoogle Sheets APIの使い方については説明いたしませんので、
Google Sheets APIの詳しい使い方を知りたい場合は他の記事をご参照ください。

## Google Sheets API ついて

Googleドライブ上のスプレッドシートを操作するためのGoogleが提供しているAPIです。
スプレッドシートのセルの値を読み取ったり、セルに値を書き込んだりすることができます。

RubyからはGoogle公式のGemを使うと便利に操作することができます。
[https://github.com/googleapis/google-api-ruby-client](https://github.com/googleapis/google-api-ruby-client)

```sql
gem 'google-api-client'
```

## Google Sheets APIの回数制限について

Google Sheets APIは、下記URLページに記載されているように、API呼び出しに回数制限が設けられています。
[https://developers.google.com/sheets/api/limits](https://developers.google.com/sheets/api/limits)

![スクリーンショット 2022-09-10 9.42.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261675/5a86147a-0467-6ee7-84fb-2fd007a4919e.png)
Googleに対して割り当ての増加リクエストを行うことができますが、トラフィック量が多いと料金が発生する可能性があるようです。

## Google Sheets APIの例外

Google Sheets APIを短時間に集中して実行すると

**「Quota exceeded … 」といった例外が発生することがあります。**

前掲の表に記載されている制限を超えた場合に発生する例外です。
１分間、時間を空けて再度APIを実行すると今度はエラーが発生せずにAPIを実行できます。

 
## Rubyによる再処理の方法

1. 「begin  rescue  retry」で記述する
2. メソッドやクラスに切り出す
3. Gemを使う（Retryable）

## 1.「begin  rescue  retry」で記述する

サンプルコード

```ruby

SLEEP_SEC = 60
MAX_RETRY = 3

retry_count = 0

begin

  # Google Sheets API の呼び出し処理

rescue => e
  if retry_count >= MAX_RETRY
    # リトライ回数オーバー
    raise e
  end
  retry_count += 1
  # リトライ待機中
  sleep SLEEP_SEC
  retry
end

```

（すべての例外をrescueしているのは目をつむってもらうとして）


```text
Google Sheets API の呼び出し処理（1）

他の処理（1）

Google Sheets API の呼び出し処理（2）

他の処理（2）

Google Sheets API の呼び出し処理（3）

他の処理（3）
```

といったAPI呼び出しを行っている場合、そのまま再処理を記述すると次のようになります。

```ruby
SLEEP_SEC = 60
MAX_RETRY = 3

begin
  # Google Sheets API の呼び出し処理（1）
rescue => e
  if retry_count >= MAX_RETRY
    # リトライ回数オーバー
    raise e
  end
  retry_count += 1
  # リトライ待機中
  sleep SLEEP_SEC
  retry
end

# 他の処理（1）

begin
  # Google Sheets API の呼び出し処理（2）
rescue => e
  if retry_count >= MAX_RETRY
    # リトライ回数オーバー
    raise e
  end
  retry_count += 1
  # リトライ待機中
  sleep SLEEP_SEC
  retry
end

# 他の処理（2）

begin
  # Google Sheets API の呼び出し処理（3）
rescue => e
  if retry_count >= MAX_RETRY
    # リトライ回数オーバー
    raise e
  end
  retry_count += 1
  # リトライ待機中
  sleep SLEEP_SEC
  retry
end

# 他の処理（3）

```

Google Sheets APIではどの処理で回数制限がオーバーするかわからないので、
リトライさせたい処理が多いとコードが煩雑になります。

## 2. メソッドやクラスに切り出す

```ruby
class RescueRetryUtil

  def initialize(sleep_sec:, retry_count:)
    @sleep_sec = sleep_sec
    @retry_count = retry_count
  end

  def call(&block)
    retry_count = 0
    begin
      block.call
    rescue => e
      if retry_count >= @retry_count
		    # リトライ回数オーバー
        raise e
      end
      retry_count += 1
		  # リトライ待機中
      sleep @sleep_sec
      retry
    end
  end

end

# 呼び出し例

util = RescueRetryUtil.new(sleep_sec: 60, retry_count: 3)

util.call do
  # Google Sheets API の呼び出し処理（1）
end

# 他の処理（1）

util.call do
  # Google Sheets API の呼び出し処理（2）
end

# 他の処理（2）

util.call do
  # Google Sheets API の呼び出し処理（3）
end

# 他の処理（3）

```

この例ではクラスにしていますが、状況に応じてコントローラーのメソッドなどに切り出しても良いでしょう。

## 3. Gemを使う（Retryable）

Retryableなどの公開されているGemを使うという手もあります。

[https://github.com/nfedyashev/retryable](https://github.com/nfedyashev/retryable)


## 最後に

Gemがあるのでそちらを使ってサクッと作るのも良いですが、
すごい高機能なので使いこなすのが大変かもしれません。

自分で再処理のコードを書くと、用途に応じてコードを追記することもできます。
例えば、リトライした場合はログに状況を書き出すとか。
（Retryableでも可能ですがひと手間かかります）

P.S.
リトライ回数は、再処理の回数を表すのか、トライの回数を表すのか、どちらが一般的なのでしょう？