ごみ検出処理をCloud Batchに移行した話

こんにちは、ピリカ開発チームの九鬼です。

2022年にGCPでCloud Batchのプレビューがリリースされ、2023年4月には東京リージョンがサポートされました。ピリカではタカノメでごみ検出処理をCloud Runで行ってきましたが、この度Cloud Batchに移行しました。その中で得た知見を共有いたします。

TL; DR

  • 1jobに渡すコマンドラインの引数は65535個以下かつ、合計で約128KB以下に収めましょう。
  • 並列度は、storageなど外部サービスが許容できる量に収まるようにセットしましょう。
    • 例えば、Cloud Storageはバケットに対し最初オブジェクト1000件/秒の書き込み制限があるので、それを越えないようにします。
    • 複数jobを並列実行する場合、全jobでの並列度の合計が対象になります。
  • Spot VMを確保できない場合、確保されるまでjobの実行が待機状態になります。また、途中でプリエンプトしたときはVMが再確保されてタスクが再実行されます
  • タスクは内部処理などでエラー終了すると、デフォルトでは再試行されません。taskGroups[].taskSpec.maxRetryCountでタスクごとの最大リトライ回数を入れると良いでしょう。
  • 確保されるVM数は、おおよそallocationPolicyのvCPU,メモリ量とcomputeResourceのvCPU,メモリ量の比で決まります。例えば2:1であれば1VMに2タスク、4:1であれば1VMに4タスクが割り当てられます。

背景

過去に、Cloud Runでのごみ検出フローの構築方法をご紹介しました。運用をしていく中で、以下の問題が顕在化していました。

  • Cloud Tasksで動画ごとにタスク発行するのは1件あたり1~2秒かかるので、動画数が多いとタスク発行完了までに数分〜十数分要することがあった
  • Cloud Runの当該サービスに対し、他のAPIを更新した場合もごみ検出系のライブラリのインストールが必要になっていた
  • どのタスクが実行成功して失敗していたか、後から確認することが難しい
    • Cloud Loggingでの保存期限後は、履歴が残らない

そこで構成を再検討していたところ、Cloud Batchでバッチ処理化することになりました。

実際の構成

従来の構成と同等のフローを実現するにあたり、ごみ検出処理をCloud Batchに分離しました。Cloud Batchでバッチ処理することにより、以下の利点が得られます。

  • Cloud Batchのjobは1~3件発行すれば良いので、Cloud Runでの実行時間を減らすことができる。バッチ処理開始までの処理フローが簡約化された
    • 従来、分割した日時ごとに動画一覧を発行していたが不要になった。バッチ処理開始までのdispatch処理にかかる時間が数秒程度に短縮されたため
  • ごみ検出処理とAPI提供で責務を分離できた
    • Cloud Batchで実行するコンテナイメージに、ごみ検出関連のライブラリを移設した。これにより、Cloud Run側で当該ライブラリをインストールする必要がなくなった
  • Cloud Batch上にjobごとの実行結果が残り、なおかつtaskごとに結果を確認できるようになった

before: 従来のごみ検出構成
before: 従来のごみ検出構成

after: ゴミ検出部をCloud Batch化
after: ごみ検出部をCloud Batch化

本システムでは過去の構成よりも安価なインスタンスを用いることにより、数割に及ぶごみ検出コストの削減効果も得られました。

Cloud Batchを利用するにあたって留意すべき点

Cloud Batchでは、コンテナイメージをArtifact Registryに用意し、それをGCP上のBatchサービスで呼び出してバッチ処理を行います。この関係で、以下の点を留意しました。

1. 1jobに渡す引数のサイズを絞る

各コンテナイメージは、指定がない限りLinux用のDocker上で実行されます。実測上、1jobで渡せるコマンドラインの引数は65535個以下かつ、合計で約128KB以下まで受け付けます(2023/7での実測の結果)。引数から実行パラメータを渡すときは、スケールアウトに伴って制限を越えないかを設計レベルで確保しましょう。

2. GCPの各種サービスおよびjob同士の実行タイミングを考慮してtaskの並列度を決める

バッチ処理では、Cloud StorageやBigQueryなどのGCP上のサービスに結果を書き込む場合があります。これらのサービスではデータの書き込み制限があり、例えばStorageは同一バケットに対しオブジェクト最初1000件/秒の書き込み制限*1があります。このため、taskの並列度を設定する場合はこれら制限に達しないように並列度を決める必要があります。

また、複数jobを並列実行する場合、全jobでの並列度の合計が正味の並列度になります。複数jobを同時に実行する場合は、例えばjobあたりの並列度 = 達成したい並列度 / job数で分割すると良いでしょう。

3. Spot VMの確保に関する挙動について

Spot VMを確保できない場合、確保されるまでjobの実行が待機状態になります。また、途中でプリエンプトしたときはVMが再確保されてタスクが再実行されます*2

4. taskの再試行でtaskを確実に完了させる

taskは内部処理などでエラー終了すると、デフォルトでは再試行されません。

taskGroups[].taskSpec.maxRetryCountでtaskごとの最大リトライ回数を入れると良いでしょう。なお、Cloud Batchでは1taskでも失敗している場合、当該jobは失敗扱いになります。

5. computeResourceの決め方について

jobに対して確保されるVM数は、おおよそallocationPolicyのvCPU,メモリ量とcomputeResourceのvCPU,メモリ量の比で決まります。例えば2:1であれば1VMに2タスク、4:1であれば1VMに4タスクが割り当てられます。 もし、e2-standard-4(4vCPU, 16GiB)のVM1つに最大で4タスクを実行させたい場合、 computeResourceのvCPUは1でメモリは4GiBと設定します。

また、それぞれのVMで、特にメモリの過度な余剰が発生しないようCloud Monitoring等でプロファイリングすると良いでしょう。


*1:Cloud Storageの割り当てと上限 バケットでのオブジェクト書き込みの最大レートより。ただし、時間経過によりStorage側がスケールアウトすると書き込める件数が増大します。

*2:公式ドキュメントによる記述より。実際の挙動は未検証です。また、Spot VMの性質上、VMが再確保されるまでに長時間要する可能性があります。