運用中のサービスに負荷試験を導入した事例の紹介

SREチーム(新卒)の市川恭佑です。今回は、Tonamelという自社サービス(Web)において負荷試験を導入した事例を紹介します。

このエントリは「先送りされがちな負荷試験の導入について心理的なハードルを下げる」ことを目的としています。 そのため、事例紹介と銘打っていますが、列挙される事実の独立性よりも文脈性を優先しています。 表現が少し冗長に感じるかもしれませんが、負荷試験について距離感を感じている方は是非お付き合いください。

負荷試験を導入するに至った経緯

Tonamelは、本格的なリリースから5年以上という、比較的長い運用歴を持つサービスです。 まず、何故このタイミングで負荷試験を導入することになったのかについて、その経緯を説明します。

ポストモーテムによる気づき(文化的な土台)

今年の3月に公開されたエントリにもあるように、カヤックでは着実にポストモーテム文化が浸透しつつあります。 詳細はリンクされた記事を参照していただきたいですが、ポストモーテムとはシステムにインシデントが発生した際に作成される事後検証資料のことです。 これは責任の追求を目的とするものではなく、事例を全社で共有し、知見を活かすために運用されています。 このような良い循環に引っ張られる形で、次第に開発チームの間でも負荷試験の重要性も認識されるようになりました。

techblog.kayac.com

ステージング環境の整備(技術的な土台)

Tonamelにおいては、去年の11月に筆者自身がアルバイト入社した時期から、負荷試験を行うための技術的な土台が用意されてきました。 具体的には、ステージング環境の作成や検証用データベースの立ち上げ自動化などです。これらは「本番環境に近い環境」の準備コストを低減させるものであり、再現性の高い負荷試験を実行するための下地と言えるでしょう。

techblog.kayac.com

Amazon Aurora MySQLのサポート期限(現実的な契機)

TonamelではAmazon Web Services (AWS)を採用しており、RDBにはAmazon Aurora (RDS)を利用しています。 これについて、公式ドキュメントにあるように、MySQL 5.6互換のDBエンジンであるAmazon Aurora MySQL Version 1のサポート終了が予告されています。

具体的なスケジュールについては以下のように記されております(一部略)。

  1. Now through February 28, 2023 – You can at any time start upgrades of Aurora MySQL version 1...
  2. January 16, 2023 – After this time, you can't create new Aurora MySQL version 1 clusters or instances from either the AWS Management Console or the AWS Command Line Interface (AWS CLI)...
  3. February 28, 2023 – After this time, we plan to automatically upgrade Aurora MySQL version 1 clusters to the default version of Aurora MySQL version 2...

特筆すべきは二つ目の項目で、要約すると「新しいインスタンス作れなくなるよ!今あるものは使えるけど。ただグローバルデータベースの場合は注意してね。」といった旨のことが書いてあります。1

Tonamelではグローバルデータベースは利用しておらず、検証用のDB立ち上げもレプリケーションの仕組みに乗っかっているので技術的な問題は発生しないことが分かっていました。しかしながら、インスタンスの新規作成という基本的な機能の制限は、迫り来るEOLの跫音を感じさせるものです。

また、現在はこのステップの予定がJanuary 16, 2023と記されていますが、最初に発表された時点ではSeptember 27, 2022とされていました。 このような事情で7月ごろからSRE定例2でMySQLのバージョンアップという課題が強く意識された結果「予期せぬパフォーマンスの劣化を防ぐために負荷試験が必要である」と結論付けられました。

負荷試験の設計および作業方針

今回、一連のタスクについて 「兎にも角にも、まず動かす」 という一貫した姿勢で挑みました。 よく耳にする "Done is better than perfect." というスローガンは、その実践にこそ最大の意味があります。

Amazon Elastic Container Service (ECS)を利用する

負荷試験においては、シナリオを実行する側のインフラも重要になってきます。 というのも、大量のリクエストを送信するためには相応の計算資源およびネットワーク環境を要求するためです。 試験実行側のリソースを十分にプロビジョニングできない場合、意図した負荷が掛けられないという状態に陥ってしまいます。

先述のとおり、TonamelではAWSを利用しており、特に計算資源はECSでプロビジョニングされています。 また、ecspresso3というOSSのツール(および開発者)を擁していることに代表されるように、カヤック社内にはECS運用に関する知識・経験が豊富にあります。

筆者自身もECS(特にFargate起動タイプ)とは仲良くしているので、試験実行環境にはECS on Fargateを採用することにしました。 くどいですが、まず動かすことが大事なので、とにかく慣れているものを優先します。

もちろん慣れ以外にも、スケーリングの自由度が優れているという大きな利点があります。 例えば、AWS Lambdaで負荷試験プログラムを実行するとファイルディスクリプタの上限にすぐ到達してしまいますが、ECSではulimitの設定によってこれを大きく緩和できます。 また、つい最近Fargateタスクの最大リソースサイズが4倍になるというニュースがありました。 これによってFargatが従来得意としていた水平スケールだけでなく垂直スケールによって計算資源の圧迫に対処できる可能性が広がりました。

単純なシナリオに絞る

シナリオについては、まずは単純なものだけを用意すると決めました。 具体的には、 ユーザーのログインを含まないシナリオに限定 しました。誰でも見られるページに対してのみ負荷を掛けるということです。 これは素朴すぎるように響くかもしれませんが、伝えたいことは「まずは本当にそのレベルでいい」ということです。

実際のところ、Tonamelでは匿名でアクセスできるページにもボトルネックになりうる箇所が存在します。 とはいえ、今の時点で見当のついていない場所にボトルネックが現れる可能性もあるので、本当は全ページを網羅したいところです。

また、自社の別案件においては、ログイン後の複数ユーザーが連携するような高度なシナリオによる負荷試験を導入した実績もあります。 外の世界に目を向けると、AIで実際のログを分析した結果からシナリオを生成する取り組みさえあるようで、好奇心が掻き立てられます。

・・・などなど。欲を言えば枚挙に暇がないです。それだけ「ログインを含まないシナリオのみ」という条件は不完全なものです。

それでも、不完全さを甘んじて受け入れることで実現可能性を確保するのが最初の一歩としては最善策です。

k6を使う

声高らかに「まずは単純なシナリオ」とは言ったものの、拡張性が著しく損なわれた状態を以て完成とするのは流石に褒められません。 例えば、ユーザーログインを含むシナリオを将来的に作れるだけの拡張性は捨てられません。

他にも、Tonamelはブラウザとサーバー間の通信においてGraphQLでスキーマを規定しています。 このため、HTMLを返す古典的なサーバーやRESTful APIの形式に局所最適化されたシナリオ記述ツールは適しません。 もちろんGraphQLリクエストを「エンドポイントが一つだけのRESTful APIへのPOSTリクエスト」と捉えてクエリ文字列などを渡すことも可能です。 しかし、汎用言語でシナリオを記述できない場合、ほとんど確実に将来的な拡張性に致命的な影響を与えることを確信していました。

こうした事情から、k6というツールを使ってシナリオを記述することにしました。4

k6はシナリオがJavaScriptによって記述されます。Web業界においてはJavaScriptが実質的なリンガ・フランカとなっているので、シナリオのメンテナンス面で持続性が高いと言えます。 また、垂直スケールの効率が良いことが強みです。公式ドキュメントには毎秒10万~30万リクエスト程度であれば1台のEC2インスタンスで捌けるという記述がありますが、この特徴はECSのスケーリング戦略とも一定の親和性があります。

なお、k6が実行するJavaScriptのランタイムはNode.jsではないのでNode Modulesが利用できません。 また、一口にJavaScriptと言っても、k6の対応しているものは言語仕様は「拡張されたECMAScript 5」なので、例えばconstletは使えません。 もちろんTypeScriptも使えません。

これらを承知の上で、トランスパイラとモジュールバンドラの助けを借りることでフロントエンドの資産を再利用できるのではないかと企んでいました。 結局そのまま使いまわせる箇所は少なく、目立ったものは「GraphQLのクエリ文字列を一つ一つ手書きしていく必要性は解消できた」程度でしたが......。

負荷試験の導入作業(概要)

実際にやったことは以下の2種類に分けられます。

  • シナリオ記述や実行側インフラの立ち上げなどの純粋なタスク
  • 負荷試験のために必要だが用意されていなかったものの作成

今回はシンプルにやることを重要視したため、前者について述べると基本的に退屈な内容になってしまいます。 かといって後者について語るとコンテキストが深い上にエントリの趣旨が濁ってしまいます。

ゆえに、ここでは概要だけ表形式で提示するに留めます。 ただ、瑣末な項目ほど、意外と同じことで悩む人がいるかもしれないので、別の機会にブログで取り上げるかもしれません。

試験対象側の環境整備

まず、試験対象側に関する作業概要です。

タスク名 説明 備考
NGINXの認証バイパス ステージング環境には通常のアプリケーションとは別の認証が追加で必要だったが、これが人間ではなく機械がアクセスする場合に相性が悪かったので別の方法を用意した。 うっかりNGINXの設定ミスにより事故を起こし掛けた=>NGINXの挙動をテストする機構を作ってみたら、これに時間が掛かった。
ステージング環境のスケーリング方針変更 もともとAPPおよびDBはお金がもったいないのでサイズだけ本番とずらしていたが、負荷試験のタイミングで本番環境と揃える必要が出てきたが「最初から本番環境とサイズを揃えておいてステージング環境を使わないときはリソースを畳む」という方向性でお茶を濁した。 クロスアカウントで情報を揃えるパイプラインやCLIを新規に作る労力が割に合わないというのが判断理由。

試験実行側の環境整備

次に、試験実行側に関する作業概要です。

タスク名 説明 備考
AWSリソースの準備 Parameter StoreやECSなどのリソースを作成し、負荷試験プログラムを動かすために必要なもの一式を揃えた ステージング環境のアカウントに構築したが、本体のリソースと混同しないようにTerraformのStateを分離した。
パイプラインの構築 環境構築や負荷試験の発火のためのパイプラインを作成した。 「まず動く」の前に余計な作り込みを避けるため負荷試験の発火については Makefile を作ったのみ。
シナリオの雛形作成 TypeScriptおよびNode Modulesを利用し、GraphQLのために妥協可能なレベルで抽象化したモジュールを記述した。 k6が利用可能な状態のECMAScriptに変換するビルド環境も作成した。

結果として、これらによって負荷試験を 「兎にも角にも、まず動かす」 ことは実現できました。

しかし「シナリオの雛形作成」については、先述したとおり、苦労した割に結局フロントエンドの資産をほとんど活用できませんでした。

たとえば、レスポンスのJSONをソースコードの型にマッピングする部分について、フロントエンドのソースコードではApollo Clientの提供するジェネリクスを利用しています。 しかし、k6はランタイムが異なるためブラウザのfetch APIなどが無効で、代わりにk6/httpというモジュールを使うことが必須です。 このため、型のマッピング部分については(満足できるクオリティではないですが)自分で実装することになりました。

助け舟: シナリオの自動生成

最終的にシナリオの開発体験は多くの伸びしろを残した形での着地となりましたが、大変ありがたいことにサーバーチームのリーダー(谷脇さん)に後日それを埋め合わせるようなソリューションを提供してもらえました。

具体的には、E2Eテストで使うPlaywrightからHTTP Archive (HAR)ファイルを経由してk6のシナリオを自動生成するものです。 下記のスライドが分かりやすいので、気になる方はぜひご覧ください。

speakerdeck.com

まとめ

負荷試験を 「兎にも角にも、まず動かす」 という姿勢を保ったまま設計を考えてタスクを進行できたのは良かったです。最後に大変ありがたい助け舟が来たのは紛れもなく谷脇さんのおかげですが、とりあえず一旦動くものを作ったことも1%ぐらいは寄与している......はずです。

また、今回は導入部分に焦点を当てたので「負荷の掛け方はどのようなパターンを採用すべきか」などの部分に踏み込むには至りませんでした。 あいにく、自分は現状この問題領域において特筆すべき洞察を持ち合わせていません。 これからも負荷試験との関わりは続く見通しなので、サービスの特性に応じたシナリオの勘所については今後の課題としたいです。

カヤックでは、開発チームと協力してサービスの信頼性を向上したいエンジニアも募集しています。

hubspot.kayac.com


  1. 詳細については典拠にある新鮮かつ正確な情報を参照してください
  2. Tonamelでは開発チームとSREで毎週定例ミーティングを行なっています
  3. ecspresso は近々v2が出ます、お見知り置きを!!(詳細はこちら
  4. 気になる方は絶賛発売中のISUCON本にも記載があります!!(宣伝)