なぜスクメロのアセットダウンロードは遅かったか(あるいは、なぜスクストは速いのか)

この記事はスクールガールストライカーズ2 Advent Calenderの9日目です。

f:id:appalerm:20201209093210p:plain

スクールガールストライカーズ トゥインクルメロディーズ(公式サイトが消滅したのでどこにもリンクをはれない)、以下スクメロがオフラインアプリケーションに移行して早いもので2年が過ぎました。このタイトルには思うところがあって、終了を期に書いた感想記事でも結構な長さで「とにかくアセット(リソース)ダウンロードが遅かったんだよなあ」と書いています。

これ以降ちょっと興味が沸き、スクメロのサービスが終わった後も他タイトルを含めてファイルダウンロードへの向き合い方についてたまに調べるなどしていました。今回はそんな幾つかの事例についてお話しできればと思います。

目次

「往復の待ち時間」の話

前提として、いわゆるゲームアプリでのファイルダウンロードでは、今のところはまだ多くの環境でHTTP(HTTPS)/1.1というワールドワイドウェブで長らく使われてきた方式を採用しています。

純化のためHTTPSでなくHTTPで話を進めるとして……。

f:id:appalerm:20201209092245p:plain

  1. 「これから通信を始めたいです」とサーバーへ伺いを立て、了解を得る
  2. 欲しいファイルのパスをサーバーに伝えると、ファイルが返ってくる
  3. 「通信を閉じます」とサーバーに通知し、その到達確認を得る

1番と3番にあたる前準備と後処理(SYN, ACK, FINという小さなTCPパケットの要求応答)を含めれば、3回の往復をもってファイルのダウンロードは完了します*1

接続を切らずに使いまわすKeep-Alive仕様によって1番3番の回数は大幅に省略できますが、とはいえ1個のファイルダウンロードのためには要求・応答のペアが1回以上存在するという原則はHTTP/1.1を使う限り変わりません。

通信が往復するということは、その分だけ待ち時間があることを意味します。「このファイルを寄越せ」という要求がサーバーに届くまでと、それへの応答がサーバーからクライアントへ届き始めるまでに、距離や設備などの理由で時間がかかるわけです(遅延、レイテンシと呼ばれます)。

Wi-Fiと仮定 モバイル通信と仮定
RTT(=信号が往復するために必要な時間) 10ミリ秒 100ミリ秒
遅延時間の合計(ファイル100個のダウンロード時) 1秒 10秒
遅延時間の合計(ファイル1000個のダウンロード時) 10秒 100秒
遅延時間の合計(ファイル10000個のダウンロード時) 100秒 1000秒

仮にファイル1個のダウンロードに対し通信の往復が1回あったとして、当たり前ですが10倍の遅延があればムダな時間は10倍に膨れ上がりますし、ファイル数が10倍あればやはりムダな時間が10倍になります。

つまりダウンロードにかかる時間とは、帯域や遅延など回線の都合と並んで、往復回数と実際に待った時間がキーであると言えます。とりわけ「たくさんのファイルを」「ひとつずつ順番に」ダウンロードする場合には、遅延が大きくなって立ちはだかってくるのです。

ダウンロードの工夫

世のアプリゲームでは数千数万のアセットを扱うことも珍しくありません。これらがどんな対策で快適さをねらっているのか? というのを、知っている範囲で幾つか紹介します。

並列処理

ゲームだけでなく多くのアプリケーションで行われているであろう方法として、複数のファイルを同時にダウンロードするというものがあります。

f:id:appalerm:20201209012426p:plain

たとえばこれはWebブラウザでF12を押すと出るやつ(デベロッパーツール)の画面ですが、ページを読み込ませてみるとタイムライン上でファイルのダウンロードが重なっていることが分かります。

ダウンロード処理が別々に動き、待ち時間の間に別の通信を滑り込ませることで、通信経路のヒマな時間を減らしていけるわけです。経路が物理的に分かれるわけではないので限界はありますし、並列数を増やしすぎるとかえって逆効果になることもありますが、例えば細かいファイルが多い場合に4~6程度の並列数があれば、遅延時間をじゅうぶんに削れることが期待できるかと思います(現代のWebブラウザの並列数は6が多いようです)。

ファイルの結合(既存のアーカイブ形式)

「そもそもファイル1個あたりに通信の往復が起こるのをやめたい」という考えから、あらかじめファイルを結合しておくことで往復の回数を減らすトリックがあります。10000個のファイルでも直列に繋げてしまえば大きな単一のファイルとして素早く落とせるという発想です。

たとえばUnityではAssetBundleというアーカイブ形式がサポートされており、ゲームで使うファイルをシーンやキャラクターなど適当な単位でひとまとめにしておくことで、総ファイル数を抑えたり、開発の見通しを良くすることが可能です。

この「細かいファイルをたくさん送りつけるよりも一つのファイルにまとめてしまう」という考えは合理的で、ゲーム以外にも多くのところで使われています。PC用アプリケーションのインストーラが実行ファイルや圧縮ファイルひとつで配られているのもファイルの結合という振る舞いですし、言ってみれば我われがファイルを送る時にzipなどにまとめるのも、ファイルひとつひとつに意識を割くことを避けるという意味では高速化の一手段です。

ただ程度によって善し悪しはあり、ゲームアセットの場合は部分的な更新でもファイル全体をダウンロードせねばならないペナルティも起こるので、分割単位は管理の楽さや実効性能、更新頻度などの綱引きでバランスを取ることになるはずです。一桁MBくらいで「ちぎり」始めるのが現実的でしょうか。

ファイルの結合(禍つヴァールハイトの事例)

同じファイル結合ですが、独自の形式でダウンロードの処理を工夫している例もあります。

www.slideshare.net

Klab社では「禍つヴァールハイト」で採用された方法のスライドを公開してくださっています。

f:id:appalerm:20201209012952j:plain
【Unite Tokyo 2019】「禍つヴァールハイト」最大100人同時プレイ!モバイルオンラインゲームの実装テクニック p.94

並列数の調整に加えて更にHTTP/1.1 Range Requestsを採用しており、これはファイルの範囲……「何バイト目から何バイトの間」をサーバーに要求し、その範囲の部分だけをダウンロードするという仕様です。

f:id:appalerm:20201209013033j:plain
【Unite Tokyo 2019】「禍つヴァールハイト」最大100人同時プレイ!モバイルオンラインゲームの実装テクニック p.96

これを使い、結合済みの大きな一個の無圧縮tarアーカイブから必要な範囲を確定し、少ない往復回数で多くのファイルをダウンロードできるという。

更に、ダウンロード速度とは直接関係ありませんが、ヴァールハイトではダウンロードしてきたファイルを個別にちぎってストレージに格納し、アプリからはそれぞれ細分化されたファイルとして扱えるようですね。

さらに、ファイルの結合(スクストの事例)

www.jp.square-enix.com

リリース順が前後しますが、このRangeリクエストはスクメロのスピンオフ元の「スクスト」でも非常に近い方法を採用していることが分かっています(独自調べ)。

f:id:appalerm:20201209013546p:plain
スクールガールストライカーズの内製クライアントエンジン p.76

スクストの場合は結合済みファイルをちぎらずにストレージに格納し*2、IDで抽象化しているという違いがあるようです。上記は運営初期のスライドですが、ファイルをバラバラに格納しないかわりに、スクリプトコンパイル時かアセット呼び出しの際にファイル名をアーカイブ名-ファイルIDのペアに変換する仕組みのように思われます*3

HTTP/2などのプロトコルに乗り換える(リーグ・オブ・ワンダーランドの事例)

一方でセガゲームス社(当時)の「リーグ・オブ・ワンダーランド」では、比較的新しい通信方式であるHTTP/2の導入を図ったという発表が行われていました。

learning.unity3d.jp

HTTP/2は現在広く使われているHTTP/1.1の欠点、たとえば先述した通信の往復回数と待ち時間の問題をまさに解決できるプロトコルです。

f:id:appalerm:20201209014457j:plain
大量のアセットも怖くない!~HTTP/2による高速な通信の実装例~ – Unity Learning Materials p.28

上記のスライドではライブラリの少なさやサーバーの対応など課題はあるとしつつも、往復の影響が極めて小さく、帯域をフルに使えるという強力さが説明されています。

ファイルをまとめてダウンロードできるという観点でいえば、先の禍つヴァールハイトやスクストのようにファイルを結合するアプローチに近いものの、独自形式の煩雑さから逃れられるのは魅力的ですね。

スクメロはどうだったのか、仮説を考える

本題に戻ってきました。

遅延要因

ここまで見てきたアセットダウンロードとの戦いによって、スクメロが何をしたか、あるいは何をできなかったかを察せるような気がしませんか。先のKlab社のスライドに照らして、私の推測を書き出します。

f:id:appalerm:20201209015715j:plain
【Unite Tokyo 2019】「禍つヴァールハイト」最大100人同時プレイ!モバイルオンラインゲームの実装テクニック p.87

スクメロもファイルの分割数が多く、また、結合などのトリックが積極的に使われた形跡はありません。これはAndroid版のファイルシステムを覗けばrootを取らなくても分かり、全体では2万ファイル以上あったことが確認できています(サーバー上での結合もないことはパケットキャプチャから確認済みです)。

f:id:appalerm:20201209024357j:plain
【Unite Tokyo 2019】「禍つヴァールハイト」最大100人同時プレイ!モバイルオンラインゲームの実装テクニック p.92

ここからはどうしても推測になりますが、ダウンロードの並列化をしていないくらいのことでもないと説明のつかない遅さです。できればこれもパケットキャプチャのログを検討したいのですが、ログ取った端末の電源が入らなくなってしまった……。

f:id:appalerm:20201209015916j:plain
【Unite Tokyo 2019】「禍つヴァールハイト」最大100人同時プレイ!モバイルオンラインゲームの実装テクニック p.90

ダウンロードの間隔がメインスレッド、つまり画面の描画待ちに依存する……というのも、まさかあるのでしょうか。スクメロの使っているBestHTTPライブラリはメインスレッド非依存らしいのですが、メインスレッドを待つようにも書けるのならば可能性はある。

でなければ残りは、どこかにダウンロード処理の工夫を台無しにしてしまうようなボトルネックが挟まっていたというものしか考えられません。例えばデータの後処理……ファイルそのものの保存処理や、「ファイルを持っている」ことを管理情報(SQLite等)に書き込む処理など……が「メインスレッド待ち」に相当する制約を持っていて、ダウンロードと同期してしまっているような作りであれば、似たような待ち時間は発生し得ます。

結論

以上、素直に作るとハマってしまいそうなところとして、KLabのスライドで挙がっていたこの3つの確度が高いと今は考えています。

  • ファイルが多く、通信の往復回数が多い(確認済)
  • ダウンロードの並列化が行われていない(仮説)
  • ダウンロードかそれに付随する処理が、メインスレッドにブロックされている(仮説)

返す返すも、本当に余程のことがなければここまでの遅さにはならないと思うし、実際のところ何があったんでしょうね……。

まとめ

おさらいです。

  • 通信にはレイテンシ(到達時間)があり、通信回数(≒ファイル数)を増やせば増やすほどレイテンシの影響は大きくなる
  • 通信の並列化やファイル結合、通信方式自体の乗り換え(HTTP/2)などの方法でレイテンシの影響を抑えられる
  • スクメロは大量のファイルを結合をせずにダウンロードしていたし、並列化もしていなかった可能性がある

なので「スクストはなぜ速いのか」のほうの答えは、一因として「ファイルを積極的にパックして通信の回数を減らしている」ということになります。

「勝ちに不思議の勝ちあり。負けに不思議の負けなし」という格言に従えば、私はスクメロの早期のサービス終了の理由としてアセットダウンロードの遅さは十分に挙げられるレベルだったと思いますが、とはいえどんなタイトルも勝てば官軍、売れれば正義です。欠点を抱えた上でも初動で爆発的なヒットさえしていれば改修されていたかもしれない……とも、思います。

開発者にしてみれば上がらんかったパフォーマンスなど百も承知で、それでもリリースせざるを得なかったのでしょうし、怠慢だとか技術力が低いとかいうつもりはありません。起こったことには致し方ない理由があるのでしょう。今回はあくまでたかがデータダウンロードに見えても色々な工夫や落とし穴があるね(ここに書いた以外にもいっぱいあると思います)という主旨でした。

正確なボトルネックや、直面したであろう「致し方ない理由」については引き続きリバースエンジニアリング名人の名推理や関係者のタレコミをお待ちしています。秘密厳守。

*1:実際にはデータ受信中にもウィンドウサイズ毎にACKが飛び、もっと頻繁な到達確認が行われています。

*2:当初は単一のファイルだったようだが、現在は数十MB単位のアーカイブが複数並んでいると思われる

*3:深堀りしていないですが、ヘッダとインデックス、ダウンロード済み範囲の関係の整理はかなり複雑になりそうな印象です。差分ダウンロード実装当初、一部環境でリソースデータ破損に至っていたのはその辺が原因じゃないかと思う。