Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連
  • インフラ

Railsアプリに最適なAWS EC2インスタンスタイプとは(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Railsアプリに最適なAWS EC2インスタンスタイプとは(翻訳)

皆さんはAmazon EC2 インスタンスタイプのリストをご覧になったことはありますか?このリストにはさまざまなサイズの仮想マシン(VM)が並んでいて、皆さんはこれらをレンタルしてコードを実行できます。グループごとにさまざまなサイズのVMが山ほどあるので、サイズによってグループ分けされています。

では「Railsアプリを動かすならEC2インスタンスのどのタイプで動かすべきでしょうか?」

その答えは見た目よりもシンプルです。百聞は一見にしかず。

皆さんは数字がお好きですか?私は大好きですが、数字を見たくない人は本記事の末尾までスキップしていただければ、いい感じにまとめた結論をご覧いただけます。なお、心の底から数字が好きでたまらない方のために、私が得た直接的な結果をすべて含んだ生データダンプも用意してあります。

インスタンスタイプと私のテスト方法の解説

既にご存知の方もいらっしゃるかと思いますが、私はある「巨大なRailsアプリ」を頻繁に実行しては多数のリクエストをどのぐらい高速に処理できるかをRails Ruby Bench(RRB)というパラレルベンチマークでチェックしています。

noahgibbs/rails_ruby_bench - GitHub

私は最初にEC2のどのインスタンスタイプで実行するかを検討し、それからベストなインスタンスタイプでスピードテストを実施します。

Railsアプリは驚くほど CPUに束縛されます。このことは、Ruby 2.0から2.6で72%も速度が向上した理由の一部でもあります。つまり、T4のようなバーストパフォーマンスインスタンス(burstable-CPU instance)は短期間のベンチマークでときどき目覚ましい結果を叩き出しますが、実行時間の長いベンチマークでは結果が落ちるということです。自分のアプリが極端にビジーになったり極端なアイドル状態になったりする場合は、T4を検討する価値があるかもしれません(とは言うものの、皆さんのアプリに通用する信頼できる数値を私が代わりに提供するわけにもいきませんが)。結果はそれらの負荷がどのぐらい均等であるかだけに依存します。そして、バーストパフォーマンスインスタンスではそれらの負荷が均等であるほど結果が悪くなる傾向があります。

DiscourseとRailsはメモり食いで、私はこれらのベンチマークをlower-RAM-per-CPUのC5インスタンスで回したことが一度もないほどです。もし皆さんのアプリがメモリ利用量が少ないかメモリ利用量を十分抑えられるのであれば、C5は素晴らしい選択肢になる可能性があります。Discourseやそれに近い多くのRailsアプリでは、メモリをたちまち食い尽くしてしまうでしょう。私は「標準的なRailsアプリ」ではC5をおすすめしません。しかしSinatraEventMachineFalcon/AsyncのようにRubyで別のことをやるのであれば、C5インスタンスは間違いなく試してみる価値があるでしょう。Ruby 3.0以降でRuby Ractorsが十分サポートされれば、C5インスタンスはRailsにとってもよいものになるでしょう。現時点ではほとんどのRailsアプリで必要とするメモリが不足しています。

RRBは古いバージョンのDiscourseを実行しています。Discourseはインターネットフォーラムをホスティングするアプリとして広く人気を得ているRailsアプリであり、入手できる「本物の」オープンソースRailsアプリとしては極めて巨大なので、「現実世界」のベンチマークに向いています。RRBは、疑似乱数でシミュレートした多数のユーザーリクエストのセットをRailsアプリに対して実行し、すべてのリクエストが完了するまでの時間を測定します。つまりスループットテストです。ご興味のある方は 自分でも実行できますが、少々込み入っていて注意を要します。現実世界のソフトウェアを使う場合、現実の複雑さとバグを踏むことが暗黒面になります。

GIL(RubyではGVL)が存在しているので、RRBを実行するときには「マルチプロセス」と「プロセスごとのマルチスレッド」のバランスを取っています。多くのスレッドがI/OやCベースのネイティブ拡張のコード実行を待てるにもかかわらず、1個のRubyプロセスではRubyコードのシングルスレッドしか実行できません。Railsの場合、1プロセスあたり5スレッド程度にバランスさせるのが普通です。なお私はRRBの特定のケースではごく小規模な改善として5よりも6を使います。DiscourseでやっているI/O待ちの量だと、EC2 2xlargeインスタンスのvCPUとRAMは基本的に10個のRailsプロセスで使い切ります。つまり「10プロセス、6スレッド/プロセス」です。

RRBが実行するコードはかなり古いものです。私がRRBを設計したのはRuby 3x3移行をベンチマークするためだったので、古いコードとの互換性を優先しています。2020年のクリスマスの日にRuby 3がリリースされたら、私は片っ端から最新バージョンにアップグレードして回るでしょう。古いバージョンはそのときまで残しておくつもりです。なおM6g ARM インスタンスの出番はまだです。古いRubyをそこでビルドすることならできますが、古代から伝わるNodeJSやlibv8などの他の古いコードもそこで動くようにするのは本当にしんどい作業でした。

もしM6gインスタンスを試してみたいのであれば、エンジニアリングにある程度余分な時間が必要になります。Intelプロセッサーはあらゆるものでデフォルトになっているので、おそらく動かすのも実行を維持するのも手こずるでしょう。M6gインスタンスをうまく活用するのに十分なサーバー予算をお持ちなら、存分に自分で測ってみてください。ただし私のベンチマークで測定が楽になるわけではありません

まとめると、「Tシリーズ」「Cシリーズ」「M6gシリーズ」のインスタンスは、特殊なRailsアプリには向いている可能性もあります。私は一般にこれらのインスタンスを万人にはおすすめしませんし、これらのスピードテストを行ったこともありません(これらのインスタンスはたいていのRailsアプリでは間違った解だからです)。

というわけで、残る選択肢は「主にM4およびM5インスタンス」です。M4のマシンとアーキテクチャは古いのですが、M4とM5の価格は似たようなものです。

「スポットインスタンス」vs「オンデマンドインスタンス」

AWS EC2には標準価格のオンデマンドインスタンスがあります。スポットインスタンスについては安く手に入る可能性もあればできない可能性もあり、ちょうど航空チケットを離陸直前に買うときのように価格は運次第です。運がよければ驚くほど低価格な未使用の容量を余分にゲットできますが、ダメなときはダメです。

そこで、この新しいインフラストラクチャ(M5)がどのぐらい高速であるかを知っておくと、M5をお手頃価格に比例して入札できるようになるので便利です。

AmazonはM4ユーザーが他のインスタンスタイプへ移行することを望んでいますが、M4スポットインスタンスはタイミング次第では安く手に入れられる可能性がまだ残されています。M4のコストパフォーマンスはどのぐらいで、どんな場合に向いているのでしょうか。

M4はシンプルか?

私は何年もかけて、主にm4.2xlargeでベンチマークを回してきました。同じインスタンスタイプを使えば長期間での数値の比較がやりやすくなります。私は本記事でさまざまなバージョンのRubyをM4とM5でチェックしています。スピードが単純に「x倍高速」となることはめったにありませんが、複数のファクターをテストすることで自分の測定結果がどのぐらいシンプルで安定しているかを見極めるのに役立ちます。

私がEC2の4つのインスタンスタイプを用いて最終的に行った測定では、Ruby 2.5.3とRuby 2.6.6をテストしました。また、スピードがどのぐらい変動するかについて当りを付けるために、それ以外の多くのインスタンスでも最初に実行してみました。同一のベンチマークを3年間回し続けていてひとつよかったのは、「通常の」安定性および変動がどのような状態なのかをかなり体感でわかってきたことです。舞台裏の数値を詳しく見てきた分、本記事ではちょっぴり細部をごまかしているところもありますが。さて本記事にある項目のうち、それを5倍〜100倍に増やさなくてもよい項目は何だと思いますか?そう、測定値です。

また、RRBは安定した数値を得られるよう、いい感じに最適化されています。RRBは同じ乱数シードを繰り返し用いて生成した同一のリクエストを実行します。RRBのリクエストは十分小さくかつ高速で、しかもハードウェアのネットワークを一切使いません。リクエストはすべてlocalhostです。EC2のように仮想化された環境ではハードウェアネットワークが不安定性の「大きな」原因となるので、単純にスキップしています。もちろん、どのインスタンスも同じスピードになるはずはありませんが、スピードのばらつきはハードウェアネットワークの場合よりもずっと小さくなります。

EC2では「ハードウェア専有インスタンス(dedicated instance)」の利用も認められており、そこでは同じ物理ハードウェアを他の誰にも共有されないようにできます。私はもう何年もハードウェア専有インスタンス上でテストを繰り返しています。ハードウェア専有インスタンスではある種の時間変動については「確実に」回避できますが、私の経験ではハードウェア専有インスタンスでも未だに速くなったり遅くなったりします。そのため、この特定の測定項目セットでは大きな利点はありません。

基本は「M4」

要するにM4インスタンスはどんな具合になるのでしょうか?

Ruby 2.5 ips Ruby 2.6 ips Ruby 2.6の速度差
m4 inst 1 168.9 175.3 +3.8%
m4 inst 2 156.8 164.0 +4.6%
m4 inst 3 169.2 176.8 +4.5%
m4 inst 4 167.4 175.6 +4.9%
m4 overall 167.4 175.3 +4.7%

(「ips」は10000件のHTTPリクエストを30回実行したときの「イテレーション/秒」のメジアン、
「m4 overall」は全リクエストを1回の長時間実行として扱うことを表す)

インスタンス2が目に見えて遅くなっています。最速のインスタンス3は(インスタンス2よりも)およそ7.8%高速です。しかしこれがまったくのランダムではないことがわかってきます。そのインスタンスはRuby 2.6で7.8%高速、Ruby 2.5で7.9%高速です。これが今回のケースで観察される「安定性」です。インスタンスによってわずかに高速なこともあれば、わずかに遅くなることもありますが、相対的な数値は類似しています。それと同様に「このタスクにおけるRuby 2.6はRuby 2.5と比べて3.8%〜4.9%高速」という結果にも若干のばらつきがありますが、このベンチマークにおけるばらつきの総量はかなり正常です。

私はこうした結果を何年も見つめ続けてきました。そしてこれは典型的な結果セットです(統計的に有意なミスでもない限り: もちろんたまにはミスぐらいしましたが)。このトピックに関する私の研究を追いかけている方なら、おそらく何年にも渡って同じ結果が出ていることに気づくでしょう。

ちょっと待った、あるインスタンスが他のより速いことがあるってマジで?」とお思いの方へ: 答えはイエスです。私は、EC2オンデマンドインスタンスの巨大なグループを立ち上げてベンチマークを回し、その中で最も遅い10%をシャットダウンしている人々を知っています。そしてこれはCPUよりもネットワークの方がずっと顕著です。私がEC2ベースのメトリクスでlocalhost以外のネットワークを含めないようにしている理由がこれです。

M4とM5を比較する

今のはすべてM4インスタンスの話でした。ではM5インスタンスの数値はどうでしょうか?ここは本記事で費用対効果を比較できる有用な部分です。

Ruby 2.5 ips Ruby 2.6 ips Ruby 2.6の速度差
m5 inst 1 206.5 213.3 +3.3%
m5 inst 2 200.7 204.7 +2.0%
m5 inst 3 203.7 213.8 +5.0%
m5 inst 4 214.1 223.5 +4.4%
m5 overall 206.1 214.4 +4.0%

(「ips」は10000件のHTTPリクエストを30回実行したときの「イテレーション/秒」のメジアン、
「m4 overall」は全リクエストを1回の長時間実行として扱うことを表す)

こちらのインスタンスの結果はややばらつきが小さくなっています。最速のインスタンスは最も遅いインスタンスより6.7%高速です。Ruby 2.5とRuby 2.6を比べた数値には依然として多少ばらつきがあるものの、overallはM5インスタンスが4.0%、M4が4.7%と似ています。言い換えると、これはすべて正常なばらつきです。

インスタンス2が+2.0%と差が小さいのはどういうことでしょう?一方はうまくいって他方は不運に見舞われることもあります。そしてRuby 2.5と2.6はたまたまそのインスタンスでそこそこ近い結果になったのです。舞台裏を覗いてみても、これは大きな異常値でもなければ突発性のスローダウンでもありませんでした。そのインスタンスでの実行が通常よりも(高変動ではなく)低変動だったのです。これは、Ruby 2.6がRuby 2.5に対して持つ長所を目減りさせる「遅いEC2インスタンス」の特殊なケースのように見えます。おそらくI/O待ちが(極めて)わずかに長くなったか、CPU時間が(極めて)わずかに長くなったかしたのでしょう。言い換えると、それが何であれ、スローダウンが1分間も続くような一時的なしゃっくりなどではなく、長期的には個別のEC2インスタンスのマイナーなスローダウンと思われます。

費用対効果

とにかく、M4インスタンスのメジアンスループットは、Ruby 2.6で175.3リクエスト/秒となり、最新のRubyとほぼ同じスピードです。そしてM5インスタンスのメジアンスループットは、同じ設定で214.4リクエスト/秒となります。これは一体何を意味しているのでしょうか?

手っ取り早く言えば、M5インスタンスの方が36%高速ということです。つまり価格設定をスポットインスタンスにするとしたら、M4インスタンスはM5インスタンスより36%安くなければ割りに合わないということになります。

ではコストはざっくりどのぐらいになるでしょうか?

いいですか、ここが重要です。オンデマンドの場合、実際にはM5インスタンスの方がM4インスタンスよりも時間当りのコストが安いのです。つまり費用をすべてオンデマンドに振り向けるのであれば、細かい比較などしなくても結論は「M5一択」で決まりです。M4インスタンスを一度もアップグレードしたことがないなら、今こそアップグレードすべきタイミングです。us-east-1のオンデマンドm4.2xlargeインスタンスは米ドルで40セント/時間ですが、m5.2xlargeは38.4セント/時間となり、(訳注: スピードの差を加味すると)M4の方が4.1%高くつきます。

つまりM5がM4よりどれだけ高速かを計算して初めて、M5のディスカウントが大きいことが見えてきます。これホントにホント。

また、以下の記事やこれらの数値は次のことも示唆しています。もし小さな速度差を最適化しているのであれば、最近のRuby(少なくとも2.6以上)にアップグレードしましょう。Ruby 2.6は2.5に比べて著しく改善されており、以下の記事にあるようにそれ以前のRubyと比べてかなり速度に差が付きます。

私たちはRuby 3.0の速度がどうなるかをいずれ目にすることになります。以下の記事にあるように、数か月前にリリースされたRuby 3.0プレビュー版は2.7と比べるとそこまで速度差は大きくありませんが。

まとめ

忙しい人向け: EC2インスタンスはM5シリーズを使いなさい、以上。私はvCPU数やRAM容量の点でm5.2xlargeが好みです。なお、必要に応じてスケールアップやスケールダウンしてもvCPU数とRAM容量の比率は変わりません。かなり特殊なケースであれば、M6g(ARMプロセッサ、移植が困難、CPUは高速)や、C5(CPU1個当りのRAMが少なめ)、T4(CPUをバーストパフォーマンスで動かせる)を検討してもよいかもしれません。

しかし普通のRailsアプリのユースケースであればやはりM5があなたの友です。「でもM4スポットインスタンスは安いし」とお思いのそこのお方、M4が価格性能比でM5と互角に勝負するには、少なくともM5より36%安くなければいけないことをお忘れなく。それでもM4を使いたい方はどうぞ慎重に。

「どうも信じられん」

まだ納得いただけませんか?公平のために申し上げると、私のコードはすべてGitHubで公開されていることにお気づきでしょうか?ドキュメントを読んで自分で動かすのも自由ですし、インスタンス数を増やすなどして再実行して、自分の環境でも私と同じ結果になるかどうかを見たい方は、@codefolioまでお声かけいただければ、私の行った正確な方法を喜んでお見せいたします(コードもすべてネット上にあります)。私のデータを使えば皆さんが自分でEC2インスタンスをレンタルするより安上がりです。

お知らせ

パフォーマンスについて詳しく知りたい方は、FastRuby.ioブログのperformanceタグにある記事をご覧ください。

関連記事

Ruby 2.5.0はどれだけ高速化したか(翻訳)

ベンチマークの詳しい理解と修正のコツ(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。