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での 高信頼かつハイパフォーマンスなシステム

2,619 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
  • I would recommend you to use Cloudways PHP hosting platform to host your PHP app on AWS server. This is the most efficient method of doing it. You can launch a server if you don't know anything about Sysadmin stuff. This platform has lots of features that makes your app more powerful, features like cron jobs, managed server and security, automated backups, free ssl and all.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

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. ご静聴ありがとうございました

×