49
AWS×PHPでの 高信頼かつハイパフォーマンスなシステム 2017.2.7 伊藤皓程

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

Embed Size (px)

Citation preview

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

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

2017.2.7 伊藤皓程

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

伊藤 皓程(いとう こうてい)

2014年(株)サイバーエージェントアルバイト

2015年(株)サイバーエージェント 入社

2016年(株)QualiArts 出向

所属プロジェクト

● by.S ● ガールフレンド(♩)

● ボーイフレンド(仮) きらめきノート

自己紹介

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

アジェンダ

1. はじめに(2分)

2. キャッシュの話(8分)

3. 自動化・自動生成の話(6分)

4. Auroraの話(3分)

5. その他(1分)

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

はじめに

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

はじめに

事前登録25万人突破し

2016年11月にリリース!

AppleStore

無料ランキング1位獲得

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

はじめに

リリースから約2ヶ月で以下の5種類の新イベントを11回開催

● マラソン

● レイド

● ハイスコア

● PVP

● バレンタイン

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

はじめに

システムの可用性

メンテナンス: 2回 6時間(1時間で終わるはずだった…)

システム障害: 2回 10分

稼働率: 約99.5%

APIサーバのパフォーマンス

平均レスポンス時間(動的コンテンツのみ): 160ms

1台あたりの最大スループット(c4.2xlarge): 350req/sec

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

はじめに

アーキテクチャ図 Auroraシャーディングは行わない

SELECTはすべてReaderを使用

ElastiCache(Redis)インスタンスタイプはm4.largeに抑

えて多く並べる

Webview、クライアントのマスター

データ、Assetsなどの静的コンテ

ンツはCDNで配信

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

はじめに

複雑・大規模Webサービスでの高信頼性かつハイパフォーマンスなシステムを

実現するには…

● 高信頼性・高可用性○ 冗長性を担保する(MultiAZなど)

○ マネージドサービスを活用する( RDS, ElastiCacheなど)○ APIが少ないコード量で実装できるような基盤を作る

○ 自動化・自動生成を行う

○ ユニットテストやデバック機能を充実させる

● ハイパフォーマンス○ PHPのバージョンを上げる(PHP7)○ Auroraを使用する

○ 各種キャッシュの活用する

○ BOT対策をする

コードを書かなけれ

ばバグは生まれな

い…

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

キャッシュの話

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

キャッシュの話

まずはPHPの仕様をざっくりと…

● リクエストごとに独立したメモリ空間を持つ

○ ステートレスで起動するのは悪くないが、パフォーマンスは劣化する

○ リクエストを横断した変数の共有には工夫が必要

● リクエストごとにスクリプトの読み込みとコンパイルが発生する

○ フルスタックなフレームワークを使用するとかなりパフォーマンスの劣化する

=> APCu+OPcacheを使用する

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

キャッシュの話

APCu OPcache

Req Req Req

共有

Add Get Set

スクリプト

コンパイル

最適化

実行

キャッシュ

初回

実行

以降

キャッシュ

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

キャッシュの話

キャッシュのスコープと保存方法

1. リクエスト

a. プレイヤーキャッシュ : 変数で保存する

2. サーバ

a. マスターキャッシュ : APCuで保存する

b. コードキャッシュ : OPcacheで保存する

3. 共通

a. マスターキャッシュ : ElastiCache(Redis)で保存する

b. ランキング: ElastiCache(Redis)で保存する

c. その他のキャッシュ : ElastiCache(Redis)で保存する

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

マスターデータ

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

マスターデータのキャッシュの話

version: v2

master_card-v2: {...}

master_music-v2: {...}

master_card-v3: {...}

master_music-v3: {...}

ElastiCacheAPI Server

API Server

APCumaster_music-v2

APCumaster_music-v2master_card-v2

Req

1 GETversion

2 GETmaster_card-v2

3 GETmaster_card-v2

4 SETmaster_card-v2

1 GETversion

2 GETmaster_card-v2

Req

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

マスターデータのキャッシュの話

正規化されたデータを整形してキャッシュする

各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”: [] } ],}

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

マスターデータのキャッシュの話

キャッシュのフォーマットの比較

環境: 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

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

マスターデータのキャッシュの話

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(); // イベントエピソードを読んだ報酬付与

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

プレイヤーデータ

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

プレイヤーデータのキャッシュ

● DBへのアクセス回数を減らしたい

○ 取得データのキャッシュ

■ 同一レコードは1回目はDBからデータを取得、2回目以降はキャッシュから取得

○ 保存情報をまとめるため追加・更新・削除データのキャッシュ

■ 同一レコードを修正しても、キャッシュを利用し保存クエリは 1回のみ発行

● リクエストの最後で初めて更新処理を実行したい

○ 途中でエラーになった際余分なロールバックをさせたくない

■ 仮想通貨は基板側で持っておりロールバックできないため、クエリ保存 ->仮想通貨の消

費・増加->コミットという順番で行いたい

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

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT2. SELECT A,B,C

A

B

C

1. SELECT A,B,Cプレイヤーキャッシュ

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

UPDATE

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT

A

B

C

1. UPDATE Aプレイヤーキャッシュ

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

UPDATE

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT

A

B

C

1. INSERT Eプレイヤーキャッシュ

INSERT

E

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

UPDATE

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT

A

B

C

1. SELECT A, B, Eプレイヤーキャッシュ

INSERT

E

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

UPDATE

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT

A

B

C

1. SELECT A, Dプレイヤーキャッシュ

INSERT

E

2. SELECT A,D

D

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

UPDATE

プレイヤーデータのキャッシュ

A

B

C

D

Aurora

SELECT

A

B

C

1. COMMITプレイヤーキャッシュ

INSERT

E

D

2. INSERT E

3. UPDATE A

4. COMMIT

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

プレイヤーデータのキャッシュ

queryAid:1

queryBtype:1

queryAの検索条件:{ id: 1, type: 1 }queryBの検索条件:{ type: 1 }

queryA ⊇ queryB(queryAがqueryBの上位集合)

resultBid:2, type:1

resultAid:1, type:1

resultA:{ id: 1, type: 1 }resultB:{ id: 1, type: 1 },     { id: 2, type: 1 },...,resultA ⊆ resultB(resultAはresultBの部分集合)

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

自動化・自動生成の話

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

自動化・自動生成の話

スプレットシートからDDLの自動生成

DBのスキーマからModelクラスの自動生成

DBのスキーマからテストのMockの自動生成

DBのスキーマからDBのJsonSchemaを自動生成

DBのJsonSchemaからクライアントのクラスを自動生成

JsonSchemaからマスターデータのバリデーションの自動化

Req/ResのJsonSchemaからクライアントのクラスを自動生成

Req/ResのJsonSchemaからAPI定義書の自動生成

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

スプレットシートから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=utf8mb4partition by linear hash (player_id) partitions 16;

'

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

自動化・自動生成の話

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', '

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

自動化・自動生成の話

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": []}

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

自動化・自動生成の話

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" },

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

自動化・自動生成の話

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; }

}}

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

自動化・自動生成の話

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" } } }}

'

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

自動化・自動生成の話

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; }

}}

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

自動化・自動生成の話

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"]}

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

自動化について

ボイきらではプレイヤーデータを

差分管理しています

ログイン処理時にクライアント側に自分に関する全プレイヤーデータを返却。そ

れ以降は変更・追加・削除があったもののみ返却。

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

自動化について

ログイン

対象プレイヤーの全データ返却

ガチャ

INSERT, UPDATE, DELETEが発生したレコードの情報を共通レスポンスとして返却

プレイヤーデータの差分管理をクライアント、サーバ共に基盤部分で自動で管

理している。つまり各APIではViewに影響を与える見える部分のレスポンスの

みを実装すれば良いので楽になる!

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

Auroraの話

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

RDSとAuroraのMulti-AZの比較

Auroraの話

RDS Aurora書き込み 同期(ミラーリング) 非同期(Quorum方式)

リードレプリカ インスタンス追加 セカンダリを使用可能

レプリ遅延(最大) N秒 Nミリ秒(概ね20ms以内)

リードレプリカのフェイルオーバー

手動 自動

リードレプリカのエンドポイント

なし あり

=> Auroraを使うならReaderを待機系として遊ばせるのは勿体ない!

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

レプリケーションはバグの温床・・・

● ボタン連打や不正ツールによる並列リクエスト

● 急な負荷増加によるレプリ遅延時間の増加

● 同一リクエストの処理内で更新したレコードに対する再取得

Auroraの話

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

並列リクエストの対策

Auroraの話

1. 各コントローラの最初でユーザIDを使用してロック

2. Writerとリクエストのトークンをチェック

a. 同じだった場合、正常処理後にトークンを更新して返却

b. 違った場合、エラーとして処理

OK Token = B

Token = BToken = B

OK Token = CNG Token = C IGNORE

但し、意図せぬ連打でエラーダイアログが出るのはユーザ体感が悪いため、「クライアントは何もしないエラー」として制御する

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

レプリ遅延増加の対策

Auroraの話

1. WriterとReaderのトークンをチェック

a. 同じだった場合、正常処理後にトークンを更新して返却

b. 違った場合、エラーとして処理

OK Token = B

Token = B

NG Token = B Retry

但し、レプリ遅延の増加時にエラーダイアログが頻発すると、ユーザ体感が悪いため、「同じリクエストでリトライするエラー」として制御する

Writer

Reader

Token = B

Token = A

Token = B

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

レコード更新後の再取得の対策

Auroraの話

プレイヤーキャッシュの仕組みに

よって起きない

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

その他

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

Zephirについて

● C言語を書かずに、PHPのエクステンションを作成可能

● PHPライクな構文で静的+動的言語

● PHPの組み込み関数を使用可能

● PhalconPHPのv3がC言語からZephirに移行

● PHPよりも高速に動作

ハイパフォーマンスPHP

=> PHP7だったら?…ということで検証してみました。

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

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: 1coreMemory: 1GB       PHP: nginx × PHP-FPM × OPcache

実行速度はPHP7 ≧ Zephir > > > PHP5.6

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

ご静聴ありがとうございました