モバゲーから提供されているソーシャルゲーム『魔法少女リリカルなのはINNOCENT』は、企画・開発・運用を(株)ユビキタスエンターテインメントが行っています。このサービスを支えるインフラは物理サーバと仮想サーバのハイブリッド構成となっており、(株)IDCフロンティアのクラウドサービスが採用されています。本稿はこの両社から、人気ソーシャルゲームならではのデータを交えながら、DevOps実現に向けての取り組みを披露してもらいます。
前編のおさらい
こんにちは。(株)ユビキタスエンターテインメント(UEI)の水野です。Software Design 2013年11月号に掲載した前編に引き続いて今回は、『魔法少女リリカルなのはINNOCENT』のDevOpsに関して書いていきます。
前編では、ソーシャルゲームに重要な目的と特徴は次のとおりであることと、運営の実際についての事例を書きました。
- 目的
プレイヤーが常に新鮮だと感じるイベントやシステム、成長素材を提供し、安定してゲームを遊んでもらうこと。
- 特徴
- 1ヵ月に数十回の小規模開発=「カード追加」「UI改善」「小機能追加」のリリースが発生すること
- 1ヵ月に数回の中規模開発=「ゲーム内イベント」のリリースが発生すること
- 数ヵ月に一度の大規模開発=「新規ゲームイベント」のリリースが発生すること
- 技術者の調査が必要なユーザサポートへの問い合わせが日に数件~数十件必ず発生すること
今回は弊社で行っているDevOpsへの取り組みを、そして、IDCフロンティアの藤城さんからは、これらを支えているクラウドサービスのインフラについてお話しいただきます。
[Part1:開発サイド]DevOpsを構成する部門要素
本作品でのDevOpsは、「開発(Dev)」「運用(Ops)」「品質保証(QA)」の3部門から成り立っています。前述の目的を達成するには、上記の3部門が互いの立場を理解してコミュニケーションをより多くとることがとても重要です。そのために、どのようなことを行っているかを解説していきたいと思います。
開発するシステムの企画構成をリーンにする
ソーシャルゲームのDevOpsをスムーズに行うためには、まず「企画構成をリーンにする」という意識が重要です。「リーン」とは「引き締まった」という意味で、基本機能をユーザが楽しむために本当に必要な企画のみに絞り、「あるといいんじゃないかな……」や「機能Aと機能Bが連動していると、ちょっと嬉しいよね?」という程度のものは勇気を持って省きます。
たとえば本作では、対CPU戦と対人間戦の機能間は、片方の進行度や勝敗の結果が他方に影響を与えることはありません。報酬も経験値とゲーム内マネー、そしてアイテムに限定されています。しかし企画者としては「対人戦で負けたら、同じようなカード組み合わせが対CPU戦に出現して、勝つまでいろいろと試せる機能があったらおもしろいよね」とか言いたくなります。
が、ここはぐっとガマンです。
もしユーザが本当にそれを求めるのであれば、その内容でイベントを企画・開発すれば良いのです。あまり基本機能を盛りだくさんにしてはいけません。
このことは、ゲーム運営に入ったときに大きなメリットとなって効いてきます。なぜなら、機能的に軽く疎結合であるほど修正や機能追加の範囲を狭くできますから、万が一問題が発生した場合の原因も特定しやすくなるからです。それは目的の1つである「プレイヤーが常に新鮮だと感じるイベントやシステムを安定して提供すること」につながります。
開発チーム構成を考える
企画の次はゲーム運営時のチーム構成を考えます。開発チーム編成が、マイルストーンに対して不足していては新機能を安定して提供していくことはもちろんできません。前述のとおり、本作品では、1ヵ月の間に最低数回のリリースが発生する以上、1チームでマイルストーンをクリアしていくことは難しく、開発チームのマルチライン化が必須です。私たちは開発を3チームに分けて、このスケジュールを次の役割構成で開発を行っています。
- メインイベントの開発
- スキル追加とUI/UX改善
- ユーザサポート
この3つのチームはそれぞれ、
- 企画からの要望による開発
- ゲーム運営からの要望による開発
- ユーザからの要望による開発
に対応しています。これらの開発の中にイベントなどの振り返りで得られた運用(Ops)からの意見も取り込んでいきます。
そして、このチームを数ヵ月ごとにローテーションで役割を変更します。これには次の意味があります。
- 役割が固定されないので飽きにくい
- それぞれの役割の相互理解が深まる
とくに、ユーザサポートを全員が体験することには大きな意味があります。ユーザサポートチームでは、ユーザの問い合わせによって内容を調査し結果を返答しますが、その調査は単純なものでなく、MySQL内のレコードからApacheログまでマッチングした結果でないと返答できないことも少なくありません。ですので、幅広い知識を自然に学習することになります。また、ユーザの生の声やUI要望を聞くことで、それぞれの開発の役割を担うときに、ユーザが理解しやすく質の高いコンテンツや、より調査や追跡がしやすいシステム設計にフィードバックしようという意識を持つことができます。
ファイル構成を分離する
サーバ上に配置するファイル構成は、高頻度でデプロイしてもミスを起こさないように設計しておきます。
全ファイルを1つのディレクトリ以下にまとめる
システム動作に必要なすべてのファイルを1つのディレクトリ以下に集約します。本作品ではPHPを使用していますが、そのライブラリもシステム側にインストールするのではなく、他のコードファイルと並列に格納しておきます(図1)。
これにより、コードと同じようにバージョン管理ができますので、ホスト間でのライブラリバージョン違いなどのわかりにくい問題が少なくなります。
機能別にディレクトリを分ける
本当に基本的なことですが、チームごとに分離して作業しやすくするために機能別にプログラムのディレクトリを分けます(図2)。
プログラムの設定ファイルをサーバに依存しないようにする
プログラムの設定ファイルは各サーバに依存しない記述にしておきます。本案件ではリスト1のように、DBのホスト名などはhostsでの解決にまかせ、設定ファイル上にIPアドレスなどのサーバ固有情報を書かないようにします。
個別の設定ファイルにサーバ固有情報などを書かないでおけば、すべてのサーバで同じ設定ファイルが利用でき、デプロイするすべてのファイルがバージョン管理できることになります。これにより、設定ファイルに記述追加が必要になったときでもデプロイミスを防げます。
インフラ構成は事前準備が重要
インフラ構築にからむ部分はサービス前の設計と事前検討が重要です。リリース後にインフラ構成の変更を行うのは一大事ですので、その備えがあるとないでは余裕が違います。
デプロイ環境専用のバージョニングシステムを用意する
開発用のバージョニングシステムだけでなく、デプロイ環境用のバージョニングシステムを用意してデプロイ履歴を管理します。
直接の手動配布やrsyncを行うことは絶対にせず、各本番サーバ経由でデプロイシステムを利用し、バージョニングシステムからファイルを適用することで更新を行うようにします。頻繁にコミットされる開発用とは分離しておくことで、問題発生時の原因絞り込みをわかりやすくし、巻き戻しやミス反映があっても相互に影響を与えることがありません。
リリース時からログを集約する
後述のユーザサポートや、リリース直後から発生するバグ調査のために、何十台ものWebサーバのアクセス/エラーログが必要になります。Fluentdなどで事前にログ集約をしておくと、いざ必要になったときにとても楽です。
負荷が想定を超えた場合を事前検討する
ソーシャルゲームは負荷との戦いです。想定外の場合でも、開発と運用で事前検討しておくことで協力して素早い対策を打つことができます。
事が起きてから「こうしておけば、スケールアウトできたのに!」では遅すぎます。リリース前に、現状の構成でスケールアウトできないポイントはどこなのか、そこに負荷がかかったらどうするのかを話し合って開発チームにフィードバックして修正しておくことが重要です。
テスト環境は可能な限り本番環境に近づける
テスト環境と本番環境の違いは、本番デプロイ後のバグを引き起こす原因になります。とくにDB回りの環境差はトラブルになりやすい原因なので、次の点が要注意ポイントです。
テスト環境でもDB分割を行う
テスト時にはDBの水平や垂直分割を行わず、本番でのみ分割しているのを見ることがあります。本番でのみ実行されるような動作は可能な限り減らすべきですので、きちんとDB分割された環境でテストすることが重要です。
テスト環境でもマスター/スレーブ構成を用意する
上記と同様、テスト環境でのマスター/スレーブ構成も行っておくと安心です。テストをマスターのみで行っていると、予期せぬ動作やレプリケーション停止を起こす可能性があり、本番反映で慌てることになります。
追跡性を確保する
携帯向けソーシャルゲームでは、デッキ構築やカード合成などの複雑な操作を狭い画面で行うということもあり、根本的にユーザの操作ミスや勘違いが起きやすい環境です。そのため、ユーザサポートには本当のバグと誤操作に基づいた報告が混ざることになりますので、それを判別できるように、すべてのデータを追跡可能にする必要があります。
たとえば本作品では、次のデータがサービスインにさかのぼってすべて追跡可能なログを残しています。
カードの出入り
ユーザが受け取った1枚1枚のカードにはすべて個別のレコードとシリアルIDが割り当てられています。これを使ってその入手方法と、削除されたのであれば論理削除フラグとその理由、その際に別のカードレコードに変化したのであれば、その変化先のカードシリアルIDが保持されています。
たとえば、「XというユーザがAというカードをイベントで手に入れ、それを合成してBを入手し、その後、Yというユーザにトレードで譲渡した」場合は、表1のようなレコードが残ります。これにより、「カードがなくなった」という問い合わせに対しても、すべての操作を追跡することができるのでバグなのかそうでないのかの判断ができます。
表1 カードの状態レコードの例(カラムの一部を変更して抜粋)
シリアルID | ユーザID | カードID | 入手方法 | 削除フラグ | 削除理由ID | 移動先シリアルID | レコード変更時刻 |
1 | X | A | イベント | 削除 | 合成 | 2 | 2013/11/28 00:00 |
2 | X | B | 合成 | 削除 | トレード | 3 | 2013/11/29 03:30 |
3 | Y | B | トレード | 未削除 | | | 2013/11/29 03:30 |
論理削除されたレコードは一定期間後にAWSのS3やGlacierなどのストレージにバックアップのうえで実削除され、必要であればDBに戻して検証を行えるようにしています。
課金ログ
カードと同様に課金ログについても、課金状態(支払処理中なのか、支払い済なのかなど)と購入アイテムの付与状態(付与中か、付与完了したかなど)のフラグを持っていて、これについてもトラッキング可能にしています。
課金は、外部のシステムと連携して行う都合上、予期せぬ異常があることは考えられますので、その場合でも「どの状態まで課金処理が進んだのか」の追跡が行えることが重要です。
開発と品質保証のコミュニケーションを取りやすくする
日々連続的に開発を進めるソーシャルゲームでは、品質保証部門(以下、QA部門)と開発部門との連携も重要です。連携にはバグトラッキングシステム(以下、BTS)を利用しますが、弊社ではこれに「QA部門がレポートを上げやすくする」という目標を設定しています。
弊社では技術的に詳しいテスターが常駐し確認を行っていますが、ゲームとしてのバランスなどを知るのに実際にプレイしている層に近いユーザの意見も取り入れたいため、何人かの学生アルバイトにもデバッグをお願いしています。このためQA部門が利用する報告のためのBTSは「開発者が見やすい」よりは「報告を上げやすい」という観点でシステム選定を行いました。
今までの案件から、学生アルバイトのような層から意見を吸い上げやすくするためには「BTSの習得コストと心理的ハードルが低い」ことが重要だというのが経験的にわかってきました。そのため、数年前から次に挙げるBTSを試験的に導入して、レポート数を比較してみました。
- BugZilla
- 影舞
- Redmine
- 2ch式スレッドフロー掲示板
- Mantis
これらの中で、実際にレポート数が一番多く、気軽に意見をもらえたのは「2ch式スレッドフロー式掲示板」でしたが、報告すべき必須項目が設定できず、また気軽すぎたのかバグと関係ない内容まで投稿されてしまって、BTSとしての収拾がつかなくなることもありました。
そのため次点であった影舞を採用しました。影舞は報告項目のコントロールもしやすく、進捗管理も明確なのが採用の理由です。レポート項目も上記を反映して、「タイトル」「参考URL(ネットの報告に基づく場合)」「再現方法」「スクリーンショット画像ファイル」に絞っています。
それ以外のものは上記2つに比べてレポート数も少なく、画面の印象が開発ツールっぽくて難しいという意見が多いようでした。私たち開発者にとっては使いやすいツールなのですが、レポートがもらえなくては意味がないので今回の選択からは外しています。
これにより小さな意見でも、学生アルバイトでも気になることがあれば報告しやすくなり、その意見を効果的に開発側に上げることが可能になりました。吸い上げられた意見は、DevOps定例にてチームで精査し、開発タスクとして取り入れるべきか否かを判断しています。
まとめ
ソーシャルゲームに限らずデプロイ頻度の高いプロジェクトでは、開発・運用・QAがそれぞれ目的を忘れず、できるだけ共通認識を持つようにし、立場の違いを超えて歩み寄って対話できるようにすることが重要だと考えています。これからも目的に沿って、より質の高いコンテンツを継続的にリリースできるよう努力してまりますので、興味を持っていただけたら、ぜひ『魔法少女リリカルなのはINNOCENT』をプレイしてみてください!
[Part2:クラウド事業者サイド]ソーシャルゲームの恐ろしい負荷
ソーシャルゲームをサーバや運用サイドから見たとき、最も注意し事前に対策を検討しておくべき1つに、急激な負荷への対策が挙げられます。急激な負荷が生じる例として、
- ゲームのサービスリリース当初
- ゲーム内イベントの開始直後と終了間際
- 機器故障や不具合での構成縮退時
と言ったケースが考えられます。このような急激な負荷への対策として、クラウドサービスを利用し、スケーラブルなシステムを構成することが一般的になってきました。フロントのWebサーバやアプリケーションサーバは簡単にスケーラブルな構成を組める一方で、ボトルネックになりやすい、つまりスケールが難しいポイントがおもに2つ挙げられます。1つ目にDBなどのディスクI/O、2つ目にサーバからのトラフィックです。
『魔法少女リリカルなのはINNOCENT』では、(株)IDCフロンティアのクラウドサービスセルフタイプ(以下、IDCFクラウド)を採用していただきましたので、スケールが難しいポイントへの対策をどのようにとられたか、クラウドプラットフォーム側から一部を紹介させていただきます。
数万IOPSをさばくには
クラウドでも速いディスクI/O
最近の各社クラウドサービスでは、IOPSを保証するオプションやフラッシュドライブを用いたディスクを提供する事業者が出てきており、クラウドサービスを利用したとしてもディスクI/Oに対して高いパフォーマンスを出せるようになってきました。
IDCFクラウドの仮想マシンでは、自動階層化にてフラッシュドライブ(SSD)をキャッシュとして利用する機能を搭載したストレージを利用しています。つまり、I/O数が多いディスク領域は自動でフラッシュドライブにキャッシュされます。これにより、仮想マシンはより高パフォーマンスなディスクI/Oを発揮することができます。また、I/O数が多いディスク領域がフラッシュドライブに退避されることで、キャッシュされなかったディスクでも安定したIOPSを得られます。これにより数千~1万IOPS程度は出すことが可能です。データベース用途でも十分な性能を出すことが可能です。
ioDriveはやっぱり速い
本作品でも、構築当初はDBサーバを仮想マシンの構成で進めていました。しかし、サービスリリースを目前にして、ゲームの事前登録の人数が多いことから高負荷によるシステム停止を起こさせないための再検討が行われ、DBにより高いI/O性能が求められてきました。
そこで、DBサーバを仮想マシンからFusionIO ioDriveを搭載した物理サーバへ変更し、構築し直しました。IDCFクラウドでは、物理サーバを選択できるオプション(プライベートコネクト)を提供しており、これを利用しています。ioDriveを利用するメリットとして、仮想マシンと比較して低レイテンシーかつ大量のIOPSを処理できるため、高速なディスクI/Oを手に入れることができます。その性能は他のフラッシュドライブを圧倒し、数万~10万IOPS以上の性能が得られますので、DBサーバとしてディスクI/Oがボトルネックになることは回避できることでしょう。
もう1つのメリットに、仮想マシンのようなパブリック環境ではなく物理サーバ占有環境であるため、ほかからの負荷の影響を受けることによるパフォーマンス低下を避けることができるという点もあります。
1Gbpsオーバーのトラフィックをさばくには
物理サーバと接続したIDCFクラウドでは図3のようなネットワーク構成となります。ここからは、図中に①~③として記したポイントについて紹介します。
高帯域な仮想マシン間通信:①
10Gbps対応機器が普及してきた昨今、各社クラウドサービスでも10Gbpsのネットワークで構成されている環境が出てきています。IDCFクラウドでは、物理環境としては、すべてのネットワークを10Gbpsで構成しています。仮想マシンから見ると、そのスペックタイプに応じて100Mbps、500Mbps、1Gbpsとプランを用意しています。本作品の環境でも使われていますが、オプションで2Gbpsを提供することも可能です。大容量の物理ネットワークと仮想マシンのトラフィックを一定の値で制限することにより、高速かつ安定したネットワークを提供しています。
先述のとおり、DBサーバのディスクI/Oボトルネックが解消することで、サーバ間の通信が増えることが想定されます。また、キャッシュサーバのように通信が集中する用途のサーバもありますので、サーバ間の通信においても安定した高帯域が必要になってきます。
物理サーバが1Gbpsで輻輳(ふくそう)した:②
ioDriveによってディスクI/Oのボトルネックが解消されたDBサーバが、ゲーム内のイベント開始直後に1Gbpsで輻輳するという事態がありました(図4)。物理サーバは1000BASE-Tにて1Gbps接続されており、2つのNICにてBondingのActive-Backup(mode=1)で冗長化しています。つまり、最大1Gbpsまでしか出せませんでした。
このときの回避策は、BondingのBalance-TLB(mode=5)によって「アクティブ-アクティブ」の構成に設定を変更するというものでした。これによってサーバからの送信については1Gbpsを2本使えるようになり、最大2Gbps出せるようになりました。あわせて、データベース側からはMySQLの通信圧縮も設定され、物理サーバからの通信ボトルネックも解消されました。
上位も10Gbps:③
インターネット側の帯域はボトルネックになりやすいポイントです。たとえば、共有の回線だったり、100Mbpsの回線だったりするかもしれません。IDCFクラウドでは、10Gbpsの共有回線を提供していますので帯域としては充分に余裕がありますが、帯域の前に上位のファイアウォール、つまり仮想ルータ[1]の処理性能がボトルネックになる可能性が想定されます。
本作品でもサービスリリース直後から、トラフィックがぐんぐん伸びていきました。図5はサービスリリース直後の仮想ルータのリソース状況です。このままのペースでトラフィックが増えていくと、アクセスのピークの時間帯である22~23時には仮想ルータがボトルネックになる可能性も出てきました。仮想ルータはパケット数や新規コネクションにより、CPUがボトルネックになることがほとんどです。
このとき、ユーザアクセスが多かったのはもちろんですが、コンテンツがCDNのキャッシュに想定したとおりにのらない問題がありました。すぐに修正されてCDNのキャッシュにのりはじめると、そのとき最大で500Mbps近いトラフィックが約半分まで下がりました。実際に今回のゲームに限らず、サービスリリースやメンテナンス直後に、想定どおりにCDNのキャッシュにのらないということはしばしば見かけますので、とくにサービスリリース時には充分な上位の帯域を確保しておくと良いでしょう。
その後、本作品ではイベント終了間際の大量のアクセスに対応するにあたり、1Gbps以上のトラフィックを出すためにファイアウォールを外して、10Gbpsの回線へ直結するネットワークオプションを利用しています。
おわりに
ここまで、クラウド上でもスケールが難しい、ディスクI/Oとネットワーク帯域について、本作品にて実際に行ったプラットフォーム側からの対応について紹介しました。ボトルネックを解消してもいつかは次のボトルネックが出てきます。そのほかにも、開発期間が短く、充分な構築やチューニングができないこともあるかもしれません。そんなときは、ミドルウェアやアプリケーション側からの対策とあわせ、プラットフォーム側からも対策ができないか検討してみてはいかがでしょうか。