ZOZOTOWNリニューアルで実施したCache Stampede対策

はじめに

こんにちは。マイグレーションチームの藤本です。

この記事では、先日のリニューアルに伴って導入したBackends For Frontends(以下、BFF)で、Redisを使ったキャッシュの事例をご紹介します。キャッシュを導入する際に起きる問題とその回避策について、サーバーサイドのアプリケーションで行った対策をもとに紹介していきます。

ZOZOTOWNリニューアルとBFF

ZOZOTOWNで導入したBFFは、複数のAPIのレスポンスをフロントエンドが必要とする形式に集約して返却することを主な目的としています。これまでの実績から、大規模セール時のアクセス数は通常時の何倍にもなることがわかっており、BFFもそれに耐えられるパフォーマンスが必要です。

しかし、BFFに来たすべてのアクセスをそのままAPIに流すと、パフォーマンスに影響する恐れが出てきました。そのため、APIからのレスポンスの一部をキャッシュとして保存しています。このキャッシュの仕組みにRedisを利用しています。

BFFを導入した経緯や構成、ZOZOTOWNにおけるBFFの目的についての解説は、こちらの記事をご覧ください。 techblog.zozo.com techblog.zozo.com

キャッシュ利用時の注意点

レスポンスの一部をキャッシュとして保存しても、無期限に持ち続けて良いわけではありません。ZOZOTOWNでは商品やショップなどの情報は常に更新されているため、キャッシュを一定期間で破棄して最新の情報を再取得する必要があります。

アクセス数が少ない場合はそれほど問題にはなりませんが、ECサイトのように常に大量のアクセスがある場合は、キャッシュが破棄されたタイミングでAPIにも同時に大量のアクセスが発生してしまいます。

この現象は一般的にCache Stampede(キャッシュスタンピード)Dog piling(ドッグパイル)などの名称で呼ばれています。 この記事ではキャッシュスタンピードと呼びます。

キャッシュスタンピードの回避

ひとたびキャッシュスタンピードが起きると、APIの負荷が増えることによるパフォーマンス低下、データベースの処理遅延、最悪の場合はサイト全体の遅延や停止などにつながる可能性があります。

これを回避するための方法として代表的なものが3つあります。

  • 別プロセスで事前にキャッシュを生成する(事前作成)
  • 期限切れ前に一定の確率で期限を更新する(期限更新)
  • 裏側のAPIへアクセスするプロセスを絞る(ロック)

それぞれの方法のメリットとデメリットを見ていきます。

代表的な回避方法の比較

事前作成

1つ目の「別プロセスで事前にキャッシュを生成する」方法は、キャッシュの書き込みと読み取りをそれぞれ別のアプリケーションとして作るので、処理がシンプルになります。そして、キャッシュが期限切れになる前に新しいデータを準備できるため、キャッシュヒット率を上げられることがメリットです。

デメリットは、管理対象のアプリケーションが増えるため運用保守のコストが増加する点と、ユーザーの検索条件を予測できないので事前に最適なキャッシュを生成しづらい点です。

期限更新

2つ目の「期限切れ前に一定の確率でキャッシュを更新する」方法のメリットは、1つ目と同様に事前にキャッシュを生成するので期限切れになる心配が少ないことです。

デメリットはどれくらい前から更新し始めるか、確率はいくらにするのかといった値を、運用開始後にも定期的に見直す必要が出てくる点です。

ロック

3つ目の「裏側のAPIへアクセスするプロセスを絞る」は、簡単に言えばロックを取得する方法です。メリットは管理対象のアプリケーションは増やさず、数値の調整などの運用時の調整もあまり必要としないため、3つの方法の中で最も運用時のコストが抑えられることです。

デメリットは、事前に新しいデータを準備できないため一時的にキャッシュ切れが発生することや、長時間のロックはできないのでキャッシュ生成にかけられる時間が短いことです。

キャッシュの期限切れ 運用コスト
事前作成 少ない 高い
期限更新 少ない ほどほど
ロック 多い 低い

ロックを選択

リニューアルの開発を進めていく中で、上記の3つの方法を比較検討していました。その際の制約として、以下の2点がありました。

  • 別プロセスでキャッシュを生成する仕組みが無いので追加開発が必要となる
  • 日付が変わる時など、固定でキャッシュを破棄するタイミングがある

まず、別プロセスでキャッシュを生成する方法は、現在のZOZOTOWNでは仕組みが存在せず、追加開発が必要でした。リニューアルの開発を進めている途中でキャッシュの導入が決まったため、スケジュールの都合で見送ることになりました。

また、ZOZOTOWNでは頻繁に日付や特定の時間を過ぎたタイミングで商品の販売開始や終了が発生したり、価格やクーポンなどの情報の変更が発生したりします。例えば、23時59分に生成したキャッシュが、1分後の24時00分には使えなくなってしまうことが起こりえます。期限を更新したキャッシュが無駄になってしまうことは避けたいと考えました。

今回は残る選択肢として、「裏側のAPIへアクセスするプロセスを絞る」方法を採用しました。

SETNXコマンド

前述の通り「裏側のAPIへアクセスするプロセスを絞る」ために、ロックを取得します。 BFFは多数のサーバーで稼働しているので、内部でロックを制御しても意味がありません。そこでRedisを使ってロックを制御します。

RedisにはSETNXという便利なコマンドが存在します。

redis.io

SETNXコマンドの特徴は次の通りです。

  • 通常のSETコマンドと同様に、key-valueのペアで登録できる
  • 既にvalueが登録済みのkeyを指定すると、上書きできず失敗となる
  • 成功したら1、失敗したら0が返ってくる

今回はこのSETNXコマンドの特徴を利用し、成功時のみ裏側のAPIへのアクセスを許可、失敗時はAPIへのアクセスを許可しないという制御をしています。こうすることで複数のサーバーで動作しているアプリケーションでもロックが可能になります。

この制御をSpring FrameworkRedisTemplateを使った場合、以下のコードのように記述できます。setIfAbsent()がRedisのSETNXコマンドに対応しています。

public boolean lock(String value, long ttl){
  String key = lockKey();
  Boolean result = redisTemplate
    .opsForValue()
    .setIfAbsent(key, value, ttl, TimeUnit.MILLISECONDS);
  return result != null && result;
}

SETNXコマンドでロックが取得できなかったプロセスは、キャッシュもなく最新の情報も取得できない状況にあるため、そのままではレスポンスを返せなくなってしまいます。この状況はできるだけ回避したいので、ロックを取得したプロセスが新しいキャッシュを登録するのを待つようにしています。

Redlockアルゴリズム

ロックを取得するプロセスを厳密に1つに絞るならば、本来はRedlockアルゴリズムを用いないといけません。詳しい説明はこちらの公式ページ、または翻訳ページに記載があります。

redis.io redis-documentasion-japanese.readthedocs.io

簡単に説明すると、以下のような考え方です。

  • ロック取得後に対象のMasterノードが落ちると、ロックが失われて別のプロセスからもう一度ロックが可能になってしまう
  • すべてのMasterノードに対してロックを試行して、過半数が取得できたらロック成功とみなす

今回のリニューアルでロックを必要とした場面は、商品やショップなどの情報を取得するためであり、データベースに保存されたデータを更新するためではありません。そのため、トランザクションのような厳密さは必要ないと判断して、Redlockは使わずにSETNXコマンドを使用しています。

重要なデータを更新するなどの厳密さが求められる場面では、Redlockアルゴリズムを用いるか、ミドルウェアのトランザクション機能を使うほうが良いです。

効果

以下のグラフは、負荷試験を行っている際に、裏側のとあるAPIへのアクセス状況をDatadogでグラフ化したものの抜粋です。

  • Before

  • After

何も対策していない場合は、定期的に15 req/sec流れていましたが、対策後は5 req/secと、約1/3に抑えることができました。

まとめ

Redisを使ったキャッシュへの取り組みと注意点、その回避方法を本記事ではご紹介しました。対策を施すことでAPIへの負荷も減り、現在は想定していたパフォーマンスを維持できています。

しかしながら、今後はよりアクセス数も増加し、それぞれのユーザーに合わせたおすすめの表示など、検索パターンや表示される商品のバリエーションが増加します。そうなると、キャッシュの意味が薄れてしまい、パフォーマンスの低下につながってしまいます。

ZOZOTOWNのリプレイスを進めつつも、既に置き換えが済んでいるAPIのパフォーマンス改善もあわせて考える必要が出てきています。

さいごに

ZOZOTOWNのリプレイスはまだまだ道半ばの段階です。新規機能の追加とパフォーマンスの維持を両立させながら前へ進む必要があるため、考えることがたくさんあります。

ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。

hrmos.co

カテゴリー