Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

コードに潜むクラウドの無駄を可視化する

By Joshua FoxMay 19, 20256 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

クラウドコスト最適化への体系的アプローチ

クラウドコスト最適化はパレートの法則(80対20の法則)に従います。つまり、ごく一部のリソースがコスト全体の大半を占めるのが通例です。まずはDoiT Cloud Intelligence™などのツールで、最も支出が大きい領域を特定するところから始めましょう。

ステップ2:管理面のクイックウィンを押さえる

コードの最適化に踏み込む前に、よりシンプルなクラウド管理上の調整から着手します。

  • 使用率の低いインスタンスのライトサイジング
  • 放置されたリソースの削除
  • 適切なストレージ階層の適用
  • オートスケーリングのパラメータ調整

こうした対策はコードに手を入れるよりも低コストで実施できます。

ステップ3:コードレベルの非効率を示すサインを探す

管理面の最適化が一段落したら、コストの高いリソースを精査し、コードに起因する非効率がないか確認します。

「効率の錯覚」が働くため、こうしたサインは見過ごされがちです。本記事の後半で取り上げる実例には、よくある典型的な兆候が示されており、その多くはGCP、AWS、Azureの各コンソールに標準搭載されているクラウドモニタリングツールで把握できます。

ステップ4:調査 ― プロファイリングと分析

クラウド上で実行できる実行プロファイリングツールを活用し、クラウドレベルからコードレベルへと踏み込みます。

  • データベース:Cloud Consoleのクエリアナライザーやパフォーマンスインサイト
  • アプリケーション:言語別のプロファイラーやメモリアナライザー
  • データパイプライン:実行グラフや分散メトリクス

Flamegraph, from Google Cloud Profiler

Google Cloud Profilerのフレームグラフ

SQLのようにクラウド側でツールが用意されている場合は容易ですが、マネージド環境で動作する分散アプリケーションのPythonメモリプロファイリングのように、難易度が高いケースもあります。

ステップ5:実装、計測、検証

特定した課題を修正して再デプロイし、AWS、GCP、Azureの各クラウドコンソールで技術面の改善を計測します。続いてCloud Intelligence™のコストレポートでコスト削減効果を検証します。

実例とその解決策

シナリオ1:メモリリークを抱えるJavaマイクロサービス

一見効率的に見えた点: あるJava製Lambdaマイクロサービスはメモリ使用率を70〜100%に保っており、リソースを最大限に活用しているように見えました。

実際のところ: アプリケーションにはメモリリークがあり、グローバルオブジェクトが参照チェーンを抱え込み、呼び出しをまたいでオブジェクトを保持し続けていました。クラッシュやインスタンス置き換えはたまにしか発生せず、SREチームの目に留まらない頻度でした。

手がかり: モニタリングで、メモリ使用量が時間経過とともにノコギリ波状のパターンを描いていることが判明。落ち込み箇所を調べたところ、CloudWatchのログに周期的なクラッシュが記録されていました。

調査: CodeGuru Profilerを有効化したところ、時間とともにメモリ使用量が増加していることが分かりました。さらにオフラインでJVMプロファイラーを使って分析し、想定外のオブジェクト保持を特定しました。

解決策: 各Webリクエストの終了時にオブジェクト参照を解放するようコードを修正しました。

結果: メモリ使用量が安定し、インスタンスの置き換えが減り、リソースコストも削減されました。

シナリオ2:非効率なデータ構造を抱えるJavaデータ処理パイプライン

一見効率的に見えた点: カスタムJavaコンテナで構築されたDataflowパイプラインが、毎日数百万件のレコードを処理し、CPU使用率も常に高い水準を保っていました。

実際のところ: コードでは非効率なデータ構造が使われており、タイトループ内でオブジェクトごとに不要な文字列連結を行うマップが含まれていたため、ガベージコレクションのオーバーヘッドが過大になっていました。

手がかり: リソース使用率の高さが、より深い調査の必要性を示していました。

調査: コンテナにGCP Cloud Profilerを追加。データセットの拡大に伴い、処理時間とメモリ使用量が線形を超えるスケーリングを示していることが分かりました。

解決策:

  • マップを、必要な情報だけを保持するカスタムオブジェクトに置き換え
  • 繰り返しの連結ではなく、適切な文字列結合(string join)を実装

結果: メモリ使用量を50%削減、処理時間を70%短縮し、より小さなワーカーマシンで済むようになり、インスタンス数も削減できました。

シナリオ3:インメモリデータ構造が招く過大なVM

一見効率的に見えた点:大容量メモリのVMは水平スケール構成と比べてコスト効率が良く見え、Pythonのインメモリデータ構造はデータベースクエリよりもアルゴリズム面で速度の優位性があるように思えました。

実際のところ:このアプローチは複数の非効率を抱えていました。

  • クラウドプロバイダーはCPUとメモリの最小比率を強制するため、使われない高コストなCPU容量が発生する。
  • メモリは事前に決められた単位でしか割り当てられず、未使用のバッファ容量にも料金が発生する。
  • 初期化に長い時間を要するため、堅牢性を確保するには高コストなインスタンスを複数同時に稼働させ続ける必要があった。

手がかり:DoiT Cloud Intelligence™で、総コストの大部分が超大型VMに集中していることが明らかになりました。これはクラウドアーキテクチャにおいてステートフル性が問題化している典型的な兆候です。

調査:アルゴリズムを詳細に分析した結果、リファクタリングによりアプリケーションメモリの外側にデータを置ける余地があることが分かりました。

解決策:

  • 必要に応じてデータベースから部分的なデータセットを取得して処理するよう、アルゴリズムをリファクタリング
  • NoSQLデータベースを導入し、Redisをインメモリキャッシュとして活用
  • 主要な参照データの全件プリロードが必要な場合でも、Redis上で最適化したデータ構造を用いることで、アプリケーションメモリ上のオブジェクトよりもメモリフットプリントを小さく抑えた

結果:このアーキテクチャ変更によりVMサイズを大幅に縮小でき、水平スケーリングが可能になり、コストも劇的に下がりました。ただし、相応のエンジニアリング工数を要しました。

過去の事例を振り返る

先に触れた記事「Stop Chasing Idle Servers」から、もう2つの事例を取り上げ、このフレームワークにどう当てはまるかを見ていきましょう。

シナリオ4:IOPSが85%に達するデータベース

一見効率的に見えた点: RDSインスタンスはフル活用されているように見え、リソース割り当ても最適と思われました。

実際のところ: 重要なインデックスが2つ欠落していたため、すべてのクエリがフルテーブルスキャンを実行し、必要なリソースが大幅に膨らんでいました。

手がかり: 通常、SQLクエリの大半は(高度にチューニングされたバッチ処理を除けば)高いリソース消費を必要としません。そのため、使用率が高いパターンは最適化の余地を示すサインでした。

調査: AWSコンソールに標準搭載されているAWS RDS Performance Optimizerを有効化し、問題のあるクエリと欠落していたインデックスを特定しました(GCPにも同様のCloud SQL Query Insightsがあります)。

解決策: 不足していたインデックスを追加しました。

結果: クエリのレイテンシが10分の1に短縮され、データベースを2段階ダウンサイジングできました。

シナリオ5:毎晩4時間、CPU使用率70%で稼働するSparkジョブ

一見効率的に見えた点: クラスターは高いCPU使用率を維持しており、リソース割り当ても適切に見えました。

実際のところ: データの80%が偏った単一のキーに集中し、ストラグラータスクが発生して処理時間が大幅に延びていました。

手がかり: 他に明確な原因が見当たらないまま、ある特定の時点から問題が顕在化していました(これは後に「ホットキー」を含む新規データの到着と一致していたと判明しました)。

調査: Sparkコードは高度に分散された環境で実行されるため、通常のアプリケーション向けプロファイラーを使うのは困難です。これは、ロジックを複雑なビジネスロジックではなくシンプルな変換にとどめるべき大きな理由でもあります。一方で、Spark UIを使ってステージごとのタスク分布を分析したところ、ストラグラーを特定できました。さらにBigTableのモニタリングにより、処理対象のデータベース内に「ホットキー」が存在することが分かりました。

解決策: 問題のキーをリパーティションし、ソルトを加えることでワークロードをより均等に分散させました。

結果: ジョブの完了時間が4時間から45分に短縮され、必要なクラスターサイズも3分の1に縮小できました。

真のクラウド効率を実現するには、クラウドの設定だけにとどまらず、コードレベルの非効率にも踏み込む必要があります。コードが過剰なクラウドコストの根本原因である場合、インフラの調整だけでは問題は解決しません。

開発チームをFinOpsやSREと連携させれば、こうした隠れた非効率を体系的なアプローチで発見・解消できます。

  1. コスト分析で浮かび上がった、最も支出の大きい領域から着手する。
  2. まずはクラウド設定レベルのクイックウィンに対処する。
  3. クラウドコンソールで、深掘り調査が必要となる兆候を探す。
  4. 適切なプロファイリングツール(可能ならクラウド上、必要ならオフライン)で非効率を特定する。
  5. コードを修正・再デプロイし、コスト改善効果を検証する。

この協働アプローチは、コスト削減だけでなくアプリケーションのパフォーマンスや信頼性の向上にもつながります。予算面でもユーザー体験の面でもメリットが得られるはずです。

DoiTのCustomer Reliability Engineeringチームでは、最適化の全プロセスを通じてお客様を支援しています。DoiT Cloud Intelligence™と長年の知見を活かし、改善機会の発見、クラウドレベルの修正提案、効率の錯覚の見極め、そしてコードレベルの改善効果の検証までを一貫してサポートします。お問い合わせはdoit.com/servicesまで。