Upgrade to Pro — share decks privately, control downloads, hide ads and more …

秒間 10,000 リクエストを "簡単に"いなすゲームサーバーを Laravel で作る設計

秒間 10,000 リクエストを "簡単に"いなすゲームサーバーを Laravel で作る設計

1秒間に PHP が受信する HTTP リクエストが最大 10,000 回以上———
そんな世界が存在します。その一つが 「ソーシャルゲーム」 です。メンテナンスが明けた瞬間、イベントが始まった・終わる瞬間、様々なタイミングでゲームサーバーは瞬間的に高負荷になります。もちろん、サービスをリリースし PR をたくさん出し始めたその瞬間が、プロジェクトで最も高負荷となるでしょう。それらに耐えうるサーバー構成が求められていますが、「リリース直後にサーバーがダウンした」「限定イベントが始まったらすぐ緊急メンテナンスが始まった」という話はちょくちょく聞こえてきます。その 瞬間的な高負荷(いわゆる "スパイク") に耐えるには、事前準備を怠らないことが重要です。
ソーシャルゲームにおいては、他の Web アプリケーションに比べ 書き込みヘビーなワークロード であることが多いです。読み込みは比較的簡単に分散できますが、書き込みを分散することは容易ではありません。
そういった要件を達成するため、私のチームで行っている、 Laravel による高負荷に耐えるサーバー設計をご紹介したいと思います。

Masaru Yamagishi

June 26, 2023
Tweet

More Decks by Masaru Yamagishi

Other Decks in Programming

Transcript

  1. 秒間 10,000 リクエストを "簡単に"
    いなすゲームサーバーを
    Laravel で作る設計
    やまゆ - PHPカンファレンス福岡2023

    View Slide

  2. こんにちはー
    - 写真撮影/SNS投稿大歓迎!
    - 後でエゴサして全部見させていただきます 👀
    - #phpconfuk #hall_fu
    - 初九州・初福岡です!
    - Ask the speaker お待ちしています!

    View Slide

  3. 概要
    1秒間に PHP が受信する HTTP リクエストが最大 10,000 回以上———
    そんな世界が存在します。その一つが 「ソーシャルゲーム」 です。メンテナンスが明けた瞬間、イベントが始まった・終わ
    る瞬間、様々なタイミングでゲームサーバーは瞬間的に高負荷になります。もちろん、サービスをリリースし PR をたくさん
    出し始めたその瞬間が、プロジェクトで最も高負荷となるでしょう。それらに耐えうるサーバー構成が求められています
    が、「リリース直後にサーバーがダウンした」「限定イベントが始まったらすぐ緊急メンテナンスが始まった」という話はちょ
    くちょく聞こえてきます。その 瞬間的な高負荷(いわゆる "スパイク") に耐えるには、事前準備を怠らないことが重要です。
    ソーシャルゲームにおいては、他の Web アプリケーションに比べ 書き込みヘビーなワークロード であることが多いで
    す。読み込みは比較的簡単に分散できますが、書き込みを分散することは容易ではありません。
    そういった要件を達成するため、私のチームで行っている、 Laravel による高負荷に耐えるサーバー設計をご紹介したい
    と思います。
    - 高負荷案件でも Laravel は使えるの?と疑問に思う方
    - どのようにスパイクをさばいているのかを知りたい方

    View Slide

  4. 1 リクエスト
    クライアント サーバ

    View Slide

  5. 1 リクエスト
    HTTP Req
    コンニチ

    クライアント サーバ

    View Slide

  6. 1 リクエスト
    コンニチ

    クライアント サーバ
    コンニチ

    HTTP Req
    HTTP Res

    View Slide

  7. 1 リクエスト
    ワーイ
    クライアント サーバ
    ヤッター
    HTTP Req
    HTTP Res
    Done!

    View Slide

  8. 秒間 1 リクエスト
    クライアント サーバ

    View Slide

  9. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    コンニチ

    View Slide

  10. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    コンニチ

    HTTP Res コンニチ

    View Slide

  11. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    アリガト イエイエ
    HTTP Res
    Done!

    View Slide

  12. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    HTTP Res
    Done!
    — 約 1 秒後 —

    View Slide

  13. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    HTTP Res
    Done!
    — 約 1 秒後 —
    HTTP Req
    マタキタ

    View Slide

  14. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    HTTP Res
    Done!
    — 約 1 秒後 —
    HTTP Req
    マタキタ

    HTTP Res
    ソウナノ

    View Slide

  15. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    HTTP Res
    Done!
    — 約 1 秒後 —
    HTTP Req
    マタクル

    HTTP Res
    アイヨ
    Done!

    View Slide

  16. 秒間 1 リクエスト
    クライアント サーバ
    HTTP Req
    HTTP Res
    Done!
    — 約 1 秒後 —
    HTTP Req
    HTTP Res
    Done!
    以下続く...

    View Slide

  17. クライアント サーバ
    秒間 10 リクエスト

    View Slide

  18. クライアント サーバ
    秒間 10 リクエスト
    HTTP Req x
    10
    コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ
    ハ コンニチ

    View Slide

  19. サーバ
    秒間 10 リクエスト
    HTTP Req x
    10
    HTTP Res
    x 10
    ワア
    クライアント
    アリガト
    オオキニ
    タスカル
    ヤッタ
    セヤナ
    オケ
    ワーイ
    ドウモ
    アザス
    ウィッス

    View Slide

  20. クライアント サーバ
    秒間 10 リクエスト
    HTTP Req x
    10
    クレ
    データ
    ホシイ
    ヨコセ
    マダー
    ハヨ
    ナニシテ

    ツギノ
    コレ
    ヨロシク
    HTTP Res
    x 10
    マタキタ
    Done!
    HTTP Req x
    10

    View Slide

  21. クライアント サーバ
    秒間 10 リクエスト
    HTTP Req x
    10
    ヨイショ
    ヤッタ
    ドモ
    ザッス
    ワーイ
    マタクル

    ヨロシク
    コレダ
    フム
    サラバ
    HTTP Res
    x 10
    イソガシ
    Done!
    HTTP Req x
    10
    HTTP Res
    x 10

    View Slide

  22. クライアント サーバ
    秒間 10 リクエスト
    HTTP Req x
    10
    ヨイショ
    ヤッタ
    ドモ
    ザッス
    ワーイ
    マタクル

    ヨロシク
    コレダ
    フム
    サラバ
    HTTP Res
    x 10
    ヒエー
    Done!
    HTTP Req x
    10
    HTTP Res
    x 10
    以下続く...
    Done!

    View Slide

  23. クライアント サーバ
    秒間 100 リクエスト
    オオイナ
    HTTP Req x
    100

    View Slide

  24. クライアント サーバ
    秒間 1,000 リクエスト
    ヒャー
    HTTP Req x
    1,000
    x10

    View Slide

  25. クライアント サーバ
    秒間 10,000 リクエスト
    (x x)
    HTTP Req x
    10,000
    x100

    View Slide

  26. 秒間 10,000 リクエスト(rps) の負荷って?
    - 約 10,000 台のクライアントが一斉にリクエストし始めること
    - その状態が「ずっと続く」こと
    - クライアント 1 台と比べて 10,000 倍の負荷がかかること
    ※以下リクエスト・パー・セカンド「rps」という

    View Slide

  27. こんにちは。
    やまゆです。

    View Slide

  28. 自称赤魔道士系エンジニア
    ㈱インフィニットループ 

    やまゆ
    この画像は自撮りでも
    いつも使っているアイコンでも構いません



    View Slide

  29. 最近の主な業務内容
    - AWS インフラストラクチャアーキテクト
    - PHP アプリケーションアーキテクト・実装
    - インフラ管理ツール実装
    - 初級者・中級者向け教育
    - 社内・社外向け広報

    View Slide

  30. 本日のお題
    - ゲームサーバーとは?
    - 負荷をいなすとは?
    - 負荷をいなすアプリ設計は?
    - Laravel いけんの?

    View Slide

  31. 本日の NOT お題 - インフラ構築の仕方は?
    - 負荷試験のやり方は?

    View Slide

  32. ゲームサーバとは

    View Slide

  33. ゲームサーバーとは
    ※ここでいうゲームとは、スマートフォン向けのソーシャルゲームとする
    ※リアルタイムサーバーは対象外とする
    - HTTP(S) 通信サーバー
    - 非常に大規模かつ素早くスケール
    - データベースへの高頻度な書きこみ
    - それ以外は案外と普通の Web サービスとあまり変わらない

    View Slide

  34. HTTP(S) 通信サーバー
    (例)
    HTTP(S)
    json/protobuf/…
    OpenAPI 3.0/gRPC/…

    View Slide

  35. HTTP(S) 通信サーバー
    現状最も安定してスケールする通信プロトコル
    json や protobuf のような構造化されたペイロードを送受信する
    サーバーとクライアントは別会社が担当することもある
    ペイロード構造の共有が重要
    弊社では様々な形式で自動生成しているが、うちは OpenAPI 3.0 を利用
    OpenAPI 3.1 では Webhook にも対応
    AsyncAPI https://www.asyncapi.com/ というのも登場した

    View Slide

  36. 非常に大規模かつ素早くスケール
    例) リリース直後 事前にスケールアウト
    大量のアクセス

    View Slide

  37. 非常に大規模かつ素早くスケール
    例) リリース直後
    事前登録によりある程度の負荷を予測し、それを超えても大丈夫な程度に大きくス
    ケールアウト
    ここでコケると離脱がかなり増えるので、費用はかさむがびっくりするくらい台数増や
    すことがある(Web数百台とか)
    負荷試験の段階で Web サーバーや DB サーバー以外の部分で詰まらないかしっか
    り確認する必要がある

    View Slide

  38. 非常に大規模かつ素早くスケール
    例) 平時 柔軟にスケール
    波のあるアクセス

    View Slide

  39. 非常に大規模かつ素早くスケール
    例) 平時
    定常的に同じ負荷ではなく、大体一日のプレイサイクルがあるので波が出る
    ソシャゲだと朝・昼・夕方が負荷高め
    リソースに遊びがあるとただ費用を垂れ流すだけなので柔軟にスケールする必要が
    ある
    最近だとサーバーレス化してこのスケールを自動化している場合も増えているのでは

    View Slide

  40. 非常に大規模かつ素早くスケール
    例) 人気イベント開始・終了前後
    アクセススパイク
    注意してスケールアウト

    View Slide

  41. 非常に大規模かつ素早くスケール
    例) 人気イベント開始・終了前後
    人気イベントの開始時は急激にスパイクする
    イベントの人気度にもよるが、事前にスケジュールでスケールアウトしておくことで
    サーバー高負荷を避ける
    どれくらいの DAU が出るかを予測するのは半分職人芸
    また、イベント終了直前は最後に走る人も多いので高負荷が続く

    View Slide

  42. 非常に大規模かつ素早くスケール
    例) 長く運用していき縮退
    段々減るアクセス
    スケールイン

    View Slide

  43. 非常に大規模かつ素早くスケール
    例) 長く運用していき縮退
    運用が数年続くとどうしてもユーザー数は減る
    そうすると負荷の波も小さくなっていくのでそれに合わせて台数を減らしく必要がある
    計画メンテナンスに入れて DB サーバーなど一旦止めないと動かせないリソースをス
    ケールダウンしたりもする

    View Slide

  44. データベースへの高頻度な書き込み
    コインを使ってアイテムを購入
    ゲームをプレイして報酬を獲得
    フレンドの申請
    プロフィールの編集
    ガチャの実行

    View Slide

  45. データベースへの高頻度な書き込み
    ソーシャルゲームではとにかくユーザーの操作によって、その人が持っているリソー
    スの更新が多い
    DB は読み込みはスケールしやすいが書き込みはスケールしづらい
    トランザクションを張ったりするので単体の負荷も読み込みより高い

    View Slide

  46. 負荷をいなす?

    View Slide

  47. 負荷をいなす
    means
    いなせる負荷を
    かけることができる

    View Slide

  48. 負荷試験
    多くのユーザーが想定されるサービスでは負荷試験が欠かせない
    10,000 rps をいなすには、 10,000 rps の負荷をかけられる必要がある
    実際の負荷試験の準備・実施・レポートのやり方については、後日フォローアップ記
    事をブログに投稿予定。お楽しみに!

    View Slide

  49. 結果: RDB が重い!

    View Slide

  50. 書き込み DB の CPU がカツカツ

    View Slide

  51. 書き込み DB の CPU がカツカツ
    今回は Amazon Aurora を使ったが、これは CPU 80 % 以上いくと急に不安定になる
    傾向がある
    70 % ならまだ安定稼働するが、これ以上の負荷は厳しい
    まだまだスケールアップは可能だが、アップには限度があるし何より費用がかさむ

    View Slide

  52. コネクション数には限界がある

    View Slide

  53. コネクション数には限界がある
    MySQL の仕様上 max_connections は 1 台あたり 16,000 が限界
    今回 10,000 rps で 9,000 ということは、 20,000 rps は耐えられないということ
    RDS Proxy で改善されるかもしれない?
    NewSQL も選択肢?

    View Slide

  54. Laravel は正直問題ない
    コストパフォーマンスを考えると他言語/FWに劣る可能性はあるが、別に安定してス
    ケールするのでフレームワークレベルで詰まることはなかった

    View Slide

  55. 結論:クエリ最適化と
    コネクション数削減が
    実装において
    最重要である

    View Slide

  56. アプリ設計の要
    - クエリ最適化が容易であること
    - 発行されるクエリが明快であること
    - ドメイン実装とクエリ最適化を分離して作業できること
    - ドメインが大規模になってもスケールすること
    - 疎結合であること
    - どこで何が実装されているかわかること
    - 運用を考えて、依存関係のアップデートが容易であること
    - Framework 等のバージョンアップがしやすいこと

    View Slide

  57. 実際のアプリ設計

    View Slide

  58. DDD-like
    Driven
    Design

    View Slide

  59. なにそれ

    View Slide

  60. DDD-like Driven Design
    このスライドを作りながら提唱(?)
    軽量 DDD と呼ばれる奴に近い
    要約:「DDD 全部は難しいので一部良さ気なとこだけ取り入れる」
    さらに要約:「アーキテクトの私がわからん奴はメンバーもわからんくなるやろ」
    ※私の DDD の見識は超絶浅いので全然違うこと書いている可能性も

    View Slide

  61. DDD-like Driven Design の三つの原則
    1. Laravel Frameworkは利用するが、
    Framework とドメイン実装は分離すること
    2. 責務がネームスペースごとに分離されていること
    3. ネームスペースごとの依存関係が明らかで、
    片方向になっていること

    View Slide

  62. 弊プロジェクトの基本設計図

    View Slide

  63. エントリポイントレイヤー

    View Slide

  64. CQRS サービスレイヤー

    View Slide

  65. CQRS?
    コマンド・クエリ責務分離のこと
    「データの更新(Insert/Update/Delete)」と「データの取得(Select)」は要件が大きく
    異なるから分けようぜ
    更新コマンド -> 低頻度、トランザクションの管理、安全にデータを更新する
    取得クエリ -> 高頻度、最適化された SQL を発行して取得する

    View Slide

  66. ドメインレイヤー

    View Slide

  67. ドメイン
    アプリケーションの本質的な実装部分
    use Illuminate\...; と書いたら処される
    ※ Illuminate\Support ネームスペースは除く
    Domain から Domain 外部方向への依存は基本的に NG
    必要であればインターフェースを定義して別の場所で実装

    View Slide

  68. エンティティ
    一意に識別できる識別子を持ったオブジェクト
    大体テーブルと 1対1(UserEntity, UserEquipmentEntity, …)
    getter/setter が用意され、プロパティの更新が可能
    ここにロジックを書くのが望ましいがあんまり書かれていない

    View Slide

  69. 値オブジェクト
    Life や Stamina など、複雑なロジック(例えば時間で回復とか)を持つ値を一つのオ
    ブジェクトとして表現する手法
    immutable なのが特徴的
    あまり使われていない。 Identity(AUTO_INCREMENT の値オブジェクト)とか作った方
    が良かったと後悔

    View Slide

  70. ドメインサービス
    複数のエンティティや値オブジェクトをまたいで何かしらの更新を行うロジックの集合
    ドメインサービスと呼ばれることは少ない
    そしてあまり使われていない...
    本当はエンティティに書いた方が良いものではある

    View Slide

  71. リポジトリインターフェース
    リレーショナルデータベースのクエリ発行を担当する部分
    クエリ発行はもちろん具体的な実装依存が必要なので、実装自体は別の場所で
    Domain 内ではインターフェースのみを定義している

    View Slide

  72. その他インターフェース
    Domain の実装の中で S3 にアクセスしたい、 Laravel のこの機能を使いたい、などと
    いった場合は、 Domain で Laravel や S3 を参照するのではなく、単純に欲しいイン
    ターフェースを定義するだけとする
    具体的に接続したりリクエストを送ったりするのは Domain の本質ではないので「イン
    フラストラクチャ」に分離する
    ※クリーンアーキテクチャっぽい考え方でもある

    View Slide

  73. インフラストラクチャレイヤー

    View Slide

  74. インフラストラクチャレイヤー
    外部(=ライブラリ、DB、S3、...)と Domain をつなぐ架け橋を担当
    クエリの発行、 HTTP リクエストの送受信などは全てここの中で行う
    Domain で定義したインターフェースを実装する
    ※名前が長いのでやめたいけど他に良いのが思いつかなかった

    View Slide

  75. インフラストラクチャレイヤー
    外部(=ライブラリ、DB、S3、...)と Domain をつなぐ架け橋を担当
    クエリの発行、 HTTP リクエストの送受信などは全てここの中で行う
    Domain で定義したインターフェースを実装する
    ※名前が長いのでやめたいけど他に良いのが思いつかなかった

    View Slide

  76. インフラストラクチャレイヤー
    外部(=ライブラリ、DB、S3、...)と Domain をつなぐ架け橋を担当
    クエリの発行、 HTTP リクエストの送受信などは全てここの中で行う
    Domain で定義したインターフェースを実装する
    ※名前が長いのでやめたいけど他に良いのが思いつかなかった
    そういえば、 Eloquent
    さんは?

    View Slide

  77. View Slide

  78. 例えば

    View Slide

  79. Eloquent を
    例えば

    View Slide

  80. Eloquent を
    投げ捨てる
    例えば

    View Slide

  81. ※諸注意
    大前提として、 Eloquent は Laravel が誇る素晴らしい機能なのは確実です。
    Eloquent を使うことで生産性が大きく向上することは間違いありません。
    今回の話は、いくつかの課題点からどうしても「使うのが難しい」と判断した結果にな
    ります。
    Eloquent が悪いのではなく、今回は偶然相性が悪かった場合の話をします。

    View Slide

  82. Why? - Active Record 型
    - 初期実装
    - シャーディング

    View Slide

  83. Why?: Active Record 型であること
    Eloquent は Active Record 型 ORM
    大規模アプリケーションにおいて Active Record を適切に運用するのは難しい(もちろ
    ん不可能ではない)
    今回の DDD-like DD において大きな課題となった
    対照となる Data Mapper 型 ORM の必要性が高まった

    View Slide

  84. Why?: 初期実装
    初期のプロトタイプ実装に illuminate/database のクエリビルダ(とそれに紐づく
    illuminate/collection)を採用した
    それらを活かすため、 Doctrine や Cycle ORM など他の Data Mapper 型 ORM の
    採用は難しかった

    View Slide

  85. Why?: シャーディング
    Eloquent 自体には水平シャーディングの機能がない
    global
    log
    user2
    user1 user3 user n


    ×

    View Slide

  86. じゃあどうやってクエ
    リ作るの?

    View Slide

  87. 自作 ORM で。

    View Slide

  88. 自作 ORM

    View Slide

  89. 自作 ORM

    View Slide

  90. 自作 ORM

    View Slide

  91. Why?
    リレーションを取得出来る ≒ N+1 クエリの危険が出る
    幸いこのプロジェクトでは大量のリレーションを管理することが少なかった
    (最初うまい設計が思い浮かばなかった)

    View Slide

  92. まとめ
    - 秒間 10,000 リクエストをさばくのは Laravel でも安定していける
    - RDB 負荷をいかに捌くかがキモ
    - DDD-like DD は結構良いぞ
    - 例えば Eloquent を投げ捨ててみると道が開けるかも
    インフィニットループでは、札幌・仙台で働くエンジニアを募集中!
    ご清聴ありがとうございました!

    View Slide