appbrew Tech Blog

appbrewのエンジニアチームの日々です

【APIサーバ改修不要バージョン】AWSで動画の自動圧縮 & 配信を行う方法

コスメのクチコミアプリ、LIPSを開発しているAppBrewのエンジニア@anoworlです。

最近はアプリのディスプレイ広告の立ち上げを行ったり、メンバーの@_ha1fさんにビシバシしごかれながらiOS開発をよちよち歩きでやっていました。継続してまだ見ぬ仲間も探しています(訳: 採用活動もしています)!

この記事では「APIサーバを改修せずにAWSのCloudFront & S3 & Lambda & MediaConvertを使ってフルマネージドで動画の自動圧縮 & 配信を行う方法」を紹介したいと思います。

f:id:appbrew-sota:20200702152613p:plain:w300
完成図。赤が動画アップロード時の自動圧縮の流れ。青が動画取得時の流れです。

目的: UX向上 & 費用削減

まずは今回の作業の目的を考えましょう。大きく二つあります。

  • ユーザの体験を改善し、回遊や滞在時間を増加する
    • 動画ロード待ちによる離脱を防ぎ、なおかつ体験を向上させることで、滞在時間を増やす
    • 「ギガが減る」を防ぎ、動画の閲覧を躊躇無く行えるようにして、主に月末の動画閲覧数を増やす
  • クラウド費用を削減する
    • データ転送料金に大きな影響を与える動画の通信量を削減する

f:id:appbrew-sota:20200626210452p:plain:w300
ギガが大量に減るイラスト

通信量に関して、リッチな動画だと1/14(例: 85MB → 6MB)になるものもありました*1

前提: 動画はS3にアップロードしている

この記事ではCGMのアプリを想定します。

  • ユーザが動画を投稿できる
  • その際、動画はアプリを通じてS3にアップロードされている
  • ユーザの投稿を閲覧する時、APIサーバからアプリに圧縮されていない動画のURLが返されている

今回のゴールは、このURLを叩いたときに圧縮された動画が返ってくるようにすることです。

方針: アプリ・APIサーバに一切改修を入れない

まず一つ目の方針として「クラウド費用を削減する」ために、配信するトラフィックは全てCloudFrontを通すことにします。

二つ目の方針として、システムを疎結合にし工数を削減するために、APIサーバには一切改修を入れずに動画の圧縮とその配信を行うことにします。

課題: 動画圧縮中のアクセスのさばき方

動画圧縮をマネージドで行うのは、私自身かなり前にElastic Transcoderでやったことがありました。

f:id:appbrew-sota:20200626214556p:plain:w200
縮めるぞ!

では今回、なぜこの記事を書いたのでしょうか?何が新規性なのでしょうか?

今回やらなければならないことと、その実現方法を一つずつ見ていきましょう。

  • 動画圧縮 → MediaConvertでいける
  • 動画アップロード検知 → S3のイベントでいける
  • 動画圧縮ジョブ作成 → Lambdaでいける
  • 動画圧縮中はオリジナル動画にフォールバックする → ?

ここで難しいのは動画圧縮中に行われた動画へのアクセスをどう捌くかです。

今回の方針は「アプリ・APIサーバに一切改修を入れない」ことなので、APIサーバで動画圧縮ジョブを作成することはもちろん、動画圧縮がなされたかどうかという情報は一切APIサーバに通知されません

f:id:appbrew-sota:20200626214015p:plain:w300
アプリ・APIサーバに一切改修を入れない

ですので、特定の動画が圧縮済みかどうかをDBに記録しておくことも、圧縮中はオリジナル動画のURLを・圧縮後は圧縮済み動画のURLを返すようにAPIのロジックを書き換えることもできません。

これが今回の課題です。

Tips: Elastic Transcoder vs. MediaConvert

動画圧縮をしようと思った時に最初に思いついたのがElastic Transcoderです。なぜなら、私が以前同じようなタスクに取り組んだ時、まだMediaConvertは無かったから…。

経験があるElastic Transcoderで済ませることも出来たのですが、そうしませんでした。それは、

MediaConvertの方が12倍料金が安い(こともある)

例えばH.264のSD解像度(720p未満)で30fps以下の場合、MediaConvertは0.0085USD/分、Elastic Transcoderは0.017USD分です(どちらも東京リージョン、2020/06/26時点)。2倍の差です。

かつ重要なこととして、MediaConvertは最低10秒での課金、Elastic Transcoderは最低1分での課金です。弊社のように短時間の動画も多く投稿されるサービスだと、ここで大きな差が出ます。Elastic Transcoderは仮に5秒動画でも1分間分課金され、MediaConvertでは10秒間分だけ課金されます。6倍の差です。

なので10秒以下の動画であれば料金は12倍の差にもなります。なおかつこれはエッジケースではなく、プラットフォームにおいては割とあり得るシナリオです。お分かり頂けたでしょうか…?

f:id:appbrew-sota:20200626214847p:plain:w250
その差12倍…

MediaConvertの方が容量を減らせる

初期設定はElastic Transcoderより面倒なのですが、MediaConvertの方が様々な設定に対応しています。特にQVBR(品質が定義された可変ビットレートモード)はお手軽で容量が減ります。

また今回は使いませんでしたがH.265やAV1(!)などの圧縮形式にも対応しています(ただし変換料金は高くなる)。

これらのことから、現在AWSで出来合いの動画ファイルの圧縮をするとしたら、MediaConvert一択なのかなと思っています(違ったら教えて下さい :pray:)。

解決編: 動画圧縮中に元動画を同じパスで返すには

検討したが駄目だった方法、最終的に採用した方法をそれぞれ紹介します*2

不採用: S3にある動画を上書きする

圧縮動画の出力先を元動画にしてしまうという方法を検討してみましょう。

今回、同一URLのファイルの内容を変えたいことから、一見良いようにも思いますが…?

デメリット: ミスるとLambdaがループで実行される

AWS Lambda を Amazon S3 に使用する」に書いてあるように、S3のイベントトリガー元のバケットと出力先を同じにするのは、ループしクラウド破産する危険性があります。

もちろんprefixで区別すれば良いのですが、今回行いたいのはファイルの上書きです。パスが同じなのでLambda内で新規作成か上書きかを判断する必要があります。

今回はAWSの推奨通りオリジナル動画と圧縮後動画のバケットは別にしました。

採用: CloudFrontのオリジンフェイルオーバーを使う

こちらが今回の肝です。解決策としてCloudFrontのオリジンフェイルオーバーを使います。

これは複数のオリジンをグルーピングし、優先順位を付けることで、優先順位の高いものから参照され、特定のステータスコードが返ってきた場合は優先順位の低いものにフェイルオーバー出来る機能です。

以下の様に二つのオリジンを指定することで、まだ動画の圧縮が終わっていない場合は、1で404が返され2にフェイルオーバーし、オリジナル動画がCloudFrontから返されます。

  1. 圧縮動画が入ったバケット
  2. オリジナル動画が入ったバケット

f:id:appbrew-sota:20200626220601p:plain
Origin Groupの設定

こうして、オリジナル動画と圧縮動画を別のバケットに入れながらも、同一URLから圧縮後の動画を取得できました。

なおかつ、MediaConvertの動画出力をトリガーにしてCloudFrontのInvalidate*3をかけることで、同じURLで圧縮後の動画をCDNを通して配信することが可能です。

おわりに

今回、予想以上に記事が長くなりそうだったので、肝の部分以外は省略しました。その部分を期待して記事を読まれた方がいたらすみません。

あとがき: "AWS"の機能に習熟することは本質的か?

AWSは次々に新機能が実装され、追うのは大変ですがどんどん便利になっています。

私は特定クラウドの機能を使ってピタゴラしている時、組み合わせるものが特定のクラウドに依存していることから、「本質的*4な技術力を向上できていないのでは」と不安にさいなまれることがありました。

f:id:appbrew-sota:20200626215304p:plain:w200
技術者としての成長に気を揉む人のイラスト

ただ最近では、機能を組み合わせて運用コストがかからずコスパ良いアーキテクチャを組めるスキルは応用が効くし、技術者としての能力向上にも繋がると感じてきました。

なぜなら、クラウドの機能はデザインパターンの蓄積と技術的な制約によって、どのベンダーでも似たものになり、またそれらのパターンをクラウド以外でも活かせるからです。

もちろん応用力は必要ですが、それが出来るのであれば特定クラウドの機能に精通することは陳腐化しやすいスキルではなく、技術者として長期的な価値をもたらす技術力の向上だと言えそうです。

今回の記事から何を読み取って頂けたでしょうか。便利な機能があるんだなということに加えて、仕組みを作り機能を使い回す方法についても考えを深めるキッカケになれば、書き手として嬉しいです。

We are Hiring!

弊社AppBrewでは現在、500万DL突破のコスメクチコミアプリ「LIPS」をはじめとした「ユーザーが熱狂するプロダクト」を、「再現性をもって開発すること」に携わるエンジニアを積極採用中です。

一緒に目的を見据えたサービス基盤を作っていきたいエンジニアの方はぜひ一度ご応募ください!お待ちしております。

*1:無論、圧縮率上げてfps下げてサイズ小さくすればいくらでも小さくなるやろというのはありますが、スマホでのUXに大きな毀損が無いよう社内で検証、調整した上での数値です。

*2:Lambda@Edgeを使う方法もありますが、実行時間と料金がかかるため今回は採用しませんでした。

*3:ただInvalidateは高い!今回TTLの制御をバケット毎に行えばこの点クリアできるのではと踏んだのですが、TTLの制御はオリジングループにかけることになるので、断念しました…。

*4:"本質"という言葉を無闇に使うとマサカリが飛び定義を問われるので注意しましょう(ブーメラン)。