dyno の使用の最適化
この記事の英語版に更新があります。ご覧の翻訳には含まれていない変更点があるかもしれません。
最終更新日 2024年03月13日(水)
Table of Contents
どのようなアプリケーションでも、その最適化のための基本的な側面は、アプリケーションが適切に設計されていることの保証です。たとえば、アプリケーションに求められるのは、リクエストの時間を短く保つために計算負荷の高いタスクにはバックグラウンドジョブを使用し、アプリケーションの各部分を個別にスケーリングできることを保証するためにプロセスモデルを使用することです。
その先の段階として、利用可能なリソースの使用効率を高めることでスケーリングまたは最適化を行うことが必要になる場合があります。たとえば、Web リクエストが短く、効率的に処理される場合、(通常は RAM 使用量の増加を代償に) Web サーバーのリクエスト並列処理能力を高めることによって、dyno でのスループットを向上できる可能性があります。
この記事では、さまざまな dyno タイプに合わせてアプリケーションの最適化を始める方法の概要を示します。機能の概要を示し、メモリ使用量と並列性に特に注目します。この記事で提案する手法は、dyno に限らず、あらゆるアプリケーション実行環境に関連しています。さまざまな環境でコストを最小化する具体的な方法については、「Optimizing Resource Costs」(リソースコストの最適化) を参照してください。
Premier または Signature Success Plan の Heroku Enterprise の顧客は、Customer Solutions Architecture (CSA) チームに、このトピックに関する詳細なガイダンスを要求できます。ここでエキスパートコーチングセッションについて学習するか、または Salesforce の担当者にお問い合わせください。
さまざまな dyno タイプの検討
Heroku では、さまざまな dyno タイプを提供しています。タイプごとに CPU および RAM プロファイルが異なります。
アプリケーションの dyno タイプを変更すると、複雑さが増します。開発者にとっては、dyno の数に加えて dyno のタイプという変数が増えたことになります。
ただし、適切に設計されたアプリは、当然ながらさまざまな dyno タイプを利用できるため、dyno の利用効率を高めるためのアプリケーション最適化について考慮することは価値のある試みです。
アプリケーションでさまざまな dyno タイプを利用する必要がないとしても、何らかの形で、これらの最適化手法を現在の dyno タイプに適用することを検討してください。
さまざまな dyno タイプは、CPU、RAM、パフォーマンスプロファイルという 3 つの重要な最適化の軸を提供します。
CPU
ほとんどのアプリケーションは、Web サーバーで CPU に制約されません。
CPU またはその他の共有リソースの制約 (データベースなど) のために個々のリクエストの処理が遅くなっている場合、dyno で並列性を最適化してもアプリケーションのスループットに寄与しない可能性があります。言い換えれば、トラフィックが少ないときにアプリケーションが低速な場合、この記事の手法ではパフォーマンスが向上しない可能性があります。
dyno タイプが異なれば CPU パフォーマンスの特性も異なり、CPU 使用率が高い状況では少しは奏功しますが、理想を言えば、最適化の最初のステップとして、コードの最適化に加えて、バックグラウンドワーカーに処理をオフロードすることを検討するべきです。
CPU の最後の側面はコア数です。特に performance
などの dyno タイプは、複数のコアを提供します。複数のコアがあれば、複数のスレッドを並列実行できる場合があります。この記事では、これらのコアを利用するために対応する必要がある項目について説明します。
この記事ではこれ以降、アプリケーションが CPU に制約されないことを前提とします。
RAM
言語や Web フレームワークにもよりますが、通常、RAM と並列性の間には直接の相関があります。
たとえば、Ruby 向けの Unicorn や Python 向けの Gunicorn などの Web サーバーでは、(ワーカーと呼ばれる) Web サーバーの同一コピーを多数、事前にフォークします。Unicorn の場合、独自の接続キューを備え、ワーカーは Web リクエストを完了したら新しいリクエストをキューから取り出します。
このシナリオで RAM を増強すると、より多くのワーカーを並列実行できるようになります。通常、RAM と並列性の間には、かなり直線的な相関関係があります。RAM に着目した並列性の最適化は、この記事で扱う内容です。
パフォーマンスプロファイル
各 dyno タイプのパフォーマンスプロファイルが影響を及ぼす可能性があります。特に、eco
、basic
、standard-1x
、standard-2x
の各 dyno は CPU 共有ベースで動作する一方、performance
dyno はシングルテナントです。
したがって、performance
dyno の方がリソース分離のレベルは上です。
アプリケーションのトラフィック受信量や最適化の良好度にもよりますが、これはアプリケーションに大きな影響を及ぼす可能性があります。特に、より一貫性のあるパフォーマンスプロファイルは、テールレイテンシーの削減につながる可能性があります。
dyno サイズの変更を試みるタイミング
さまざまな dyno タイプを検討するときに関係する要因は数多くあります。例としては、アプリケーションの内在的要因 (CPU 使用量)、(RAM の増強による) 並列性の向上によってもたらされる最適化要因、dyno 自体の内在的な特性による要因などがあります。
この複雑さは難敵ですが、CPU に制約されないアプリケーション向けにこの記事で提案している単純な手法によって、あらゆる dyno タイプに合わせた最適化がずっと扱いやすく、容易である可能性があります。
特定の dyno タイプ (たとえば standard-1x
dyno) に合わせて最適化したら、standard-2x
、performance-m
、performance-l
といった別の dyno にも同じ手法を応用しますが、その際、dyno タイプごとに異なる要因を考慮に入れます。
以下、大まかな経験則をいくつか示します。
- 受信トラフィックが極端に多くないほとんどのアプリケーションでは、
standard-1x
dyno を検討してください。 - Play や JRuby などの一部の Java ベースのフレームワークで見られるように、アプリケーションのメモリ消費が特に激しい場合は、メモリが 2 倍の
standard-2x
dyno を検討してください。 - 20 を超える
standard-1x
dyno で実行される、きわめて高ボリュームの Web アプリでは、performance-m
dyno またはperformance-l
dyno を検討してください。
メモリ最適化の基本的な方法論
言語ごとの提案に加えて、以下の手順を実行すること、また後述する可視性ツールを利用することをお勧めします。これにより、単一の dyno タイプに合わせた、また dyno タイプを切り替えながらの最適化が容易になります。
- 並列 Web サーバーを使用します。
- アプリへの負荷の影響を測定するために、インストルメンテーションをセットアップします。
- アプリのパフォーマンスを監視し、必要に応じて並列性を調整します。
最適化は反復的なプロセスであり、黄金の道はありません。言語、Web フレームワーク、アプリケーションが異なれば、動作もまったく異なります。
たとえば、標準的な Ruby アプリケーションでは、利用可能なすべての RAM を利用するために、アプリケーションの複数のコピーをフォークする Web サーバーを使用することが必要な場合があります。その一方で、標準的な Java アプリケーションでは、ヒープの割り当てを増やすために JVM へのパラメータが必要なだけという場合があります。
並列 Web サーバー
言語やプラットフォームが異なれば、並列性に対するアプローチも異なります。ここでは、Ruby、Java、Python、Node.js で実行されるアプリで並列性を確立する方法の概要を示します。
Ruby
アプリケーションを最適化する方法を確認するには、総合的な「R14 - Ruby でのメモリ割り当ての超過 (MRI)」の記事を参照してください。この記事では、Ruby アプリケーションでのメモリ肥大化に関する一般的な問題について説明し、Ruby アプリケーションのメモリ使用量の増加を突き止めて修正するための複数の診断ツールと手法についても説明しています。ActiveRecord による Ruby での並列性とデータベース接続も、並列性を最大化するためのデータベース接続のベストプラクティスを考慮する方法を評価するための優れたリソースです。
JRuby
Puma などの JRuby サーバーでは、複数のプロセスを必要とすることなく並列性を有効に利用します。ただし、dyno タイプによっては、JVM に割り当てるメモリの量をチューニングする必要があります。Ruby buildpack で実用的なデフォルト値が定義されていますが、これは JAVA_OPTS
または JRUBY_OPTS
を設定することで上書きできます。
Java、Scala、Clojure
Jetty、Tomcat、Netty などの Java Web サーバーでは、特に設定しなくても並列性が有効利用されます。ただし、dyno タイプによっては、JVM に割り当てるメモリの量をチューニングする必要があります。
これを達成するための適切な JAVA_OPTS
フラグについては、「dyno サイズのための環境の調整」を参照してください。
Python
Python アプリの場合、Gunicorn または Uvicorn のいずれかを使用することをお勧めします。これらは Heroku で自動的に並列性を使用する高性能な Python HTTP サーバーです。
スループット最大化のための Python アプリケーションのチューニングについての詳細は、「Python アプリケーションの並列性の最適化」を参照してください。
Node.js
Node では、シングルスレッド、非ブロッキングのプロセスモデルが提供されます。複数のコアを利用するために、Node では Cluster API を使用して複数の並列プロセスをフォークする必要があります。現在は並列性の使用を計画していない場合でも、さまざまなコンテナに合わせてアプリをスケーリングできるよう、アプリで Cluster を有効にすることをお勧めします。
Heroku で Node の Cluster API を使用して並列性を設定する方法については、「Optimizing Node.js Concurrency」(Node.js 並列性の最適化) を参照してください。
PHP
PHP または HHVM ランタイムを使用するアプリケーションでは、アプリケーションが実行される dyno のタイプに応じて、アプリケーションのワーカープロセスまたはスレッドの数を自動的に調整します。プロセスまたはスレッドの数を決定する主な要因は、アプリケーションに対して設定されている PHP メモリの制限です。
スループット最大化のための PHP アプリケーションのチューニングについての詳細は、「Optimizing PHP Application Concurrency」(PHP アプリケーション並列性の最適化) を参照してください。
測定
並列 Web サーバーをセットアップした後は、特定の dyno タイプに合わせてサーバーをチューニングする必要があります。メモリとスループットを測定すれば、変更の影響を判断するためのガイダンスとしては十分です。
log-runtime-metrics を使用したメモリの測定
Heroku Labs の log-runtime-metrics 機能は、実行中の dyno の負荷およびメモリ使用量を可視化するためのサポートを追加します。
メモリ使用、スワップ使用、および負荷平均に関する dyno ごとの統計がアプリのログストリームに挿入されます。
この機能を有効にした場合の出力例を次に示します。
source=web.1 dyno=heroku.2808254.d97d0ea7-cf3d-411b-b453-d2943a50b456 sample#load_avg_1m=2.46 sample#load_avg_5m=1.06 sample#load_avg_15m=0.99
source=web.1 dyno=heroku.2808254.d97d0ea7-cf3d-411b-b453-d2943a50b456 sample#memory_total=21.00MB sample#memory_rss=21.22MB sample#memory_cache=0.00MB sample#memory_swap=0.00MB sample#memory_pgpgin=348836pages sample#memory_pgpgout=343403pages
最も重要な数字は memory_rss
で、この値から合計常駐メモリを推測できます。使用する dyno タイプのメモリを超えないようにしてください。また、一定の余裕を残しておいてください。同様に、スワップ使用量を最小限に抑え、スワップアクティビティ (memory_pgpgin
/memory_pgpgout
) が最小であることを確認してください。時間が経っても memory_pgpgin
/memory_pgpgout
があまり変化しない (変化率がゼロ) のが理想的です。
これらの数字を解釈する方法については、log-runtime-metrics を参照してください。
log-runtime-metrics の出力は、dyno ごとのメモリ使用量がわかるという点で特に役立ちます。プロビジョニングが過剰な場合、1 つの dyno が他のどの dyno よりも先にピークに達することがあります。
このメモリデータを視覚化する他の方法があります。
Librato アドオンは、Nickel プラン以上で、log-runtime-metrics からのさまざまな出力をグラフ化し、すべての dyno 間で値を平均化する方法を提供します。
次に、4 つの Unicorn ワーカーを使用した standard-1x
dyno 上の Rails アプリケーションのサンプル出力を示します。ピーク時のメモリは約 359 MB で、standard-1x
の 512 MB の RAM に十分収まっています。
スループットと応答時間の測定
スループット (1 分あたりのリクエスト処理数) と応答時間は、最適化が dyno のパフォーマンスに及ぼした影響についての特に有効な指標です。
特に、Librato や New Relic などのアドオンによって提供される 95 および 99 パーセンタイルの応答時間の値を注意深く監視してください。