こんにちは。技術本部SRE部ZOZO-SREブロックに所属している杉山です。SRE部のテックリードとして、オンプレ/クラウドのインフラを担当しています。
ZOZOTOWNでは、既存システムのリプレイスプロジェクトを進めています。各サービスのマイクロサービス化は進んでいますが、バックエンドでは「WindowsServer + IIS」で稼働しているシステムがまだ多く残っています。そのリプレイスプロジェクトを進めるうえで重要なポイントとなる、セッションストアのリプレイス「セッションオフロードPhase 2」が完了しました。本記事では、リプレイスしていくうえでの工夫や課題への対応を紹介します。
目次
セッションオフロードPhase 2について
プロジェクト概要
セッションオフロードプロジェクトは、CacheStoreリプレイスのPhase 1と、SessionStoreリプレイスのPhase 2で構成されています。
- Phase 1:CacheStoreのリプレイス
- Phase 2:SessionStoreのリプレイス
Phase 1:CacheStoreのリプレイス
Phase 1:CacheStoreのリプレイスについては、こちらの記事をご覧ください。
Phase 2:SessionStoreのリプレイス
セッションオフロードPhase 2は、WebサーバーであるIISのサーバー内セッションの機能を無効にし、外部SessionStoreにオフロードさせるリプレイスプロジェクトです。ZOZOTOWNが抱えていた、スケーリング運用やフロントエンドのリプレイスの課題を解決することを目的としています。
ZOZOTOWNが抱える、セッション管理の課題
以下のような課題がありました。
- スティッキーセッションのため、スケーリング運用に支障がある
- スティッキーセッションのため、サーバー負荷が偏る
- オフロードしないとフロントエンドリプレイスができない
それぞれの課題について、具体的に説明します。
1:スティッキーセッションのため、スケーリング運用に支障がある
ユーザーセッションにIISの機能であるセッションを利用しており、ユーザーセッションがWebサーバーに紐づきます。LoadBlancerでは、Cookie Persistenceと呼ばれる機能で振り分け先の固定が必要です。そのため、Webサーバーのスケーリング運用のうち「スケールイン」に注意が必要です。ユーザーのセッションが期限切れになるのを待ったり、夜中の時間帯を狙ってスケールインする必要があるなど、運用に支障がありました。
2:スティッキーセッションのため、サーバー負荷が偏る
ユーザーセッションごとにWebサーバーを固定する必要があるので、リクエストのロードバランシングで偏りが発生します。これにより、一部のサーバーは負荷が高くなってしまうこともありました。
3:オフロードしないとフロントエンドリプレイスができない
ユーザーセッションがWebサーバーに対してスティッキーなため、フロントエンドを段階的にモダンな技術でリプレイスするという手法が取れません。例えば、まずはトップページをコンテナ化しEKSで運用したいが、セッション情報が特定のWebサーバーに保存されているため、セッション情報を維持しにくいです。
Webサーバー内のセッションを、外部のデータストアにオフロードすることで、これらの課題を解決しユーザーリクエストがどのWebサーバーに割り振られてもセッションを維持できるようにする事が目的です。
リプレイス前後の構成
リプレイス前(左)は、WebサーバーのIIS内のセッションを、独自ライブラリを介して利用していました。リプレイス後(右)は、外部SessionStoreにセッションデータをオフロードし、独自ライブラリに実装したクライアントを用いて利用します。
また、リプレイス時のアプリケーションの改修コストを抑えるため、独自ライブラリ内でRedisクライアントをラップして実装しました。そして、アプリケーションコードの改修を最小限に抑えて、Redisへの接続に切り替えることができるようにしました。
採用技術について
Amazon Elasticache Redis
世間で広く使われている技術のRedisを採用しました。クラウドサービスとしては「Amazon Elasticache Redis」(以下、Redisという)の「Clusterモード有効」を採用しました。
Redisは、負荷特性に応じてClusterモードの有効/無効を選択できます。ホットキーの多い負荷特性の場合は、Clusterモード無効にして複数のリードレプリカとリーダーエンドポイント使う方が効果的な場合もあります。
セッションをオフロードすることでRedisは全Webサーバーから多くのリクエストを受けることになりますが、セッションデータはユーザー毎のデータなのでホットキーはありません。このことから、Clusterモード有効のRedis(以下、RedisClusterという)を利用してシャードのスケールアウトで負荷分散できるようにしました。
例えば、平常時は30シャード、セール時は40シャード、ZOZOTOWN最大の負荷となる冬セールは60シャード。というように、必要に応じてシャード数を増やし負荷分散させています。
RedisClusterのシャードとは
- シャード:1~6個のRedisノードで構成される集合の単位。
- ノード:Reidsノード単体のことで、「Primary / Replica」の種類がある。
本記事では、「シャード」をスケーリングの単位。「ノード」をRedisノードとして表現します。
冗長構成について
RedisClusterの各シャードは、プライマリノードの他に別AZのレプリカを持たせることができ、ノード障害時に「プライマリノードのフェイルオーバー」で自動復旧させることが可能です。検証では、プライマリノードのフェイルオーバーでの復旧は障害の内容にもよりますが、1分弱程度から数分でフェイルオーバーが実現できました。しかし、ユーザーセッションを取り扱うとても大事なシステムとなるため、弊社では30秒程度の復旧を目標としていました。
この要件を満たすために、シングルAZのRedisCluster(レプリカ無し)において、プライマリクラスター/セカンダリクラスターでAZ違いの2クラスター運用にしました。独自ヘルスチェックシステムをEKSで動かし、ノード障害発生時には速やかにクラスターフェイルオーバーを行う事で、約15秒~30秒程度でのフェイルオーバーを実現しました。
2クラスター運用としたことにより、メンテナンスの際にセカンダリを活用することで、ローリングでメンテナンスを実施可能になったことも1つのメリットとなっています。
セッション期限切れ処理について
ZOZOTOWNは、ユーザーの買い物体験を向上するための機能として「在庫引き当て」という機能があります。この機能は、商品をカートに投入したユーザーがセッションを維持している間は、決済前でも在庫を確保するという機能です。この在庫引き当ては、セッション情報が紐づいているため、セッション期限切れ(Expire)時に在庫を戻す処理が必要です。
Redisでは、keyにTTLを設定し自動でExpireさせる機能がありますが、検証当時はExpireをトリガーとして任意の処理を実行できませんでした。Pub/Subモードを利用するという事も考えましたが、何らかの障害でSubscriberがメッセージを受信できなかった場合、その商品の在庫引き当て解除ができない事態となることが考えられました。
可能な限り確実に処理を実行できることを担保したい。障害が発生した後でも、在庫引き当て解除の処理が確実に実行されるようにする。これらを実現するために、外部ワーカーでRedisのデータをスキャンし「セッション期限切れの処理」と「在庫引き当て解除」の処理を行っています。
このワーカーは、EKSでJobを稼働させ、後ろで待ち構えているQueueに「在庫引き当て解除リクエスト」を流しています。
本番リリース
「Cookie Persistence」を利用した、N%リリース
本番リリースでは、AkamaiALBを活用することで、スティッキーを外しながらN%リリースしました。リリース時の構成は以下のような構成になります。
AkamaiALBで「スティッキーON / サーバー内セッション」のサーバープールAに向けたOrigin-Aと「スティッキーOFF / セッションオフロード状態」のサーバープールBに向けたOrigin-Bを用意します。
AkamaiALBの「Cookie Persistence」を利用してリクエストの振り分け先をパーセンテージで制御します。これにより、一度Origin-Bに振り分けられたユーザーのリクエストはOrigin-Bに振り続けられます。
切り戻しを行う場合には、Origin-Bのパーセンテージを0%にしてから、CookiePersistence用のCookie設定を再設定します。これにより、Origin-Bへリクエストが振り分けられなくなります。
AkamaiALBを利用した振り分け手法は、過去のブログでも紹介しています。
リリース後に発生した想定外のトラブル
ZOZOTOWNは、オンプレ/クラウドのハイブリッド環境で運用しており、Direct Connect(以下DXという)を利用しています。
セール時にはAmazon Elastic Compute Cloud(以下、EC2という)も活用することで、Webサーバーをスケーリング運用していますが、平常時のWebサーバーの多くはオンプレで稼働している運用でした。
N%リリースが50%の時のとある日。平常時よりも負荷が高い状況ではあったのですが、負荷のピーク時間帯なると急激にDXのデータ通信量が増え、10G回線の帯域が上限に達してしまうという事態となりました。この時は切り戻しを行い10%リリースまで縮小させることで対応しました。
分析した結果、アプリケーションのあるロジックが非常に大きなデータをセッション情報に読み書きを行っていて、ユーザーリクエスト数に応じてDXのデータ通信量が増加するというロジックになっていました。
特定の条件下において発生する事象でした。全てのWebサーバーがEC2であればDXの帯域枯渇は起こらなかったでしょう。突き止めた原因に対してロジックの改修を行い、セッションデータの最適化を行う事で、現在はデータ通信量が急増することは少なくなりました。
この対応の後、2週間ほどかけて無事100%リリースとなりました。ハイブリッド環境では、DXにも注意が必要であると痛感しました。
まとめ
セッションオフロードプロジェクトは、Phase 1 / Phase 2と合わせると1年以上の長い期間をかけたプロジェクトでした。ZOZOTOWNの大規模サービスを支えるためのインフラの工夫やハイブリッド環境ならではのトラブルなど、いろいろなことを経て無事リリース完了できました。何よりも、今後のフロントエンドリプレイスを加速させていく準備を整えられたことが良かったです。
ZOZOでは、インフラSREを募集しています。ご興味がある方は以下のリンクから是非ご応募ください!