スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

スタディサプリ動画基盤におけるFastlyの利用と陥った落とし穴

はじめに

こんにちは、スタディサプリのプロダクト基盤...その中でも特に動画基盤の開発を行っている @highwide です。

これは スタディサプリProduct Team Advent Calendar 2024 16日目の記事です。

今日は動画基盤チームで利用しているFastlyの構成や、我々がハマってしまった落とし穴について書きたいと思います。

これは自分で掘った穴にハマっている長男です

動画基盤におけるFastlyの使い方

スタディサプリでは、動画の配信はFastlyを通じてHLS(HTTP Live Streaming)*1によって行っています。 HLSでは、動画再生のために必要なファイルを列挙したマニフェストファイル(m3u8)と、動画コンテンツそのものであるセグメントファイルの組み合わせによって動画の視聴を実現します。

スタディサプリでは、学習コンテンツに紐づく動画のメタデータはアプリケーションのDBに保存しており、以下のような流れで動画視聴が行われています。

  1. 学習コンテンツを取得するHTTPリクエストに対して、動画のメタデータを返却する
  2. 動画のメタデータとして取得したm3u8ファイルのURLに対して動画プレイヤーがリクエストを行う
  3. m3u8ファイルに記載されたセグメントファイル(スタディサプリではm2ts)に対して動画プレイヤーが順次リクエストを行っていく

この流れにおいて、m3u8ファイルやm2tsファイルに対するリクエストがFastlyのCDNに対して行われます。

余談ですが、自分がチームにジョインしたばかりの頃、m2tsファイルの .ts という拡張子を見て、TypeScriptで動画配信をしているのだろうかと思ってしまった恥ずかしいエピソードがあります。

我々のFastlyの利用の仕方としては、CDNの他、一部リクエストを特定用途のためにEdge Computing基盤であるFastly Compute*2に流しています。CDNから見ると"Backend"として「動画ファイルの置き場所であるAmazon S3」と「一部リクエストについての処理を行うCompute」を持ち、CDNの設定ファイルであるVCLに書かれた条件に応じてリクエストを振り分けている、という構成です。

 

Fastlyを利用していての落とし穴

ここで我々が陥った落とし穴について紹介したいと思います。

先ほど、CDNが複数のBackendにリクエストを振り分けている話をしました。たとえば、VCL上で「特定のpathにマッチする場合はComputeにリクエストを流す」といったことができます。

sub vcl_recv {
  #FASTLY recv
  if (req.url.path !~ ".+\.m3u8.*") {
    set req.backend = F_backend_compute_service;
  }
}

では、このような「明示的に req.backend を指定する処理」を通らなかったリクエストはどうなるでしょうか? 答えは、「デフォルトBackend」にリクエストが流れることとなります。では、デフォルトBackendはどのように決まるでしょうか?

実は"先勝ち"...つまり、最も古いタイムスタンプのBackendが暗黙的にデフォルトとして扱われます。 Fastlyでは、GUIで定義できる設定情報と自分たちで定義したCustom VCLをマージして、最終的に利用されるVCLが生成されますが、「Show VCL」メニューからその生成されたVCLを見ると、vcl_recv サブルーチンにおいてデフォルトBackendが決定されている様子を確認することができます。

# default conditions
set req.backend = F_s3_ap_northeast_1_amazonaws_com;

ところで、Fastlyの設定は、スタディサプリの他のインフラリソース同様、Terraformで管理しています。TerraformにおけるFastlyのBackendの指定は以下のようになります。

resource "fastly_service_vcl" "our_service" {
  backend {
    name      = "our_s3"
    address = "s3-ap-northeast-1.amazonaws.com"

   # 省略
  }

  backend {
    name     = "our_compute_service"
    address = "compute.our.domain"

    # 省略
  }
}

このTerraformがapplyされる際、実は「必ずしも書いた順でFastly上にresourceが作成されるかは保証されない」というのが今回の運用における大きな落とし穴でした。 Terraformを利用してリソースの更新を行った結果、全く同じtimestampでbackendを作り直してしまい、結果として選択されたデフォルトBackendが入れ替わってしまったのです。

その結果、

  • 特定のリクエストのみComputeに流したい
  • それ以外の全てのリクエストはS3ã‚’Backendとしたい

を本来意図していたはずが、

  • 特定のリクエストがComputeに流れる
  • それ以外の全てのリクエストもComputeに流れてしまう(デフォルトBackendがS3でなくComputeになってしまっているため)

...ということが起こってしまいました。

「S3のbackendが使われない」というwarning。気がついたのはapplyされてからだった

実際に、Backendが入れ替わってしまったのは、Terraformのdiffとしては全然関係ない箇所の変更をapplyしたときだったので、原因の究明に時間がかかりました。(Fastlyのサポートの方にかなり助けていただきました)

また、暫定対応のためのロールバックにあたっても、問題となったTerraformを単にrevertするだけではデフォルトBackendを元に戻すVCLが生成されず、Fastlyのコンソール上からバージョンを戻さないと、意図したVCLに戻らないことも問題の対処をより難しくさせました。

この問題が起こって以降は

  • 全ての条件において明示的にBackendが指定されるようにVCLを修正
  • terraform apply によって即座にFastly上に変更が反映されるのではなく、Fastly上ではdraftのバージョンが作られるように変更
  • draftのバージョンをactivateするには最終的に生成されるVCLを確認してから行うという運用の変更

という対応を行いました。

Terraformがapplyされた際に、即座にそのバージョンをactivateするのではなくdraftのバージョンが作られるようにするには、 fastly_service_vcl resourceにおいて、 activate フィールドをfalseにすると実現することができます。*3

resource "fastly_service_vcl" "our_service" {
    activate = false

実は、「明示的なBackendの指定」というのはFastlyを利用するうえでのベストプラクティスとして知られていて、Fastly社の福田さんが書かれた記事でも「Backendを上書き時にはデフォルト設定が適用されないように注意」というドンピシャな言及があります。

qiita.com

また、メルカリ社のブログにおいても「バックエンド設定の意図しない上書きの発生」という言及があります。特にRequest Conditionを利用して、特定のBackendに対するリクエストを来ないようにするテクニックというのはとても参考になりました。

engineering.mercari.com

あらかじめ読んでいれば...という後悔もありますが、我々のこの記事も、これらのありがたい注意喚起に続くことができれば幸いです。

おわりに

スタディサプリProduct Team Advent Calendar 2024 16日目の記事として、スタディサプリ動画基盤チームでは、このようにトライアンドエラーを重ねながら、動画技術や配信技術についての知見を深め、改善を続けています、というご紹介でした。

明日は @ryomaejii さんの「New Joiner こそ Working Out Loud」です!たのしみ〜〜!