OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

Guzzleにキャッシュやリトライは任せちゃおう

曇天が続きますね。 @kalibora です。

今日は小ネタを書かせていただきます。

PHPer のみなさんは HTTP クライアントは Guzzle を使うことが多いと思います。

昔は curl をそのまま使うことも多かったと思いますが、今はあまりそういう状況も少なくなってきたのではないでしょうか。

さて、そんな Guzzle ですが、 Middleware を使っている方はどれくらいいますでしょうか。

Middleware は Guzzle を機能強化する仕組みなんですが、僕が使って便利だなと思ったものを2つばかり紹介したいと思います。

1. kevinrob/guzzle-cache-middleware

kevinrob/guzzle-cache-middleware - Packagist

RFC 7234 に準拠した HTTP Cache の機能を追加するミドルウェアです。

むしろこれを入れないと、いくらAPIなどのサーバー側で Cache-Control ヘッダを返そうが何しようが Guzzle 側ではキャッシュしません。

これを入れれば、レスポンスヘッダーに max-age があると、その間APIにアクセスしないでキャッシュしたレスポンスを使いますし、ETag などがあって 304 が返ってくれば、それもまたキャッシュしたレスポンスを使いまわしてくれます。

キャッシュストレージとキャッシュ戦略(Cache Strategy)とを自分で選ぶことができるので、だいたいどんなケースにも対応できるんじゃないでしょうか。

選択可能なキャッシュストレージ

  1. Psr16CacheStorage
  2. Psr6CacheStorage
  3. DoctrineCacheStorage
    • Doctrine\Common\Cache\Cache を使う
  4. CompressedDoctrineCacheStorage
    • Doctrine\Common\Cache\Cache を使うが、値の保存に gzcompress で圧縮を行う
  5. LaravelCacheStorage
    • Laravel用
    • Illuminate\Contracts\Cache\Repository を使う
  6. FlysystemStorage
  7. WordPressObjectCacheStorage
    • wordpress用
    • 内部で wp_cache_get などのグローバルなWP関数を使う
  8. VolatileRuntimeStorage
    • arrayを使うので、デバッグや開発時の用途かな

キャッシュ戦略

  1. PublicCacheStrategy
    • 共有キャッシュ(複数のユーザーで使い回せるもの)をキャッシュする
  2. PrivateCacheStrategy
    • プライベートキャッシュ(特定の一人のユーザーのためのキャッシュ)をキャッシュする
  3. GreedyCacheStrategy
    • 自分で設定したTTLでキャッシュする
  4. NullCacheStrategy
    • 何もキャッシュしない

2. RetryMiddleware または caseyamcl/guzzle_retry_middleware

下記の2つはどちらも通信に失敗した場合にリトライしてくれるミドルウェアです。

サーバーとの通信は不安定なので必ず成功するわけでもないですし、単純にリトライしたら成功するケースも多いかと思います。

その場合、自前でリトライ処理を書いてしまうと、インフラ的なコードがアプリケーション層に漏れ出る形になってソースコードが読みづらくなるので、こういうものに任せてしまったほうが楽ですね。

この2つのミドルウェアの違いは

  • \GuzzleHttp\RetryMiddleware
    • リトライするかどうかの判断は自前(クロージャー)で設定
      • 例えばステータスコードで判断したり、リトライ回数で判断したりすることができる
    • リトライ時のsleep時間は自前でも設定できる
      • デフォルトはリトライ回数に応じてべき乗で増える(1, 2, 4, 8, 16 ...)
  • \GuzzleRetry\GuzzleRetryMiddleware
    • リトライするかどうかの判断は自動(ただし設定である程度変更できる)
      • 下記の条件に当てはまるとリトライしない
        • max_retry_attempts (デフォルトは10)で設定した最大リトライ回数を超えた
        • retry_only_if_retry_after_header の設定で必須にしているのに Retry-After ヘッダーがない
        • retry_on_status (デフォルトは 429, 503)で設定したステータスコードではない
        • 接続がタイムアウトしたが、 retry_on_timeout(デフォルトは false) の設定がされていない
        • タイムアウト以外の接続時の例外
    • リトライ時のsleep時間は下記のように決定される
      • default_retry_multiplier (デフォルトは1.5)* リトライ回数(1.5, 3, 4,5, ...)
      • Retry-After ヘッダーがあればその値を使う

といった感じです。

Retry-After ヘッダーを吐くサーバーと通信するなら後者、そうでないなら前者といった使い分けでしょうか。

外部サービスのAPIクライアントのSDKを使っている場合でも、内部的には Guzzle を使用しているケースも多く、 そういった場合でもミドルウェアを設定すればいいので便利ですね。

(ちなみに私は Zendesk API を使用するケースで \GuzzleRetry\GuzzleRetryMiddleware にお世話になりました)

といった感じで、今日はこの辺でまた。