イベント

スマホゲーム開発最前線を支える技術! 知っておくとリスクが回避できるノウハウ大公開|ヒカ☆ラボレポート

  • この記事をシェアする
  • 0
  • 1
  • 0
hikalab_title

山口-yamaguchi こんにちは、山口です。

ヒットすればするほど安定したインフラ基盤を求められるスマホゲーム業界。守られない規約、成功しないビルド、起動しないアプリ……開発の壁は尽きることはありません。

今回は、そんなスマホゲーム業界の最前線を走る株式会社アカツキのエンジニア3名をお迎えしたイベントの内容をレポート。大ヒットアプリの開発を支えるノウハウを大公開します!

大トラフィックのサーバーサイド、ネイティブアプリのゲーム開発やNode.jsに興味のある方、必見です!

★企業情報
株式会社アカツキ
・スマートフォンサービスやソーシャルアプリの企画開発会社
・ソーシャルゲーム主要タイトル
【シンデレラナイン】・【シンデレライレブン】・【サウザンドメモリーズ】
http://aktsk.jp/

★講演者プロフィール
(1)田中 勇輔氏(@csouls)
CTO ハッカーLv.6
Rubyとインフラが好物で、アプリ開発と並行してアカツキのインフラ基盤を整備してきました。 将来はWeb技術を用いて人類社会の富を大きく増加させるサービスを作り出すことが夢です。現在は、アカツキのCTOとして、エンジニアが気持ちよく働けるための環境づくりにも力を入れています。

(2)宮島 亮氏(@sergeant-wizard)
DvorakとTiling Window Managerが好きです。最近Hubotで遊んでます。Dvorak配列に変えたら家族が増え、良い転職もできました。

(3)谷口 大樹氏(@kidach1)
NodeとRubyが好きです。最近はTypeScriptに興味あり。QwertyからDvorak配列に変えてから腱鞘炎が治り、身長も伸び、彼女もできました。

田中 勇輔 氏

RoRとAWSを使って超えた1分間で10万リクエスト処理するインフラ基盤構築の壁

株式会社アカツキのCTO、田中勇輔です。
今日は、Ruby on Rails(RoR)とAWSで1分間で10万リクエストを処理するにはどうすればいいか?ということについてお話しさせていただきます。

RESTなAPIサーバであるRoRを選んだきっかけ

そもそも「なぜRoRを選んだのか?」という理由は、ずばりRoRなら社内の既存資産を活かすことを考えたからです。既存資産というのは、認証や課金のライブラリ、環境構築やデプロイの仕組みといったものです。学習コストはかかるものの、RoRがあれば大抵のことができるので、企業としてはとても楽です。

4

RoR、AWSで1分間に10万リクエストを処理するために必要な3つのこと

RoR、AWSで1分間に10リクエストを処理しようとした場合、3つのポイントを押さえておく必要があります。

その3つとは
・スケールアウト戦略
・負荷テスト
・地雷をうまく避ける
です。

7

大量のリクエストがあっても、スケールアウトしておけば怖くない

前もってスケールアウト構築しておけば、急激にリクエストが増加してもしっかりと対応することが可能です。もちろん、スケールアウトしたアプリケーションサーバの構成管理やデプロイ方法についてはきちんと考えておく必要があります。基本的には「アプリケーション構成+デプロイ」で対応するということです。

DBはRDBを用いました。一般的なWebサービスであれば、DBパラメータはRDSデフォルトでほぼ問題ありません。この辺りのデフォルト値のチューニングはうまくバランスを取っていてAWSはすごいと思います。

RoRのShardingはOctopusを使用しました。構成は、ゲーム共通のデータを管理する単一の共通DBと、ユーザの資産を管理する複数のユーザDBで分かれています。ユーザDBのShardingは、config/shards.yml を用意して、リクエスト単位(コントローラーレベル)にOctopus.usingで接続先を指定しています。

そうすると、共通DBへのアクセスは、ActiveRecord レベルでusing(:master)を指定することになります。ここで毎回接続先を指定するのは辛いので、共通データ系のモデルにはusing(:master)を指定したクラスから継承させたいという発想が出てくると思います。が、ここにはOctopusの難点があって、「継承したクラスでoctopus_establish_connection がうまくいかない」という問題があります。

最終的には「毎回using(:master)を書く」という対処に落ち着きました。それは、ゲーム共通データもUser Shardに置いてしまうと、データ更新のトランザクションが完了するタイミングがズレてしまうからです。また、一部のDBのみ失敗してしまう可能性もあります。

しかし、実際やってみて、ゲーム共通データもUser Shardに置く方が辛くなかったと思います。データ更新のズレはなるべくコミットするタイミングをあわせることで少なくできます。DB単位の整合性はとれるので、データ更新の失敗もすぐに対応すれば大きな問題にはならないはずです。その対応コストよりも、毎回using(:master)を指定するコストの方が大きかったかなと今は考えています。

スケールアウト戦略は、やはり設計段階が重要です。その理由は、例えば、今お話ししたようなOctopusを後で導入しようと思っても、全てのDB接続に関するコードを正しく書き換えるのは正直辛いものがあるからです。ただ、全てにおいてスケールアウト戦略を立てる必要はなく、2万Req/minを越えた辺りから検討しても良いのでは?と思っています。

22

負荷テスト

負荷テストで一番使われているものといえばJMeterですね。ただ、JMeterのjmxスクリプトを作るのは中々大変な作業です。そのような時はジェネレータを作ってしまえばいいのですが、Rubyの世界にはruby-jmeterという便利なGemが存在します。ruby-jmeterで、jmxスクリプトが簡単に作れるようになり、負荷テストスクリプトのメンテナンスもすごく楽になります。

例えば、スレッド数の指定やリクエストヘッダの指定、リクエストbodyの指定などがスムーズにできるようになるのが良いです。負荷テストは運用中でも役に立つものなので、ぜひ継続的に開発できる仕組みを整えていきましょう。

30

RoR、Radisに関する失敗談から学ぶ「地雷をうまく避ける」方法

地雷をうまく避けるにはどうすればよいか?一番よいのは、他者の失敗から学ぶことです。ですから、ここでは自分達が失敗したことと、その対策についてお話したいと思います。

<失敗1>
RoR 4.1/Arel 5.0 でコネクション切断時にスキーマキャッシュが使われない

Railsのコネクションプーリングをそのまま使うと、UserShardの数 * プロセス数 * ConnectionPool数のコネクションが張りっぱなしになって、使いまわされます。そのリソースを節約するために、sonots/activerecord-refresh_connection を利用していたのですが、リクエストの度に、SHOW FULL FIELDS FROM ~~ が発行されていました。

そのため、急激な負荷が発生した時に、共通データDBのCPU負荷が高くなりすぎるという問題がありました。当時は「SHOW FULL FIELDS FROM ~~」の発生原因を潰していく時間的な余裕がなかったので、sonots/activerecord-refresh_connectionを止まるという選択をしました。

これから踏む人は少ないと思いますが、対処法としては、「winebarrelさんのarel_columns_hash を使う」もしくは,「Arel 7.0のバージョンを使う」です。

42

<失敗2>
Redis: KEYS patternを使っていた

RedisではO(N)以上の計算量のコマンドを使うときは十分注意する必要があります。KEYS patternの計算量は、O(N)なのですが、もっと悪いことに、全てのマッチするKEYを取得しなければならないので、全てのShardにリクエストが送られます。この負荷に対しては、サーバを増やしても意味がありません。

当時原因をきちんと特定せずに急遽サーバを増やすということをやってしまい、64台にもなってしまいました。本当は8台で十分な負荷量です。コードレビューでの漏れや、負荷テストにおけるRedisのデータ量チェック漏れ、深夜の(思考能力が低下している状態での)緊急対応ということが重なった結果の悲劇です。

データ量が多い前提の負荷テストはやると思いますが、キャッシュサーバは漏れがちだと思いますので、特にRedisには注意してもらうのが良いと思います。

最後に

「RoRとAWSで1分間で10万リクエストを処理するために必要なこと」として、「スケールアウト戦略」「負荷テスト」「地雷をうまく避ける」の3つを紹介しました。特に、「負荷テスト」は「地雷をうまく避ける」ことにも通じてきますので、ぜひ押さえていただければと思います。今日はありがとうございました。

55