「クラウドならではの失敗、障害を前提に」――機械学習基盤の実運用におけるトラブルと対策コンテナベースの機械学習基盤を大解剖(終)

リクルートのエンジニアが内製している機械学習基盤について詳しく解説していく本連載。最終回は、実際に起きたトラブルを取り上げながら、クラウドが提供するマネージドサービスを利用する際に必要な考え方を紹介します。

» 2022年09月20日 05時00分 公開
[宮井康宏, 秋庭伸也リクルート]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

 第2回では「Crois」「Crafto」のシステム構成を共有しました。今回はクラウドが提供するマネージドサービスを利用したプラットフォームを提供する際に起こり得るトラブルとその対策について、実際のトラブル例を紹介しながら説明します。

ローンチ後のCroisの利用状況

 Croisは2018年にローンチされた後、社内で少しずつ採用されるようになりました。その中で実績を積んだことが評価され、他の組織にも利用が拡大しています。特に2021年から2022年初頭にかけて急激に利用者が増えました。この間だけで1日当たりのジョブ実行数が倍以上(600ジョブ→1500ジョブ)に増えており、現在も日々増加しています。

 スケーラビリティを重視した設計をしているため、基本的なシステムの設計は変える必要はありませんでした。しかし、クラウド特有の問題が発生するケースが幾つかありました。

失敗を前提とした設計で高い稼働率を維持

 Croisは独自のヘルスチェック機構を実装しています。エンドツーエンド(E2E)テストのようにログインからワークフローの閲覧、ジョブの実行、スケジュール実行まで一通りテストし問題が起きていないかどうかを定期的にチェックしています。利用しているAWS(Amazon Web Services)で障害が起きることもありましたが、上記のヘルスチェックでは99.9%以上の高い稼働率を維持できています。

 とはいえ多くのマネージドサービスを活用した構成になっているため、AWSの障害に対して弱いのではないかと思われるかもしれません。AWSでは多くの提供サービスで高いSLA(Service Level Agreement)が定められていますし、Crois内部でも不定期に発生する障害に対してのリトライ機構を設けています。そのため複数のマネージドサービスを使ったことによる稼働率の低下は感じていません。

 第2回でCroisはコンテナの実行に「AWS Fargate」や「AWS Batch」を使っていることを紹介しました。これらのサービスに対して指定したコンテナの起動をリクエストする際、起動失敗エラーが返ってきて実行できない場合があります。このエラーにはネットワークインタフェースの初期化失敗、APIのスロットリングエラー、原因不明のエラーが含まれます。

 特に厄介なものが原因不明のエラーでコンテナ内の処理失敗と見分けがつきません。処理をするコンテナはユーザーが作成するものであり、そこが確実に成功するものであるとはいえず、Croisのシステム起因なのかユーザーの処理起因なのか区別できないのです。

 またユーザーの定義した処理には冪等性(べきとうせい)があるかどうか分からないため、再実行できるのかCroisのシステム側から分かりません。こうした問題があり、起動失敗を単純にリトライするわけにはいかない状況でした。

 そこで、第2回で紹介したコンテナにエージェントを注入してエージェントを介して処理を実行する仕組みが役立ちました。エージェントの起動を「Amazon DynamoDB」に記録し、そのレコードがなければ「AWS Step Functions」(以下、Step Functions)のStateMachine上からリトライすることで対処したのです。

 Croisのシステムが起因となる起動失敗の場合には起動フラグの書き込みが行われず、起動フラグをチェックする「AWS Lambda」(以下、Lambda)関数の結果からリトライのフローに入ります。一方でユーザーの処理に起因した失敗の場合はエージェントが起動フラグを書き込んでいるので、リトライは行われません。

 こうしたリトライの仕組みを作ったことで今後も種類が増えていくかもしれないコンテナ起動失敗エラーに対応し続ける必要がなくなり、一括で対処できました。クラウドが提供するマネージドサービスを単に使うだけでは小さな障害が起きただけでリカバリーに運用工数が発生してしまいます。不定期に小さな障害やエラーが起こるということを前提に、どうやってハンドリングするかを考えるのが、エンジニアとしての腕の見せ所になります。

制限緩和できない仕様への対処

 クラウドには多くの機能に制限が設けられています。分かりやすいのが、作成できるリソースの数や量の制限です。これらの制限のうち一部は緩和申請を出すことで引き上げることもできますが、それ以外の制限は緩和できないことがあります。こうした制限は各サービスの公式ドキュメントにまとめられているので、利用する際には要件を満たせるのか確認してから利用しましょう。

 ただ事前に確認していたとしても、利用方法が変わったり、想定と異なる仕様だったりして制限に抵触してしまうことがあります。Croisでも、サービスの利用を制限され、一部のジョブの実行に障害が発生したこともありました。そうした事例を2つ挙げて、どう対策したかを紹介します。

 1つ目の制限がStep Functionsの「タスク、状態、実行の最大の入力または出力サイズ」です。「タスク、状態、実行の入力または出力」とは、State Machineの中で前のステップから後のステップへ引き渡すデータのことです。最大サイズはCroisのリリース当初は64KBでその後256KBになりました。設計時は「64KBもあれば十分だろう」と思っていたのですが、State Machineの中でParallelやMapを使って多くのタスクを並列実行すると、タスクの数だけ入力サイズが倍になることが判明しました。

 これは並列実行した各タスクの出力がまとめられた配列となって渡ってくるためです。最大サイズがあることと配列で渡ってくるというそれぞれの事実は認識していたのですが、想定した以上の並列数とパラメーターの数があるワークフローが実行されたことで判明した問題でした。

 この事象に対しては、AWSの公式ドキュメントで回避方法の説明があります。図のようにタスク間を流れていた出力データをLambda関数を使い「Amazon S3」(以下、S3)へストアし、パスのみを出力データに格納します。次のタスクの始めにS3からロードすることで、タスク間で引き渡すデータはS3上のオブジェクトのパスだけにします。

 この対策により大量の並列実行をしても入力サイズの超過が起きなくなりました。この事例のように公式ドキュメントにベストプラクティスとして取り上げられているものも数多くあります。事前にそうした設計を取り込んでおくことで安定したシステムを作ることが可能になります。

 2つ目はStep Functionsの「実行履歴の最大サイズ」を超過するという問題です。この問題は大量のタスクをループ実行した際に起きました。Step Functionsでは実行履歴の最大数に制限があり25000イベントまでとなっています。このイベントという単位は1つのLambda関数を実行するだけでも複数のイベントになります。Croisのタスクの実行には複数のLambda関数が関わっているため、ワークフロー中に大量の要素を持つループがあると実行履歴数がすぐに制限を超えてしまいます。

 この問題への対処としてループを別State Machineに切り出して、そのState Machineに対して分割された配列を渡すことで実行履歴数が制限以下に収まるようにしました。元の1段のループに対して一定数で配列を分割し、その分割された配列でもう一度ループを回す構成になっています。配列を分割することで要素の数が必ず一定数以下になりループ内の処理では、よほどタスクが多くない限りは実行履歴数の制限には抵触しません。また親となる元のState Machine側の実行履歴にはState Machineの実行に関する履歴しか残らず、子のState Machine内部の履歴は含まれません。結果として両方のState Machineの実行履歴数を抑えることに成功しました。

イベントドリブンなアプリケーションのデプロイ

 Croisの開発で最も気を使っているのが、デプロイ手順です。Croisは複数のサービスを組み合わせた構成となっており、前述のようにイベントを介して非同期的に連携されているところが多々あります。またジョブによっては処理時間が長くデプロイ前の古い仕様のままの状態で動いていることがあります。

 コンポーネントのデプロイ順序を誤ると、互換性のないデータが後続の処理へ渡ってしまったり、古い仕様のデータに対して互換性のない処理が先にリリースされてしまったりして、エラーの原因となります。ジョブスケジューラーは安定的にジョブを実行できることが必須になるため、可能な限りこうしたエラーは避ける必要があります。この例に限らずクラウドを利用する場合には非同期で連携されることが多々ありますので、どう対処していくかは運用の観点でも重要です。

 ローンチ当初は開発者が順番を意識して各コンポーネントのリリース作業をする状況でした。しかし開発者が増えるにつれて新規参画者には難しいという問題が出てきました。Croisは開発を「GitHub Enterprise」で行っており、コードの変更点をPull Requestとして出すことで開発を進めています。この際に依存関係のある他のPull Requestを本文に記載して、その依存関係を元にデプロイ順を自動作成してデプロイできるツールを自作しました。

 自作したツールを使うフローにしたことでPull Request同士の依存関係を含めてレビューができるようになり、複数コンポーネントのリリースも一括してできることでデプロイ作業の煩雑さも減りました。一方で古い仕様のジョブが動いている状態でエラーが起きないかどうかといった互換性はレビューで担保されている状態で課題感は残っています。

 Croisの事例を通じて、クラウドネイティブなシステムでは失敗を前提として設計すること、制限緩和できない仕様を考慮すること、非同期なアプリケーションのデプロイ順番に気を付けることの3点を扱いました。

 これら以外にクラウドネイティブなシステムを組むときに考慮すべきことの一つにマネージドサービスをどう使うかがあります。Croisでは運用工数の削減やスケーラビリティ、セキュリティの担保ができたと述べました。部分的にでもマネージドサービスが使えるのであれば積極的に活用していこうというのが筆者の考えです。

 Croisの設計は考えていた設計にStep Functionsを当てはめたのではなく、Step Functionsというピースが決まったところから周りの設計を考えていきました。設計を満たすマネージドサービスを使ったのではなく、マネージドサービスを生かすための設計を考えたのです。

 マネージドサービスは日々増えていき、各サービスも進化していきます。クラウドが提供するマネージドサービスの進化に取り残されないようにすることが大切です。Croisのリリース当初は「Amazon SageMaker」という機械学習を対象としたサービスはありませんでしたし、Step FunctionsのワークフローをWebUI上で操作できるものもありませんでした。今でもCroisの機能の完全な代替とまではなっていませんが、そうしたサービスの進化を業務に取り入れられるか、日々考えていかなければなりません。

 Step Functions自体にも機能追加が多々ありました。例えばCroisのリリース当初は、State Machine上からLambda関数を介してAWS Batchでコンテナを起動し、コンテナ実行ステータスをポーリングして取得するという処理でした。

 2018年にはState Machine上から直接AWS Batchのジョブを実行できるようになり、それを取り入れたことでポーリング処理の間隔により実行遅延が起こらなくなりジョブ実行時間の短縮につながりました。2019年にはMap機能がリリースされ、Croisはループ実行に対応しました。

 マネージドサービス自体が進化していくので、最初の設計はどんどん古くなっていきます。新しい機能に対して追随していくだけでなくプロダクト自体もそれに合わせて継続的に進化させていく姿勢が必要です。

今後のCrois/Craftoの展開

 最後に、Crois/Craftoの将来像を紹介します。Croisの開発により当社では、データ処理プロセスの再利用可能性の向上やデータ処理を行うためのスケーラビリティ、セキュリティの確保されたインフラの自動提供が可能になりました。今後もCroisの活用を広げていくに当たり、課題を感じるところが2点あります。

 1つ目がマルチクラウドへの本格的な対応です。当社は「Google Cloud Platform」(以下、GCP)で提供される「BigQuery」というDWH(データウェアハウス)を中心としたデータ基盤があるため、同じGCP上のネットワークが利用できる環境にコンテナインスタンスを立ち上げてBigQueryと通信したいという要望があります。AWSとGCPの間をインターネット経由で接続するとデータ量の大きさからネットワーク費用が課題になることもあります。コンテナに割り当てる権限の調整も同一クラウド内にした方が柔軟に行えます。

 2つ目が社内OSS(オープンソースソフトウェア)としての開発の活性化です。Croisではよく使われるデータ処理を公式モジュールとして提供しているという話がありました。この公式モジュールはさまざまなチームに利用されており機能追加の要望が上がってきます。欲しい機能を説明してCroisチームが実装するよりは、OSSのようなフローでPull Requestを送ってもらい、それを取り込む方がお互いの時間の節約にもなることから、そうしたフローを推進しています。

 Crois本体は、社内OSSというレベルまでは至っていないもののソースコードは社内に公開されているので、それを利用して自身の管理するAWS環境でCroisを構築、運用してるチームもあります。組織を横断して使われるプロダクトにおける悩みの種は、どのくらいのサービスレベルを維持すればよいのかという点です。最高のサービスレベルで提供すれば全てのユーザーの要望を満たすことができますが、それにはかなりの運用負荷や費用がかかりますし実際のところそこまでのサービスレベルを求めているユーザーは少ないです。

 そこで、高いサービスレベルを求めるユーザーにはCroisを自身の環境に構築してもらい、自身のチームで運用しながらCrois本体へフィードバックを返してもらう仕組み作りを行っています。こうすることでCrois開発チームへの負荷と運用コストのバランスを取りながらプロダクトを開発していくことが可能になりました。現時点では、こうした形での利用は少ないですが、よりミッションクリティカルな部分で使われるようになると増えていくことが予想されるため、仕組みの整備が必要だと感じています。

 機械学習アプリケーションのCraftoによって、これまでデータサイエンティストやML(機械学習)エンジニアが行っていた作業を簡単に実行できるようになりました。Craftoが提供するモデリング機能は、データ変換やハイパーパラメーターチューニングのようなタスクを自動で行います。機械学習を専門にしていない職種の人でも、モデル構築と予測の実行が可能です。

 Craftoユーザーは、簡単にモデリングに取り組めるだけでなく、インフラ準備やコンピュータリソースの設定なども意識する必要がないため、ビジネスの課題に集中できます。特にフィージビリティスタディーの段階では、機械学習の導入がビジネスに対してどのくらい貢献するのかを素早く調査できます。

 一方で、機械学習システム全体の構築を考えたときには、現在のCraftoではカバーできていない部分も多く残されています。近年「MLOps」というワードが注目されていますが、モデリング以外のデータ準備やモデルデプロイ、モデルサービングの監視など、エンドツーエンドで機械学習システムの改善に取り組む必要があります。

 Craftoはモデル構築の機能を重視したツールとなっており、それ以外の部分、例えば定常的に予測タスクを実行して結果を監視するような場合は、MLエンジニアが追加で開発をする必要があります。ここ数年でMLOpsのプラクティスが議論され、フレームワークの開発も盛んに行われています。Craftoはまだまだ改善余地のあるアプリケーションです。機械学習の案件で、エンドツーエンドの形で利用できるように機能を拡張していき、開発や運用のニーズを満たしたプラットフォームに強化していきたいと考えています。

筆者紹介

宮井康宏

リクルート プロダクト統括本部 プロダクト開発統括室 データ推進室 データテクノロジーユニット アジリティテクノロジー部 所属

大阪大学大学院で経営学修士および工学修士を取得。複数の企業でログ解析基盤の構築に携わり、2018年にリクルートコミュニケーションズに中途入社。データエンジニアとしてデータ解析基盤の構築に関わる。

秋庭伸也

リクルート プロダクト統括本部 プロダクト開発統括室 データ推進室 データプロダクトユニット データプロダクトマネジメント1部 所属

早稲田大学大学院を卒業後、リクルートコミュニケーションズに新卒で入社。分析業務や機械学習システムの開発に、機械学習エンジニアとして携わる。


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。