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
Scaling GitHub
holman
458
140k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
95
17k
Fireside Chat
paigeccino
34
3.1k
Side Projects
sachag
452
42k
How GitHub (no longer) Works
holman
311
140k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Mobile First: as difficult as doing things right
swwweet
222
9k
For a Future-Friendly Web
brad_frost
175
9.4k
GraphQLとの向き合い方2022年版
quramy
44
13k
Put a Button on it: Removing Barriers to Going Fast.
kastner
59
3.6k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
38
1.9k
Code Reviewing Like a Champion
maltzj
520
39k
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) • 設計変更 >チューニング •
射幸心怖い
まとめ • ソーシャルゲームでは常に想定外の自体が起こりうる • 予測流入数も所詮予測にしか過ぎない • 障害を防ぐことは不可能 • 想定障害のケースは準備しておく •
想定外の事態にも対応できる余裕ある運用体制 • 過度に神経質にならない
是非フィードバックの方、よろしくおねがいします ご清聴ありがとうございました