Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

_sobataro
August 03, 2023

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

_sobataro

August 03, 2023
Tweet

More Decks by _sobataro

Other Decks in Programming

Transcript

  1. MIXI, Inc. 目次 • 自己紹介 • 「家族アルバム みてね」の紹介 • 1秒動画とは

    • 1秒動画の生成の流れ 1. 対象家族抽出 2. 素材選択 3. 動画エンコード 4. 配信 ※このスライドの内容は以下記事の内容と重複しています: https://gihyo.jp/article/2023/07/mitene-07-1sec-movie
 • Sidekiq Batchを用いた 動画エンコードの詳細 • インフラとSidekiq Batchの 最適化事例 • まとめ 2

  2. MIXI, Inc. 自己紹介 松石浩輔 @_sobataro 所属:Vantageスタジオ みてねプロダクト開発部 DataEngineeringグループ • 1秒動画、自動提案フォトブック、「人物ごとのアルバム」自動分類などの

    研究開発とバックエンド開発を担当するグループ • AI/MLエンジニア2名、バックエンドエンジニア5名 仕事:エンジニアリングマネージャ • ピープルマネジメントなどマネジメント系業務全般 & バックエンドエンジニア 3

  3. MIXI, Inc. 「家族アルバム みてね」の紹介 • お子さまの写真・動画を、家族内で 無料・無制限に共有できるスマートフォンアプリ • https://mitene.us/ •

    特徴 ◦ 8周年(2015年4月リリース) ◦ 7言語に対応 ◦ 利用者数1800万人(2023年5月現在) ◦ 日本国内では新生児のママ・パパの約半数に ご利用いただいています(※2) ※1 以下「みてね」と表記します 
 ※2 みてね調べ
 (※1)
 5

  4. MIXI, Inc. 1秒動画とは • みてねにアップロードされた動画・写真を 1秒ずつ切り出して繋ぎ合わせた自動配信のダイジェスト動画 • 3種類のバリエーション ◦ 四季版:3ヶ月ごとに配信

    ◦ 月版:毎月配信、みてねプレミアム限定 ◦ 年間版:毎年年始に配信、みてねプレミアム限定 四季版のサンプル動画 
 7

  5. MIXI, Inc. 1秒動画の生成の流れ 1. 対象家族抽出 • その日、どの家族に、どのバージョン・どの期間の 1秒動画を生成するかの抽出バッチ • 素材数などの条件を満たす家族のみ抽出

    2. 素材選択 • 独自の素材選択AIにより、どの動画・写真を使うか選択 • ルールベース • 顔検出などのMLモデルの出力と、 撮影日時やお気に入りなどのメタデータとを考慮 9

  6. MIXI, Inc. 1秒動画生成処理の設計 マイクロサービス“transcoder”
 • 1秒動画の動画ファイル生成サービス 
 • Amazon EKS上で独自にスケーリング

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

  7. 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

  8. 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

  9. 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

  10. 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

  11. 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

  12. 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

  13. 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

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

    パイプラインの各ジョブ(家族)ごとに、 ひとつのステップのジョブが終わってから 次のステップのジョブが enqueueされる 当日分の家族のジョブはバッチ処理的に まとめてenqueueされる Sidekiq Batchの仕様上、 同じqueueのジョブは 全パイプライン共通で 積まれた順に処理される 22

  15. 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

  16. MIXI, Inc. • このときのtranscoder全体の処理順序: 「全家族のステップA」→「全家族のステップB」→…… と進む パイプライン先頭のWorkerのみ優先度最低にして解決 全ジョブの完了前に配信時間を迎えても それまでに生成完了した家族には配信できる (問題1を解決)

    ある時点におけるEKS Deploymentは 各ステップの処理をごちゃまぜに 実行しているので リソース割り当てが最適化しやすい (問題2を解決) ステップB以降のジョブがqueueにある限り 新しいステップAのジョブに着手しない 26

  17. MIXI, Inc. まとめ • 1秒動画の概要と生成処理の流れについて紹介 • Sidekiq Batchを用いた動画エンコードを説明 • インフラとSidekiq

    Batchの最適化事例を紹介 • Sidekiq Batch: ある程度のboilerplateがあり、パフォーマンス面にも注意が必要だが Sidekiqをすでに利用している場合には便利に使えるかも 28