コンテンツにスキップ

7. スケーリングとパフォーマンス

Build.ioのようなプラットフォームで運用する大きな利点の1つは、スケーリングにサーバーの用意が不要なことです。Dynoの数とサイズを調整すれば、残りの処理はプラットフォーム側が担います。ロードバランシング、コンテナの配置、トラフィックの分散は、すべて自動で行われます。

7.1 水平スケーリング(インスタンスの追加)

Section titled “7.1 水平スケーリング(インスタンスの追加)”

水平スケーリングとは、同じタイプのDynoの数を増やすことです。追加したDynoはそれぞれ独立したコンテナとしてコードを実行し、受信したリクエストはBuild.ioのルーターが全Dynoに分散します。

ダッシュボードでは、アプリのResourcesタブを開きます。プロセスタイプ(web、workerなど)ごとに、スライダーまたは入力フィールドでDyno数を調整します。

CLIから:

$ bld ps:scale web=3 -a my-app
Scaling dynos... done, now running web at 3

現在のフォーメーションを確認するには:

$ bld ps:list -a my-app

水平スケーリングが適切な場合

Section titled “水平スケーリングが適切な場合”

Dynoが同時リクエストを処理しきれず、レスポンスタイムが伸びている場合は、Dyno数を増やすタイミングです。判断の目安となる兆候は、リクエストのキュー時間の増加、負荷時のレイテンシの上昇、タイムアウトエラーの発生の3つです。

Dynoの追加が効果を発揮するのは、アプリケーションがI/Oバウンドな場合です。データベースクエリや外部API呼び出し、ファイル操作の完了を待っている状態がこれにあたります。あるDynoがI/Oでブロックされている間も、別のDynoが独立してリクエストを処理できるためです。

どのリクエストもCPUバウンドで遅い場合は、Dynoを増やしても効果は限定的です。1リクエストあたりの処理を最適化するか、垂直スケーリングを検討してください。

フォーメーションからプロセスを削除せずに一時停止する場合は、Dyno数をゼロまでスケールダウンできます。メンテナンス中や、設定を残したままバックグラウンドワーカーを停止する場合に使用します。

$ bld ps:scale web=0 -a my-app

再開する際は、再度スケールアップします。アプリに環境変数として渡される設定値(Config Var)やアドオン、その他の設定は変更されません。

7.2 垂直スケーリング(インスタンスサイズ)

Section titled “7.2 垂直スケーリング(インスタンスサイズ)”

垂直スケーリングとは、メモリとCPUがより多く割り当てられた大きなDynoサイズへ移行することです。インスタンスを増やすのではなく、1インスタンスあたりのリソースを増やします。

Dynoサイズは複数用意されており、アプリケーションのリソース要件に合わせて選択できます。小さいサイズは開発、ステージング、トラフィックの少ない本番アプリに適しています。リソースを大量に消費するワークロードには、メモリとコンピュートの多い大きなサイズを割り当てます。

サイズの変更は、アプリのResourcesタブから行います。

垂直スケーリングが適切な場合

Section titled “垂直スケーリングが適切な場合”

垂直スケーリングが適しているのは、Dynoのメモリが不足している場合と、1リクエストの処理に必要なCPU時間が不足している場合の2つです。

メモリ不足 — メモリ制限によってDynoが再起動されている場合は、より大きなDynoサイズが必要です。主な原因は、メモリリーク、大きなインメモリデータ構造、メモリを大量に消費するランタイムの使用です。

CPUバウンドの処理 — 画像処理、レポート生成、データ変換といった負荷の高い計算により個々のリクエストが遅い場合は、CPU容量の大きなDynoへの引き上げが有効です。ただし、この種の処理は、バックグラウンドのWorker Dynoへ移すほうが適切な場合が多くあります。Worker DynoのサイズはWeb Dynoとは独立して設定できます。

水平スケーリングと垂直スケーリングの組み合わせ

Section titled “水平スケーリングと垂直スケーリングの組み合わせ”

本番アプリでは、多くの場合、両方を組み合わせて使用します。代表的な構成は、中程度のサイズのWeb Dynoを複数と、バックグラウンド処理用に大きめのWorker Dynoを1〜2つ稼働させる形です。Webのレスポンスタイムを短く保ちながら、負荷の高いバックグラウンドジョブに必要なリソースを確保できます。

7.3 パフォーマンスチューニング

Section titled “7.3 パフォーマンスチューニング”

スケーリングは、パフォーマンスを改善する有効な手段の1つです。ただし、スケールする前にアプリケーション自体を見直すことで、より少ないコストで効果の高い改善策が見つかる場合があります。代表的な見直し箇所は次の4つです。

パフォーマンス問題の大半は、ごく少数の遅いエンドポイントが原因です。ロギングとモニタリングのツールで時間のかかっているリクエストを特定し、優先的に対処してください。

よくある原因は次の3つです:

  • インデックスのないデータベースクエリ
  • N+1クエリパターン(1回のクエリで取得できる関連レコードを、ループで個別に取得している)
  • リクエストサイクル内で同期的に呼び出している、レスポンスの遅い外部API

負荷の高い処理はバックグラウンドジョブに移す

Section titled “負荷の高い処理はバックグラウンドジョブに移す”

ユーザーがレスポンスを受け取るまでに完了している必要のない処理は、バックグラウンドジョブへ移します。対象となるのは、メール配信、レポート生成、画像処理、Webhookの送信、サードパーティAPIの呼び出しなどです。

負荷の高い処理をバックグラウンドワーカーへ移すことで、Web Dynoのレスポンスタイムの悪化を防げます。長時間実行されるリクエストにキャパシティを占有されることもありません。ワーカーは独立してスケールでき、ジョブが失敗した場合も、ユーザー体験に影響を与えずに再試行できます。

効果が最も大きい改善策は、多くの場合キャッシュです。同じ計算やデータ取得を繰り返している場合は、その結果をキャッシュしてください。

アプリケーションレベルのキャッシュ — Redis(セクション5.2を参照)に結果をキャッシュします。対象はデータベースのクエリ結果、レンダリング済みのページフラグメント、シリアライズしたAPIレスポンス、コストの高い計算結果などです。TTLが30〜60秒と短くても、トラフィックのスパイク時のデータベース負荷を大幅に低減できます。

HTTPキャッシュ — 頻繁に変更されないレスポンスに、適切なCache-Controlヘッダーを設定します。静的アセットは、有効期限を長く設定し、ファイル名に内容のハッシュを含める方式(キャッシュバスティング)で積極的にキャッシュしてください。

Webサーバーの並行性チューニング

Section titled “Webサーバーの並行性チューニング”

多くのWebフレームワークは、デフォルトでは保守的な並行性設定になっています。Dynoサイズとワークロードに合わせて調整することで、Dynoを増やさずにスループットを大きく向上できます。

Ruby(Puma) — ワーカー数とスレッド数を増やします。標準Dynoでは、まずワーカー2〜4個、スレッド各5本から始めます。ワーカーはそれぞれ独立したプロセスであるため、メモリ使用量を確認しながらバランスを調整してください。

Python(Gunicorn) — ワーカー数を増やします。一般的な目安は(2 × CPUコア数) + 1です。アプリがI/Oバウンドの場合は、非同期ワーカー(geventまたはuvicorn)を使用してください。

Node.js — Nodeはデフォルトではシングルスレッドで動作します。clusterモジュールで複数のワーカープロセスをフォークするか、Dyno 1つにNodeプロセス1つを配置して水平方向にスケールさせます。