$30 off During Our Annual Pro Sale. View Details »

最大100倍高速化!PHPからJavaへのFFIを実現する、JNIを用いた高速なサーバAPIの実装方法

Cygames
August 31, 2023

 最大100倍高速化!PHPからJavaへのFFIを実現する、JNIを用いた高速なサーバAPIの実装方法

2022/08/25 CEDEC2022

Cygames

August 31, 2023
Tweet

More Decks by Cygames

Other Decks in Technology

Transcript

  1. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. 5/36
    経緯と検証の流れ

    View Slide

  6. 6/36
    経緯
    サーバAPIにJWSの署名検証が必要になった















    JWS
    (JSON Web Signature)
    JOSE Header
    JWS Payload
    JWS Signature

    View Slide

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

    View Slide

  8. 8/36
    Pure PHPでの実装

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 12/36
    Jettyによる実装

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. 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の再ビルドも不要

    View Slide

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

    View Slide

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

    View Slide

  25. 25/36
    本番導入準備

    View Slide

  26. 26/36
    本番導入準備: なぜここが大事か
    PJにとっての問題解決が大前提

    良いパフォーマンスでも使い勝手が悪い、高
    コスト、不安定であったりなどでは実際のPJ
    で使いづらい

    メリットだけ求めて障害を起こすと、次に別
    件でメリットを訴えても訴求力が下がる

    新しい試みだからこそ検証や導入まで丁寧に
    してこそメリットが生かせる

    View Slide

  27. 27/36
    本番導入準備: なぜここが大事か
    次の新しい試みがやりやすくなる

    検証を十分に行いリリースに万全を期すことで、
    新しい挑戦がしやすくなる

    副次的にどのような点を事前に考慮しておくと
    安全にリリースできるかのノウハウが貯まる

    View Slide

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

    View Slide

  29. 29/36
    本番導入準備: 負荷試験
    実際に改善したか必ず実測して確認
    「これで速くなったハズ」で終わっては危険
    ※レスポンスを遅い順に並べたときの上位10%
    極端に遅いレスポンスがないか?

    GCのスパイクなどで一時的に遅くなるケースが
    ないか注意する

    90パーセンタイル(※)以上と中央値で大きくか
    け離れていないかにも注目

    View Slide

  30. 30/36
    本番導入準備: 耐久試験
    時間がたってからメモリの使用量が増えていないか
    メモリリークを確認
    安定度を確認

    次第にレスポンスが悪化していないか?

    ログにSegmentation Faultなどエラーはでていないか?

    PHPのプロセスが作り直されたときに不具合は起きないか?

    View Slide

  31. 31/36
    本番導入準備: デプロイ手順の策定
    ミドルウェアのバージョンを把握
    意図したとおりに配置できるように

    バージョンの違いによる意図しない動作を防ぐ

    マイナーバージョンまで確定するのが望ましい

    手順やシェルを用意しておく

    新しい環境やほかのメンバの環境で再現できるか

    View Slide

  32. 32/36
    本番導入準備: 切り戻し、代替手段の準備
    関係者と認識を合わせておく
    元に戻す方法を必ず用意

    プロジェクトメンバや監視をするインフラチームとも
    “どうなったらまずい”というポイントを共有しておく

    緊急時の対応をスムーズにするためにも重要

    いわゆる切り戻しの手順を策定

    いつでも予期せぬ不具合の発生を考慮し今回は、
    FFI版➡PurePHP版➡動作スキップとフォールバックを準備

    View Slide

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

    View Slide

  34. 34/36
    まとめ

    View Slide

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

    View Slide

  36. 36/36

    View Slide