Slide 1

Slide 1 text

ソーシャルゲームが高負荷 に見舞われる原因と対策 builderscon 2018 Takeda Akihito

Slide 2

Slide 2 text

自己紹介 竹田 昭仁 @takihito github.com/takihito

Slide 3

Slide 3 text

「つくる人を増やす」「面白く働く」

Slide 4

Slide 4 text

自己紹介 面白法人カヤック 技術部 ゲーム事業部 (2013~

Slide 5

Slide 5 text

話すこと • スマホアプリでゲームが動く仕組み • 開発と運営チームの体制について • リリースから現在までに起きた問題と対策対応

Slide 6

Slide 6 text

アジェンダ • ゲームの仕組み • ゲーム制作チームとプロジェクト • 高負荷に至った原因と対策対応 • リリース • マルチプレイ • アプリに仕込まれていた機能 • 想定外のアクセス上昇

Slide 7

Slide 7 text

ゲームの仕組み

Slide 8

Slide 8 text

開発運営チーム • プロデューサー • ディレクター • プロジェクトマネージャー • レベルデザイナー • デザイナー • イラストレーター • クライアントアプリエンジニア • サーバエンジニア • インフラエンジニア • テスター • プロモーション • カスタマーサポート :

Slide 9

Slide 9 text

ゲームの開発とは…. コスト周りの話などを少々

Slide 10

Slide 10 text

開発コスト ・開発期間は2〜3年以上 ・その間にかかるコストは投資 ・リリースして投資を回収できるようになる ↓ 初期開発費の回収を達成 = リクープ

Slide 11

Slide 11 text

ゲームシステムの構成要素 • スマホアプリ(iOS/Android) • アプリ内で保存管理するデータ • マスターデータ (キャラクター/クエスト/武器/装備) • 素材データ (画像/音声) • プレイヤー情報 (レベル/進行状況/入手キャラクター) • API (https) • リアルタイム通信 (websocket/photon)

Slide 12

Slide 12 text

Webapp Server Realtime Server MasterData Image Sound

Slide 13

Slide 13 text

ゲーム特有のサーバ事情 • 予測と結果 • 構成要素と複雑さ • クライアントアプリ • 施策とアクセス状況の関係 数年に渡り運用したタイトルに起きた事例を元に話します

Slide 14

Slide 14 text

負荷の原因と対策

Slide 15

Slide 15 text

事例1 リリース時

Slide 16

Slide 16 text

リリースまで 多くのチェックポイントを通過しリリースとなります • αテスト • βテスト • 予測流入数の算出 • 負荷試験 • 障害試験 (Failover試験) • QA (インゲーム/アウトゲーム/課金試験) • 運用演習 (リリース/データ更新) : :

Slide 17

Slide 17 text

リリース時サーバ構成 ・アプリケーションサーバ x N台 ・ DB Server Master x 1 ・ Cache Server(Redis) x 1 ・管理用サーバ(ログサーバ兼用)

Slide 18

Slide 18 text

当初の予測流入数 • 他ゲームタイトルの実績 • βテストのKPI(継続率/ゲームサイクル) • 広告による流入見込み

Slide 19

Slide 19 text

いよいよリリース

Slide 20

Slide 20 text

実際の流入数 予測流入数

Slide 21

Slide 21 text

予測流入数を越えてきたので DB Server • DBのインスタンスタイプを変更しスケールアップ • Master x 1 → Master x 1 / Slave x 2 • DB SlaveはHAProxyにより分散 WebApp Server • 既にMaster x Slaves構成を考慮し開発をしていた • 事前にピーク時最大アクセス数での負荷試験も終えていた • インスタンスを順次追加(当時はオートスケールではなかった)

Slide 22

Slide 22 text

想定内!!

Slide 23

Slide 23 text

めでたしめでたし…とはなりませんでした <「Redshiftの容量が増加しています」

Slide 24

Slide 24 text

盲点… ココ→管理用サーバ(ログサーバ兼用)

Slide 25

Slide 25 text

ログ集約の流れ WebApp Server Admin Server Log Server Redshi ft S 3 KPI batch 集計遅延 容量増加 ログ投稿遅延

Slide 26

Slide 26 text

対策:管理サーバ(ログサーバ) • 過負荷によりログの投稿遅延が発生 • インスタンスタイプを変更しスケールアップ • td-agentプロセスを用途別に分離(アクセスログ/行動ログ)

Slide 27

Slide 27 text

対応:ログ容量 • Redshiftに行動ログがたまり続ける • 不要なログを削除 • ループしていたログ出力クエリをまとめてポスト

Slide 28

Slide 28 text

対応:データの圧縮 ・既存のテーブルカラムは未圧縮で定義されていた ・ANALYZE COMPRESSIONで既存テーブルを解析 ・解析に従って圧縮定義(delta/lzo)したテーブルに移行

Slide 29

Slide 29 text

対応:集計遅延 • テーブル設計ミス • 1カラムに大きめのデータを投入していた • データ圧縮したことで集計時間が改善

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

学び ログ集約はサービスに即座に影響が出るとこでは無いが… • リリース直後はサービス対応に時間がとられ後手に回る • 気軽にスケールアウトやスケールアップしにくい • 全体最適化の視点が欠けていた

Slide 32

Slide 32 text

事例2 マルチプレイ

Slide 33

Slide 33 text

マルチプレイ • リアルタイムサーバを介して通信 • 複数ユーザがゲーム状況をリアルタイムで共有 • 一人のユーザの操作が他ユーザにも伝わり同期される

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Webapp Server Realtime Server Room A Room B RoomAの状況を同期 Room A Room A Room B Room A Open or Close ? クライアントを介して RoomAの状況を伝える

Slide 36

Slide 36 text

リリース数日経過… 昨日のピークでredisだいぶヤバい どんなクエリが飛んでるんだろうなあ… 昨日のピーク時、山というかビルみたいになっている

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

keysが時間くってるんじゃん! keysはredis殺すから本番じゃつかっちゃだめだよ!

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Redisでkyesを使うと死ぬ

Slide 41

Slide 41 text

# 公開中のRoom一覧返す my @room_list = $self->redis->keys($self->publish_room . '*'); シングルプロセスであるRedisでkyesを実行するヤバさ

Slide 42

Slide 42 text

Webapp Server Realtime Server Room A Room B Room A Room A Room B KeyRoomA: open, members, .. KeyRoomB: close, members, .. keys keys keys

Slide 43

Slide 43 text

対応:第1手 ・keysをサーバアプリケーションから剥がす ・常駐プロセスを立てkeysの定期実行結果をSetに保存 ・サーバアプリケーションからはSetを参照

Slide 44

Slide 44 text

Webapp Server Realtime Server Room A Room B Room A Room A Room B srandmemb er srandmemb er srandmemb er keys worker sad d

Slide 45

Slide 45 text

投入

Slide 46

Slide 46 text

対応:第2手 ・Itemが増加傾向なのでkeysもいずれ限界を迎える ↓ ・ZADD + ZRAGEBYSCORE ・SCOREにはepoch timeを利用して時間範囲検索

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

事前に露見しなかった? • βテストでは全くマルチプレイが使われていなかった • 負荷試験シナリオもマルチプレイの優先度を下げていた • レビュー検証不足 • とにかく忙しかった βテストの結果を経てマルチプレイ報酬を手厚くしユーザ動線が変化 マルチプレイが相当使われていたのは想定外

Slide 49

Slide 49 text

学び チーム内外のメンバーに助けられた • リリース直後ということで他でも火を吹いていた • 実際に目と手が足りていなかったので大変助かった • スピードとチームワークが結構楽しい

Slide 50

Slide 50 text

(fujiwara) リアルISUCONはあれを見つけてから1時間で実装する必要がある…

Slide 51

Slide 51 text

事例3 クライントに仕込まれていた機能

Slide 52

Slide 52 text

経緯 運営も順調なので大規模な流入施策を打つことが決定 施策実施前にサーバ側の検証と対策を行う

Slide 53

Slide 53 text

設計見直し アクセスログからalpを使って現況を分析 新規ユーザの場合に既存ユーザ専用処理をスキップ

Slide 54

Slide 54 text

予想アクセス数 • 既存ユーザ数 • 新規ユーザ数 • 復帰ユーザ数 • イベントスケジュールの確認 • 広告スケジュールの確認 • 短時間あたりの最大アクセス数を見積もる

Slide 55

Slide 55 text

負荷試験 • 短時間あたりの最大アクセス数を元に秒間を見積もる • 新規ユーザ/既存ユーザの比率をシュミレート • ユーザのプレイサイクルを再現したスクリプト(シナリオ)を準備 • 本番同等の環境を準備しスクリプトを実行する • サーバの状況をモニタリングし経過観察

Slide 56

Slide 56 text

インフラ • インスタンスタイプ(アプリケーションサーバ/DB) • DBとRedisの最大接続数 • ネットワーク転送量 • Photonの最大同時接続数の確認 • マイクロサービスへの影響 • ELBの暖機

Slide 57

Slide 57 text

障害対応手順確認 エスカレーション DB キャッシュサーバ :

Slide 58

Slide 58 text

準備完了

Slide 59

Slide 59 text

施策実施1週間前 APIへのアクセスパターンが変わっている?! ↓ • 何故か重めのAPIが頻繁に呼ばれている • 基本的にはアプリ立ち上げ時に1度だけ呼ばれるはずだが

Slide 60

Slide 60 text

重めのAPIとは ユーザの全情報を取ってくるAPI = UserInfo API

Slide 61

Slide 61 text

うっかりクライアントに実装 • ユーザデータは差分取得しアプリ内でmergeが基本 • しかしユーザ全情報取得が手っ取り早い

Slide 62

Slide 62 text

サーバ側で対策できるか アプリをバージョンアップする時間は無い • APIを研ぎ澄ます • Slave参照 • キャッシュ • 心の準備 できることは少なかった…

Slide 63

Slide 63 text

結果

Slide 64

Slide 64 text

学び サーバ側だけでの対応には限界がある • 今回は大事に至らなかった • アプリ側との認識不一致 • API存在意図の伝承

Slide 65

Slide 65 text

事例4 想定外のアクセス上昇

Slide 66

Slide 66 text

某日某時間 過負荷によりオートスケールが発動 アプリケーションサーバが次々と追加

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

そして、ついに… DBが詰まりAPIが502を返し始める 緊急メンテナンス突入

Slide 69

Slide 69 text

完全に想定外の事態

Slide 70

Slide 70 text

原因はガチャでした

Slide 71

Slide 71 text

ガチャ引かれすぎ問題 イベント概要 • ユーザは手持ちポイント持て余していた • ポイントで引けるガチャを投入 • ガチャでレア合成アイテムと限定キャラクターが入手 プレイサイクルが施策後全く変わってしまった チームメンバーもガチャを引きまくってた

Slide 72

Slide 72 text

自分も引きまくってた

Slide 73

Slide 73 text

対応方針 • 普段はそれほど過剰に叩かれるAPIでない • 取りえず効果がありそうな事は全部やってみる • なにが決定打であったか確証取れない

Slide 74

Slide 74 text

問題はDB • MySQL • Slow log • Masterでshow processlistを毎分発行済み

Slide 75

Slide 75 text

ログから対応を判断 show processlist のログから以下を実施 • Closing tables/Opening tablesがstatusに頻出 • キャッシュヒットミスが起きている可能性 • MySQLのtable_open_cache値を上げる • System Lockがstatusに頻出 • アイテムをカウントしているクエリをSlave参照に

Slide 76

Slide 76 text

アイテムをカウントしているクエリ? SELECT COUNT(*) FROM user_item where type = 1; • 施策前はレアアイテムだったので手持ちは数個ほどであった • ガチャ施策後一気に行数が増えて1000個を超えるようになった • 結果多くの行を読むクエリが誕生してしまった ↓ • 一部サーバ側でのカウントを停止し、クライアントアプリ側でのカウントを正とする

Slide 77

Slide 77 text

slow log • min_examined_row_limit=1000を設定していた • レコード数増加によりslow log大量に出力 • 結果Opening Tables/System Lockが発生している可能性 ↓ min_examined_row_limit=3000に変更しログ出力を抑制

Slide 78

Slide 78 text

トランザクションログ innodb_flush_log_at_trx_commit=2 一時しのぎではあるが…diskへの同期回数を1sync/sec

Slide 79

Slide 79 text

メンテ終了サービス再開 • 通常のイベント施策であればピークは1〜2時間 • 今回はガチャが数時間以上に渡って引かれている • さらに障害を聞きつけてアクセス上昇 • みなさま、学校やお仕事はどうしてるのでしょうか

Slide 80

Slide 80 text

対策 時間稼ぎができたので、本格的な対策を行う

Slide 81

Slide 81 text

修正方針 • DB上でのアイテムの持ち方が良くない • アイテムN個 = Nレコード ↓ 要件的にN個を1レコードにまとめても問題ない あらゆる所に影響するので大規模改修が必要

Slide 82

Slide 82 text

アイテム B アイテム A アイテム B アイテム B アイテム B アイテム A x 2 アイテム B x 3 旧方式:旧テーブル 新方式:新テーブル

Slide 83

Slide 83 text

修正作戦 • 特定バージョン以降でアイテムの管理方法を新方式に変更 • メンテナンス中に全ユーザのアイテムをバッチで変換し新テーブルに移動 • 本番と同じDBを手配し変換検証を行う • 変換に失敗した場合に備えてクラスタを1セット用意しておく • アプリケーションのコードを新方式に頑張ってなおす

Slide 84

Slide 84 text

結果

Slide 85

Slide 85 text

学び ガチャは偉大な発明 • 計測大切(show processlist /slowlog) • 設計変更 >チューニング • 射幸心怖い

Slide 86

Slide 86 text

まとめ • ソーシャルゲームでは常に想定外の自体が起こりうる • 予測流入数も所詮予測にしか過ぎない • 障害を防ぐことは不可能 • 想定障害のケースは準備しておく • 想定外の事態にも対応できる余裕ある運用体制 • 過度に神経質にならない

Slide 87

Slide 87 text

是非フィードバックの方、よろしくおねがいします ご清聴ありがとうございました