Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ソーシャルゲームが高負荷に陥っているとき、何が起こっているのか
Search
akihito
September 08, 2018
7
13k
ソーシャルゲームが高負荷に陥っているとき、何が起こっているのか
builderscon 2018 の 9/7 セッションのスライドになります
akihito
September 08, 2018
Tweet
Share
More Decks by akihito
See All by akihito
start_slide
takihito
0
78
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
How GitHub (no longer) Works
holman
310
140k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Optimising Largest Contentful Paint
csswizardry
33
2.9k
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
Into the Great Unknown - MozCon
thekraken
32
1.5k
Site-Speed That Sticks
csswizardry
0
31
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
47
2.1k
GitHub's CSS Performance
jonrohan
1030
460k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
131
33k
Transcript
ソーシャルゲームが高負荷 に見舞われる原因と対策 builderscon 2018 Takeda Akihito
自己紹介 竹田 昭仁 @takihito github.com/takihito
「つくる人を増やす」「面白く働く」
自己紹介 面白法人カヤック 技術部 ゲーム事業部 (2013~
話すこと • スマホアプリでゲームが動く仕組み • 開発と運営チームの体制について • リリースから現在までに起きた問題と対策対応
アジェンダ • ゲームの仕組み • ゲーム制作チームとプロジェクト • 高負荷に至った原因と対策対応 • リリース •
マルチプレイ • アプリに仕込まれていた機能 • 想定外のアクセス上昇
ゲームの仕組み
開発運営チーム • プロデューサー • ディレクター • プロジェクトマネージャー • レベルデザイナー •
デザイナー • イラストレーター • クライアントアプリエンジニア • サーバエンジニア • インフラエンジニア • テスター • プロモーション • カスタマーサポート :
ゲームの開発とは…. コスト周りの話などを少々
開発コスト ・開発期間は2〜3年以上 ・その間にかかるコストは投資 ・リリースして投資を回収できるようになる ↓ 初期開発費の回収を達成 = リクープ
ゲームシステムの構成要素 • スマホアプリ(iOS/Android) • アプリ内で保存管理するデータ • マスターデータ (キャラクター/クエスト/武器/装備) • 素材データ
(画像/音声) • プレイヤー情報 (レベル/進行状況/入手キャラクター) • API (https) • リアルタイム通信 (websocket/photon)
Webapp Server Realtime Server MasterData Image Sound
ゲーム特有のサーバ事情 • 予測と結果 • 構成要素と複雑さ • クライアントアプリ • 施策とアクセス状況の関係 数年に渡り運用したタイトルに起きた事例を元に話します
負荷の原因と対策
事例1 リリース時
リリースまで 多くのチェックポイントを通過しリリースとなります • αテスト • βテスト • 予測流入数の算出 • 負荷試験
• 障害試験 (Failover試験) • QA (インゲーム/アウトゲーム/課金試験) • 運用演習 (リリース/データ更新) : :
リリース時サーバ構成 ・アプリケーションサーバ x N台 ・ DB Server Master x 1
・ Cache Server(Redis) x 1 ・管理用サーバ(ログサーバ兼用)
当初の予測流入数 • 他ゲームタイトルの実績 • βテストのKPI(継続率/ゲームサイクル) • 広告による流入見込み
いよいよリリース
実際の流入数 予測流入数
予測流入数を越えてきたので DB Server • DBのインスタンスタイプを変更しスケールアップ • Master x 1 →
Master x 1 / Slave x 2 • DB SlaveはHAProxyにより分散 WebApp Server • 既にMaster x Slaves構成を考慮し開発をしていた • 事前にピーク時最大アクセス数での負荷試験も終えていた • インスタンスを順次追加(当時はオートスケールではなかった)
想定内!!
めでたしめでたし…とはなりませんでした <「Redshiftの容量が増加しています」
盲点… ココ→管理用サーバ(ログサーバ兼用)
ログ集約の流れ WebApp Server Admin Server Log Server Redshi ft S
3 KPI batch 集計遅延 容量増加 ログ投稿遅延
対策:管理サーバ(ログサーバ) • 過負荷によりログの投稿遅延が発生 • インスタンスタイプを変更しスケールアップ • td-agentプロセスを用途別に分離(アクセスログ/行動ログ)
対応:ログ容量 • Redshiftに行動ログがたまり続ける • 不要なログを削除 • ループしていたログ出力クエリをまとめてポスト
対応:データの圧縮 ・既存のテーブルカラムは未圧縮で定義されていた ・ANALYZE COMPRESSIONで既存テーブルを解析 ・解析に従って圧縮定義(delta/lzo)したテーブルに移行
対応:集計遅延 • テーブル設計ミス • 1カラムに大きめのデータを投入していた • データ圧縮したことで集計時間が改善
None
学び ログ集約はサービスに即座に影響が出るとこでは無いが… • リリース直後はサービス対応に時間がとられ後手に回る • 気軽にスケールアウトやスケールアップしにくい • 全体最適化の視点が欠けていた
事例2 マルチプレイ
マルチプレイ • リアルタイムサーバを介して通信 • 複数ユーザがゲーム状況をリアルタイムで共有 • 一人のユーザの操作が他ユーザにも伝わり同期される
None
Webapp Server Realtime Server Room A Room B RoomAの状況を同期 Room
A Room A Room B Room A Open or Close ? クライアントを介して RoomAの状況を伝える
リリース数日経過… <fujiwara> 昨日のピークでredisだいぶヤバい <fujiwara__> どんなクエリが飛んでるんだろうなあ… <akihito> 昨日のピーク時、山というかビルみたいになっている
None
<acidlemon> keysが時間くってるんじゃん! <acidlemon> keysはredis殺すから本番じゃつかっちゃだめだよ!
None
Redisでkyesを使うと死ぬ
# 公開中のRoom一覧返す my @room_list = $self->redis->keys($self->publish_room . '*'); シングルプロセスであるRedisでkyesを実行するヤバさ
Webapp Server Realtime Server Room A Room B Room A
Room A Room B KeyRoomA: open, members, .. KeyRoomB: close, members, .. keys keys keys
対応:第1手 ・keysをサーバアプリケーションから剥がす ・常駐プロセスを立てkeysの定期実行結果をSetに保存 ・サーバアプリケーションからはSetを参照
Webapp Server Realtime Server Room A Room B Room A
Room A Room B srandmemb er srandmemb er srandmemb er keys worker sad d
投入
対応:第2手 ・Itemが増加傾向なのでkeysもいずれ限界を迎える ↓ ・ZADD + ZRAGEBYSCORE ・SCOREにはepoch timeを利用して時間範囲検索
Webapp Server Realtime Server Room A Room B Room A
Room A Room B srandmember/za dd srandmember/zad d srandmember/zadd zremrangebyscor e worker sad d
事前に露見しなかった? • βテストでは全くマルチプレイが使われていなかった • 負荷試験シナリオもマルチプレイの優先度を下げていた • レビュー検証不足 • とにかく忙しかった βテストの結果を経てマルチプレイ報酬を手厚くしユーザ動線が変化
マルチプレイが相当使われていたのは想定外
学び チーム内外のメンバーに助けられた • リリース直後ということで他でも火を吹いていた • 実際に目と手が足りていなかったので大変助かった • スピードとチームワークが結構楽しい
(fujiwara) リアルISUCONはあれを見つけてから1時間で実装する必要がある…
事例3 クライントに仕込まれていた機能
経緯 運営も順調なので大規模な流入施策を打つことが決定 施策実施前にサーバ側の検証と対策を行う
設計見直し アクセスログからalpを使って現況を分析 新規ユーザの場合に既存ユーザ専用処理をスキップ
予想アクセス数 • 既存ユーザ数 • 新規ユーザ数 • 復帰ユーザ数 • イベントスケジュールの確認 •
広告スケジュールの確認 • 短時間あたりの最大アクセス数を見積もる
負荷試験 • 短時間あたりの最大アクセス数を元に秒間を見積もる • 新規ユーザ/既存ユーザの比率をシュミレート • ユーザのプレイサイクルを再現したスクリプト(シナリオ)を準備 • 本番同等の環境を準備しスクリプトを実行する •
サーバの状況をモニタリングし経過観察
インフラ • インスタンスタイプ(アプリケーションサーバ/DB) • DBとRedisの最大接続数 • ネットワーク転送量 • Photonの最大同時接続数の確認 •
マイクロサービスへの影響 • ELBの暖機
障害対応手順確認 エスカレーション DB キャッシュサーバ :
準備完了
施策実施1週間前 APIへのアクセスパターンが変わっている?! ↓ • 何故か重めのAPIが頻繁に呼ばれている • 基本的にはアプリ立ち上げ時に1度だけ呼ばれるはずだが
重めのAPIとは ユーザの全情報を取ってくるAPI = UserInfo API
うっかりクライアントに実装 • ユーザデータは差分取得しアプリ内でmergeが基本 • しかしユーザ全情報取得が手っ取り早い
サーバ側で対策できるか アプリをバージョンアップする時間は無い • APIを研ぎ澄ます • Slave参照 • キャッシュ • 心の準備
できることは少なかった…
結果
学び サーバ側だけでの対応には限界がある • 今回は大事に至らなかった • アプリ側との認識不一致 • API存在意図の伝承
事例4 想定外のアクセス上昇
某日某時間 過負荷によりオートスケールが発動 アプリケーションサーバが次々と追加
None
そして、ついに… DBが詰まりAPIが502を返し始める 緊急メンテナンス突入
完全に想定外の事態
原因はガチャでした
ガチャ引かれすぎ問題 イベント概要 • ユーザは手持ちポイント持て余していた • ポイントで引けるガチャを投入 • ガチャでレア合成アイテムと限定キャラクターが入手 プレイサイクルが施策後全く変わってしまった チームメンバーもガチャを引きまくってた
自分も引きまくってた
対応方針 • 普段はそれほど過剰に叩かれるAPIでない • 取りえず効果がありそうな事は全部やってみる • なにが決定打であったか確証取れない
問題はDB • MySQL • Slow log • Masterでshow processlistを毎分発行済み
ログから対応を判断 show processlist のログから以下を実施 • Closing tables/Opening tablesがstatusに頻出 • キャッシュヒットミスが起きている可能性
• MySQLのtable_open_cache値を上げる • System Lockがstatusに頻出 • アイテムをカウントしているクエリをSlave参照に
アイテムをカウントしているクエリ? SELECT COUNT(*) FROM user_item where type = 1; •
施策前はレアアイテムだったので手持ちは数個ほどであった • ガチャ施策後一気に行数が増えて1000個を超えるようになった • 結果多くの行を読むクエリが誕生してしまった ↓ • 一部サーバ側でのカウントを停止し、クライアントアプリ側でのカウントを正とする
slow log • min_examined_row_limit=1000を設定していた • レコード数増加によりslow log大量に出力 • 結果Opening Tables/System
Lockが発生している可能性 ↓ min_examined_row_limit=3000に変更しログ出力を抑制
トランザクションログ innodb_flush_log_at_trx_commit=2 一時しのぎではあるが…diskへの同期回数を1sync/sec
メンテ終了サービス再開 • 通常のイベント施策であればピークは1〜2時間 • 今回はガチャが数時間以上に渡って引かれている • さらに障害を聞きつけてアクセス上昇 • みなさま、学校やお仕事はどうしてるのでしょうか
対策 時間稼ぎができたので、本格的な対策を行う
修正方針 • DB上でのアイテムの持ち方が良くない • アイテムN個 = Nレコード ↓ 要件的にN個を1レコードにまとめても問題ない あらゆる所に影響するので大規模改修が必要
アイテム B アイテム A アイテム B アイテム B アイテム B アイテム A x 2 アイテム B
x 3 旧方式:旧テーブル 新方式:新テーブル
修正作戦 • 特定バージョン以降でアイテムの管理方法を新方式に変更 • メンテナンス中に全ユーザのアイテムをバッチで変換し新テーブルに移動 • 本番と同じDBを手配し変換検証を行う • 変換に失敗した場合に備えてクラスタを1セット用意しておく •
アプリケーションのコードを新方式に頑張ってなおす
結果
学び ガチャは偉大な発明 • 計測大切(show processlist /slowlog) • 設計変更 >チューニング •
射幸心怖い
まとめ • ソーシャルゲームでは常に想定外の自体が起こりうる • 予測流入数も所詮予測にしか過ぎない • 障害を防ぐことは不可能 • 想定障害のケースは準備しておく •
想定外の事態にも対応できる余裕ある運用体制 • 過度に神経質にならない
是非フィードバックの方、よろしくおねがいします ご清聴ありがとうございました