builderscon 2018 の 9/7 セッションのスライドになります
ソーシャルゲームが高負荷に見舞われる原因と対策builderscon 2018Takeda Akihito
View Slide
自己紹介竹田 昭仁@takihitogithub.com/takihito
「つくる人を増やす」「面白く働く」
自己紹介面白法人カヤック技術部ゲーム事業部 (2013~
話すこと• スマホアプリでゲームが動く仕組み• 開発と運営チームの体制について• リリースから現在までに起きた問題と対策対応
アジェンダ• ゲームの仕組み• ゲーム制作チームとプロジェクト• 高負荷に至った原因と対策対応• リリース• マルチプレイ• アプリに仕込まれていた機能• 想定外のアクセス上昇
ゲームの仕組み
開発運営チーム• プロデューサー• ディレクター• プロジェクトマネージャー• レベルデザイナー• デザイナー• イラストレーター• クライアントアプリエンジニア• サーバエンジニア• インフラエンジニア• テスター• プロモーション• カスタマーサポート:
ゲームの開発とは….コスト周りの話などを少々
開発コスト・開発期間は2〜3年以上・その間にかかるコストは投資・リリースして投資を回収できるようになる↓初期開発費の回収を達成 = リクープ
ゲームシステムの構成要素• スマホアプリ(iOS/Android)• アプリ内で保存管理するデータ• マスターデータ (キャラクター/クエスト/武器/装備)• 素材データ (画像/音声)• プレイヤー情報 (レベル/進行状況/入手キャラクター)• API (https)• リアルタイム通信 (websocket/photon)
WebappServerRealtimeServerMasterDataImageSound
ゲーム特有のサーバ事情• 予測と結果• 構成要素と複雑さ• クライアントアプリ• 施策とアクセス状況の関係数年に渡り運用したタイトルに起きた事例を元に話します
負荷の原因と対策
事例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の容量が増加しています」
盲点…ココ→管理用サーバ(ログサーバ兼用)
ログ集約の流れWebAppServerAdminServerLog ServerRedshiftS3KPI batch集計遅延容量増加ログ投稿遅延
対策:管理サーバ(ログサーバ)• 過負荷によりログの投稿遅延が発生• インスタンスタイプを変更しスケールアップ• td-agentプロセスを用途別に分離(アクセスログ/行動ログ)
対応:ログ容量• Redshiftに行動ログがたまり続ける• 不要なログを削除• ループしていたログ出力クエリをまとめてポスト
対応:データの圧縮・既存のテーブルカラムは未圧縮で定義されていた・ANALYZE COMPRESSIONで既存テーブルを解析・解析に従って圧縮定義(delta/lzo)したテーブルに移行
対応:集計遅延• テーブル設計ミス• 1カラムに大きめのデータを投入していた• データ圧縮したことで集計時間が改善
学びログ集約はサービスに即座に影響が出るとこでは無いが…• リリース直後はサービス対応に時間がとられ後手に回る• 気軽にスケールアウトやスケールアップしにくい• 全体最適化の視点が欠けていた
事例2マルチプレイ
マルチプレイ• リアルタイムサーバを介して通信• 複数ユーザがゲーム状況をリアルタイムで共有• 一人のユーザの操作が他ユーザにも伝わり同期される
WebappServerRealtimeServerRoomARoomBRoomAの状況を同期RoomARoomARoomBRoom AOpen or Close ?クライアントを介してRoomAの状況を伝える
リリース数日経過…昨日のピークでredisだいぶヤバいどんなクエリが飛んでるんだろうなあ…昨日のピーク時、山というかビルみたいになっている
keysが時間くってるんじゃん!keysはredis殺すから本番じゃつかっちゃだめだよ!
Redisでkyesを使うと死ぬ
# 公開中のRoom一覧返すmy @room_list = $self->redis->keys($self->publish_room . '*');シングルプロセスであるRedisでkyesを実行するヤバさ
WebappServerRealtimeServerRoomARoomBRoomARoomARoomBKeyRoomA: open,members, ..KeyRoomB: close,members, ..keyskeyskeys
対応:第1手・keysをサーバアプリケーションから剥がす・常駐プロセスを立てkeysの定期実行結果をSetに保存・サーバアプリケーションからはSetを参照
WebappServerRealtimeServerRoomARoomBRoomARoomARoomBsrandmembersrandmembersrandmemberkeysworkersadd
投入
対応:第2手・Itemが増加傾向なのでkeysもいずれ限界を迎える↓・ZADD + ZRAGEBYSCORE・SCOREにはepoch timeを利用して時間範囲検索
WebappServerRealtimeServerRoomARoomBRoomARoomARoomBsrandmember/zaddsrandmember/zaddsrandmember/zaddzremrangebyscoreworkersadd
事前に露見しなかった?• βテストでは全くマルチプレイが使われていなかった• 負荷試験シナリオもマルチプレイの優先度を下げていた• レビュー検証不足• とにかく忙しかったβテストの結果を経てマルチプレイ報酬を手厚くしユーザ動線が変化マルチプレイが相当使われていたのは想定外
学びチーム内外のメンバーに助けられた• リリース直後ということで他でも火を吹いていた• 実際に目と手が足りていなかったので大変助かった• スピードとチームワークが結構楽しい
(fujiwara)リアルISUCONはあれを見つけてから1時間で実装する必要がある…
事例3クライントに仕込まれていた機能
経緯運営も順調なので大規模な流入施策を打つことが決定施策実施前にサーバ側の検証と対策を行う
設計見直しアクセスログからalpを使って現況を分析新規ユーザの場合に既存ユーザ専用処理をスキップ
予想アクセス数• 既存ユーザ数• 新規ユーザ数• 復帰ユーザ数• イベントスケジュールの確認• 広告スケジュールの確認• 短時間あたりの最大アクセス数を見積もる
負荷試験• 短時間あたりの最大アクセス数を元に秒間を見積もる• 新規ユーザ/既存ユーザの比率をシュミレート• ユーザのプレイサイクルを再現したスクリプト(シナリオ)を準備• 本番同等の環境を準備しスクリプトを実行する• サーバの状況をモニタリングし経過観察
インフラ• インスタンスタイプ(アプリケーションサーバ/DB)• DBとRedisの最大接続数• ネットワーク転送量• Photonの最大同時接続数の確認• マイクロサービスへの影響• ELBの暖機
障害対応手順確認エスカレーションDBキャッシュサーバ:
準備完了
施策実施1週間前APIへのアクセスパターンが変わっている?!↓• 何故か重めのAPIが頻繁に呼ばれている• 基本的にはアプリ立ち上げ時に1度だけ呼ばれるはずだが
重めのAPIとはユーザの全情報を取ってくるAPI = UserInfo API
うっかりクライアントに実装• ユーザデータは差分取得しアプリ内でmergeが基本• しかしユーザ全情報取得が手っ取り早い
サーバ側で対策できるかアプリをバージョンアップする時間は無い• APIを研ぎ澄ます• Slave参照• キャッシュ• 心の準備できることは少なかった…
結果
学びサーバ側だけでの対応には限界がある• 今回は大事に至らなかった• アプリ側との認識不一致• API存在意図の伝承
事例4想定外のアクセス上昇
某日某時間過負荷によりオートスケールが発動アプリケーションサーバが次々と追加
そして、ついに…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)• 設計変更 >チューニング• 射幸心怖い
まとめ• ソーシャルゲームでは常に想定外の自体が起こりうる• 予測流入数も所詮予測にしか過ぎない• 障害を防ぐことは不可能• 想定障害のケースは準備しておく• 想定外の事態にも対応できる余裕ある運用体制• 過度に神経質にならない
是非フィードバックの方、よろしくおねがいしますご清聴ありがとうございました