2016/11/03 PHPカンファレンス 2016
View Slide
© Cygames, Inc.今回お話したいことCygamesのアプリはピーク時には秒間5万アクセス100万クエリを捌く必要がありますサーバーサイドは、最⾼高のコンテンツを開発し、それを⾼高速かつ安定的に提供することを⽬目指しています。今回はそのサーバー構成/技術や、どのような価値観で開発しているかと、⾼高速化の取り組みを紹介します!
© Cygames, Inc.今回お話したいこと• 前半• Cygamesのサーバー構成/技術スタック• コンテンツ開発に対してのマインドセット• 負荷分散/⾼高速化/リリースでの取り組み• 後半• ⾼高速化のためZephir導⼊入検討• Zephirの概要と導⼊入• 運⽤用してみてとこれから
© Cygames, Inc.⾃自⼰己紹介• ⼩小笠笠原空宙(たかひろ)• サーバーサイドエンジニーア• ほぼPHP• ちょっとperl• 2014年年7⽉月 Cygames join• その前はモバイルサイト/ソーシャルゲーム開発
© Cygames, Inc.© Cygames, Inc.5ちょっと紹介
© Cygames, Inc.
© Cygames, Inc.© Cygames, Inc.
© Cygames, Inc.© Cygames, Inc.MySQLに変わるDB「 CySQL 」を開発中!•シャドーバースポータルで⼀一部稼働済み
© Cygames, Inc.5000万DL300万⼈人プレイ/daily
© Cygames, Inc.とあるアプリの現状• ⽉月間300億PV• ピーク時、秒間5万アクセス• ピーク時、秒間100万クエリ• 1⽇日のDBのトランザクションデータ増加量量• 1〜~2TB• 1⽇日のログデータ増加量量• 60GB〜~
© Cygames, Inc.Cygamesを⽀支えるサーバーサイド• サーバー構成/技術スタック• 何を、どのように利利⽤用しているのか• 開発スタイル/マインドセット• どのような価値観で開発しているか• 負荷分散/⾼高速化/リリースでの取り組み• 開発~∼運⽤用までの取り組み• 今/これからの取り組み• 今後どう前進しようとしているか
© Cygames, Inc.© Cygames, Inc.15とあるサーバー構成例例
© Cygames, Inc.とあるサーバー構成例例(⼤大雑把な)Batch PHPNode.jsPHP-‐‑‒FPMNginxMySQLMemcachedredis
© Cygames, Inc.とあるアプリの周辺技術
© Cygames, Inc.とあるアプリの周辺技術AnsibleVagrantMuninMackerelJenkinsNew RelicGoogle BigQueryFluentdElasticsearchKibana
© Cygames, Inc.© Cygames, Inc.25開発スタイルマインドセット
© Cygames, Inc.© Cygames, Inc.26⾯面⽩白くなければ意味がない
© Cygames, Inc.⾯面⽩白くなければ意味がない• ⾯面⽩白さにゴールはない…
© Cygames, Inc.⾯面⽩白くなければ意味がない• ⾯面⽩白さにゴールはない…• 素早くトライ&エラーを繰り返す• 素早く微調整を繰り返す• それが⾏行行いやすい実装• ⾯面⽩白くなければばっさり捨てることも厭わない
© Cygames, Inc.© Cygames, Inc.29CS最優先
© Cygames, Inc.CS(カスタマーサポート)最優先• お客様からの問い合わせには即対応• 即対応できるようなログの設計• ログの可視化ツールの構築• 本番環境のデータを複製し、実機で動きを確認できる検証環境• 確証を持って対応できるように
© Cygames, Inc.© Cygames, Inc.31当たり前のことを当たり前にやる
© Cygames, Inc.当たり前のことを当たり前にやる• ⼩小さな差がピーク時に⼤大きな差に• 不不必要な処理理は書かない• 不不必要なデータは取得しない• 適切切なリファクタリング• 運⽤用し易易い実装、拡張しやすい実装• ⽇日々の積み重ねが、良良くも悪くもピーク時に顕在化する
© Cygames, Inc.当たり前のことを当たり前にやる• 当たり前のレベルがスピードにつながる• ほぼ毎⽇日複数回デプロイ• ほぼ常にイベント開催• 新機能追加、機能改善、etc• PJに関わる⼈人間も多くなる• 当たり前のことを増やし、積み上げることでチームのレベルを上げていく
© Cygames, Inc.© Cygames, Inc.34負荷分散/⾼高速化/リリースでの取り組み
© Cygames, Inc.秒間5万アクセス秒間5万アクセスのアクセスを捌くため負荷分散/処理理の⾼高速化はサーバーサイドの最重要課題!
© Cygames, Inc.負荷分散/スケールアウトの取り組み• DBは⽔水平、垂直分割• キャッシュはTwemproxyで分散• Node.jsはNginxでバランシング• 各種ログは⾮非同期で保存• バッチ処理理できるものはバッチ処理理へ• 分散/スケールアウトできるように• できるだけキャッシュ• 同期処理理は必要最低限に
© Cygames, Inc.© Cygames, Inc.37⾼高速化の取り組み
© Cygames, Inc.プロファイリングの取り組み• 「推測するな、計測せよ」• New relicで解析• 以前はxhprofなど• 重いクエリ、最適化できる処理理などを地道に対応• FW側の不不要な処理理なども最適化• 4.5秒以上かかったリクエストを⽇日次で集計、通知• 検知 ⇔ 最適化の地道な対応イベント開始><;負荷対策リリース\(^o^)/レスポンスタイム推移
© Cygames, Inc.DB関連での取り組み• ボトルネックの多くはDB関連• トランザクション中の処理理は最⼩小限に• まとめられるクエリはまとめる• indexはカーディナリティが⾼高い順に• ⽇日次で開発環境で実⾏行行された全クエリをexplain• 問題のありそうなクエリは通知
© Cygames, Inc.DB関連での取り組み• 本番DBのGeneral_̲Log,Slow_̲Queryも解析
© Cygames, Inc.DB関連での取り組み• 本番DBのGeneral_̲Log,Slow_̲Queryも解析• テーブル単位でサマリー• ロック、処理理件数など確認できる
© Cygames, Inc.DB関連での取り組み• 本番DBのGeneral_̲Log,Slow_̲Queryも解析• 実際に実⾏行行されたクエリを⼀一覧• 渡されたパラメータ等確認• 運⽤用の過程でレコードの偏りなどで不不適切切なindexが選択されることも
© Cygames, Inc.DB関連での取り組み• 本番DBのGeneral_̲Log,Slow_̲Queryも解析• 対象クエリのexplain表⽰示• index,type,partition など確認
© Cygames, Inc.DB関連での取り組み• とにかくデータがもりもり増えていく• トランザクションデータは毎⽇日1~∼2TB単位で増加• CS/分析⽤用途の⾏行行動ログも毎⽇日60GB〜~で増加• 必要なくなったデータは順次パージしないとディスク容量量を圧迫してしまう
© Cygames, Inc.DB関連での取り組み• 必要なくなったデータは順次パージ• イベント単位でテーブル作成、終了了後にdrop• パーティション単位で毎⽇日drop&add• 不不要なテーブルは週次でアラートを通知• 常に必要なデータのみを保持するように
© Cygames, Inc.DB関連での取り組み• 数⼗十GBのテーブルを安全にdropする• データファイルが⼤大きいとファイル削除時、io_̲waitで数秒間ではあるがレスポンス劣劣化i. drop対象のデータファイルにhard link貼るii. drop table実⾏行行iii. hard linkしたファイルのサイズを切切り詰めた後に、remove(linuxのtruncateコマンド)• ゆるやかに削除することで数百テーブル、合計数百GBのテーブル削除もレスポンス劣劣化なく実⾏行行
© Cygames, Inc.リリース時の取り組み• リリース前には職種/チーム問わずレビュー• 全スタッフ ≒ ゲームユーザ• プランナー/エンジニア/デザイナ/バックオフィス• ⾯面⽩白いと確信が持てるまでブラッシュアップ• リリース前には実際に運⽤用テスト• 各種更更新作業などの通常運⽤用リハーサル• 緊急デプロイなどの障害対応フローなども確認
© Cygames, Inc.© Cygames, Inc.48今/これからの取り組み
© Cygames, Inc.今/これからの取り組み• PHP7対応• FWのcoreを拡張させている箇所の対応• 各種extensionの対応状況の調査• 品質と⽣生産性の向上、仕組みづくり• ゲーム特有の問題を検知できる仕組み• ユニット/E2Eテストだけではない何か• Zephirによる⾼高速化/共通化• この後詳しく・・・
© Cygames, Inc.⼀一旦まとめ• Cygamesサーバーサイドは、最⾼高のコンテンツを開発し、それを⾼高速/安定的な提供を⽬目標としています• ⽇日々、トライ&エラー/実装⇔計測を地道に繰り返し実績を積み上げ、上記の実現を⽬目指しています!
© Cygames, Inc.© Cygames, Inc.Zephirやってみた⼩小笠笠原 空宙
© Cygames, Inc.経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたい
© Cygames, Inc.Zephir検討への経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたいミドルウェアのバージョン上げようぜ!
© Cygames, Inc.Zephir検討への経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたいミドルウェアのバージョン上げようぜ!フレームワーク変えようぜ!
© Cygames, Inc.Zephir検討への経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたいミドルウェアのバージョン上げようぜ!フレームワーク変えようぜ!⾔言語変えようぜ!
© Cygames, Inc.Zephir検討への経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたいミドルウェアのバージョン上げようぜ!フレームワーク変えようぜ!⾔言語変えようぜ!⾯面⽩白くなければ意味がないエンジニア的にはやってみたいんだがしかし・・・
© Cygames, Inc.Zephir検討への経緯• レスポンスの⾼高速化は最優先事項の1つ• クエリ/処理理最適化もある程度度やった感• (まだまだ最適化できることはあるけど…)• 違う⾓角度度からの最適化を⾏行行いたいミドルウェアのバージョン上げようぜ!フレームワーク変えようぜ!⾔言語変えようぜ!⾯面⽩白くなければ意味がないエンジニア的にはやってみたいんだがしかし・・・Zephirっていうのがあるらしいぞい
© Cygames, Inc.Zephirとは• PHPerでも簡単にextensionが書ける⾔言語• PHPのような記述でextensionが作成できる• if⽂文の書式が違う• 変数に$使わない• 変数上書き時は、let を使う• 静的/動的型⾔言語• メモリ管理理等が必要ない• ⾼高速FW PhalconはZephirで実装
© Cygames, Inc.Zephirについて• Hello Worldnamespace Utils;class Hello{//say funcpublic static function say(){echo "Hello World!";}}Utils¥Hello::say(); // prints Hello World!$zephir build
© Cygames, Inc.Extensionが⽣生成/実⾏行行までのフロー1. Zephirのスケルトンを作成$zephir init utilsutils├── ext //Zephirのcore,⽣生成されるextensionなどが配置される│└── utils //ここに.zepファイルを作成していく
© Cygames, Inc.Extensionが⽣生成/実⾏行行までのフロー2.Zephirのファイルを作成$vim utils/hello.zepnamespace Utils;class Hello{public static function say(){echo "Hello World!";}}
© Cygames, Inc.Extensionが⽣生成/実⾏行行までのフロー3.Zephirでextension⽣生成→compile• ZephirがPHPで.zepファイルを解析• C⾔言語のextensionファイル群⽣生成• gccでコンパイル、.soファイルをext_̲dirにコピー$zephir buildCompiling...Installing...Extension installed!Don't forget to restart your web serverhello.zepC⾔言語utils extファイル群utils.soPHP gcc
© Cygames, Inc.どんなソースが⽣生成される?• Zephirでフィボナッチ数列列• 定義通り再帰でやってみるclass Fib{public function calc(int n) -‐‑‒> int{if n < 2 {return n;}return self::calc(n -‐‑‒ 1) + self::calc(n -‐‑‒ 2);}}
© Cygames, Inc.どんなソースが⽣生成される?PHP_̲METHOD(Utils_̲Fib, calc) {zval *n_̲param = NULL, *_̲0 = NULL, *_̲1 = NULL, *_̲2 = NULL;int n, ZEPHIR_̲LAST_̲CALL_̲STATUS;ZEPHIR_̲MM_̲GROW();zephir_̲fetch_̲params(1, 1, 0, &n_̲param);n = zephir_̲get_̲intval(n_̲param);if (n < 2) { RETURN_̲MM_̲LONG(n);}ZEPHIR_̲INIT_̲VAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 1));ZEPHIR_̲CALL_̲SELF(&_̲0, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();ZEPHIR_̲INIT_̲NVAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 2));ZEPHIR_̲CALL_̲SELF(&_̲2, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();zephir_̲add_̲function(return_̲value, _̲0, _̲2);RETURN_̲MM();}
© Cygames, Inc.どんなソースが⽣生成される?PHP_̲METHOD(Utils_̲Fib, calc) {zval *n_̲param = NULL, *_̲0 = NULL, *_̲1 = NULL, *_̲2 = NULL;int n, ZEPHIR_̲LAST_̲CALL_̲STATUS;ZEPHIR_̲MM_̲GROW();zephir_̲fetch_̲params(1, 1, 0, &n_̲param);n = zephir_̲get_̲intval(n_̲param);if (n < 2) { RETURN_̲MM_̲LONG(n);}ZEPHIR_̲INIT_̲VAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 1));ZEPHIR_̲CALL_̲SELF(&_̲0, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();ZEPHIR_̲INIT_̲NVAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 2));ZEPHIR_̲CALL_̲SELF(&_̲2, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();zephir_̲add_̲function(return_̲value, _̲0, _̲2);RETURN_̲MM();}int型で⽐比較を⾏行行っている
© Cygames, Inc.どんなソースが⽣生成される?PHP_̲METHOD(Utils_̲Fib, calc) {zval *n_̲param = NULL, *_̲0 = NULL, *_̲1 = NULL, *_̲2 = NULL;int n, ZEPHIR_̲LAST_̲CALL_̲STATUS;ZEPHIR_̲MM_̲GROW();zephir_̲fetch_̲params(1, 1, 0, &n_̲param);n = zephir_̲get_̲intval(n_̲param);if (n < 2) { RETURN_̲MM_̲LONG(n);}ZEPHIR_̲INIT_̲VAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 1));ZEPHIR_̲CALL_̲SELF(&_̲0, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();ZEPHIR_̲INIT_̲NVAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 2));ZEPHIR_̲CALL_̲SELF(&_̲2, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();zephir_̲add_̲function(return_̲value, _̲0, _̲2);RETURN_̲MM();}intではなく、ZVALで値のやり取りをしている
© Cygames, Inc.どんなソースが⽣生成される?PHP_̲METHOD(Utils_̲Fib, calc) {zval *n_̲param = NULL, *_̲0 = NULL, *_̲1 = NULL, *_̲2 = NULL;int n, ZEPHIR_̲LAST_̲CALL_̲STATUS;ZEPHIR_̲MM_̲GROW();zephir_̲fetch_̲params(1, 1, 0, &n_̲param);n = zephir_̲get_̲intval(n_̲param);if (n < 2) { RETURN_̲MM_̲LONG(n);}ZEPHIR_̲INIT_̲VAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 1));ZEPHIR_̲CALL_̲SELF(&_̲0, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();ZEPHIR_̲INIT_̲NVAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 2));ZEPHIR_̲CALL_̲SELF(&_̲2, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();zephir_̲add_̲function(return_̲value, _̲0, _̲2);RETURN_̲MM();}再帰部分の関数コール
© Cygames, Inc.どんなソースが⽣生成される?PHP_̲METHOD(Utils_̲Fib, calc) {zval *n_̲param = NULL, *_̲0 = NULL, *_̲1 = NULL, *_̲2 = NULL;int n, ZEPHIR_̲LAST_̲CALL_̲STATUS;ZEPHIR_̲MM_̲GROW();zephir_̲fetch_̲params(1, 1, 0, &n_̲param);n = zephir_̲get_̲intval(n_̲param);if (n < 2) { RETURN_̲MM_̲LONG(n);}ZEPHIR_̲INIT_̲VAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 1));ZEPHIR_̲CALL_̲SELF(&_̲0, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();ZEPHIR_̲INIT_̲NVAR(_̲1);ZVAL_̲LONG(_̲1, (n -‐‑‒ 2));ZEPHIR_̲CALL_̲SELF(&_̲2, "calc", NULL, 0, _̲1);zephir_̲check_̲call_̲status();zephir_̲add_̲function(return_̲value, _̲0, _̲2);RETURN_̲MM();}ZVALなのでintの⾜足し算も関数コールになっている
© Cygames, Inc.Zephirでフィボナッチ数列列• ZVALでの値のやりとり、関数コールもほぼ同じようなのでそこまで⼤大きな差はでなかった• が、ほぼソース変えず⾼高速化00.511.522.533.544.5PHP Zephir90%程度度に[s]
© Cygames, Inc.© Cygames, Inc.70もう少し実⽤用的なもの
© Cygames, Inc.Zephirでログ出⼒力力• fwrite()でtsv形式でファイル出⼒力力$fp = fopen($file, "a");fwrite($fp, implode("¥t", $msg_̲arr).PHP_̲EOL);fclose($fp);public function write(string file, array msg_̲arr){var fp = fopen(file, "a");fwrite(fp, implode("¥t", msg_̲arr).PHP_̲EOL);fclose(fp);}
© Cygames, Inc.Zephirでログ出⼒力力• fwrite()でtsv形式でファイル出⼒力力• 単純な関数コールなので差は出にくい0102030405060PHP Zephirほぼ変わらず[ms]
© Cygames, Inc.ZephirでNGワードチェック• NGワード配列列をループさせてstrpos()で検索索• NGワードは50語public $ng_̲list= [“ダメな⾔言葉葉”,“いけない⾔言葉葉”,“やばい⾔言葉葉",…];public function check($str){foreach ($this-‐‑‒>ng_̲list as $key => $value) {if (strpos($str, $value) !== false) {$is_̲ng = true;//break; ⽐比較のためbreakはしない}}}
© Cygames, Inc.ZephirでNGワードチェック• NGワード配列列をループさせてstrpos()で検索索• 配列列が⼤大きくなるほど改善あり• strpos()はZephir側で⾼高速化されている00.050.10.150.20.250.30.350.4PHP Zephir約20%程度度に!![ms]
© Cygames, Inc.⼀一旦ここまでわかったこと• PHPとの互換性のためZVALでの計算など、実質PHPがやっていることと同様の事を⾏行行っている様⼦子。C⾔言語的に最適化の余地はある• 配列列処理理などループ処理理などは速くなる• Zephirで標準関数のいくつか⾼高速化されている• PHPのコードをほぼ変えずに、Zephirの構⽂文規則に整えればextension化ができる• 引数の型などはチェックは確実に⾏行行う• PHPだとwarningでもFATAL ERRORになる
© Cygames, Inc.改めてZephir/extension化すること• Pros• ⾼高速化できる• PJ/FWなど関係なく利利⽤用可能• 特定箇所のみに適応できる• PHPに戻すことも可能• Cons• デプロイフローが増える• 修正がしにくい
© Cygames, Inc.導⼊入箇所例例• コード修正がほぼない共通処理理• ループを多⽤用する箇所• アプリ要件的に多⽤用される処理理• Zephir側で⾼高速化された標準関数を多⽤用している箇所• Library/Optimizers/以下
© Cygames, Inc.⼀一部導⼊入してみた結果• ゲームアプリで多い抽選処理理で⼀一部適応• 2〜~6ms レスポンスタイムの改善
© Cygames, Inc.Zephirまとめ• やっぱり銀の弾丸はありませんでした(・∀・)• 条件さえ合えば影響範囲を絞って適応できる• ロールバックも可能• ⽤用法と⽤用量量を守りつつ選択肢の⼀一つに
© Cygames, Inc.全体まとめ• Cygamesは、最⾼高のコンテンツを作りたいと思っています• それには⾼高速なトライ&エラーが⽋欠かせない• 同時に⾼高速なレスポンスも⽋欠かせない• それらを両⽴立立させるためPHPを使っています!