エンジニアの権守です。今回は、VASILYのWebフロントチームがWeb版iQONのエラーレートを0.1%から0.003%以下まで減少させた際に、行った取り組みについて紹介します。
概要
今回行った取り組みを、ひとことで言うと、テストとデバッグの強化です。 具体的には、次の3つの取り組みを行いました。
- APIモックを用いたテストの廃止
- テストの高速化
- New Relicの活用
各項目について、詳しく説明していきます。
APIモックを用いたテストの廃止
iQONは、iOSアプリ、Androidアプリ、Webの3つのプラットフォームを展開しており、 そのために、主なロジックをAPIサーバーで処理しています。 そして、それぞれのフロントのテストにはAPIモックを利用していました。 しかし、次の2つの理由からWebフロントで利用していたAPIモックの保守は行われていませんでした。
- APIの更新をフロントチームが常に把握するのが困難
- APIの更新の度にその変更をリポジトリに取り込みたくない
他のチームがAPIを更新したことを察知して、該当するエントリポイントに対してモックを作成するのは手間がかかります。 また、その都度、フロント側のリポジトリを更新し、コミットするのは気乗りしませんでした (当時、モックはAPIのレスポンスをjsonで保存し、Webフロントのgitリポジトリで管理していました)。
そのような背景からAPIモックが保守されなくなり、 テストを通っていてもエラーが発生するという事態が起こっていました。
そこで、Webフロントチームでは、モックではなく開発サーバー上で動作しているAPIを利用するようにしました (ここで参照するAPIは、APIリポジトリの最新のmasterブランチを自動的に取り込んだものです)。
これにより、フロントチームが保守せずとも自動的に最新のAPIを参照するため、テストの正当性が保たれるようになりました。
しかし、JSONで保存されたモックを利用するのとは異なり、実際にリクエストを投げるため、 テストにかかる時間は伸び、15分ほどになってしまいました。 テスト時間が伸びたことによりチームの士気は下がり、また、新たなテストの追加を躊躇するようになりました。 そこで、次に、チームはテストの高速化に取り組みました。
テストの高速化
まず、最初に行ったのはAPIの開発サーバーをwebrickで立ち上げていたのをunicornに変えました。 これは非常に手軽な変更でしたが、テスト時間を2, 3分減らすことができました。 しかし、それでもまだ10分以上かかっていたので、さらに高速化を続けました。
次に、取り組んだのはテストの並列化です。 実は、ローカルではテストに1分ほどしかかかっていませんでした。 10分以上かかっていたのはCircle CI上からリクエストを投げることによって、 通信に時間がかかっていたのです。 そこで、テストの並列化を行うことにしました。 iQONではテストにRSpecを利用しています。 RSpecを並列処理するライブラリの候補として、次の3つを検討しました。
結果的にtest-queueを導入することにしました。 test-queueを選んだ理由としては、次に2つが挙げられます。
- 割り振るプロセスを動的に決めてくれる
- 導入が簡単
parallel_testsはテストを単純にワーカー数で割るため、重たいテストが一つのプロセスに集中すると、全体の実行時間が長くなってしまいます。 rrrspecは、その点は問題ありませんが、導入にはMySQLやRedisの準備が必要でしたので、今回は見送りました。
test-queueの導入は非常に簡単で、Gemfileへのtest-queueの追加と、Circle Ciの設定ファイルの変更のみで済みました。
# Gemfile group :test do gem 'test-queue', '0.2.13' end
# circle.yml test: pre: override: - TEST_QUEUE_WORKERS=10 bundle exec rspec-queue spec/controllers
test-queueを導入した結果、Circle CI上のテストの実行時間も5分程度に収まるようになりました。
New Relicの活用
テストがよく運用されるようになりましたが、それでも全てのバグを予め潰せるわけではありません。 そこで、起こってしまったエラーをより早く検知し、対処できるように、New Relicを活用することにしました。 Webフロントチームでは、元々、エラーの検知やパフォーマンスの測定のために、New Relicを導入していましたが、 様々な要因で、まともに機能していませんでした。 そこで、次のような改善を行いました。
- 404エラーの除去
- カスタムパラメーターの追加
- アラートの設定
404エラーの除去
以前は、正常に処理した結果としてNotFound (404) になるような場合にも、 New Relicにエラーとして送信していました。 そのため、本当に問題のあるエラーが埋もれがちでした。 そこで、404エラーの場合は、New Relicに送信するのをやめました。 また、その際に、正常に処理できていない場合に404にするといった、 適切でない例外処理を除き、エラーをNew Relicに適切に送信するようにしました。 これによって、サイト上で起こっているエラーが一目でわかるようになりました。
カスタムパラメーターの追加
New Relicにはカスタムパラメーターという機能があり、エラーの送信時に、 情報を付加することができます。 そこで、デバッグの効率を改善するために以下のパラメーターを追加しました。
パラメーター名 | 値 | 備考 |
---|---|---|
full_url | クエリパラメーターを含まないURL全体 | デフォルトで送られるURLはurlでなくpathなので、item.iqon.jpとwww.iqon.jpを判別するために追加しました |
params | リクエストに渡されたパラメーター | 同じURLでもパラメーターによって処理が異なることもよくあるので、デバッグに必須です。パスワードやメールアドレスなどの個人情報については送信しないようにしています |
referrer | リファラー | エラーが発生した際にそのページが不要な場合があります。それを判断するためによく用います |
user_agent | ユーザーエージェント | 同じURLでもPCとスマホで処理が異なるので、その判別や、botによるアクセスかを判断するために用いています |
iQONのWebフロントはRailsで実装されています。 今回のカスタムパラメーターの追加は、application_controllerに以下の様なコードを追加することで実装しました。
before_action :add_custom_requests_to_newrelic def add_custom_requests_to_newrelic copied_params = params.clone copied_params[:email] = copied_params[:email].gsub(/./, "*") if copied_params[:email].present? copied_params[:password] = copied_params[:password].gsub(/./, "*") if copied_params[:password].present? ::NewRelic::Agent.add_custom_parameters( params: copied_params.inspect, full_url: request.original_url, referrer: request.referrer, user_agent: request.user_agent, ) end
上の画像はNew Relicに送られてきたエラーの詳細画面です。 追加したカスタムパラメーターは画面の右下部分に表示されています。 これらのカスタムパラメーターを追加することで、デバッグの効率が大きく改善しました。
アラートの設定
エラーレートが一定以上に高まった場合に、Slackに通知がくるように設定しました。 これによって、万が一、デプロイした内容にバグが含まれていた場合などにもいち早く事態に気づくことができるようになりました。
まとめ
適切なテストを行うための仕組みを作ることと、デバッグの効率化を図ることで、 エラーレートを下げることに成功しました。 特に、404エラーの送信を止めたことがエラーレートを下げた一因ではありますが、 それによって他のエラーが表面化することで、保守されていなかったページのエラーや、 特定の条件でのみ発生するエラーに気づけたことが大きかったです。
また、今回行ったカスタムパラメーターの追加やテストの並列化はそれぞれ一日で導入できたのに対し、 非常に効果的でしたので、同様のパフォーマンス監視ツールやテスト方法を用いている方におすすめです。
最後に
VASILYでは、プロダクトの品質に責任をもてるエンジニアを募集しています。 ご興味のある方は是非こちらからご応募よろしくお願いいたします。