Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

2/36 本講演でお伝えすること PHPで他の言語のライブラリを使用したいといった状況では通 常そのライブラリを移植するといった手法がとられます。し かし移植の労力やパフォーマンスの面で問題が出るケースが 多く高処理効率と運用のしやすさの両立を目指す必要がある ゲームサーバとしては問題となりがちです。 これを解決するためにCによるPHP Extensionを実装しPHPか らJavaライブラリを直接実行できるFFI(Foreign Function Interface)を実現した結果、処理時間はPHPに移植した場合に 比べ約1/100となりました。この事例をもとに実装に至った経 緯や、具体的な設計構成についてご紹介します。

Slide 3

Slide 3 text

3/36 伊藤 英知 サーバーサイド /シニアエンジニア サイバーエージェントグループのソーシャルゲーム会社を経て、2012年よ り株式会社Cygamesに参画。 フィーチャーフォン/スマートフォンのブラウザおよび、スマートフォンの ネイティブゲーム、コンシューマゲームの開発・運用に携わる。 負荷対策やミドルウェアの検証や社内で使用しているサーバフレームワーク のメンテナンスなど社内のプロジェクトを横断的に支援、より安定して快適 に遊べるよう日々開発を行っている。 自己紹介

Slide 4

Slide 4 text

4/36 1. 経緯と検証の流れ 2. Pure PHPでの実装 3. Jettyによる実装 4. JNIを使ったFFIの実装 5. 本番導入準備 6. まとめ アジェンダ

Slide 5

Slide 5 text

5/36 経緯と検証の流れ

Slide 6

Slide 6 text

6/36 経緯 サーバAPIにJWSの署名検証が必要になった サ ー バ 改 竄 チ ェ ッ ク ク ラ イ ア ン ト JWS (JSON Web Signature) JOSE Header JWS Payload JWS Signature

Slide 7

Slide 7 text

7/36 検証の流れ ※FFI…Foreign Function Interface(≒他の言語の呼び出し) JavaへのFFI(※)を実装し PHPから実行されるJavaのクラスで署名検証 Pure PHPで署名検証を実装 JettyでローカルHTTP Serverを立てて そのプロセス内で署名検証

Slide 8

Slide 8 text

8/36 Pure PHPでの実装

Slide 9

Slide 9 text

9/36 Pure PHPでの実装 PHPのみで署名検証を実装 インフラ構成は通常と変化なし 要求仕様のリファレンスである Javaのライブラリをもとに移植実装 通常のNginx+PHP-FPMの構成

Slide 10

Slide 10 text

10/36 Pure PHPでの実装: 構成 PHP-FPMプロセス リクエスト PHPのみで 署名検証を実装 PHP NGINX

Slide 11

Slide 11 text

11/36 Pure PHPでの実装: 課題 遅い PHPでの移植が等価である証明が大変 ライブラリバージョンアップへの追従 約100msかかる。サーバ用途としては致命的 要求仕様がJavaのライブラリの実装である以上 この確認はかなり手間がかかる 変更点のポーティングなどメンテナンスコストが高い もちろん変更したあとは同様に等価である確認が必要

Slide 12

Slide 12 text

12/36 Jettyによる実装

Slide 13

Slide 13 text

13/36 Jettyによる実装 別プロセスでHTTPをListenする 署名検証結果をレスポンスする JettyでHTTPのサービスを作り ステートを持たないため各サーバにこのプロセスを配置 PHPプロセスからGETリクエストとしてJWSの文字 列を送り、署名検証をJavaのライブラリで行い、 0か1のレスポンスをPHPプロセスに返す

Slide 14

Slide 14 text

14/36 Jettyによる実装: 構成 Javaプロセス port 3520で Listen HTTP GET JWS文字列 Load 署名検証 PHP-FPMプロセス リクエスト HTTP Response 署名検証結果 .jar PHP NGINX

Slide 15

Slide 15 text

15/36 Jettyによる実装: 結果 パフォーマンスは一応の解決 ローカルHTTPが一つ増えた HTTPのオーバーヘッドがある 処理時間は1/10の約10msほどになった 死活監視対象が増え運用上は望ましくない Javaのみで該当の処理時間を計測した場合に比べて遅い (署名検証1回あたり1ms以下)

Slide 16

Slide 16 text

16/36 JNIを使ったFFIの実装

Slide 17

Slide 17 text

17/36 JNIを使ったFFIの実装 C/C++とJavaのコードを互いに呼べる 今回はCからJavaを実行する為に使用 いわゆるInteropの機能を提供する C#で言うところのCoreCLRやEmbeddingMono等 にあたる PHPのExtensionはCで記述される点に着目し PHPからJavaへのFFIを実現

Slide 18

Slide 18 text

18/36 JNIを使ったFFIの実装: 構成 PHP-FPMプロセス PHP C Extension JNI Load 署名検証 リクエスト 今回実装したFFI Java .jar PHP NGINX

Slide 19

Slide 19 text

19/36 JNIを使ったFFIの実装: 結果 パフォーマンスはほぼ理想的 PHPプロセスのメモリがJVMの分増加 1/100の約1msほどで処理できた 大量にforkする場合は注意 JVMのヒープメモリ指定で対処(例: -Xmx32m) PHPからの使い方もシンプルにできた CURLでリクエスト&レスポンスのパースが不要に 例: $is_valid = verify_jws(‘JWSの文字列’); -Xmx32m -Xmx32m

Slide 20

Slide 20 text

20/36 JNIを使ったFFIの実装: パフォーマンス PHP-FPMプロセス PHP C Extension JNI Load 署名検証 リクエスト 今回実装したFFI 同一プロセス内で完結 || IPCのオーバヘッドがない Java .jar JITでコンパイル済みの コードが実行され続ける (PHPのプロセスが作り直されるまで) PHP NGINX

Slide 21

Slide 21 text

21/36 JNIを使ったFFIの実装: アップデートの容易さ PHP-FPMプロセス PHP C Extension JNI Load 署名検証 リクエスト 今回実装したFFI ここを差し替えるだけで バージョンアップできる Java .jar PHP NGINX

Slide 22

Slide 22 text

22/36 JNIを使ったFFIの実装: アップデートの容易さ ; Java classpath for JVM. separate with ':’. verify_jws.d_java.class.path='-Djava.class.path=./:/usr/lib64/php/modules/SomeJavaClass.jar’ ; JVM min. heap size verify_jws.xms='-Xms16m' ; JVM Max heap size. 32MB is sufficient for typical cases. verify_jws.xmx='-Xmx32m’ ; T arget Class Config verify_jws.invoke.class=‘SomeJavaClass' verify_jws.invoke.method=‘methodName' verify_jws.invoke.method_signature='(Ljava/lang/String;)Ljava/lang/String;' PHP extensionのiniで差替え設定可能 PHP extensionの再ビルドも不要

Slide 23

Slide 23 text

23/36 JNIを使ったFFIの実装: 運用のしやすさ PHP-FPMプロセス PHP C Extension JNI Load 署名検証 リクエスト 今回実装したFFI このプロセスだけ監視 (従来通り) Java .jar NGINX PHP

Slide 24

Slide 24 text

24/36 JNIを使ったFFIの実装: 他の方式との比較 JNI FFI PHP Extension Jetty Server Pure PHP 処理速度 約1ms 約10ms 約100ms メンテナンスの しやすさ Jarを差替える Jarを差替える 移植実装が必要 追加の監視 不要 必要 不要

Slide 25

Slide 25 text

25/36 本番導入準備

Slide 26

Slide 26 text

26/36 本番導入準備: なぜここが大事か PJにとっての問題解決が大前提 ◼ 良いパフォーマンスでも使い勝手が悪い、高 コスト、不安定であったりなどでは実際のPJ で使いづらい ◼ メリットだけ求めて障害を起こすと、次に別 件でメリットを訴えても訴求力が下がる ◼ 新しい試みだからこそ検証や導入まで丁寧に してこそメリットが生かせる

Slide 27

Slide 27 text

27/36 本番導入準備: なぜここが大事か 次の新しい試みがやりやすくなる ◼ 検証を十分に行いリリースに万全を期すことで、 新しい挑戦がしやすくなる ◼ 副次的にどのような点を事前に考慮しておくと 安全にリリースできるかのノウハウが貯まる

Slide 28

Slide 28 text

28/36 本番導入準備: 行ったこと 負荷試験 耐久試験 デプロイ手順の策定 切り戻し・代替手段の準備

Slide 29

Slide 29 text

29/36 本番導入準備: 負荷試験 実際に改善したか必ず実測して確認 「これで速くなったハズ」で終わっては危険 ※レスポンスを遅い順に並べたときの上位10% 極端に遅いレスポンスがないか? ◼ GCのスパイクなどで一時的に遅くなるケースが ないか注意する ◼ 90パーセンタイル(※)以上と中央値で大きくか け離れていないかにも注目

Slide 30

Slide 30 text

30/36 本番導入準備: 耐久試験 時間がたってからメモリの使用量が増えていないか メモリリークを確認 安定度を確認 ◼ 次第にレスポンスが悪化していないか? ◼ ログにSegmentation Faultなどエラーはでていないか? ◼ PHPのプロセスが作り直されたときに不具合は起きないか?

Slide 31

Slide 31 text

31/36 本番導入準備: デプロイ手順の策定 ミドルウェアのバージョンを把握 意図したとおりに配置できるように ◼ バージョンの違いによる意図しない動作を防ぐ ◼ マイナーバージョンまで確定するのが望ましい ◼ 手順やシェルを用意しておく ◼ 新しい環境やほかのメンバの環境で再現できるか

Slide 32

Slide 32 text

32/36 本番導入準備: 切り戻し、代替手段の準備 関係者と認識を合わせておく 元に戻す方法を必ず用意 ◼ プロジェクトメンバや監視をするインフラチームとも “どうなったらまずい”というポイントを共有しておく ◼ 緊急時の対応をスムーズにするためにも重要 ◼ いわゆる切り戻しの手順を策定 ◼ いつでも予期せぬ不具合の発生を考慮し今回は、 FFI版➡PurePHP版➡動作スキップとフォールバックを準備

Slide 33

Slide 33 text

33/36 本番導入準備: 導入タイトル

Slide 34

Slide 34 text

34/36 まとめ

Slide 35

Slide 35 text

35/36 まとめ 手段が目的化しないように注意 目的に対して適切な手段を選ぶ 似た複数ソフトウェア製品の特徴を理解しておく 技術の仕組みまで理解しておくのがベスト 多様な手段を知っておく 安全に実用化できるまでを考える プロジェクトの課題解決が目的という点を忘れない 導入フローまで考える

Slide 36

Slide 36 text

36/36