ダウンロード時間を大幅減!
~大量のアセットをさばく高速な実装と運用事例の共有~
© SEGA
ゲームコンテンツ&サービス事業本部 技術本部 開発技術部
竹原 涼
自己紹介
【登壇歴】
CEDEC 2020 「技術同人作家になろう ~働き方改革時代におけるエン
ジニアのレベルアップの一例~」
Unite Tokyo 2019 「大量のアセットも怖くない!~HTTP/2による高
速な通信の実装例~」
CEDEC 2018、CEDEC 2016、CEDEC 2015 プロダクション関連のラ
ウンドテーブル
GDC2016 報告会 「GDC16にみる自動化技術とテストのトレンド」
祝・HTTP/3 RFC発行(間近)!
© SEGA
ゲームコンテンツ&サービス事業本部 技術本部 開発技術部
山田 英伸
自己紹介
【登壇歴】
CEDEC 2020
「技術同人作家になろう
~働き方改革時代におけるエンジニアのレベルアップの一例~」
Unite Tokyo 2019
「大量のアセットも怖くない
~HTTP/2による高速な通信の実装例~」
© SEGA
• 本講演内容の撮影、SNS投稿は OK
• 後日、CEDIL で資料を公開
• 講演中の質疑が可能です。
コメントに書き込んでください
• 資料に記載されている会社名、システム名、製品名、サービス名は各社の登録商標または
商標です
• Android ロボットは、Google が作成および提供している作品から複製または変更したもの
であり、
クリエイティブ・コモンズ表示 3.0 ライセンスに記載された条件に従って使用しています
© SEGA
• CDN に配置したアセットを端末にダウンロード
• HTTP/1.1 と HTTP/2 の差に注目
効果を先にお見せします
© SEGA
• HTTP/2 なら接続数が少なくても、
HTTP/1.1 より圧倒的に早い (待ち時間短縮)
HTTP/2による効果
プロトコル 速度 完了までの時間 備考
HTTP/2 約 280 Mbps 約 3.6 s 1 Connection
HTTP/1.1 約 110 Mbps 約 9.2 s 6 Connection
HTTP/1.1 約 59 Mbps 約 17.2 s 3 Connection
諸条件
Pixel3 (Android10) 使用
AWS CloudFront 使用(国内リージョン)
総数 2000 ファイル / 1ファイル64KB
※ 300-340Mbps が出る回線にて Wi-Fi 接続状態
© SEGA
• ゲーム開発者向け HTTP/2 仕様の説明
• HTTP/2 を使うための実装について
• HTTP/2 導入時の勘所
• HTTP/2 トラブル集
• HTTP/2 の採用によるインターネット全体への波及効果
• まとめ
本講演の流れ
© SEGA
ゲーム開発者のための HTTP/2
© SEGA
• HTTP/2 2015年 標準化完了&公開
– HTTP/1.1 を基本的に継承
• 現在の普及率 約45.6% (w3techs.com 2021/06時点)
ゲーム開発者のための HTTP/2
© SEGA
• 多重化
• HPACK
• バイナリフレーム
(ゲームと関連性の深いものに限定)
HTTP/2 の仕様
© SEGA
HTTP/2 多重化
© SEGA
• 1つのコネクションに複数の HTTP リクエスト
多重化 – HTTP/2 の仕様
GET
GET
GET
APP SRV
Connection
ストリーム
© SEGA
• HTTP の Head of Line Blocking:HoLB (HTTP/1.1)
– 何らかの要因で先行リクエスト処理に時間が掛かると
後続のリクエスト処理が遅れてしまう
HTTP/1.1の課題 - 多重化 – HTTP/2 の仕様
1.png
2.png
3.png
この分の遅れが後続に響く
1.png
© SEGA
• HTTP/2 は HTTP の HoLB を回避
– 複数ストリームの多重化により
後続のリクエストを並行に処理可能
多重化でHoLB回避 - 多重化 – HTTP/2 の仕様
1.png この分の遅れは
他のリクエストに影響しない
1.png
2.png
3.png
© SEGA
• HTTP/1.1 と HTTP/2 通信状態の比較
帯域の利用 - 多重化 – HTTP/2 の仕様
HTTP/1.1 HTTP/2
時間
使用可能な
回線帯域
時間
HTTP/1.1 は Keep Alive 有効を想定.
但しファイルサイズが小さいと広帯域を活用できない.
© SEGA
• HTTP/2 は使える帯域を有効活用
帯域の利用 - 多重化 – HTTP/2 の仕様
HTTP/1.1 HTTP/2
時間
使用可能な
回線帯域
時間
帯域を活用できていない 多重化で帯域をしっかり活用
© SEGA
 細かいアセットダウンロードに有効
 効率的に回線帯域を使用
 クライアント・サーバ間通信
 RTT による待ち時間削減
(モバイル回線 など)
ゲームとの関連 – 多重化 – HTTP/2の仕様
RTT
RTT
APP SRV
SRV
APP
1RTT 分で複数リクエスト
RTT
HTTP/1.1
HTTP/2
© SEGA
HTTP/2 HPACK
© SEGA
• HTTP ヘッダを圧縮する仕組み (RFC 7541)
• 技術要素
 Huffman coding
 Indexing Table
Static Table
Dynamic Table
HPACK – HTTP/2 の仕様
© SEGA
• 出現頻度の高い文字に短いビット列を割当てて圧縮
Huffman coding - HPACK – HTTP/2 の仕様
HTTP/1.1 HTTP/2
10 BYTES
から
8 BYTES へ
© SEGA
• 出現頻度の高い文字に短いビット列を割当てて圧縮
Huffman coding - HPACK – HTTP/2 の仕様
※ < > 内はパディング. オクテット境界に揃える
© SEGA
• 出現頻度の高い文字に短いビット列を割当てて圧縮
Huffman coding - HPACK – HTTP/2 の仕様
HTTP/1.1 HTTP/2
59 BYTES 48 BYTES
※ 参考値です。実際に送信されるバイト数とは異なります
約 81.4% に削減
HTTP/2
63 BYTES
© SEGA
• インデックス値を用いた圧縮機構
– Static Table
– Dynamic Table
Indexing Table - HPACK – HTTP/2 の仕様
© SEGA
• 事前定義されているテーブルのインデックス値を使う
Static Table - Indexing Table - HPACK – HTTP/2 の仕様
HTTP/2
※ 参考値です。実際に送信されるバイト数とは異なります
31 BYTES
63 BYTES
約 49.2% に削減
© SEGA
• 通信に使用したデータをテーブルに登録し、
テーブルのインデックス値を使う
Dynamic Table - Indexing Table - HPACK – HTTP/2 の仕様
※ 参考値です。実際に送信されるバイト数とは異なります
5 BYTES
約 8.4% に削減
テーブルに登録
同内容でリクエスト時
© SEGA
• フレーム: ストリーム上を流れる最小単位
• バイナリデータ送受でテキストより効率が良い
バイナリフレーム – HTTP/2の仕様
フレームヘッダは 9Bytes
フレームの構造
© SEGA
• HTTP/1.1 の場合
• テキストベースのプロトコル
• 解釈の処理で負荷が高め
ゲームとの関連 – HPACK/バイナリフレーム
HTTP/1.1 200 OK
Date: Wed, 30 Jun 2021 02:19:48 GMT
Content-Type:image/jpeg
Transfer-Encoding: chunked
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
456
Chunk1のデータ ....
0
: の後に空白を許可
ヘッダ領域との区切り
ヘッダ名は
大文字小文字区別なし
Chunk終了の CRLF
データサイズはまだ不明
© SEGA
• HPACK, バイナリフレーム
 API 通信のデータ削減・処理負荷軽減
ゲームとの関連 – HPACK/バイナリフレーム
HTTP/1.1 200 OK
Date: Wed, 30 Jun 2021 02:19:48 GMT
Content-Type:image/jpeg
Transfer-Encoding: chunked
Trailer: Expires
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
456
Chunk1のデータ ....
0
データサイズは先頭
HPACKで圧縮
データサイズは先頭
© SEGA
• TCP, RUDP を使えば?
– HTTP/2をトランスポートレイヤーに持つ
gRPC や WebSocket
– サーバ設定や導入コストの圧縮が見込める
– 対応したサーバレスコンテナの登場
• Cloud Run (Google)
閑話 : API 通信に HTTP を用いるメリット
© SEGA
HTTP/2 を使う実装
Client
Server
クライアント
サーバ
© SEGA
• 自力で全て実装は現実的ではない
• ゲームエンジンが内蔵しているもの
&オープンソースの利用
HTTP/2 を使う実装 Client
© SEGA
• C/C++による
自社製ゲームエンジンやミドルウェアを利用
 C/C++製の OSS は選択肢が豊富
 中でも libcurl, nghttp2 がお勧め
ケース1 - HTTP/2 を使う実装 Client
© SEGA
• Unity を使用している場合
 Unityが採用している C#ランタイム/クラスライブラ
リでは HTTP/2 が利用できない
 前述のC/C++製OSSを用いた
ネイティブプラグインを実装
ケース2 - HTTP/2 を使う実装 Client
© SEGA
• Unreal Engine4 を使用している場合
 Windows で WinHttp 使用時のみ HTTP/2 対応
 C/C++製OSSを用いて対処を考える
ケース3 - HTTP/2 を使う実装 Client
© SEGA
• C#による
自社ゲームエンジンやミドルウェアを利用
 C# 標準 API (HttpClient) が HTTP/2 対応
• .NET Framework 4.6
• .NET Core は注意. .NET Core 3.0 で対応(*1)
 但し、パフォーマンスに懸念
ケース4 - HTTP/2 を使う実装 Client
*1 : https://docs.microsoft.com/ja-jp/dotnet/core/whats-new/dotnet-core-3-0#http2-support
© SEGA
• CDN の選定
• サーバソフトウェアの選定
HTTP/2 を使う実装 Server
© SEGA
• 主要なCDNは全て HTTP/2 対応済み!
 CloudFront (Amazon)
 Cloud CDN (Google)
 Azure CDN (Microsoft Azure)
 Fastly (Fastly)
 Akamai CDN (Akamai)
 CloudFlare (CloudFlare)
CDNの選定 - HTTP/2 を使う実装 Server
© SEGA
• サーバソフトウェアの選定
– 主要なサーバプログラムは全て HTTP/2 対応済み!
• IIS, nginx, apache など
– 前段にあるロードバランサに注意 (ADC含む)
• HTTP/2を終端する可能性
ソフトウェア選定 - HTTP/2 を使う実装 Server
© SEGA
HTTP/2 導入時の勘所
© SEGA
• ダウンロードするデータサイズや順序によっては
期待した結果が出ない
 ファイルサイズが大きいものが支配的な場合
 ファイルサイズでソートされている場合
パフォーマンスが出ない① Problem!
© SEGA
• プロジェクトに有効かをまず判断!
 ファイルサイズのヒストグラム
• ダウンロードリストの作り方に工夫が必要
パフォーマンスが出ない① Solution!
私たちが遭遇したので、
後で詳しく説明します
© SEGA
• コネクション数に応じて、
逐次的にリクエスト発行をしている場合
– HTTP/1.1 を意識した実装の場合、
3~6程度でリクエストを送信となることが多い
パフォーマンスが出ない② Problem!
© SEGA
• リクエストを束で受け取る設計
– 多くのリクエストを同時に発行する
– ライブラリを作るときに意識する
パフォーマンスが出ない② Solution!
© SEGA
• リクエスト数が数万になることも!
– ファイル保存時、ファイル I/O のコスト
– メモリに展開時、メモリ使用量が爆発
大量リクエストを捌くために Problem!
© SEGA
 ファイル保存時、分散書込みなど、負荷軽減策が必要
 メモリに展開時、一旦ファイルに保存
 実装によっては、思わぬボトルネックを生むので注意
大量リクエストを捌くために Solution!
私たちが遭遇したので、
後で詳しく説明します
© SEGA
• HTTP/1.1 時
「コネクションエラー=通信エラー」だったが、
考え方を改める必要あり
エラーへの考え方 Problem!
© SEGA
• HTTP/2 ストリーム単位でのエラーは
復帰できる可能性がある
 即、通信エラーにしてはいけない
エラーへの考え方をアップデート Solution!
APP SRV
Connection
正常なストリームは通信中
© SEGA
• 1 Connection内に複数ストリームで通信
– 高パケロス環境では
複数Connection HTTP/1.1 より速度が劣ることも
パケットロス率に注意 Problem!
APP SRV
他のストリームも
再送の影響を受ける
パケットロスで
通信エラーが発生
© SEGA
• HTTP/2 を悪環境で利用時
 一定のパケットロス環境では
HTTP/1.1 にフォールバックする対策
 フォールバックする場合の割合は、
各プロジェクトで計測して判断が必要
パケットロス率に注意 Solution!
© SEGA
• 本番・テストのサーバは対応しているか?
• サーバ側の HTTP/2 機能を有効にし忘れていないか?
• このような事態が発生すると、
HTTP/2 使っても速くない、と誤解される恐れがある
サーバの対応を確認 Problem!
© SEGA
• HTTP/2 の誤った認識をさせないため
 HTTP/2 通信できているかの
チェックツールを用意しておくのも良い
サーバの対応を確認 Solution!
© SEGA
• 優先度(プライオリティ)の仕様がある
• 各ストリームに重みづけや依存関係を設定し、
先に欲しいリソースをサーバに通知する仕組み
HTTP/2 の優先度設定
© SEGA
• ダウンロードの順序制御に
HTTP/2 の優先度制御を使うことはできない
– CDN 実装の問題で事実上使えない
– HTTP として優先度オプションの協議
– 現状 RFC 策定されていない
優先度制御の課題 Problem!
© SEGA
• リクエスト順を制御したい場合には、
自前で実装するのが無難
優先度制御 Solution!
© SEGA
• TLS 1.2 以上が必要 (サーバ)
– 仕様上 HTTP/2では TLS1.2
• AWS ロードバランサ
– ALB の https 通信で HTTP/2 が使用可能
その他
© SEGA
HTTP/2 トラブル集
C
S
クライアント事例
サーバ事例
© SEGA
ダウンロード速度が出ない①
C
S
クライアント事例
サーバ事例
© SEGA
• 通信速度が一定を超えるとそれ以上のダウン
ロード速度にならない問題が発生
ダウンロード速度が出ない① Problem!
C
© SEGA
• 受信バッファが満タンになった際に待ちが発生
 送受信処理をメインループに同期させていた為
ダウンロード速度が出ない① Cause
C
HTTP/2ライブラリ
受信処理
ゲームメインループ
同期 同期
受信バッファ満タン
パケット
受け取れないので待つ
© SEGA
• 受信を別スレッドに分離し、取りこぼしを抑制
ダウンロード速度が出ない① Solution!
C
HTTP/2ライブラリ
受信処理
ゲームメインループ
取得
パケット
受信バッファ
保存
© SEGA
ダウンロード速度が出ない②
C
S
クライアント事例
サーバ事例
© SEGA
• 前項の「通信速度が一定を超えるとそれ以上の
ダウンロード以上にならない問題」が特定の端
末でのみ再発
ダウンロード速度が出ない② Problem!
C
© SEGA
• 下位グレードの端末で以下の逆転現象が発生
 通信速度 > ファイルI/O速度
• 特にファイル I/O の回数がボトルネックに
ダウンロード速度が出ない② C Cause
© SEGA
• メモリマネージャの実装により解消
 一定のサイズまではメモリに書き溜めておく
 上記サイズを上回った段階でファイルに書き込む
• 一定のサイズの設定値には注意が必要
 大き過ぎるとこれまたボトルネックに
ダウンロード速度が出ない② Solution!
C
© SEGA
ダウンロード速度が出ない③
C
S
クライアント事例
サーバ事例
© SEGA
• 特定のタイトルでダウンロード速度が安定しな
い問題が発生
ダウンロード速度が出ない③ Problem!
C S
© SEGA
• 帯域を使い切ることができないケースがある
 リスト上に小さいファイルが固まっている時
ダウンロード速度が出ない③ C Cause
使用可能な回線帯域
ここが使えていない!
© SEGA
• ダウンロードリストの作り方を工夫する
 ランダムに入れ替えるだけでも効果あり
ダウンロード速度が出ない③
ここの部分を使えるようになった
Solution!
C
© SEGA
• ソートしてもダメなケース
ダウンロード速度が出ない③ C Cause
使用可能な回線帯域
(高速)
帯域次第で使い切れないケースが発生
© SEGA
• ファイルの同時ダウンロード数はサーバ固有
 HTTP/2的にはストリーム最大数と表現
 RFC的にはMAX_CONCURRENT_STREAMS
• クライアントとサーバで別
 お互い自分の受付可能なストリーム最大数を送り合う
ダウンロード速度が出ない③ C S Cause
© SEGA
• コネクション数を増やすことにより解決
 使用していたCDNのストリーム最大数が変更不可
• 最大3コネクションまで張る仕様とした
 300~400ストリームで安定感のある速度になった
 例 : 200Mbps ÷ 384 (stream) → 65kb (1filesize)
ダウンロード速度が出ない③ Solution!
C
© SEGA
主要なCDNのストリーム最大数
ダウンロード速度が出ない③ Appendix
S
Amazon CloudFront 128
Azure CDN 100
Google Cloud CDN 100
akamai 128
Fastly 100
Cloudflare 256
実測値 + 一部 https://netsec.ccert.edu.cn/files/papers/ndss-2020-cdn-judo.pdf を参考
© SEGA
• .Net 6のHTTP/2の機能でも同様の議論有り
 ストリームが上限に達したらコネクションを追加する
 https://github.com/dotnet/runtime/issues/35088.Net
ダウンロード速度が出ない③ Appendix
C
© SEGA
ダウンロード速度が出ない④
C
S
クライアント事例
サーバ事例
© SEGA
• 特定のタイトルでのみダウンロード速度が極端
に低下する問題が発生
ダウンロード速度が出ない④ Problem!
C
© SEGA
• ファイル処理負荷が高い構成だった
 1ディレクトリ下に大量のファイルを保存
 ハッシュ化した長めのファイル名を使用
ダウンロード速度が出ない④ C Cause
© SEGA
• 一時ファイルの処理を変更して回避
 専用のディレクトリを用意
 短いファイル名(内部的なリクエスト番号)
ダウンロード速度が出ない④ Solution!
C
© SEGA
速度出ない問題との闘いに終止符
© SEGA
ダウンロード速度が出すぎた
C
S
クライアント事例
サーバ事例
© SEGA
• とあるタイトルの大型アップデート時に、CDN
のデータ転送レート設定上限値を超える通信量
が発生することが判明した
ダウンロード速度が出すぎた Problem!
C S
© SEGA
• CDNによってはデータ転送レートに制限が掛
かっていることがある
• Amazon CloudFrontの旧制限は40Gbps (※)
 200Mbps出る端末の同時ダウンロード可能数
 40Gbps ÷ 200Mbps = 200 (端末)
ダウンロード速度が出すぎた S Cause
※2020/5/20に150Gbpsに更新された
© SEGA
• 混雑時間帯のみHTTP/1.1へダウングレード
• お金で解決するのも手
 制限があるCDNも転送レート上限の引き上げが可能
な所がほとんど
• CDN選定条件にデータ転送レートも考慮しよう
ダウンロード速度が出すぎた Solution!
C
© SEGA
ファイルオープンエラー発生
C
S
クライアント事例
サーバ事例
© SEGA
• タイトル側でファイル上限エラーが発生
• HTTP/2ライブラリは以下の80%を上限に使用
 Windows (VCRUNTIME) : _getmaxstdio
 Android/iOS : /proc/self/limits
ファイルオープンエラー発生 Problem!
C
© SEGA
• iOS11でファイルオープン上限数が256に
• 結果、タイトル側でファイルリソースが枯渇
 256 * 0.2 = 51 ファイル
ファイルオープンエラー発生 C Cause
© SEGA
• 上限数の変更を行う実装に修正
 setrlimit/getrlimitを使用
• タイトルのファイル使用上限を受け取るように
 setrlimit/getrlimitが効かない端末対策
• ファイルオープン数の削減
 オープンタイミングの見直し
ファイルオープンエラー発生 Solution!
C
© SEGA
ファイルのrenameに失敗
C
S
クライアント事例
サーバ事例
© SEGA
• 特定の端末でのみダウンロード完了後のファイ
ルのrenameに失敗する問題が発生
ファイルのrenameに失敗 Problem!
C
© SEGA
• Android11からExternalStorage境界をまたぐ
rename(POSIX)呼び出しが禁止
 Javaはコピー&削除にフォールバックされる
• 以下の境界をまたぐとNG
 PublicDirectory(Download), Cache, Files,
Files(Download), Media
ファイルのrenameに失敗 C Cause
© SEGA
• Android11でも必ず再現する訳ではない
• sdcardfs採用のカーネルでは発生しない
 cat /proc/filesystems の結果にsdcardfsがあれば発
生しない
ファイルのrenameに失敗 C Cause
© SEGA
• ファイルのrenameが失敗する端末ではコピー&
削除にフォールバック
• renameに比べパフォーマンスペナルティあり
 ExternalStorageの境界を跨がない設計をお勧め
ファイルのrenameに失敗 Solution!
C S
© SEGA
タイムアウトしない
C
S
クライアント事例
サーバ事例
© SEGA
• データを全くダウンロードできないにも関わら
ず、タイムアウトが発生しない問題が発生
タイムアウトしない Problem!
C S
© SEGA
• HTTP/2特有のコネクションクローズ問題
 コネクション切断時に送信されるGOAWAYフレーム
がロストしたことが原因と推測
• 複数のストリームが非同期に絡むのでHTTP/2の
コネクションクローズの実装は複雑となりがち
タイムアウトしない C S Cause
© SEGA
• 独自のタイムアウト処理を追加した
 タイムアウト判定時にはハンドシェイクからやり直し
• graceful shutdownに対応したサーバやライブ
ラリを使っていれば不要
 サーバ側が担保できなかったので独自に実装
タイムアウトしない Solution!
C S
© SEGA
iPhoneで通信中に不正終了
C
S
クライアント事例
サーバ事例
© SEGA
• ダウンロード中に極稀に不正終了が発生
 検証環境では7万回に一回程度発生
iPhoneで通信中に不正終了 Problem!
C
© SEGA
• 利用しているOSSの不具合だった
 HTTP/2のconnection reuse発生時に加えていくつか
の条件を満たした時のみ発生
 数千~数万ファイルを一気にダウンロードするのは
ゲームくらいなのでレアなものを踏む
iPhoneで通信中に不正終了 C Cause
© SEGA
• その後OSSの更新で無事に修正された
• HTTP/2のゲーム活用も枯れてきている
 他社の大型タイトルでも採用が増えてきた
iPhoneで通信中に不正終了 Solution!
C
© SEGA
HTTP/2独自の設定をしたい
C
S
クライアント事例
サーバ事例
© SEGA
• 5G時代に備え、もっと速度を……!
 HTTP/2独自の設定をカスタマイズすればいけそう?
• 設定例
 HPACK Dynamic Table設定
 ストリームの初期ウィンドウサイズ
HTTP/2独自の設定をしたい Problem!
C S
© SEGA
• 諦めた
 利用しているOSSは設定を内部パラメータとして隠蔽
 CDNも設定できない場合が多い
• OSS選定のタイミングで考慮しておこう
HTTP/2独自の設定をしたい Solution!
C S
© SEGA
インターネット全体への波及効果
© SEGA
インターネット全体への波及効果
コネクション数削減による効果
© SEGA
ゲームはコネクションを多数用いる
アセットダウンロード
ランキング
ゲーム通信
チャット
© SEGA
• コネクション数の増加はそのままインターネッ
ト上に存在する中継器の負荷増大に繋がる
コネクション数による負荷増大
© SEGA
• 日本をはじめ全世界に余裕がない状態
インターネットは社会全体の資源
総務省 :我が国のインターネットにおけるトラヒックの集計・試算 2020年11月分の集計結果 より
© SEGA
• HTTP/2であれば従来に比べてコネクション数を
大幅に削減可能
 多重化
 コネクションの再利用
HTTP/2によるコネクション数削減
© SEGA
• Webブラウザや動画ストリーミングサービス等
の非ゲーム分野ではコネクション数は減少傾向
非ゲーム分野では既に導入が進んでいる
Web Almanac 2020 HTTP/2 - HTTP/2のインパクト より
© SEGA
HTTP/2のゲームへの導入のメリット
開発者 ユーザ体験の向上(離脱率低下)
ユーザ ダウンロード待ち短縮
インターネット 負荷軽減
© SEGA
コネクション数削減による効果
HTTP/2を使って
開発者、ユーザ、インターネット
に優しいゲーム作りを目指そう!!
© SEGA
インターネット全体への波及効果
ゲームのデータ配信とトラフィック
© SEGA
• AAAタイトルの発売日や大型アップデートの日
にインターネットトラフィックが非常に混雑す
るというのが最近問題になっている
 CEDEC 2020 [JANOG×CEDECコラボセッション]
インターネットとゲームトラフィック でも話題に
ゲームのデータ配信の影響
© SEGA
• ゲーム業界はインターネットを使わせてもらっ
ている立場
 少しでもインフラへの負荷を減らすべき
• 必要になったタイミングで都度ダウンロード
 モバイルゲームでは採用しているタイトルも多い
 非モバイルゲームは大型パッチが主流
データ配信方式の転換の提案
© SEGA
• ゲームの適正とよく相談する必要はある
 ジャンル
 プラットフォーム
• 採用メリットも
 従来に比べ管理や差し替えが容易になる
パッチ配信とトラフィック
© SEGA
まとめ
© SEGA
• HTTP/2はアセットダウンロードに効果大
• HTTP/2はクライアントサーバ間の通信の効率
化にも効果大
• HTTP/2はインターネットにも優しい
HTTP/2を使おう!!
© SEGA
ご清聴ありがとうございました

CEDEC2021 ダウンロード時間を大幅減!~大量のアセットをさばく高速な実装と運用事例の共有~

Editor's Notes

  • #2 それでは ダウンロード時間を大幅減!~大量のアセットをさばく高速な実装と運用事例の共有~ というタイトルで、株式会社セガの私竹原と山田さんの2人で講演を始めてさせて頂きます
  • #3 まずは軽く自己紹介をさせて頂きます セガ開発技術部の竹原と申します 自動化やツール、ネットワークまわりが主戦場で、最近ではサウンドにも手を出し始めました、よろしくお願いします 自己紹介ついでに一言コメントということで、 祝 HTTP/3 RFC 発行! 昨日の Fastly の奥さん講演を聞いた方も多いと思いますが、ついにHTTP/2の次期規格であるHTTP/3の標準化が完了しようとしています 今回共有するHTTP/2のノウハウの多くはHTTP/3でも活用できるものだったりしますので、HTTP/3に備える意味でも是非当講演を聞いていって頂けると嬉しく思います さて、私は後半パートの担当ですので、ここからは山田さんにバトンタッチします。また後程お会いしましょう
  • #4 私は開発技術部に所属している山田と申します。 普段は開発者向けツール作成やグラフィックスAPIサポート、ネットワーク関連など色々と幅広くやっております。 最近の趣味は技術同人誌で幅広く情報を集めることです。 技術同人誌は比較的ページ数が多く、割と積みあがってきました。PDFで配布される電子版も多いので、この写真に一緒に積みあがった様子を映すことはできませんでした。 私は前半を担当し、主にHTTP/2 ついてお話します。
  • #6 早速ですが、HTTP/2を使ってファイルのダウンロードがどのくらい早くなるのか、というのを見てもらおうと思います。 計測の環境としては、CDNに配置したデータをHTTP 1.1 と HTTP2 でそれぞれダウンロードし、完了までの時間を計測しました。 それでは、ダウンロード時間計測の様子をみてみましょう。 アセットの数 2000 ファイル1つあたり 64KB 計測したのは Android の Pixel3 です。 このダウンロードが完了していく様子は特徴的ですね。 もう一度、動画を再生します。 HTTP/1.1 側は6コネクションを使っています。 HTTP/2はHTTP/1.1に比べて2.5倍速くダウンロードが完了しており、大きく待ち時間を減らせています。 アセットは日本リージョンに配置 Wi-Fi を使用
  • #7 今の結果を整理したものが、こちらの表となります。 HTTP/2を使った場合、ダウンロード完了までは 3.6秒ほどでした。HTTP/1.1の場合では、9秒ほど掛かっていました。 これらのダウンロード処理中の平均速度はこのようになっており、 HTTP/2 では 280Mbps ほどの速度が出せていました。 HTTP/1.1で3コネクション使う場合も多くあると思いますので、こちらで計測したデータも載せています。 このように HTTP/2 を使うことで、細かなアセットデータを多くダウンロードするという状況で、待ち時間をかなり減らすことができます。 よく使われる HTTP/1.1 3コネクションと比べると圧倒的に時間短縮が出来ているところは良いですね! HTTP/2 の性能を確認して、興味を持ってもらったところで、詳しい話を始めていきましょう。 講演のタイトルにもある「ダウンロード時間を大幅減」のためには、 「HTTP/2 プロトコルをゲーム向けに使いこなす」というのが肝となってきます。
  • #8 本講演では、このような流れで説明をしていきます。 始めにゲーム開発者に向けたHTTP/2の仕様とゲームとの相性を説明します。 それからHTTP/2を使うための実装と、導入についての勘所をお話します。 後半では竹原さんに交代して、HTTP/2に関連するトラブル集と、HTTP/2の採用でインターネット全体への波及効果についてお話します。
  • #9 HTTP/2 の話は膨大になるので、ここでは我々ゲーム開発者に向けて関連性の高いものを説明していきます。
  • #10 まず HTTP/2 プロトコルは 2015年標準化完了し、公開されました。基本的に HTTP/1.1 の仕様を継承しています。 現在の普及率は、全世界で約50% といったところです https://w3techs.com/technologies/details/ce-http2
  • #11 このHTTP/2 の仕様のうち、ゲーム開発の分野と親和性の高い、多重化、 HPACK、バイナリフレーム、という要素を説明していきます。 そろそろ HTTP/3 の話が聞こえてくるところですが、これらの仕様は基本的に継承されています。
  • #12 それでは HTTP/2 最大の特徴ともいえる多重化について説明していきます。
  • #13 これはサーバに対し1つのコネクションで、複数の HTTP リクエストを同時に送ることが可能となります。 HTTP/1.1の1つのコネクションに相当するものを、 HTTP2ではストリームという概念で表現します。 そして、 HTTP/2 の1つのコネクションの中には、複数のストリームが存在し、それぞれのストリームが同時にデータを通信します。 この図ではストリームを矢印で表現しており、1つのコネクションの中に3つのストリームで通信をしている、という図になっています。 複数のストリームを1つの接続の中で束ねて通信をしているということから多重化と呼んでいます。 ここでは3個のストリームで書いていますが、実際に使う場合にはストリームの数はもっと多くの数を使います。 なお、 HTTP/1.1 で並列ダウンロードを実装するためには複数のコネクションを使用します。
  • #14 多重化のメリットを話す前にHTTP/1.1の話を先にさせてください。 HTTP/1.1にはHead of Line Blocking という課題があります。 1つのコネクションにおいては、先行するリクエスト・レスポンスが完了しないと、次のリクエストを投げることができません。これはプロトコルの仕様です。 この課題をあらわしたものがこの図です。3つのファイルを1つのコネクションを使ってダウンロードする場合、リクエストとレスポンスを順番に処理してダウンロードを行います。 ここで、なんらかの要因で、先行するリクエストの処理完了が遅れると、それが後続リクエストの開始に響いてきます。 このような問題が HTTPのHead of Line Blockingです。
  • #15 HTTP/2 では、この Head of line blocking の課題を解決してます。 多重化によって並行してストリームの通信ができるため、1つのリクエストの処理が遅れても、他のリクエストの開始に影響を与えません。 この図でも先ほどと同様に3つのファイルをダウンロードをしているときの状態ですが、1つ目のファイルのリクエストを出した後、後続のファイルに対してのリクエストも発行できることを表現しています。 仮に1つ目のファイルが何らかの要因で多少時間が掛かっても、他のファイルはスムーズに通信を完了することが出来ます。
  • #16 多重化の効果はまだ続きます。多重化によって小さなファイルのダウンロードであっても効率的に帯域を使用することができます。 この図は HTTP/1.1 と HTTP/2 の通信でファイルのダウンロードの真っ最中、定常状態をモデル化したものです。 どちらの場合も1つの接続を用いて、リソースをパックしていない、ファイルサイズは小さめの、たくさんのアセットデータをダウンロードしている途中の状態を示しています。 それぞれ色分けされたブロックは1ファイル全体、もしくはファイルの一部データがおくられていると読んでください。 また、縦軸はその状況において使用可能な回線帯域を示し、各ファイルがどの程度使用中であるかを示しています。 HTTP/1.1では、回線帯域に余裕があり、十分な通信バッファがあるときでも、小さなファイルを多くダウンロードしている状況ではせっかくの広帯域を使用できていません。 通信のリクエストに対し、最大でもファイルサイズ分のデータのみが送られるためです。 HTTP2では、多重化が使えるため、小さなファイルを同時に通信します。その結果、使用可能な回線の帯域を十分に活用することが出来ます。
  • #17 両者を比較してみると、HTTP/1.1ではこの塗りつぶした部分が、うまく帯域を使えていなかった部分になります。 一方で、 HTTP/2では使用可能な回線帯域をほぼほぼ活用する形で通信が行えています。 1つの接続の中で、ぎっしりとストリームが詰まって通信がおこなわれている状態になっています。 このように、使用できるリソース、今の場合は使用可能な回線帯域ですが、しっかり活用して無駄な時間を減らす、ということが HTTP/2 なら可能となっています。
  • #18 今度はこの多重化をゲームとの相性という観点からみてみましょう。 効率的に回線帯域を使用可能であるということから、アセットのダウンロードに有効です。 細かなファイルのダウンロードでも十分に速度がだせるので、パッキングやアーカイブが不要となることも多いでしょう。 もう1つ、クライアント・サーバ間の通信について速度の向上というメリットがあります。 複数のリクエストを束ねて送ることが出来ることから、1RTT 分の待ち時間で、例えば 100 リクエストを処理することも可能でしょう。 HTTP/1.1 の場合ならば、これは確実に 100 RTT 分の待ち時間が必要なので、待ち時間を減らす効果があると考えられます。 さすがに、API 通信で 100リクエストは大げさかもしれませんが、モバイル回線ではレイテンシが大きいため、この RTT 削減というのは目に見える差となって現れてきます。 レイテイシが大きいという点では、海外のサーバに接続する、という状況でも当てはまることでしょう。
  • #19 HPACK は HTTP ヘッダを圧縮する仕組みです。
  • #20 HPACK の技術要素は、ハフマンコーディング、インデックステーブルとして スタティックテーブルとダイナミックテーブルです。これらを今から順番に説明します。 この HPACKは RFC 7541 で記載されています。内容をしっかり理解したい人はそちらを参照してください。 これから出てくる圧縮の数値データはあくまで説明のための概算です。リアルの値とは少々異なる点にはご注意ください。
  • #21 それではハフマンコーディングについて説明します。 ハフマンコーディング自体は可逆圧縮の方式として有名なため、どこかで聞いたことがあるという人も多いかもしれませんね。 ハフマンコーディングは、よく出現する文字・シンボルを少ないビット列で表現し、データを圧縮するという手法です。 このハフマンコーディングによって、どのような感じでHTTPのデータが表現されるのかを確認していきます。 左は HTTP/1.1 のヘッダ、右側にはHTTP/2での同等のヘッダを表現したものです。 ここで1行目をハフマンコーディングによってどのようなビット列になるかを示したものが下段の赤枠の部分です。 このビット列はバイト数にして8バイトとなっています。元の文字列に比べ縮んでいることがわかります。 各シンボルに対してどのようなビット列になるのかは、RFCにて定義されています。
  • #22 そして、この HTTP/2 のヘッダ分全体をハフマンコーディングしたものが、赤枠のなかのものです。 ハフマンコーディングによる符号化後のデータは奥手っと境界に整列していることが仕様で決まっているので、そのパディングを三角括弧で表示しています。 この結果、どのくらいのバイト数になるかを計算すると、(次のスライドへ続く)
  • #23 このようになります。ハフマンコーディングにより48バイトになります。 HTTP/1.1と比較すると、 およそ 81% のデータ量となり、2割程度のデータを削減できています。 このように頻出文字で構成されるデータであれば大幅に圧縮が可能ですが、一般的に出現頻度が低いとされるデータが入った場合には圧縮率の低下が発生します。 圧縮率低下だけならまだしも、単純にハフマンコーディングだけでは、最悪ケースとして元データよりも大きくなる可能性は存在します。
  • #24 次にHPACKに含まれるインデックステーブルについて説明します。 これはインデックス値を用いた圧縮の仕組みですが、 StaticTableとDynamicTableというものの2種類があります。 それでは、 Static Table から説明を始めていきます。
  • #25 Static Tableではヘッダの各部分を、 RFC で定義されているテーブルのインデックス値に置き換えていきます。 この図の水色枠部分がRFCで定義されているテーブルの抜粋となります。 先ほどと同じヘッダデータを、このインデックスで置き換えていったところ、この右側緑枠の箇所の結果となります。 点線で囲った部分がインデックス値に置き換えられた箇所です。このとき、データは31バイトとなり、元のデータと比べ約半分のデータ量に圧縮されています。
  • #26 今度は Dynamic Table を説明します。 Dynamic Tableは通信に使用したヘッダフィールドをテーブルに登録していき、次回の通信では StaticTableと同じようにインデックス値を用いて圧縮する機構です。 テーブルへの登録は Static Table の続きとして追加されていきます。先ほどと同じデータを再度通信する場合、これらのインデックス値を用いると、たった5バイトで済みます。 これは元のデータと比べると、 約8%程のデータ量となり、かなり圧縮できたことを示しています。
  • #27 話は変わって、今度は HTTP/2 の通信においての最小単位 フレーム、と呼ばれるものの話になります。 フレームとは次のような構造となっており、バイナリデータとして構成されています。そのためバイナリフレームと呼んでいます。 この構造の先頭にはペイロード部の長さや、データタイプ、ストリームIDといったヘッダがあり、ペイロードが後ろにつく形をとっています。 例えばリクエストヘッダの場合、タイプに HEADERS を指定してペイロード部分には HPACK で処理された結果を格納します。 HTTP/1.1はテキストデータで処理でしたが、 HTTP/2ではバイナリデータで処理するように変わっています。このことは HTTP/2 での大きな変化の1つです。
  • #28 さて、これまでに登場した HPACK とバイナリフレームについて、ゲームとの相性を考えてみましょう。 HTTP/1.1 の通信を考えてみると、このスライド右側のようなデータをやり取りしている状態でした。 HTTP/1.1 はテキストベースのプロトコルであり、各ヘッダの改行コードやスペースの取り扱いで煩雑な箇所が存在し、通信を処理するライブラリ層に負荷がかかっている状態でした。 これがバイナリフレームの採用により、データのパース処理について処理負荷の軽減になっています。 この様子について次のスライドでもう少し見てみましょう。
  • #29 同じデータをHTTP/2にした場合を図にしめしました。  バイナリフレームを用いてこれらのデータを送ることを考えると、フォーマットが決まっているバイナリフレームではデータ解析のオーバーヘッドを減らし、通信サイズもコンパクトにできます。 特にフレーム長が先頭にあることも重要です。 さらに HTTP のヘッダ部分は、 HPACK によって圧縮が施され、少ないデータで済むようになっています。そのため通信の効率も上がっています。 注意したいのが、バイナリフレームのやり取りになってテキストのパースよりは処理負荷が軽減した、ということであり、 HPACK の圧縮・展開という点は従来にはなく追加された処理のため、実行コストが追加されています。 それでも高速な通信ができているのであれば、トータルで効率化が成功していると考えられます。
  • #30 さて、ここまでは HTTP/2 の仕様の話が多くて、聞いている皆さんも少し疲れたかなと思います。ちょっと休憩しましょう。 API 通信では TCP, RUDP を使うという選択もありますが、HTTP/2を使うこともお勧めできます。 例えば API 通信でよく使用される gRPC や WebSocket ですが、これらのトランスポートレイヤーではHTTP/2が使われていたりします。 もしかすると既に皆さんの環境でも HTTP/2 を使っていた、ということがあるかもしれませんね。 そして、プロトコルが HTTP ということでサーバの設定の容易さや導入コストの圧縮が可能な場合もあるでしょう。 また、これらに対応したCloud Runのようなサーバレスコンテナの登場もあり、導入がし易い環境が整ってきています。
  • #31 HTTP/2 そのものについて理解してもらったところで、実装について説明していきます。 クライアントとサーバのそれぞれについて順番に説明します。 どちらの話をしているのかが分かりやすいように、右側に示しているタグをそれぞれのスライドで付けています。
  • #32 HTTP/2 を用いた通信をするためのクライアントの実装を自力で実装するのは現実的ではありません。 HTTP/1.1 や HTTP/2 の仕様が膨大で、複雑なためです。 そこで、クライアント側ではゲームエンジンが内蔵しているものやオープンソースを利用することにします。
  • #33 まずケース1、 C/C++を使っている場合で、自社製ゲームエンジンやミドルウェアを使っている場合です。 このときには、採用を検討できるオープンソースは豊富にあります。 そのなかでも、我々は libcurl, nghttp2 の採用をお勧めします。 Libcurlの HTTP/2実装として使われているのが nghttp2 で、HTTP/2にのみ対応するモジュールを作成するのであれば nghttp2 を使ってシンプルに実装できると考えています。 汎用的な HTTP による通信を扱うのであれば、機能が豊富な libcurl がお勧めです。
  • #34 次にケース2として Unity を使用している場合です。 Unityが採用しているC#のランタイムやクラスライブラリでは、HTTP/2を利用できません。 そこで、先に紹介した C/C++のオープンソースを利用して、ネイティブプラグインとして実装することが現実的な対応策となります。 もし将来的に C# ランタイムおよびクラスライブラリが更新され、HTTP/2を利用可能になったとしても、数万リクエストを捌けるのか、パフォーマンスは問題ないかなど、注意すべき点があります。 ちなみに私たちは先に紹介した libcurl を用いたネイティブプラグインを実装して Unity で利用しています。
  • #35 今度はケース3、UnrealEngine4を利用している場合です。このとき HTTP/2が使えるかどうかは、対象プラットフォームによって対応が分かれます。 Windows で WinHTTP を使用する状態のときにのみ HTTP/2が使用可能という状態で、多くの環境では非対応です。 よって、 C/C++ 製のオープンソースを用いて対処することが必要と考えます。 UnrealEngine4 の内部の話になるのですが、使用している libcurl が HTTP/2対応したものに更新され、さらに Apple 製品向けとして NSURLSession を用いた実装に更新されれば、 簡単に HTTP/2が使えると私は考えています。
  • #36 最後にケース4としてC#を用いて、自社製ゲームエンジンやミドルウェアを使っている場合です。 Windows上でMicrosoftの.NET Frameworkや.Net CoreではHTTP/2を使うことが出来ます。 他の動作プラットフォームを考慮した場合、 .NET Core は HttpClient の実装がわかれていて非対応の場合がありましたが、 .NET Core3以降は多くの環境で使用可能です。 C#を使っている関係で、ゲームアセットのダウンロードなど大量のリクエストを処理させたときのパフォーマンスには懸念が残ります。
  • #37 ここからは、サーバ側の話になります。 HTTP/2 を用いた通信をするためにはサーバ側も対応していることが必要です。 HTTP/2 は既に Web ページアクセスで先行して導入されている関係で、比較的枯れているものが多く、クライアント実装に比べるとトラブルが少ないように思います。
  • #38 主要な CDN は HTTP/2 に対応済みとなっています。 そのためこれらを使用しているのであれば、既にHTTP/2は利用可能であったり、設定により簡単に有効化が出来る状態にあります。 余談ですが、一部の CDN は既に HTTP/3 も対応済みとなっています。
  • #39 代表的な HTTP サーバソフトウェアは、現在全て HTTP/2 に対応しています。 ただしネットワーク構成として、サーバよりも前段に配置されるロードバランサーには注意が必要です。 ロードバランサが HTTP/2 通信を終端してしまい、End To End での HTTP/2 通信ができていない場合があります。
  • #40 さて、ここまでで説明してきたのはどちらかといえば準備の話でした。 ここからは我々が遭遇した HTTP/2 の導入にあたってのコツを共有していきます。
  • #41 まずは、パフォーマンスがでない① です。 HTTP/2を導入して期待するのが高速なダウンロードでしょう。それが思ったほどではないとなると残念です。 重要なポイントは、ダウンロードするデータによってはHTTP/2に切り替えるだけでは、期待したパフォーマンスが得られないということがあるということです。 ここでは2つ例を上げています。 まず、1つがファイルサイズが大きいものが支配的である場合です。この場合には、既にHTTP/1.1でも十分に速度が出ていると考えられます。 もう1つが、ファイルサイズでダウンロードのリストがソートされている場合です。
  • #42 このような状況を避けるために次のことを提案します。 まず、導入するプロジェクトに有効なのかを判断しましょう。ダウンロードするファイルのサイズヒストグラムを取ってみましょう。 そして、ダウンロードのリストについて、作り方にひと工夫をいれることが必要です。 これは、私たちも見事に踏んだ課題でして、この状況や解説については後半パートで詳しくお話します。
  • #43 次に、パフォーマンスが出ない②です。 HTTP/1.1の実装をベースとしている場合、同時に発行するリクエストが3~6程度となっていることが考えられます。 この程度のリクエストを逐次的にリクエスト発行する場合には、HTTP/2で性能を生かすことができません。
  • #44 これを解決するには、リクエストをまとめて発行することが重要です。 少し前に説明したように、HTTP/2では同時に多くのリクエストを処理することが可能であり、そのような時に帯域を有効活用できます。 HTTP/2を意識した設計として、リクエストを束で受け取る関数インタフェースで設計しておくことが大切です。
  • #45 そして、たくさんのリクエストを同時に発行しましょうと、話したところで、次の課題です。 リクエスト数が大量になるため、それに応じたコストに注意が必要です。 ファイル保存の際にはファイルI/Oのコストが、メモリに結果を保持する場合にはメモリの使用量が溢れるなどあります。
  • #46 この課題に対処するためには、ファイル保存時にはバッファリングを活用しての分散した書込み、メモリへの展開時には一旦ファイルに保存するなどが必要となります。 また大量のリクエストを処理する必要があるので、雑な実装ではボトルネックになることが考えられます。 高速に処理できる管理機構や、通信処理をブロックさせない仕組みなど、配慮した実装を行うことが求められます。 より詳細な説明を後半で用意していますので、ここでは解決の指標のみにとどめておきます。
  • #47 さて、リクエストを捌き終わって、正常系が問題なく動くようになったところで、今度はエラーについて目を向けてみましょう。 HTTP/2 になってからは、エラーの取り扱いについても注意して考える必要があります。 HTTP/1.1のときにはコネクションエラーは通信エラーとして扱えました。 しかしHTTP/2 では多重化で通信している関係で、この考えでは不味そうです。
  • #48 HTTP/2ではコネクションが確立したあとの、個々のストリームでの通信エラーはリトライによる復帰可能性があるということです。 そのため、ストリームを従来のコネクションと考えて、ストリームでのエラーを即通信エラーとしてはいけません。 ここで通信エラーとしてしまうと、他の正常な通信をしているストリームを止めてしまうことになります。
  • #49 通信エラーの関連して、今度はパケットロスの話題をあげています。 HTTP/2になってパケットロス率に注意しましょうという話題があります。 これがなぜ重要かというと、 HTTP/2 では1コネクションの中に複数ストリームを束ねて通信を行っています。 そして、 HTTP/2ではTCPを使って動作をしているため、パケットロスの影響が全てのストリームに影響してしまいます。 これは TCP の Head of line blocking という症状です。 この結果として、複数コネクションを使用した HTTP/1.1に速度が劣ってしまうという可能性があります。
  • #50 残念ながら、HTTP/2 で TCP の Head of Line blocking を回避することはできません。 HTTP/3 が普及して使えるようになるとこれは解決できます。 HTTP/2 までしか使えない状況で、さて、どう対策するかというと、品質のわるい環境では HTTP/1.1にフォールバックする実装を対策としていれるのがよいと考えています。 どの程度のロス率でフォールバックすべきかという点については、各プロジェクト毎に指標が異なるため各プロジェクトで計測して決める必要があります。
  • #51 次はサーバの対応状態を確認しましょうという話です。 本番のクラウドサービス、開発中のサーバやテストのサーバで HTTP/2対応をした状態かを確認しましょう。 運用中のタイトルに HTTP/2を新規導入する場合には、サーバ側での有効・無効スイッチを変更し忘れるケースもあります。 このような事態が発生すると、 HTTP/2 に対応したのにダウンロードが速くならないと誤解される恐れがあります。
  • #52 そういったことを避ける意味でも、 HTTP/2 通信が出来ているのかどうかをチェックするツールを用意しておくのがよさそうと考えています。
  • #53 HTTP/2 の優先度に関係する課題があるのですが、その前に HTTP/2 の優先度とは何かを説明します。 HTTP/2 では優先度の仕組みが仕様として定義されており、各ストリームに重みづけや依存関係を設定をすることができました。 これにより、先に欲しいリソースをサーバに通知するということができ、例えばブラウザではhtmlやスタイルシート、JavaScriptに高い優先度を与え、画像データは後回しというようなことが可能となりました。
  • #54 そこで、この HTTP/2 の優先度制御を活用して、ダウンロードの順序制御に使おうという考えが浮上します。 しかし、実際にはいくつかの CDN で、正しいふるまいを実装しておらず、安心して使えない状態です。 また、 HTTP として優先度オプションの協議が始まっており、これは HTTP/2 とは切り離して話が進んでいます。 こちらは現時点においては、 RFC も策定されていません。
  • #55 ダウンロードの順序制御についての解決策は、自前で順序制御を実装する、という素直な方法が考えられます。 実際に、私たちが実装した通信ライブラリでは、自前でのこの機能を実装しました。
  • #56 もう少しだけ、私たちが遭遇した話を共有しておきます。 HTTP/2 の仕様上、 TLS 1.2 以上を必要とします。しかし、実際のところそれ以下のバージョンや、生http での通信で HTTP/2 が使用可能な場合もあります。 AWS の ALB では https での通信を行ったときのみ HTTP/2 が使用可能でした。
  • #57 ここからは竹原が担当させて頂きます、よろしくお願いします まずは実際に HTTP/2 に対応した通信ライブラリを運用した際に発生したトラブルとその解決方法について共有させて頂きます 先ほどの山田さんのパート同様にアイコンでクライアント、サーバ、もしくはその両方を示していますので参考にしてください それでは、いってみましょー
  • #58 まずはダウンロード速度が思った通りに出なかった、というトラブル事例となります 1とついていることからわかる通り、2,3 と続いていきます…… 山田さんパートで説明したように、導入時にもある程度パフォーマンスは意識していたのですが、それでも様々な要因で速度が出なかったので、その苦闘の歴史を是非共有させて頂ければと
  • #59 というわけで、まずは通信速度が一定以上になるとそれ以上ダウンロード速度が出ない、という問題です
  • #60 これは、データの送受信処理をメインループに同期させていた為、通信速度が一定値を超えるとソケットの受信バッファが満タンになり待ちが発生しているのが原因でした スマホのゲームは 30FPS のものが割と多いので、ゲーム側のメインループに同期させると結構簡単に受信バッファが満タンになってしまいます 通信プログラムでは割とありがちな問題ですね
  • #61 対策としては、素直に受信処理の非同期化を採用しました しかし、非同期化したことにより abort 処理やリトライ処理、プログレスの計算がかなり複雑になってしまいバグも何度か出ました 多くのストリームを同時に扱うので、非同期化した際の実装難易度は HTTP/1.1 までより高くなります。注意してください 可能であれば、後付けでスレッド化しないで最初の設計時に盛り込んでおいた方が良いでしょう
  • #62 次はダウンロード速度が出ない第二弾です
  • #63 ①の対策で安心したのもつかの間、特定の端末で前項の「通信速度が一定を超えるとそれ以上のダウンロード速度にならない問題」が再発してしまいました
  • #64 調査の結果、下位グレードの端末で通信速度がファイル I/O 速度を上回ってしまった為に発生していることが判明しました 特にファイル I/O の回数がボトルネックになっていました 下位グレードの端末はファイル I/O が驚く程低速のものがあるので、しっかりテストしておくことをお勧めします
  • #65 こちらの解決策として、メモリマネージャを導入しました ライブラリの実装がダウンロードしたそばからファイルに書き込みをしているあまりよろしくない実装だったので、一定のサイズまではメモリに書き溜めておき、そのサイズを上回った段階でファイルに書き込むことでファイル I/O の負荷を軽減しました 一定のサイズの設定値が大き過ぎると、これまたボトルネックになったりするので注意が必要です アセットサイズ分布によりこの一定サイズの最適解は異なるので、アプリ毎に設定を変えられるのが理想です 私たちは現在デフォルト値を 64kb として、ライブラリ初期化時にこの値を変更可能な形で運用中です
  • #66 やれやれ、これでようやく HTTP/2 の爆速ダウンロードを味わえる……と思いきやまたまたダウンロード速度が出ない問題が発生しました
  • #67 ②までの対策でしばらく安定した速度が出ていたのですが、ある日ライブラリ側では何も修正していない、 大事なことなので二度言いますが、何も修正していないのに、突然特定のタイトルでダウンロード速度が安定しなかったり、低下する問題が発生しました
  • #68 これは、そのタイトルにおけるダウンロード対象のアセットリストの構成が変わっており、小さいファイルが集中しているゾーンが発生し、帯域を使い切れていないケースが発生してしまったのが原因でした スマホタイトルは割と小さいサイズのアセット中心で構成されていることが多く、案外この問題に遭遇することは多いです
  • #69 この問題は前半戦で山田さんが紹介したダウンロードリストの作り方を工夫することで回避できます まず、ダウンロードするファイル順序に気を配り、サイズでのソート済みという状況を避けましょう できれば常に帯域が埋まるようにリクエストを投入していくのが理想ですが、単純にランダムにリストの順序を入れ替えることでもある程度効果があります
  • #70 しかし、ソートしてあげてもダメなケースがあります 例えば最大で 128 ファイルを同時並行でダウンロード可能な実装とした場合、200Mbps の帯域では1 ファイル 195 kB 程度はないと帯域に余りがでてしまいます スマホタイトルではこのレベルのサイズのファイルは割と少なく、アセット構成次第ではいくら頑張っても同時ダウンロードするファイルサイズの合計が帯域より小さくなってしまうことがあります この 195kBを求めた計算はかなりざっくりとしたもので、実際にはウィンドウサイズや RTT により値は大きく変動することに注意してください
  • #71 補足となりますが、 HTTP/2 において、同時にダウンロード可能な数、つまりリクエスト数はコネクションあたりのストリーム数に依存します これを HTTP/2 的にはストリーム最大数、 RFC では MAX_CONCURRENT_STREAMS と呼びます この値はクライアントとサーバで別々に管理しており、自分の受付可能なストリーム最大数をお互いに送信し合うことにより設定を行います
  • #72 このストリーム最大数を変更すれば同時ダウンロードするファイルサイズが帯域を上回るような設定が可能そうです しかし、使用していた CDN ではストリーム最大数を変更できませんでした そこで、ストリームが上限に達したら次のコネクションを追加し、最大でコネクションを 3 つまで張る方向で対応しました それでも 1.1 までの実装で多く見受けられる 6 コネクション構成に比べて半分の数になっています また、副次効果として前述した“パケットロス率が高い環境では HTTP/1.1 より遅くなる可能性がある”問題についても対応できました 私達が運用していた環境では、ストリーム最大数が 128 だったので、これにより最大 384 ファイルを同時並行でダウンロード可能になりました 384 平行ダウンロードできると、例えば 200Mbps 出る回線では 1 ファイル 65 kB 程度あれば十分に帯域を活用できるようになり、速度にも安定感が出ます この計算も先ほど同様ざっくり算出なので、あくまでも目安程度と考えてください ちなみに、前述のメモリマネージャのデフォルトサイズの 64kB はここからきていたりします
  • #73 当問題に関する補足です Amazon Cloud Front をはじめ、多くの CDN ではストリーム最大数は 100 ~ 200 程度に設定されており、この値は CDN 設定で変更できないものがほとんどです CDN 業者の方がもしこの講演聞いていたら、ストリーム最大数 MAX_CONCURRENT_STREAMS を変更できるようにして頂けるとゲーム業界的には大変助かります
  • #74 補足その②です 実は .Net 6 の HTTP/2 の通信実装でも、このストリーム最大数を超えた場合にどうするか、という議論が行われたようです そして、最終的には私たちの実装同様に、ストリームが上限に達したらコネクションを追加する、という結論に落ち着いたようです この問題からも分かる通り、ストリーム最大数は仕様が難解で、うまく活用されているかというと怪しい現状があります またこの現状は、ゲーム業界以外ではこの値を変更したいケースがあまりなさそう、という事情も絡んでそうです というわけで、これで速度問題は解決、次の問題はなんでしょうか?
  • #75 はい、もちろんダウウンロード速度が出ない問題です
  • #76 今度は HTTP/2 ライブラリを別のタイトルにも導入しよう、となった際に、そのタイトルではなぜか速度が出ない問題が発生しました 更にこの問題は、そのタイトルで HTTP/2 ライブラリの導入を検証しているフェーズで発生したので、このままでは採用が危ぶまれると急ぎ調査をしました
  • #77 結果、そのタイトルのディレクトリ構成だと HTTP/2 ライブラリ内部で C++ で実装されていた rename や delete の処理の負荷が高いことが原因とわかりました そのタイトルでは 1 ディレクトリに数千のアセットファイルが存在しており、更にハッシュ化された長めのファイル名が使われていました 私達が実装した HTTP/2 ライブラリでは、ダウンロード中の一時ファイルを “ファイル名.tmp” として同じディレクトリで管理していたので、このディレクトリ内での rename や delete の処理が重くなってしまっていました
  • #78 検証を行っていたのが既に運用中のタイトルでしたので、ディレクトリ構成を変えて貰うのは影響が大きく、ライブラリ側の一時ファイルの処理を変更して問題を回避しました tmp ファイル専用のディレクトリを用意し、その中で内部的なリクエスト番号を使った一意な短いファイル名を使って管理しています 今回のケースは Android で問題が発生しましたが、 1 ディレクトリに大量のファイルを配置するのは、多くのファイルシステムにおいてパフォーマンス低下を招く可能性があるので避けるのが無難です (原因についてどこまで触れるかは未定 → 調査 Issue : https://ghe01.segard1dev.jp/SGC-DT/UniHttp2/issues/719)
  • #79 当問題を解決したことによりついに速度でない問題は全て解消! これからは爆速アセットダウンロードの時代!!
  • #80 と喜び勇んでいたのですが、今度はダウンロード速度が出すぎることにより問題が発生しました……
  • #81 とあるタイトルの大型アップデート時に、CDN のデータ転送レート設定上限値を超える通信量が発生することが判明しました
  • #82 CDN によってはデータ転送レートに制限が掛かっていることがあります 例えばこの問題が発生した際は Amazon Cloud Front のデフォルトの上限値は 40Gbps でした 40Gbps というのは案外小さい数字で、 200Mbps でアセットダウンロードをする端末がある場合 200 ユーザーで詰まってしまいます HTTP/2 にすると 200Mbps 程度は余裕で出るようになるので注意が必要です ちなみに、Amazon Cloud Front のデフォルトの上限値は 2020/05/20 に 150Gbps に更新されています。ありがたいことです
  • #83 幸いアップデート前にタイトルの方が気付いてくださり、混雑時間帯のみ HTTP/1.1 へダウングレードする方向で問題を回避し、大事にはならずに済みました 制限がある CDN も課金すれば転送レート上限の引き上げが可能な所がほとんどなので、お金で解決するのも手です HTTP/2 利用時には CDN 選定条件にデータ転送レートについて入れるのをお勧めします
  • #84 次はファイルオープンに関する話です
  • #85 ある日、 HTTP/2 ライブラリを使っているタイトル側の処理内でファイル上限エラーが発生したとの報告が入りました ライブラリは Windows は VCRUNTIME の _getmaxstdio を Android/iOS では /proc/self/limits の値を基準として、この値の 80% までのファイルハンドルを使用しています _getmaxstdio の上限は 512 程度なので、 Windows では 409 まで同時オープン可能です これは前述する 384 ストリームに耐えうる数字です Android/iOS は /proc/self/limits の値より実測値が大きいことがありましたが、下回ることはなく、且つ検証したすべての端末で実測値が 1024 以上だったので問題なしと判断しました
  • #86 ところが iOS11 からファイルオープンのデフォルトの上限が 256 になったようで、ライブラリ側で 80% も使ってしまうとタイトル側で使えるファイル数が 51 ファイルと極端に少なくなってしまっていました
  • #87 幸い iOS は BSD 系の OS なのでファイルオープンの上限数の変更を行う setrlimit/getrlimit が利用できます この関数を用い、コネクション数とストリーム上限数から利用するファイルハンドル数を算出して変更する実装に切り替えることにより問題を解消しました また、 setrlimit/getrlimit が効かない端末を考慮し、タイトルで使用するファイル上限数を受け取るようにして、最低でもその値はファイルリソースを残すような実装としています 更に iOS だけでなく Android 端末でもこの問題が発生しうるので同様の対策を入れました 最後に、通信開始時にファイルオープンしていた処理を、ファイル書き込みする段階になってはじめてオープンするように変更する等、可能な限りファイルリソースを節約するように処理を修正しました
  • #88 次はファイルの rename に失敗する問題です
  • #89 これは割と最近の話なんですが、特定の端末でのみダウンロード完了後のファイルの rename に失敗する問題が発生しました rename は先ほど述べた .tmp を正しいファイル名にする処理が該当します 端末も古いものではなく、Pixel 5 とむしろとても新しいものでのみ問題が発生しました
  • #90 調査はかなり難航したのですが、最終的には Android11 から ExternalStorage の境界をまたぐ POSIX の rename の呼び出しが禁止になったのが原因なことがわかりました 対象は PublicDirectory(Download), Cache, Files, Files(Download), Media で、お互いの境界をまたぐような rename が失敗します rename(POSIX) と書いてあるのは C/C++ から呼び出した場合のみ問題が発生するからで、 Java の rename については内部で COPY + 削除にフォールバックする処理が Android11 から入っているので問題が発生しません
  • #91 また、全ての Android11 端末で発生するわけではなく、sdcardfs が使用されているカーネル、具体的には cat /proc/filesystems の結果に sdcardfs が存在する場合は問題は発生しません この為、調査の際は Pixel3,4 を Android11 にしても再現せず、原因究明にかなり苦戦しました 条件を満たせば Android11 のエミュレータでも再現するので、当問題の確認を行う際は活用を検討ください
  • #92 対策としては、 Java と同様に C/C++ の実装でもファイルの rename 失敗時にはコピー&削除にフォールバックするような処理を入れました ただし rename に比べてパフォーマンスのペナルティはあるので、大量のファイルを取り扱う HTTP/2 ではできる限り ExternalStorage の境界を跨がないようなディレクトリ構成で設計することをお勧めします
  • #93 次はタイムアウトが発生しない問題です
  • #94 Amazon Cloud Front との通信で、データを全くダウンロードできないにも関わらず、タイムアウトしない問題が発生しました
  • #95 発生頻度がかなり稀だった為に確証を掴むことはできなかったのですが、挙動からコネクション切断時に送信を行う GOAWAY フレームがロストした際の問題と推測を付けました 複数のストリーム処理が絡むので HTTP/2 のコネクションクローズの実装は複雑となりがちです
  • #96 この問題には、独自のタイムアウト処理を追加することにより対応しました タイムアウト判定時にはハンドシェイクからやり直すことになりますが、このパフォーマンスロスは受け入れる方針としました ちなみに、 HTTP/2 の graceful shutdown に対応したサーバ・ライブラリを使っていればこの問題は回避可能です 私達はライブラリの実装担当であり、サーバの選定はタイトル側の開発者が行う、という事情で対応が担保できなかった為に自前実装を選択しています
  • #97 次は iPhone で通信中に不正終了する問題です
  • #98 大量のアセットの一括ダウンロード中、極稀に不正終了する問題が発生しました 検証環境では7万回に一度程度発生しています 数字だけ見るとかなり発生条件がレアに見えますが、取り扱うアセットファイルの数が数千オーダーなので、割とひんぱつする問題と言えます
  • #99 こちらの調査もかなり難航したのですが、最終的には利用している OSS の不具合だと分かりました HTTP/2 の connection reuse 発生時に加え、いくつかの条件を満たした時のみ発生していました いくつかの条件についてはかなり複雑なので割愛しますが、数千~数万ファイルを一気にダウンロードするのはゲームくらいなので、レアなバグを踏むことがあります
  • #100 その後 OSS の更新で無事に問題は修正された レアなバグはあるものの、他社様の大型タイトルで HTTP/2 を採用しているものも見かけるようになって来た為、全体としては枯れていく流れが来ていると思います 時代は HTTP/2 です
  • #101 最後に HTTP/2 独自の設定をしたい、という問題というよりは実装修正の話です
  • #102 私達のライブラリは、現状設計としては 1.5Gbps までを限界の通信速度として設定しています しかし、 5G 時代に備え、もっと速度を出したい、という要望がいつ来るとも限りません そこで、速度改善の方法として、HTTP/2 独自設定のカスタマイズについても検討を行いました 例えば、 ストリームの初期ウィンドウサイズ を変更して通信開始時のパフォーマンスロスを減らしたい、という話や 速度にはあまり影響を与えませんが、 HPACK の Dynamic Table 設定を変更したい、という話も挙がりました
  • #103 しかし、利用している OSS はこれらの設定を内部パラメータとして隠蔽していました 改修して使う手もありましたが、現状まだ速度的には余裕があるのとメンテナンス性を考慮して今のところ対応は諦めています 今後 5G がより一般的に普及し、帯域に余裕が出てきたらまた考え直すかもしれません 皆さんが HTTP/2 を採用する場合は、こうした設定を行いたいか否かを OSS 選定のタイミングで考慮しておくのをお勧めします
  • #104 ここまでは開発者視点で HTTP/2 のメリットを語ってきました この項目では、少し観点を変えて HTTP/2 がインターネットそのものに与える影響について触れてみようと思います
  • #105 まずはコネクション削減による効果です
  • #106 今までの講演でも触れてきましたが、ゲームはコネクションを多数張って各種通信を行う実装になっていることが多くあります アセットダウンロード、ゲームそのものの通信、ランキング等付加情報の通信、チャット等、送受信を行うデータも様々で、それぞれの通信に対して複数のコネクションを張ることも珍しくありません
  • #107 こうした形でコネクションを多数用いると、その分インターネット上に存在する中継器の負荷増大に繋がります
  • #108 普段何気なく使っているインターネット通信ですが、そのリソースは社会全体のインフラ、すなわち資源です こちらはコネクションではなくトラフィックに関するグラフとなりますが、ゲームのオンライン化や動画サイトの一般化、家電をはじめとする様々なものの IoT 化、新型コロナウィルスによる在宅ワークの普及等により、全世界のインターネットには余裕がない状況が続いています
  • #109 これまで何度も触れてきた通り、 HTTP/2 であれば従来の通信に比べてコネクション数を削減可能です 最初に山田さんから説明のあった多重化に加え、特定の条件でコネクションの再利用をする機能もあり、HTTP/1.1 に比べて効率的にコネクションの運用ができます
  • #110 既に非ゲーム分野では HTTP/2 化、果ては同様の効果を持つ HTTP/3 化も始まっており、コネクション数は減少傾向です こちらの資料は Web Almanac が公開している 2016 年から 2020 年の間に web ページ 1 ページあたりの TCP 接続数がどれくらい減少したかを表すグラフです 縦軸が web ページ 1 ページあたりの TCP 接続数、横軸は Percentile で、緑色が 2016 年の、紺色が 2020 年のデータです 中央値で 23 から 13 に、約 44% ものコネクション数が削減されているのがわかります 反面、ゲーム業界全体での HTTP/2 採用率は、前述した通り主要なゲームエンジンで HTTP/2 が標準採用されていないのもあり、まだまだの状況です
  • #111 HTTP/2 の導入は開発者にとってはユーザ体験を向上させ、離脱率の低下等に繋げることができます 更にユーザにとっては待ち時間が減り、インターネット全体の負荷も下げることができます まさしくいいことずくめです
  • #112 ゲーム業界の皆さんも HTTP/2 を使って開発者にもユーザーにもインターネットにも優しいゲーム作りを目指しましょう
  • #113 次にゲームのデータ配信とインターネットのトラフィックに関係する話です
  • #114 昨年の CEDEC 2020 [JANOG×CEDECコラボセッション] インターネットとゲームトラフィック でも話題に上りましたが、最近は AAA タイトルの発売日や大型アップデート日のインターネットトラフィックのスパイクが問題になっています
  • #115 ゲーム業界は社会全体のインフラであるインターネットを使わせてもらっている立場であり、無尽蔵にそのリソースを占有していい訳ではありません そこで、一括ダウンロードはなるべく避け、必要になったタイミングで都度データをダウンロードしてくる方式の採用を提案します 都度ロードが挟まることでユーザの体験が損なわれる恐れがありますが、そこは HTTP/2 で通信を効率化することによりカバー可能です 既にモバイルゲームではこうしたデータのダウンロード形式が一般的なので、非モバイルゲームにおいても当方式の採用を検討して貰えると嬉しく思います モバイルゲームで都度ダウンロード形式でない実装のタイトルの方もご検討お願いします
  • #116 勿論、 FPS のようなリアルタイム性の高い対人ゲームのように都度ダウンロード形式が相性が悪いものは存在しています 反面、RPG や MMO、 MO といったそこまで導入のハードルが高くないジャンルも多くあります 随時ダウンロード形式は管理やデータの差し替えが容易というメリットもありますし、 業界全体でまずは導入が容易なタイトルから改善を行い、その後リアルタイム性が高いゲームへの適用方法も考えていきたいですね
  • #117 最後にまとめです
  • #118 HTTP/2 はアセットダウンロードに効果大です HTTP/2 はクライアントサーバ間の通信の効率化にも効果大です HTTP/2 はインターネットにも優しいです 皆さん HTTP/2 を使いましょう!