DeLMO(identify)エンジニアブログ

DeLMO(identify株式会社)のエンジニア・デザイナーが色々書くブログです

Google Cloud Batchを使ってバッチの処理待ち時間を1/30以下にしたので紹介させて欲しい

この記事について

今回、Google CloudのBatchを利用した動画変換処理を実装したので、どのようにしたのか、どこにハマったのか(ハマっているのか)、その効果についてまとめました。端的に現状を3行でまとめると、以下のようになります。

  • 動画変換処理にGoogle CloudのBatchを使いました
  • GPUを利用することで処理時間を短くできました
  • コンテナでGPUをうまく使えずシェルスクリプトとGoで作ったバイナリを活用しています(コンテナ化の知見募集中です)

はじめに

はじめまして。identify株式会社 CTOの@suthioです。 弊社、identify株式会社では動画素材サービスDeLMOの運営を行っています。

service.delm0.jp

簡単に言うと、動画素材を提供するサービスとなります。

課題

弊社ではGoogle Cloud Platformをインフラとして利用しています。DeLMOというサービスは動画素材のプラットフォームという特性上、動画を変換する処理自体が事業のコアとなっており、GCEインスタンスにインストールしたffmpegを用いて動画の変換処理を行っていました。

サービス開始当初は特に問題なかったのですが、関わるクリエイターの方々が増えたことや日々アップロードされる動画素材数が格段に増えたこと、iPhoneの高解像度化により変換処理が滞ることになりました。

弊社の運用は下記となっています。

①動画アップロード→②変換→③審査→④公開

動画アップロードの数が増えたことにより、最大で30時間ほど変換処理待ちが発生していました。この影響により、審査に待ちが発生し動画素材を公開できないといった状態が定常的に発生していたので、こちらを解決するべく「動画変換高速化プロジェクト」を始動する運びとなりました。

旧動画バッチフロー

下記の画像は3月5日から3月13日までの変換サーバーのCPU使用率となります

変換サーバーCPU使用率

解決したいことの整理

  • 最大30時間もの変換処理待ち状態となるのを解決したい
    • 1動画変換あたりの時間を高速化
    • 複数台のVMやCPUを使用して並列で処理を行えるようにしたい
  • あわよくばこのタイミングでコスト削減もできたら理想
    • 変換処理が一番インフラコストが高くなっているかつコストが高くなると予想されるため

下記の資料の通り、スマートフォンの背面カメラは日々高解像度しており、今後も1動画あたりの変換コストが高くなる想定をして 3年後も耐えられるような変換処理を目指しました。(2023年現在では非常にオーバースペックな構成) www.counterpointresearch.com

並列・高速な変換処理の検討

特に動画変換においてはGPUが利用できれば時間の短縮が見込まれるため、GPUが利用でき、かつ並列でバッチ処理ができるサービスを検討し、Google Cloudのアーキテクチャレビューを使用してGoogle Cloudの方にも相談させていただいた結果、ちょうど昨年にGAしたばかりのBatchを採用することになりました。その他に検討したものは下記になります。

  1. Cloud Run jobs
  2. 動画変換サービス

1のCloud Run jobsについてはGPUが利用できないため今回は見送りました。2の動画変換サービスという観点では、Googleが提供しているTranscoder APIや AWSが提供しているAmazon Elastic Transcoder などがあります。よしなにやってくれるのはよいのですが、動画変換の要件に見合わないことや、動画変換時に必要なコストが非常に高くなってしまうことが見込まれるので採用を見送りました。※DeLMOの動画素材は30秒以下の動画が非常に多いのに対し、動画変換サービスは60秒がミニマムの課金だったので30秒の変換を行っても60秒の料金がかかるため高額になってしまうという予測でした。

※弊社はパフォーマンスや審査の最適化のために1つの動画につき10個以上の動画に変換を施しているので動画変換サービスと相性がそこまで良くなかったというのも独自でBatchを使用する要因になりました。

Batchについて

2022/10/11にGAとなった比較的新しいサービスです。Batchで実行してほしい処理をコンテナ化またはシェルスクリプトで記述しておき、どのような環境(主にコンピュートリソース)で実行するかを設定すればあとはよしなに適切なGCEインスタンスを起動し、処理を実行し、終わったらインスタンスを削除するところまで自動・並列で行ってくれます。

cloud.google.com

処理部分の実装

Batchに行わせたい処理としては以下になります。

  • Batchの呼び出し元から変換対象の動画IDを受けとる
  • 動画IDを使って変換対象の動画ファイルの格納場所(GCS)をDBから取得する
  • 変換対象の動画ファイルをGCSからダウンロードする
  • 変換処理を行う(ffmpeg)
  • 変換後の動画ファイルをGCSへアップロードする
  • DBにある動画のステータステーブルを更新する

処理としてはGCEで行っているものが既にあるため大きく変えず、ffmpegでの変換の際にGPUを利用するオプションを加えるなどしました。コンテナ化にあたりベースイメージにffmpegをインストールしておき、Goバイナリを注入してそこから呼び出すつくりとしました。

ハマったポイント1:コンテナからGPUが使えない

GPUを利用したジョブのドキュメントを見ながら、コンテナにGPUの諸々をマウントして、ffmpegのオプション(h264_nvenc)を設定して変換しようとすると必要なライブラリが見つからない旨のエラーがでました。

Cannot load libcuda.so.1

色々調べた結果、使っていたffmpegが参照しているライブラリディレクトリ(--libdir=/usr/lib/x86_64-linux-gnu)に上記のファイル(GPU用のライブラリファイル?)が存在しない、というふうに捉えました。ただ、単純にコンテナ内の上記ディレクトリにGPUのライブラリディレクトリをvolumeマウントしてしまうと、もともと入っていたライブラリが上書きされてしまって結局ffmpegが立ち上がらない、ということになりました。そこでカスタムVMを起動し、そこにGPUのライブラリも、ffmpegが標準で使うライブラリも、全部先に入れておいてそれをコンテナでマウントすればいいのではないか、と考えました。が、うまくいきませんでした。※詳細は後述します。

ハマったポイント2:自作のインスタンステンプレートを使うとGCEが立ち上がらない

別途GCEインスタンスを立ち上げ、ffmpegをビルドし、GPUのドライバもインストールしてからイメージおよびインスタンステンプレートを作成しました。ここで、用意したインスタンステンプレートでBatchを起動するとGCEが立ち上がってこないという現象に陥りました。サポート問い合わせすると以下のコマンドでインスタンステンプレートを作成してくださいと回答をいただきました。ポイントとしてはおそらく以下かと思います。

  • イメージ作成時にfamilyを指定する(--image-family)
  • 未検証ですがその他オプション(--maintenance-policy、--restart-on-failure)もあったほうがよさそう
gcloud compute instance-templates create xxxxxxxx \
    --machine-type n1-standard-2 \
    --boot-disk-size 250GB \
    --accelerator type=nvidia-tesla-t4,count=1 \
    --image-family xxxxxxxx \
    --image-project xxxxxxxx \
    --maintenance-policy TERMINATE \
    --network-interface=network-tier=PREMIUM,subnet=xxxxxxxx \
    --restart-on-failure

また、他プロジェクトで作成したイメージで作ったインスタンステンプレートを使おうとすると、CODE_GCE_PERMISSION_DENIEDとなりインスタンスが起動できない現象にもぶつかりました。ここでもサポート問い合わせを行い、Batchが利用するServiceAgent(service-PROJECT_NUMBER@gcp-sa-cloudbatch.iam.gserviceaccount.com)に、 利用するイメージプロジェクトでのイメージ利用者権限を付与すればOKということがわかりました。

コンテナを諦めてシェルスクリプトへ

ライブラリ全部入りのVMを立ち上げてコンテナにマウントする作戦も色々やってみたのですが

Cannot init CUDA

のエラーがどうしても倒せず、コンテナからGCE側にアタッチされたGPUを使うのは一旦諦めました。(なんとなくBatchの問題というよりはGPUをコンテナで扱う一般的な問題にあたっている気はするのですが。。。)幸い処理部分は弊社が利用している言語がGolangだったのでGoのバイナリを作って配置すればいいこと、GCEに直接sshした場合ではGPUオプションのffmpegが起動できたことからコンテナを利用せず、シェルスクリプトからGoのバイナリを呼び出しそこからインスタンスのffmpegを呼び出すことでGPUを利用した動画変換が実現できました。

最終的な構成

最終的には以下のような構成になりました。

バッチ起動用GCEイメージの下準備

VMのベースとして、 Debian 10 based Deep Learning VM for TensorFlow Enterprise 2.11 with CUDA 11.3 M104 を利用し、ここでh264_nvencコーデックを有効にしたffmpegをビルドしてイメージとして保存しておきます。(ファミリーを指定するのを忘れずに!)

インスタンステンプレートの作成

上記で記載したコマンドでイメージからインスタンステンプレートを作成します。

Batchの起動

以下の設定ファイルを使ってBatchを起動します。※実際にはGolangで書いているので cloud.google.com/go/batch を使って起動しています。 アプリケーションは事前にGCSに格納しておいて、直接マウントしつつ取り出してキックしています。

{
    "taskGroups": [
        {
            "taskSpec": {
                "runnables": [
                    {
                        "script": {
                            "text": "sudo /opt/deeplearning/install-driver.sh" // なぜか毎回初期化が必要
                        }
                    },
                    {
                        "script": {
                            "text": "cp /cmd/app /app && cp -r /cmd/resources/ /resources" // アプリケーションをマウント先からコピー
                        }
                    },
                    {
                        "script": {
                            "text": 
                                "/app" // 環境変数の値を使ってアプリケーションの実行
                        },
                        "environment": {
                            "variables": // 環境変数諸々
                        } 
                    }
                ],
                "volumes": [ 
                    { 
                        "gcs": { 
                            "remotePath": "xxxxxx" // Goバイナリが入っているGCS
                        }, 
                        "mountPath": "/cmd/"
                    } 
                ]
            },
        }
    ],
    "allocationPolicy": {
        "instances": [
            {
                "installGpuDrivers": false,
                "instanceTemplate": "xxxxx" // 作ったインスタンステンプレート
            }
        ]
    },
    "logsPolicy": {
        "destination": "CLOUD_LOGGING"
    }
}

結果

GPUで変換できるようになってから、試しに重めの変換処理を行ってみました。結果、CPU版では667秒(11分7秒)かかった処理がGPU版では141秒(2分21秒)となり、約5倍早くなりました。重い動画の処理ほど処理速度向上の恩恵を受けることができそうです。
※平均、4倍から5倍程度高速化に成功

元々問題となっていた処理待ちの事象もほぼなくなり、動画の変換待ち時間は今のところではありますが最大で10分程となりました。Batchの呼び出し元で動画IDを10個単位に分割して起動しているので、仮に100個の動画変換タスクがキューに積まれても10並列で変換することで待ち時間を短くすることに成功し、Batchの恩恵を実感しています。

料金的には処理時間が短くなること、Spot VMを使用していることから結果として80%ほど削減できる見込みです。

おまけのハマりポイント:gcsfuseのインストールに失敗してジョブがコケる

検証環境ではうまくいっていたのですが、本番環境で稼働させるようになってから ジョブが多数失敗するようになりました。

エラーログを見てみると、記述した処理が始まる前の、gcsfuseのインストールに失敗しているようでした。上記設定ファイルにあるように、GCSをマウントする設定を入れている場合は自動でgcsfuseがインストールされる仕組みになっているようです。それ自体は納得できるのですが、そこでエラーが出てしまうと本来の処理にいかずにジョブが終了してしまうため、変換処理が進みません。サポートに問い合わせつつ色々見てみると、カスタムイメージにもとから入っていたgcsfuseがどうやら悪さをしていたようで、こちらをアンインストールしたイメージを再作成すると失敗しなくなりました。あまり意識していなかったのですが、Google Cloud公式イメージには最初からgcsfuseが入っていることがある、ということで今後似たようなことがあれば気をつけたいと思います。

検索でたどり着いた人のためにエラーメッセージを貼っておきます。

Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)
Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?

まとめ

Google CloudのBatchを使った動画変換処理を実装しました。結局当初想定していた、コンテナを使った実装はGPUをうまく扱えずに断念する結果となりました。(ここらへんの知見のある方、アドバイスいただけると嬉しいです。。。)ただ、少し遠回りではありつつも当初やりたかったことは実現でき、フルマネージドで並列、高速な変換処理を行えるBatchの魅力を感じています。 この記事がなにかの参考になれば幸いです。

また、今回副業として手伝ってもらっている@saramuneにBatchのインフラ周りの設定や調査、構築などを行ってもらっています。今回の構成に関する話や、コンテナでGPUをうまく使うやり方をご存知の方は@suthioか、@saramuneまでご連絡いただければ幸いです。

最後にいつものやつですが、弊社ではソフトウェアエンジニアを募集しております。 @suthioにDMしていただくか、下記での応募をお待ちしております。 www.wantedly.com