OpenRestyを導入して負荷と戦ったけど、戦えなかった話

この投稿はアイスタイル Advent Calendar 2019の22日目の記事です。

みなさん、こんにちは。
普段、検索基盤を担当しているmiyaharasです。

この記事の趣旨

とあるシステムの可用性を向上させるPJTにアサインしました。
(検索基盤とは、完全に別システムです。)

そのPJTで、OpenRestyを導入することになり、
なかなか使い勝手が良かったので、
PJTの振り返りとともに、OpenRestyについて書いていこうかと思います。

PJT発足の経緯

さてさて、PJT発足の理由としては、
とあるイベントで、大量のトラフィック(見込みで秒間1200アクセスぐらい)が発生するので、
システムの可用性を上げて、サイトを落とさないようにしてくれというものでした。

開発期間は約3ヶ月で、
チームメンバーの全員が、
仕様もアーキテクチャも何もわからないところからのスタートでした。

リプレイス前のアーキテクチャについて

アーキテクチャ
※AZやサブネットは、省略しています。

さて、リプレイス前のアーキテクチャをさらっと紹介すると、
RDSから取得した項目で、php(web)で動的にhtmlを生成しています。

取得した項目の一部はキャッシュしていて、
ある程度は負荷対策ができているようですが、
キャッシュしていない項目もあるらしく、
秒間1200アクセスには耐えられそうにありませんでした。

大量のトラフィックが発生した際に、
パフォーマンスで一番ボトルネックになるのはDBが多いので、
DBへの通信をどうにかこうにか減らす必要がありました。

リプレイス後のアーキテクチャ

アーキテクチャ図

最終的に、上記のアーキテクチャ(リバースプロキシ)となりました。

phpで生成したHTMLをまるっと、OpenResty経由でキャッシュしています。
キャッシュストアについては、マネージドサービスのElastiCache(Redis)を採用しました。

さて、OpenRestyという、あまり聞き慣れないミドルウェアが出てきました。

リバースプロキシに利用されるミドルウェアとしては、
nginxをイメージする方も多いのかなと思います。
少なくとも私は、このPJTに携わるまで存在すら知らなかったです。

なので、忘備録も兼ねて、
– OpenRestyとは?
– なぜ、OpenRestyを採用したのか?
– 他のミドルウェア、アーキテクチャだと実現できなかったのか?

についても書いていきます。

OpenRestyとは?

OpenRestyとは、nignxにサードパーティモジュールを予め拡張した状態で、提供されているOSSのアプリケーションフレームワークです。
ですので、結局のところnginxなのですね。
違いとしては、サードパーティの拡張モジュールをセットで用意してくれるか否かです。

なぜ、OpenRestyを採用したのか?

結論としては、より少ないコストで、多くの効果を得られるからになります。

どういうことかを説明する前に、
ボツとなってしまったアーキテクチャ案を紹介します。

ボツ案

htmlを動的に生成するのではなく、予め静的に生成しておく

リプレイス対象のページは、約6万ページほど存在していました。

こちらを予め静的コンテンツとして吐き出しておいて、
ある程度、スケールさせておけば、
DBへ通信がなくなり、可用性も高くなります。

ただ、こちらの案は、
運用のしづらさでボツとなりました。

予め、HTML吐き出すことは、実現できると思うのですが、
コンテンツの更新に伴う運用コストが高くなってしまうという問題があります。

例えば、特定の時間帯で文言が変わったり、ページを追加したりといった作業は、
プログラムの条件分岐、DBのフラグで制御しているパターンが多いと思います。

これらを検知して、HTMLに吐き出して、スケールさせたサーバーに配信するのは、開発コストがかかりそうだったので断念しました。

nginxで、リバースプロキシ

PJT開始当初は、OpenRestyの知見がなかったので、
素のnginxでのアーキテクチャが検討されていました。

アーキテクチャ図としては、下記になります。
アーキテクチャ図

冗長化されたプロキシサーバー単体でキャッシュすると、
冗長化する分、キャッシュヒット率が下がってしまいます。

そこで、EFSで各プロキシサーバーに、
EFSでキャッシュファイルをマウントするという案が出てきました。

キャッシュヒット率は下がらず、コンテンツ更新による運用も、
EFSで特定のキャッシュファイルを削除するだけですので低コストです。

ただ、問題は負荷がかかった際のパフォーマンスです。

弊社の別案件で、imgファイル等の静的コンテンツを、
数十台のEC2にマウントした状態で、大量のトラフィックが発生した際に、パフォーマンスが劣化したことがありました。

この経験則から大量のファイルに対する頻繁なIOに適さない可能性がありそうだという判断となり、断念しました。

なぜ、OpenRestyを採用したのか? part2

さて、ここまでボツ案を書いてきましたが、
それぞれの案で課題があったと思います。

  • 開発コスト
  • パフォーマンス
  • コンテンツの更新、削除に伴う、運用コスト

これらをまとめて、解決するのがOpenRestyでした。

OpenRestyなら、RedisやMemcachedと、
通信するためのモジュールが一式揃っているので、下記の効果を得られました。

■キャッシュストアを一箇所に集約することができる

一箇所に集約することで、キャッシュヒット率を高めることができますし、
コンテンツの更新、削除に伴う、運用コストが少ないです。

また、全コンテンツにまたがるようなバグがあっても、修正後にFlushすれば、コンテンツを更新できます。

■AWSのマネージドなキャッシュストアを利用できる

Redisとの通信ができるので、ElastiCache(Redis)を導入でき、
キャッシュサーバーの構築、運用コストもで削減できました。

■副次的な効果で

PJTが進んでいくうちに、オリジンサーバーへプロキシする際に、
Request headerの一部を、Cookieにコピーする必要が出てきました。
こちらも、OpenRestyに含まれているngx_luaで、実装することができました。

実際に、負荷に耐えられたのか?

実際にリリースしてみてどうだったのかというと、、、
いらすとや

大人の事情で、リリースできませんでした( ;∀;)

理由としては、このPJTのリリース判定直前で、
バグのようなものが見つかったためです。

リリース判定後に、そのバグのようなものは、仕様だったことが分かったのですが、、、
後一歩間に合いませんでした。

ただ、負荷テストでは、秒間1200アクセスで、
エラー率0%で捌くことができたので、
アーキテクチャとしては、間違ってなかったと思います。

今回利用した拡張モジュールの紹介

下記です。
– https://github.com/openresty/srcache-nginx-module
– https://github.com/openresty/lua-nginx-module

最後に

リリースできなかったのは、本当に残念でした。

それでも、AWSを業務で初めて使用しましたし、
高負荷に耐えられる(想定の)アーキテクチャをインフラから構築するという、面白い経験をさせていただきました。

また、別のPJTで、この経験を活かせていければなと思っています。

ではでは〜 ノシ

検索基盤担当してます。たまに、別の基盤APIに出張してます。 シマコー好きです。