Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Aws×phpでの 高信頼かつハイパフォーマンスなシステム

3,139 views

Published on

【DMM GAMES主催!】「複雑・大規模webサービスを支える技術勉強会」伊藤皓程 発表資料
https://eventdots.jp/event/610781?utm_source=top&utm_medium=social&utm_campaign=feed&utm_content=tw1571891239

Published in: Engineering
  • Login to see the comments

Aws×phpでの 高信頼かつハイパフォーマンスなシステム

  1. 1. AWS×PHPでの 高信頼かつハイパフォーマンスなシステム 2017.2.7 伊藤皓程
  2. 2. 伊藤 皓程(いとう こうてい) 2014年(株)サイバーエージェントアルバイト 2015年(株)サイバーエージェント 入社 2016年(株)QualiArts 出向 所属プロジェクト ● by.S ● ガールフレンド(♩) ● ボーイフレンド(仮) きらめきノート 自己紹介
  3. 3. アジェンダ 1. はじめに(2分) 2. キャッシュの話(8分) 3. 自動化・自動生成の話(6分) 4. Auroraの話(3分) 5. その他(1分)
  4. 4. はじめに
  5. 5. はじめに 事前登録25万人突破し 2016年11月にリリース! AppleStore 無料ランキング1位獲得
  6. 6. はじめに リリースから約2ヶ月で以下の5種類の新イベントを11回開催 ● マラソン ● レイド ● ハイスコア ● PVP ● バレンタイン
  7. 7. はじめに システムの可用性 メンテナンス: 2回 6時間(1時間で終わるはずだった…) システム障害: 2回 10分 稼働率: 約99.5% APIサーバのパフォーマンス 平均レスポンス時間(動的コンテンツのみ): 160ms 1台あたりの最大スループット(c4.2xlarge): 350req/sec
  8. 8. はじめに アーキテクチャ図 Aurora シャーディングは行わない SELECTはすべてReaderを使用 ElastiCache(Redis) インスタンスタイプはm4.largeに抑 えて多く並べる Webview、クライアントのマスター データ、Assetsなどの静的コンテ ンツはCDNで配信
  9. 9. はじめに 複雑・大規模Webサービスでの高信頼性かつハイパフォーマンスなシステムを 実現するには… ● 高信頼性・高可用性 ○ 冗長性を担保する(MultiAZなど) ○ マネージドサービスを活用する( RDS, ElastiCacheなど) ○ APIが少ないコード量で実装できるような基盤を作る ○ 自動化・自動生成を行う ○ ユニットテストやデバック機能を充実させる ● ハイパフォーマンス ○ PHPのバージョンを上げる( PHP7) ○ Auroraを使用する ○ 各種キャッシュの活用する ○ BOT対策をする コードを書かなけれ ばバグは生まれな い…
  10. 10. キャッシュの話
  11. 11. キャッシュの話 まずはPHPの仕様をざっくりと… ● リクエストごとに独立したメモリ空間を持つ ○ ステートレスで起動するのは悪くないが、パフォーマンスは劣化する ○ リクエストを横断した変数の共有には工夫が必要 ● リクエストごとにスクリプトの読み込みとコンパイルが発生する ○ フルスタックなフレームワークを使用するとかなりパフォーマンスの劣化する => APCu+OPcacheを使用する
  12. 12. キャッシュの話 APCu OPcache Req Req Req 共有 Add Get Set ス ク リ プ ト コ ン パ イ ル 最 適 化 実 行 キ ャ ッ シ ュ 初回 実 行 以降 キ ャ ッ シ ュ
  13. 13. キャッシュの話 キャッシュのスコープと保存方法 1. リクエスト a. プレイヤーキャッシュ : 変数で保存する 2. サーバ a. マスターキャッシュ: APCuで保存する b. コードキャッシュ: OPcacheで保存する 3. 共通 a. マスターキャッシュ: ElastiCache(Redis)で保存する b. ランキング: ElastiCache(Redis)で保存する c. その他のキャッシュ: ElastiCache(Redis)で保存する
  14. 14. マスターデータ
  15. 15. マスターデータのキャッシュの話 version: v2 master_card-v2: {...} master_music-v2: {...} master_card-v3: {...} master_music-v3: {...} ElastiCacheAPI Server API Server APCu master_music-v2 APCu master_music-v2 master_card-v2 Req 1 GET version 2 GET master_card-v2 3 GET master_card-v2 4 SET master_card-v2 1 GET version 2 GET master_card-v2 Req
  16. 16. マスターデータのキャッシュの話 正規化されたデータを整形してキャッシュする 各APIで整形する必要がなくなるのでロジックがシンプルになる。計算量も減少 する master_event master_event_music master_event_reward master_event_stage master_event_episode master_event_card master_event { “music_list”: [ { “music_id”, “stage_map”: {} } ], “episode_list”: [ { “episode_id”, “reward_list”: [] } ], }
  17. 17. マスターデータのキャッシュの話 キャッシュのフォーマットの比較 環境: PHP7, OS: CentOS 6.5, CPU: 1core, memory: 1GB マスターデータ: カードマスター, 27カラム, 1000レコード Serializeが約3倍に速い!Serializeのほうが良い理由は速さだけじゃない。 JSON Serialize エンコード(1000回) 2.16s 0.86s デコード(1000回) 4.83s 1.76s メモリサイズ 608KB 692KB
  18. 18. マスターデータのキャッシュの話 Serializeを使用することでマスターデータのクラスのオブジェクトをそのままキャッシュ可 能! ロジックがさらにシンプルになる。 // イベントエピソードを読むAPIのロジックのイメージ $master_event_cache = MasterEventCache::forge(); $master_event_string = master_event_cahce->get($event_id); // エンコードされたオブジェクト $master_event = unserialize($master_event_string); // デコードする(実際はgetする時にunserializeしている) $master_event->check_term(); // 期間チェック $master_event_episode = $master_event->get_music($episode_id); // イベントエピソードのモデルを取得 $master_event_episode->provide_reward(); // イベントエピソードを読んだ報酬付与
  19. 19. プレイヤーデータ
  20. 20. プレイヤーデータのキャッシュ ● DBへのアクセス回数を減らしたい ○ 取得データのキャッシュ ■ 同一レコードは1回目は DBからデータを取得、2回目以降はキャッシュから取得 ○ 保存情報をまとめるため追加・更新・削除データのキャッシュ ■ 同一レコードを修正しても、キャッシュを利用し保存クエリは 1回のみ発行 ● リクエストの最後で初めて更新処理を実行したい ○ 途中でエラーになった際余分なロールバックをさせたくない ■ 仮想通貨は基板側で持っておりロールバックできないため、クエリ保存 ->仮想通貨の消 費・増加->コミットという順番で行いたい
  21. 21. プレイヤーデータのキャッシュ A B C D Aurora SELECT 2. SELECT A,B,C A B C 1. SELECT A,B,C プレイヤーキャッシュ
  22. 22. UPDATE プレイヤーデータのキャッシュ A B C D Aurora SELECT A B C 1. UPDATE A プレイヤーキャッシュ
  23. 23. UPDATE プレイヤーデータのキャッシュ A B C D Aurora SELECT A B C 1. INSERT E プレイヤーキャッシュ INSERT E
  24. 24. UPDATE プレイヤーデータのキャッシュ A B C D Aurora SELECT A B C 1. SELECT A, B, E プレイヤーキャッシュ INSERT E
  25. 25. UPDATE プレイヤーデータのキャッシュ A B C D Aurora SELECT A B C 1. SELECT A, D プレイヤーキャッシュ INSERT E 2. SELECT A,D D
  26. 26. UPDATE プレイヤーデータのキャッシュ A B C D Aurora SELECT A B C 1. COMMIT プレイヤーキャッシュ INSERT E D 2. INSERT E 3. UPDATE A 4. COMMIT
  27. 27. プレイヤーデータのキャッシュ queryA id:1 queryB type:1 queryAの検索条件:{ id: 1, type: 1 } queryBの検索条件:{ type: 1 } queryA ⊇ queryB (queryAがqueryBの上位集合) resultB id:2, type:1 resultA id:1, type:1 resultA:{ id: 1, type: 1 } resultB:{ id: 1, type: 1 },      { id: 2, type: 1 },..., resultA ⊆ resultB (resultAはresultBの部分集合)
  28. 28. 自動化・自動生成の話
  29. 29. 自動化・自動生成の話 スプレットシートからDDLの自動生成 DBのスキーマからModelクラスの自動生成 DBのスキーマからテストのMockの自動生成 DBのスキーマからDBのJsonSchemaを自動生成 DBのJsonSchemaからクライアントのクラスを自動生成 JsonSchemaからマスターデータのバリデーションの自動化 Req/ResのJsonSchemaからクライアントのクラスを自動生成 Req/ResのJsonSchemaからAPI定義書の自動生成
  30. 30. スプレットシートからDDLの自動生成 自動化・自動生成の話 drop table if exists player_main_episode cascade; create table player_main_episode ( player_id int(10) unsigned NOT NULL comment 'プレイヤーID', main_episode_id int(10) unsigned NOT NULL comment 'エピソードID', read_flg tinyint(3) unsigned NOT NULL comment '既読フラグ 0: 未読,1: 既読 ', created_at datetime comment '作成日', updated_at datetime comment '更新日', deleted_at datetime default null comment '削除日 セットされるとレコード削除 扱い', constraint player_main_episode_PKC primary key (player_id,main_episode_id) ) comment 'プレイヤーメインエピソード ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 partition by linear hash (player_id) partitions 16; '
  31. 31. 自動化・自動生成の話 DBのスキーマからModelクラスの自動生成 class Model_Db_PlayerMainEpisode extends Model_OwnPlayer { protected static $_table_name = 'player_main_episode'; protected static $_primary_key = ['player_id', 'main_episode_id']; protected static $_properties = [ 'player_id' => [ 'schema' => [ 'data_type' => 'int', 'constraint' => 10, 'unsigned' => true, 'null' => false, 'comment' => 'プレイヤーID', ], 'validation' => [ 'required', 'numeric_min' => [0], 'numeric_max' => [4294967295] ] ], 'main_episode_id' => [ 'schema' => [ 'data_type' => 'int', '
  32. 32. 自動化・自動生成の話 DBのスキーマからテストのMockの自動生成 { "default": { "player_id": 1, "main_episode_id": 1, "read_flg": 0, "created_at": "2015-01-01 00:00:00", "updated_at": "2015-01-01 00:00:00", "deleted_at": null }, "data": [] }
  33. 33. 自動化・自動生成の話 DBのスキーマからDBのJsonSchemaを自動生成 { "$schema": "http://json-schema.org/draft-04/schema#", "title": "MasterMainEpisodeStory", "type": "object", "properties": { "MainEpisodeId": { "type": "integer", "description": "メインエピソードID" }, "ChapterId": { "type": "integer", "description": "章ID" }, "StoryId": { "type": "integer", "description": "話ID" },
  34. 34. 自動化・自動生成の話 DBのJsonSchemaからクライアントのクラスを自動生成 { "$schema": "http://json-schema.org/draft-04/schema#", "id": "definitions/player/characterpresent.json", "title": "MainEpisodeInfo", "type": "object", "properties": { "MainEpisodeId": { "type": "integer", "description": "メインエピソードID" }, "ReadFlg": { "type": "integer", "description": "0:未読, 1:既読" } }, "keys": ["MainEpisodeId"], "required": ["MainEpisodeId", "ReadFlg"] } using System; using System.Collections.Generic; namespace XXX { /// <summary> /// No document /// </summary> [Serializable] public class MainEpisodeInfo : PlayerInfoBase<MainEpisodeInfo> { /// <summary> /// メインエピソードID /// </summary> public int MainEpisodeId { get; set; } /// <summary> /// 0:未読, 1:既読 /// </summary> public int ReadFlg { get; set; } } }
  35. 35. 自動化・自動生成の話 DBのJsonSchemaからマスターデータのバリデーションの自動化 { "$schema": "http://json-schema.org/draft-04/schema#", "title": "master_main_episode_story", "type": "object", "properties": { "main_episode_id": { "type": "integer", "description": "メインエピソードID" }, "chapter_id": { "type": "integer", "description": "章ID", "relation": { "table": "master_main_episode_chapter", "column": "chapter_id" } } } } '
  36. 36. 自動化・自動生成の話 Req/ResのJsonSchemaからクライアントのクラスを自動生成 { "$schema": "http://json-schema.org/draft-04/schema#", "id": "req/episode/readevent", "type": "object", "properties": { "EventEpisodeId": { "type": "integer", "description": "イベントエピソード ID" } }, "required": ["EventEpisodeId"] } using System; using System.Collections.Generic; namespace XXX { /// <summary> /// No document /// </summary> [Serializable] public class RequestEpisodeReadmain : RequestBase { /// <summary> /// メインエピソード ID /// </summary> public int MainEpisodeId { get; set; } } }
  37. 37. 自動化・自動生成の話 Req/ResのJsonSchemaからAPI定義書の自動生成 { "$schema": "http://json-schema.org/draft-04/schema#", "id": "req/episode/readevent", "type": "object", "properties": { "EventEpisodeId": { "type": "integer", "description": "イベントエピソード ID" } }, "required": ["EventEpisodeId"] }
  38. 38. 自動化について ボイきらではプレイヤーデータを 差分管理しています ログイン処理時にクライアント側に自分に関する全プレイヤーデータを返却。そ れ以降は変更・追加・削除があったもののみ返却。
  39. 39. 自動化について ログイン 対象プレイヤーの全データ返却 ガチャ INSERT, UPDATE, DELETEが発生した レコードの情報を共通レスポンスとして返 却 プレイヤーデータの差分管理をクライアント、サーバ共に基盤部分で自動で管 理している。つまり各APIではViewに影響を与える見える部分のレスポンスの みを実装すれば良いので楽になる!
  40. 40. Auroraの話
  41. 41. RDSとAuroraのMulti-AZの比較 Auroraの話 RDS Aurora 書き込み 同期(ミラーリング) 非同期(Quorum方式) リードレプリカ インスタンス追加 セカンダリを使用可能 レプリ遅延(最大) N秒 Nミリ秒(概ね20ms以内) リードレプリカのフェイル オーバー 手動 自動 リードレプリカのエンドポイ ント なし あり => Auroraを使うならReaderを待機系として遊ばせるのは勿体ない!
  42. 42. レプリケーションはバグの温床・・・ ● ボタン連打や不正ツールによる並列リクエスト ● 急な負荷増加によるレプリ遅延時間の増加 ● 同一リクエストの処理内で更新したレコードに対する再取得 Auroraの話
  43. 43. 並列リクエストの対策 Auroraの話 1. 各コントローラの最初でユーザIDを使用してロック 2. Writerとリクエストのトークンをチェック a. 同じだった場合、正常処理後にトークンを更新して返却 b. 違った場合、エラーとして処理 OK Token = B Token = B Token = B OK Token = C NG Token = C IGNORE 但し、意図せぬ連打でエラーダイアログが出るのはユーザ体感が悪いため、「クラ イアントは何もしないエラー」として制御する
  44. 44. レプリ遅延増加の対策 Auroraの話 1. WriterとReaderのトークンをチェック a. 同じだった場合、正常処理後にトークンを更新して返却 b. 違った場合、エラーとして処理 OK Token = B Token = B NG Token = B Retry 但し、レプリ遅延の増加時にエラーダイアログが頻発すると、ユーザ体感が悪いた め、「同じリクエストでリトライするエラー」として制御する Writer Reader Token = B Token = A Token = B
  45. 45. レコード更新後の再取得の対策 Auroraの話 プレイヤーキャッシュの仕組みに よって起きない
  46. 46. その他
  47. 47. Zephirについて ● C言語を書かずに、PHPのエクステンションを作成可能 ● PHPライクな構文で静的+動的言語 ● PHPの組み込み関数を使用可能 ● PhalconPHPのv3がC言語からZephirに移行 ● PHPよりも高速に動作 ハイパフォーマンスPHP => PHP7だったら?…ということで検証してみました。
  48. 48. Zephir vs PHP7 ハイパフォーマンスPHP PHP5.6 Zephir PHP7 each(1,000,000) 470ms 167ms 105ms without(1,000,000) 4800ms 90ms 40ms search(1,000,000) 30ms 20ms 20ms repeat(1,000,000) 80ms 10ms 10ms フィボナッチ数列(38) 16s 23s 9s 検証環境 OS: CentOS 6.5 (vagrant) CPU: 1core Memory: 1GB       PHP: nginx × PHP-FPM × OPcache 実行速度はPHP7 ≧ Zephir > > > PHP5.6
  49. 49. ご静聴ありがとうございました

×