$30 off During Our Annual Pro Sale. View Details »

1秒動画の作り方―「家族アルバム みてね」における 動画エンコードパイプラインとその最適化事例 / 1s Movie Under the Hood

1秒動画の作り方―「家族アルバム みてね」における 動画エンコードパイプラインとその最適化事例 / 1s Movie Under the Hood

_sobataro

August 03, 2023
Tweet

More Decks by _sobataro

Other Decks in Programming

Transcript

  1. 1秒動画のつくり方
    「家族アルバム みてね」における
    動画エンコードパイプラインとその最適化事例
    2023-08-03 MIXI × bitbank 合同勉強会
    株式会社MIXI 松石浩輔 (@_sobataro)

    View Slide

  2. MIXI, Inc.
    目次
    ● 自己紹介
    ● 「家族アルバム みてね」の紹介
    ● 1秒動画とは
    ● 1秒動画の生成の流れ
    1. 対象家族抽出
    2. 素材選択
    3. 動画エンコード
    4. 配信
    ※このスライドの内容は以下記事の内容と重複しています:
    https://gihyo.jp/article/2023/07/mitene-07-1sec-movie

    ● Sidekiq Batchを用いた
    動画エンコードの詳細
    ● インフラとSidekiq Batchの
    最適化事例
    ● まとめ
    2


    View Slide

  3. MIXI, Inc.
    自己紹介
    松石浩輔 @_sobataro
    所属:Vantageスタジオ みてねプロダクト開発部 DataEngineeringグループ
    ● 1秒動画、自動提案フォトブック、「人物ごとのアルバム」自動分類などの
    研究開発とバックエンド開発を担当するグループ
    ● AI/MLエンジニア2名、バックエンドエンジニア5名
    仕事:エンジニアリングマネージャ
    ● ピープルマネジメントなどマネジメント系業務全般 & バックエンドエンジニア
    3


    View Slide

  4. MIXI, Inc.
    「家族アルバム みてね」の紹介

    View Slide

  5. MIXI, Inc.
    「家族アルバム みてね」の紹介
    ● お子さまの写真・動画を、家族内で
    無料・無制限に共有できるスマートフォンアプリ
    ● https://mitene.us/
    ● 特徴
    ○ 8周年(2015年4月リリース)
    ○ 7言語に対応
    ○ 利用者数1800万人(2023年5月現在)
    ○ 日本国内では新生児のママ・パパの約半数に
    ご利用いただいています(※2)
    ※1 以下「みてね」と表記します

    ※2 みてね調べ

    (※1)

    5


    View Slide

  6. MIXI, Inc.
    1秒動画とは

    View Slide

  7. MIXI, Inc.
    1秒動画とは
    ● みてねにアップロードされた動画・写真を
    1秒ずつ切り出して繋ぎ合わせた自動配信のダイジェスト動画
    ● 3種類のバリエーション
    ○ 四季版:3ヶ月ごとに配信
    ○ 月版:毎月配信、みてねプレミアム限定
    ○ 年間版:毎年年始に配信、みてねプレミアム限定
    四季版のサンプル動画 

    7


    View Slide

  8. MIXI, Inc.
    1秒動画の生成の流れ

    View Slide

  9. MIXI, Inc.
    1秒動画の生成の流れ
    1. 対象家族抽出
    ● その日、どの家族に、どのバージョン・どの期間の
    1秒動画を生成するかの抽出バッチ
    ● 素材数などの条件を満たす家族のみ抽出
    2. 素材選択
    ● 独自の素材選択AIにより、どの動画・写真を使うか選択
    ● ルールベース
    ● 顔検出などのMLモデルの出力と、
    撮影日時やお気に入りなどのメタデータとを考慮
    9


    View Slide

  10. MIXI, Inc.
    1秒動画の生成の流れ
    3. 動画エンコード
    ● 素材選択結果に従い、1秒動画の動画ファイル本体を生成
    ● 専用のマイクロサービス“transcoder”で処理
    ● 詳しくは後述
    4. 配信
    ● iOS/Androidアプリへプッシュ通知を送ってお届け
    10


    View Slide

  11. MIXI, Inc.
    Sidekiq Batchを用いた動画エンコードの詳細

    View Slide

  12. MIXI, Inc.
    1秒動画生成処理の設計
    マイクロサービス“transcoder”

    ● 1秒動画の動画ファイル生成サービス

    ● Amazon EKS上で独自にスケーリング

    1秒動画の生成パイプライン
    ● 全体をSidekiq Batchとして定義・実行
    ○ Sidekiq: Ruby製のジョブキュー
    ● 動画の切り出し、写真の動画化などの各ステップは
    FFmpeg filterを用い、Sidekiq Workerとして実装
    1秒動画の生成パイプライン 

    (イメージ)
 12


    View Slide

  13. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(1/3)
    # パイプライン全体の実装
    class OneSecondMoviePipeline
    include Sidekiq::Worker
    def perform
    trim(nil, {}) # パイプラインの先頭は trimの処理
    end
    # ヘルパメソッド self.define_sidekiq_method については gihyo.jp の記事参照
    # パイプラインの定義。 trim, effect, add_watermark, crossfadeの順で処理する
    define_sidekiq_method(method: 'trim', worker_class: TrimWorker, on_success_method: 'effect',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'effect', worker_class: EffectWorker, on_success_method: 'add_watermark',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'add_watermark', worker_class: AddWatermarkWorker, on_success_method: 'crossfade',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'crossfade', worker_class: CrossfadeWorker, on_success_method: 'on_success',
    on_success_options: proc { |_| {} })
    def on_complete(status, options)
    # パイプライン完了後の処理として成果物として得られた動画ファイルの保存・返却を行う(省略)
    end
    end
    13


    View Slide

  14. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(1/3)
    # パイプライン全体の実装
    class OneSecondMoviePipeline
    include Sidekiq::Worker
    def perform
    trim(nil, {}) # パイプラインの先頭は trimの処理
    end
    # ヘルパメソッド self.define_sidekiq_method については gihyo.jp の記事参照
    # パイプラインの定義。 trim, effect, add_watermark, crossfadeの順で処理する
    define_sidekiq_method(method: 'trim', worker_class: TrimWorker, on_success_method: 'effect',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'effect', worker_class: EffectWorker, on_success_method: 'add_watermark',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'add_watermark', worker_class: AddWatermarkWorker, on_success_method: 'crossfade',
    on_success_options: proc { |params| { outputs: params.map { |p| p.output.to_h } } })
    define_sidekiq_method(method: 'crossfade', worker_class: CrossfadeWorker, on_success_method: 'on_success',
    on_success_options: proc { |_| {} })
    def on_complete(status, options)
    # パイプライン完了後の処理として成果物として得られた動画ファイルの保存・返却を行う(省略)
    end
    end
    パイプライン本体の定義
    各ステップの処理は
    通常のSidekiq Workerとして実装
    パイプライン全体も
    ひとつのSidekiq Worker
    14


    View Slide

  15. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(2/3)
    # パイプライン定義のためのhelper
    def self.define_sidekiq_method(method:, worker_class:, on_success_method:, on_success_options:)
    define_method(method) do |status, options|
    parameters = worker_class.create_parameters(options: options)
    # @see https://github.com/mperham/sidekiq/issues/3522
    parent_batch = ::Sidekiq::Batch.new(status.try(:parent_bid) || bid)
    parent_batch.jobs do
    options = on_success_options.call(parameters)
    step = ::Sidekiq::Batch.new
    step.callback_queue = 'one_second_movie_callback'
    step.on(:success, "#{self.class}##{on_success_method}", options)
    step.on(:complete, "#{self.class}#on_complete", options)
    step.description = "#{self.class}##{__method__}"
    # 各Workerのenqueueメソッドでperform_asyncすることで、このstepにおけるジョブを登録する
    step.jobs { worker_class.enqueue(parameters: parameters, queue: 'one_second_movie') }
    end
    end
    end
    15


    View Slide

  16. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(2/3)
    # パイプライン定義のためのhelper
    def self.define_sidekiq_method(method:, worker_class:, on_success_method:, on_success_options:)
    define_method(method) do |status, options|
    parameters = worker_class.create_parameters(options: options)
    # @see https://github.com/mperham/sidekiq/issues/3522
    parent_batch = ::Sidekiq::Batch.new(status.try(:parent_bid) || bid)
    parent_batch.jobs do
    options = on_success_options.call(parameters)
    step = ::Sidekiq::Batch.new
    step.callback_queue = 'one_second_movie_callback'
    step.on(:success, "#{self.class}##{on_success_method}", options)
    step.on(:complete, "#{self.class}#on_complete", options)
    step.description = "#{self.class}##{__method__}"
    # 各Workerのenqueueメソッドでperform_asyncすることで、このstepにおけるジョブを登録する
    step.jobs { worker_class.enqueue(parameters: parameters, queue: 'one_second_movie') }
    end
    end
    end
    パイプライン定義のための helper
    Sidekiq::Batchを使って
    ステップの親子関係や
    コールバックを設定
    このステップ内で行うべきジョブ
    (Sidekiq Worker)を
    perform_asyncして登録
    16


    View Slide

  17. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(3/3)
    # 各ステップの実装
    class TrimWorker
    include Sidekiq::Worker
    def perform(parameters)
    # FFmpegで実際にtrim処理を実行する(省略)
    end
    def self.create_parameters(options:)
    # trim処理に必要なパラメータを組み立てる(省略)
    end
    def self.enqueue(parameters:, queue:)
    # trim処理を行うSidekiqジョブを登録する
    parameters.each do |parameter|
    TrimWorker.set(queue: queue).perform_async(parameter.map(&:to_h))
    end
    end
    end
    # EffectWorker, AddWatermarkWorkerなども同様に実装する(省略)
    17


    View Slide

  18. MIXI, Inc.
    1秒動画の生成パイプライン実装イメージ(3/3)
    # 各ステップの実装
    class TrimWorker
    include Sidekiq::Worker
    def perform(parameters)
    # FFmpegで実際にtrim処理を実行する(省略)
    end
    def self.create_parameters(options:)
    # trim処理に必要なパラメータを組み立てる(省略)
    end
    def self.enqueue(parameters:, queue:)
    # trim処理を行うSidekiqジョブを登録する
    parameters.each do |parameter|
    TrimWorker.set(queue: queue).perform_async(parameter.map(&:to_h))
    end
    end
    end
    # EffectWorker, AddWatermarkWorkerなども同様に実装する(省略)
    各ステップの処理は
    通常のSidekiq Workerとして実装
    さきほど定義したhelper
    で使う処理を記述
    18


    View Slide

  19. MIXI, Inc.
    インフラとSidekiq Batchの最適化事例

    View Slide

  20. MIXI, Inc.
    ● 従来は全Workerを同じ優先順位で処理していた
    パイプラインの全Workerを同じSidekiq queueで処理する問題
    # 各Workerにおけるqueueの指定例
    class OneSecondMoviePipeline
    include Sidekiq::Worker
    sidekiq_options queue: 'one_second_movie' # 他のTrimWorker, EffectWorkerなども同様
    end
    # EKS環境の1秒動画生成用DeploymentにおけるSidekiqプロセスの実行例
    # このSidekiqプロセスでは[one_second_movie_callback, one_second_movie]キューの優先順位でジョブを実行する
    # one_second_movie_callbackキューでは、Sidekiq Batchによるコールバックの処理が実行され、
    # これは最優先で実行する必要があるためキューを分けている
    # see also: https://github.com/sidekiq/sidekiq/wiki/Advanced-Options
    sidekiq -q one_second_movie_callback -q one_second_movie
    全Workerを同じqueueで処理
    queueはひとつだけ
    20


    View Slide

  21. MIXI, Inc.
    パイプラインの全Workerを同じSidekiq queueで処理する問題
    ● このときのtranscoder全体の処理順序:
    「全家族のステップA」→「全家族のステップB」→…… と進む
    21


    View Slide

  22. MIXI, Inc.
    パイプラインの全Workerを同じSidekiq queueで処理する問題
    ● このときのtranscoder全体の処理順序:
    「全家族のステップA」→「全家族のステップB」→…… と進む
    Sidekiq Batchの仕様上、
    パイプラインの各ジョブ(家族)ごとに、
    ひとつのステップのジョブが終わってから
    次のステップのジョブが enqueueされる
    当日分の家族のジョブはバッチ処理的に
    まとめてenqueueされる
    Sidekiq Batchの仕様上、
    同じqueueのジョブは
    全パイプライン共通で
    積まれた順に処理される
    22


    View Slide

  23. MIXI, Inc.
    パイプラインの全Workerを同じSidekiq queueで処理する問題
    ● このときのtranscoder全体の処理順序:
    「全家族のステップA」→「全家族のステップB」→…… と進む
    このあたりで配信時間を迎えると
    当日分の生成処理全件が
    配信時間に間に合わない(問題 1)
    ステップごとにCPU・メモリ消費量が
    異なり、コンテナのリソース割り当て
    を最適化できない(問題 2)
    23


    View Slide

  24. MIXI, Inc.
    ● パイプライン先頭のWorkerのみ優先度最低に変更
    パイプライン先頭のWorkerのみ優先度最低にして解決
    # 各Workerにおけるqueueの指定例
    class OneSecondMoviePipeline
    include Sidekiq::Worker
    sidekiq_options queue: 'one_second_movie_low'
    end
    # EKS環境の1秒動画生成用DeploymentにおけるSidekiqプロセスの実行例
    # このSidekiqプロセスでは[one_second_movie_callback, one_second_movie]キューの優先順位でジョブを実行する
    # one_second_movie_callbackキューでは、Sidekiq Batchによるコールバックの処理が実行され、
    # これは最優先で実行する必要があるためキューを分けている
    # see also: https://github.com/sidekiq/sidekiq/wiki/Advanced-Options
    sidekiq -q one_second_movie_callback -q one_second_movie -q one_second_movie_low
    パイプライン先頭のWorkerのみ
    別のqueueに分離
    優先順位最低で
    one_second_movie_low
    queueを追加
    24


    View Slide

  25. MIXI, Inc.
    ● このときのtranscoder全体の処理順序:
    「全家族のステップA」→「全家族のステップB」→…… と進む
    パイプライン先頭のWorkerのみ優先度最低にして解決
    25


    View Slide

  26. MIXI, Inc.
    ● このときのtranscoder全体の処理順序:
    「全家族のステップA」→「全家族のステップB」→…… と進む
    パイプライン先頭のWorkerのみ優先度最低にして解決
    全ジョブの完了前に配信時間を迎えても
    それまでに生成完了した家族には配信できる
    (問題1を解決)
    ある時点におけるEKS Deploymentは
    各ステップの処理をごちゃまぜに
    実行しているので
    リソース割り当てが最適化しやすい
    (問題2を解決)
    ステップB以降のジョブがqueueにある限り
    新しいステップAのジョブに着手しない
    26


    View Slide

  27. MIXI, Inc.
    まとめ

    View Slide

  28. MIXI, Inc.
    まとめ
    ● 1秒動画の概要と生成処理の流れについて紹介
    ● Sidekiq Batchを用いた動画エンコードを説明
    ● インフラとSidekiq Batchの最適化事例を紹介
    ● Sidekiq Batch: ある程度のboilerplateがあり、パフォーマンス面にも注意が必要だが
    Sidekiqをすでに利用している場合には便利に使えるかも
    28


    View Slide

  29. MIXI, Inc.

    View Slide