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

High (Availability|Performance) WebSocket for Perl Real-Time Application

mackee
March 03, 2018

High (Availability|Performance) WebSocket for Perl Real-Time Application

このスライドはYAPC::Okinawa ONNASON 2018で行ったトークです。

トーク概要 http://yapcjapan.org/2018okinawa/talks.html#/detail/6

mackee

March 03, 2018
Tweet

More Decks by mackee

Other Decks in Programming

Transcript

  1. High (Availability|Performance)
    High (Availability|Performance)
    WebSocket for Perl Real-Time
    WebSocket for Perl Real-Time
    Application
    Application
    macopy a.k.a @mackee_w
    YAPC::Okinawa 2018 ONNASON

    View Slide

  2. 自己紹介
    自己紹介
    インターネット及び会社ではマコピーと呼ばれています
    所属:
    面白法人カヤック
    主な職業:
    スマホゲームのサーバサイドエンジニア
    ゲーム開発,
    サーバ運用,
    ワークフロー構築 etc...

    View Slide


  3. 「トーカナイザの守護霊」
    興味があること:
    リアルタイム通信, 3D
    プリンタ,
    自作キーボード
    最近の出来事:
    右肘を骨折,
    リハビリ中
    Twitter: @mackee_w GitHub: @mackee
    PAUSE: MACOPY

    View Slide

  4. Perl Hackers Hub
    にDB
    を使ったテストの話などを寄稿させていただ
    きました

    View Slide

  5. 予告編の内容
    予告編の内容
    課題と背景について説明します
    サーバプッシュを従来の同期型Prefork
    アーキテクチャで扱った
    場合の問題点
    何故kuiperbelt
    が生まれたのかの説明
    スマホゲームの場合の事例です

    View Slide

  6. このトークの内容
    このトークの内容
    背景があった上でどう解決したかを解説
    永続接続を扱うスケーラブルなミドルウェアの設計
    そのミドルウェアを趣味で作って運用に持っていく話
    Proof of Concept
    から Production Ready
    になるまで

    View Slide

  7. !!! CAUTION!!!
    このトークでの
    High Availability
    High Availability
    高可用性 これほどコンテキストによって変わる高ナントカはあ
    まりないと思う
    SPOF
    がない
    片方落ちたときにクライアントの再接続でもう片方でサービス継
    続が可能
    リトライ可能

    View Slide

  8. 人間諦めが肝心
    人間諦めが肝心
    信頼性が期待されているミドルウェアは、何を提供するミドルウ
    ェアでどこからどこまでを保証するかをはじめに決める
    はじめにルールを決めておけば信頼性を損ないかねない機能追加
    の要望を説明して突っぱねられる
    代替案は提案した方がいいけれど

    View Slide

  9. !!! CAUTION!!!
    このトークでの
    High Performance
    High Performance
    WebSocket
    同時接続数 10k
    ~ 100k
    上記数字は実際の本番での運用での数字
    横に並べられる ボトルネックになることを避ける設計
    もっといけるとは思う

    View Slide

  10. !!! CAUTION!!!
    このトークでの
    リアルタイム
    リアルタイム
    「リアルタイムっていうからリアルタイムOS
    とかのリアルタイ
    ムかと思った」ごめんなさい違います。なのでそういうの言うだ
    けのブクマはしないで欲しい
    Web
    およびスマホゲームで使われる言葉。技術的観点からはサー
    バプッシュを用いた機能

    View Slide

  11. 第1

    第1

    リアルタイム通信がめんどく
    リアルタイム通信がめんどく
    さい件
    さい件

    View Slide

  12. リアルタイム通信とは
    リアルタイム通信とは
    あるユーザの操作
    時間経過やゲーム内オブジェクトの状態によって引き起こされるイ
    ベント

    別のユーザに即座に伝わる
    アーキテクチャ/
    ゲームシステム のことを指す

    View Slide

  13. 例:
    ターン制カードゲーム
    例:
    ターン制カードゲーム
    相手のターンから自分のターンになる時に
    場に出すカードを選択するなどしてターンを終了する

    View Slide

  14. 例:
    ターン制カードゲーム
    例:
    ターン制カードゲーム
    既にターンが終了しているかどうかを出来るだけ素早く知るに
    は?
    今の場の状態をサーバに定期的に問い合わせる(=
    ポーリング)
    サーバから情報が降ってくるのを待つ(=
    サーバプッシュ)

    View Slide

  15. ターン終了時の通信
    ターン終了時の通信

    View Slide

  16. ポーリング
    ポーリング

    View Slide

  17. サーバプッシュ
    サーバプッシュ

    View Slide

  18. 実現するためには
    実現するためには
    クライアントからのリクエス
    クライアントからのリクエス
    ト無しでサーバから情報を送
    ト無しでサーバから情報を送
    る必要がある
    る必要がある
    =>
    サーバプッシュ
    =>
    サーバプッシュ

    View Slide

  19. 本物と擬似
    本物と擬似
    従来のクライアント起点のリクエストでも似たような体験を実現
    出来なくはない
    これがポーリング
    クライアントが定期的にサーバに新しい情報がないかを取りに来
    る。更新されていたら画面とかも更新する
    疑似リアルタイムと呼ばれる

    View Slide

  20. ポーリング サーバプッシュ
    リアルタイム性 低 高
    通信量 大 小
    サーバリソース 大 小
    技術的飛躍度 低 高
    枯れている感 高 低

    View Slide

  21. モバイルの場合
    モバイルの場合
    ポーリング サーバプッシュ
    電池消費 小 大
    サーバプッシュはサーバに対してクライアントが通信を繋ぎっぱ
    なしになるため電池を消費しがち
    ポーリングは通信が間欠的になるため比較的電池消費をしない

    View Slide

  22. メリット・デメリットあるけ
    メリット・デメリットあるけ
    れど本物が必要なアプリケー
    れど本物が必要なアプリケー
    ションは多いので本物のリア
    ションは多いので本物のリア
    ルタイム技術を採用せざるを
    ルタイム技術を採用せざるを
    得ない今日このごろ
    得ない今日このごろ

    View Slide

  23. 本物のリアルタイムでもラグ
    本物のリアルタイムでもラグ
    はある
    はある
    驚くべきことに光は1ms
    に300km
    しか進まない
    驚くべきことに通信回線の資源や計算資源は有限
    驚くべきことにインターネットはコンピュータのバケツリレーで
    出来ている

    View Slide

  24. あなたはそばに相手がいると思うかもしれないが、それはインター
    ネットの中だけで実は地球の反対側にいるかもしれない。それがイ
    ンターネットの良いところ

    View Slide

  25. ラグを見せないゲームデザイン
    [CEDEC 2010]
    ネットゲームの裏で何が起こっているのか。ネット
    ワークエンジニアから見た,ゲームデザインの大原則
    行動パターンが送られてこない場合でも,それ
    を誤魔化す小さいアクション(火を吹くなど)
    が用意されているとのこと

    View Slide

  26. Web
    以外のゲームでは
    Web
    以外のゲームでは
    家庭用ゲーム機/
    オンラインゲームの世界
    もっぱら生TCP/
    生UDP
    の世界
    各々がそれぞれやっている(
    シリアライズ形式とか)

    View Slide

  27. スマホゲーム界では
    スマホゲーム界では
    ノウハウを持ってない会社が参入してきたのでミドルウェアを提
    供する会社が出てきた
    Photon Server / Photon Cloud (Exit Games, Inc)
    モノビットエンジン (
    モノビット株式会社)

    View Slide

  28. 一方我々は
    一方我々は
    面白法人カヤックって出自がWeb
    の会社なんですよね
    最近はゲームやったりハードウェアを設計する人もいるけれど
    使われる技術スタックはWeb
    由来のもの
    いきなり家庭用ゲーム機やPC
    ゲーム由来の技術を使おうとして
    もノウハウがない
    Photon
    使っているゲームもある

    View Slide

  29. Web
    の技術
    Web
    の技術
    ニアリーイコール
    ブラウザで使える技術
    ブラウザで使える技術

    View Slide

  30. HTTP
    の世界観
    HTTP
    の世界観
    リクエストとレスポンスは1
    対1
    レスポンスが生まれるにはリクエストが必要
    この時点でサーバプッシュとは矛盾している

    View Slide

  31. HTTP

    ステートレスなプロトコル
    ステートレスなプロトコル
    あるリクエスト/
    レスポンスの対と、別のあるリクエスト/
    レスポ
    ンスの対は独立している

    View Slide

  32. 重要な点
    重要な点
    一般的にWeb
    サービスの1
    リクエストは長くても数秒以内にレスポ
    ンスが返ってくる
    このへんが後々効いてくる大事な話です

    View Slide

  33. HTTP
    の特性を踏まえた上での
    HTTP
    の特性を踏まえた上での
    Web
    でのサーバプッシュはど
    Web
    でのサーバプッシュはど
    うやる?
    うやる?

    View Slide

  34. ロングポーリング/Comet
    ロングポーリング/Comet
    ふつうにHTTP
    リクエスト投げる
    サーバは送りたい情報が出るまでレスポンスを送らず焦らす
    送りたい情報が生まれたらレスポンスを返す
    クライアントは処理したらまたリクエストを投げる

    View Slide

  35. ロングポーリング/Comet
    ロングポーリング/Comet
    メリット:
    普通のHTTP
    通信が長くなっただけ
    デメリット:
    一個情報を受け取るのに一回リクエスト送るオーバ
    ーヘッド,
    レスポンスを受け取ってから次にリクエストを送るま
    では情報をリアルタイムに受け取れない

    View Slide

  36. Sever-Sent Events(SSE)
    Sever-Sent Events(SSE)
    ふつうにリクエスト投げる
    サーバはコネクションを切らずにだらだら1
    行ずつ書く
    クライアントは1
    行データが送られてきたら読む、コネクシ
    ョンは切らない

    View Slide

  37. Sever-Sent Events(SSE)
    Sever-Sent Events(SSE)
    メリット:
    ただのHTTP
    のアクロバティック的使用法
    デメリット:
    一方通行,
    ライブラリ実装の少なさ

    View Slide

  38. WebSocket
    WebSocket
    Upgrade
    ヘッダが入ったリクエストを投げる
    サーバがWebSocket
    を介したいんだなって思って101 Switching
    Protocols
    を返す
    HTTP
    とは違う独自のWebSocket
    プロトコルによる通信が開始
    される

    View Slide

  39. WebSocket
    WebSocket
    メリット:
    現代においては広く使われている,
    標準化もされてい
    る,
    双方向
    デメリット: HTTPS
    じゃないWebSocket
    だとフォワードプロキシが
    ある環境で使えないことがある,
    デバッグ方法が通常のHTTP
    の作
    法と違う

    View Slide

  40. 他にも未来の技術達
    他にも未来の技術達
    HTTP2 Server Push
    html
    を取りに来たクライアントにこれも必要だから持ってけっ
    て言ってJS
    やら画像を送りつけることが出来るやつ
    ゲームなどのサーバプッシュに使えるかどうかはあまりわからない……
    この前のセッションでトークした
    人達に聞いてみましょう

    View Slide

  41. 他にも未来の技術達
    他にも未来の技術達
    WebRTC
    ブラウザでビデオチャットとかするやつ
    条件が合えばP2P
    通信ができる。また画像や音声以外にもデー
    タも送れる。さらにUDP
    を選択できるのでオーバーヘッドも少
    ない。ただしNAT
    越え……
    gRPC
    みたいなWeb
    の技術に乗っかっているサーバプッシュの仕組みを持ったものもあるが、ブラウザでは使え
    ない

    View Slide

  42. Web
    サーバアーキテクチャの
    Web
    サーバアーキテクチャの


    サーバプッシュを適用するにはアーキテクチャの変更が必要になる
    場合がある……

    View Slide

  43. 太古の昔: CGI
    太古の昔: CGI
    HTTP
    リクエストが飛んできたら
    サーバ内のコマンドを起動してリクエストを起動したプロセス
    に渡す
    プロセスが吐いた出力をレスポンスとして返す

    View Slide

  44. 太古の昔: CGI
    太古の昔: CGI
    メリット:
    簡単,
    分かりやすい,
    デバッグしやすい
    デメリット:
    プロセスの起動ってコストがかかるんや……

    View Slide

  45. 時代が移ろいでいきPerl
    界では
    mod_perl
    perl
    インタプリタの起動とモジュールの読み込みは予めしてお

    FastCGI
    Apache
    からの解脱, lighttpd
    ってのがあってだな
    PSGI
    もうperl
    がHTTP
    をしゃべっちゃう
    Perl
    ウェブ開発の中世~CGI
    と Plack
    の間~ - YAPC::Kansai 2017

    View Slide

  46. しかしプロセスの使われ方は
    しかしプロセスの使われ方は
    変わっていない
    変わっていない

    View Slide

  47. 同期的Prefork
    モデル
    同期的Prefork
    モデル
    Pre(=
    事前に)fork
    あらかじめリクエストを処理するプログラムのプロセスを複数立
    てておき、リクエストが来たら暇しているプロセスに配る
    1
    プロセスだと1
    度に一つのリクエストしかさばけないが、
    Prefork
    モデルはfork
    したプロセス分さばける

    View Slide

  48. Prefork
    モデルは他のLL
    でも使
    Prefork
    モデルは他のLL
    でも使
    われている
    われている
    Perl/PSGI - starman, starlet
    Ruby/Rack - unicorn
    Python/WSGI - gunicorn

    View Slide

  49. 同期的Prefork
    モデルが使われ
    同期的Prefork
    モデルが使われ
    る理由
    る理由
    ->
    簡単
    ->
    簡単

    View Slide

  50. マルチスレッドではないのでデータ競合で変数壊れるとか無い
    そもそも並行プログラミングを考えなくて良い、コードも上から
    下に読んでいけば良い
    LB
    とインスタンスの関係と同じなので同じ考え方が適用できる
    共有リソースがないのでサーバを横に簡単に並べられる
    =>
    スケールアウト

    View Slide

  51. しかし同期的Prefork
    モデルに
    しかし同期的Prefork
    モデルに
    も限界が
    も限界が
    もし、一つのHTTP
    リクエストが数十秒、数百秒かかっていたら?
    Prefork
    モデルはfork
    したプロセス分さばける

    View Slide

  52. Prefork
    したプロセス以上のリクエストがHTTP
    リクエストの寿命の
    間に殺到したときに破滅する

    View Slide

  53. 刹那的な場合はプロセス数以上のリクエストが来てもなんとかさばける

    View Slide

  54. ここに永続的な場合にプロセス数以上のリクエストが来るとレスポンスが詰まる

    View Slide

  55. 破滅です
    破滅です
    同時接続数が少ないアプリケーションでは破滅しないこともあります

    View Slide

  56. Q.
    何故、同期的Prefork
    モデル
    Q.
    何故、同期的Prefork
    モデル
    が成り立っていたか
    が成り立っていたか
    A.
    普通のHTTP
    通信は刹那的な
    A.
    普通のHTTP
    通信は刹那的な
    プロトコルだから
    プロトコルだから

    View Slide

  57. 今までのHTTP
    では1
    つのリク
    今までのHTTP
    では1
    つのリク
    エストが1
    つのプロセスを専
    エストが1
    つのプロセスを専
    有する時間が短い。だからこ
    有する時間が短い。だからこ
    れでやってこれた
    れでやってこれた

    View Slide

  58. 刹那的接続から永続的接続
    刹那的接続から永続的接続
    へ……
    へ……
    ロングポーリング, SSE, WebSocket...
    これらはすべて1
    回のHTTP
    リクエストの寿命が長い
    従来のアーキテクチャではプロセスが枯渇する

    View Slide

  59. C10K
    の話をしています
    C10K
    の話をしています
    C10K = Connection 10,000 Problem
    サーバリソースはあるのにアーキテクチャの問題でリクエストを
    さばくことが出来ない問題
    本来はHTTP Keep-Alive
    の文脈からも論ぜられますが、Keep-Alive
    はnginx
    とかが維持してくれるし、刹那的HTTP

    変換可能なのでアプリケーションの変更はいらない

    View Slide

  60. C10K
    の解法
    並行プログラミングの導入
    並行プログラミングの導入
    イベント駆動(Node.js, AnyEvent, etc...)
    1
    プロセスで複数の接続を扱うために、処理を細切れにして動
    かす
    コールバック地獄になる...

    View Slide

  61. ネイティブスレッド(Java
    族, C
    族, etc...)
    データ競合...
    マルチスレッドプログラミングはバグを知るのが
    難しい...
    fork
    よりはマシだけれどネイティブスレッドも重いからスレッ
    ドプールが必要なのよねえ
    今のPerl5
    では使えない(
    そもそもithread
    は……)
    ithread
    はメモリ共有モデルではない

    View Slide

  62. 軽量スレッド(Erlang/OTP, Elixer, Go, etc...)
    もっとも今まで通りに書けるが1
    プロセスで複数の処理が並行
    で走るのは変わらないので、バグ潰しが難しい

    View Slide

  63. リアルタイム通信をやりたい
    リアルタイム通信をやりたい
    だけでアプリケーション全体
    だけでアプリケーション全体
    の言語やプログラミングパラ
    の言語やプログラミングパラ
    ダイムを変更するのは理にか
    ダイムを変更するのは理にか
    なっているか
    なっているか

    View Slide

  64. わからん!
    わからん!
    場合による!!
    場合による!!
    自分で考えてくれ!!!
    自分で考えてくれ!!!

    View Slide

  65. イチから作るか、動いている物に導入するか
    今の言語で出来るかどうか
    移行先の言語やパラダイムに精通している人がいるかどうか
    カネと時間があるかどうか
    作ったもののパフォーマンスが出るか、検証できるかどうか

    View Slide

  66. 別件:
    アプリケーションと密結
    別件:
    アプリケーションと密結
    合の問題
    合の問題
    アプリ更新したいから再起動するぞ!!!!

    プロセスが殺されるから全部一旦切るで~~

    え??????
    クライアントの再接続が一気に起こります

    View Slide

  67. 別の解法 =>
    サーバプッシュだ
    別の解法 =>
    サーバプッシュだ
    け別にやる
    け別にやる
    Node.js
    でSocket.IO
    しゃべってRedis pub/sub
    でWebApp
    からつなげ

    Go
    で似たようなことをgRPC/SSE
    でやる
    ActionCable
    Ruby on Rails
    でWebSocket
    をしゃべる機能
    kuiperbelt <-
    本日はコレの話
    openfresh/plasma

    View Slide

  68. 第1
    章まとめ
    第1
    章まとめ
    同期的Prefork
    では大量の寿命の長い接続をさばくのは困難であ

    現実的にやるには並行プログラミングを導入するか、サーバプッ
    シュだけ別のサーバでやる手法が考えられる

    View Slide

  69. 第2

    第2

    kuiperbelt
    とは
    kuiperbelt
    とは

    View Slide

  70. kuiperbelt
    とは
    kuiperbelt
    とは
    WebSocket
    とHTTP/1.1
    の相互変換プロキシサーバ
    by
    独立したWeb
    サーバデーモンとして動作する
    Go
    で書かれているが使うのにGo
    の知識は要らない
    mackee/kuiperbelt
    README.ja.md

    View Slide

  71. 機能
    機能
    WebSocket
    の接続を維持する 及び 現実的にやるための仕組みの
    提供
    接続認証の方法
    スケールアウト戦略

    View Slide

  72. 思想
    思想
    WebSocket
    をしゃべるサーバを直接インターネットに晒さない。
    ロードバランサーに入れる
    他の事例では直接インターネットにさらしているケースが多い
    大量接続に耐えるWebSocket
    アプリケーションサーバ構築のコ
    ツ - pixiv inside

    View Slide

  73. 何故ロードバランサーに入れ
    何故ロードバランサーに入れ
    る?
    る?
    LB
    に入れることで管理コストを下げる
    WebApp
    とWebSocket
    を疎結合にする

    View Slide

  74. 思想
    思想
    独自のデータ構造を作らない。HTTP
    の仕組みをそのままできる
    だけ使う
    HTTP
    がしゃべれるアプリケーションであれば適用可能

    View Slide

  75. 接続時の通信の流れ
    接続時の通信の流れ

    View Slide

  76. 試し方
    試し方
    から最新版のバイナリを落としてパスを通す
    Go
    のコマンドやデーモンはバイナリリリースがあれば自分でビ
    ルドせずバイナリリリースを使うのが好ましい
    のチュートリアルを読むか、 のPlack
    アプ
    リを読むと良い
    GitHub Releases
    README.ja.md example

    View Slide

  77. 認証用のHTTP
    サーバを作る
    my $app = sub {
    my $env = shift; my $req = Plack::Request->new($env);
    # some authentication processes
    my $endpoint = $req->header("X-Kuiperbelt-Endpoint");
    my $session_id = generate_uuid();
    $KVS->set("$user_id:kuiperbelt_session",
    { session => $session_id, endpoint => $endpoint });
    return [200,
    [
    "Content-Type" => "application/json",
    "X-Kuiperbelt-Session" => $session_id,
    ],
    ['{"message": "Hello WebSocket World!"}'],

    View Slide

  78. 認証用のアドレスを設定して起動
    さっきのを で保存して起動する
    kuiperbelt
    用config
    に立てたサーバのアドレスを設定ファイル
    ( )
    に書く
    書いた設定を使ってkuiperbelt
    を起動する
    $ plackup --port=5000 app.psgi
    callback:
    connect: http://localhost:5000/
    $ ekbo -config=config.yml

    View Slide

  79. メッセージ送信時の通信の流れ

    View Slide

  80. Perl
    からメッセージを送る
    なんでもいいけれどHTTP
    でAPI
    叩けば良いです。Body
    の形式は問い
    ません。
    # $target_user_id 対 $message 送
    my $furl = Furl->new;
    my $session =
    $KVS->get("$target_user_id:kuiperbelt_session");
    $furl->post(
    $session->{endpoint} . "/send",
    [ "X-Kuiperbelt-Session" => $session->{session_id} ],
    $message,
    );

    View Slide

  81. もともとの動機
    もともとの動機
    Perl5
    でWebSocket
    とかSSE
    を喋りたかったが、
    AnyEvent
    とかMojo
    使えばPerl5
    でWebSocket
    使えるやん?
    あ、そのせいでイベント駆動プログラミングをしないといけなく
    なった……
    DB
    のトランザクションかけようとしたら、混ざるんじゃないの
    これ?

    View Slide

  82. そこで思いついた
    そこで思いついた
    WebSocket
    の接続を維持するだけのサーバを作ればよいのでは?
    それが普通のHTTP API
    の口を持っていて、アプリケーションサー
    バからAPI
    を叩いてWebSocket
    に送るメッセージを送信すればよ
    いのでは?

    View Slide

  83. 先人の知恵
    APNS::Agent
    APNS::Agent
    iOS
    端末にプッシュ通知(
    スリープしていても届くと音がなって振
    動するやつ)
    をサーバから送るAPI
    が独自の生TCP
    かつ、非同期型
    Net::APNS::Extended
    などの同期型のモジュールもあるが、Prefork
    のWebApp
    から送ると当然詰まる

    View Slide

  84. 先人の知恵
    APNS::Agent
    APNS::Agent
    AnyEvent::APNS
    と言うモジュールもあるが、イベント駆動にしな
    いと
    AnyEvent::APNS
    を単体デーモンに切り出したのがAPNS::Agent

    View Slide

  85. 仕組み
    仕組み
    1. HTTP API
    でWebApp
    からメッセージを受け取る
    2. AnyEvent::APNS
    を使ってApple
    のサーバにつながっているソケッ
    トにメッセージと送り先トークンを書き込む
    3.
    プッシュ通知が届く
    HTTP
    でiOS
    のpush
    通知を送れる未来がやってきた(APNS::Agent
    の紹
    介)

    View Slide

  86. 偉大なところ
    偉大なところ
    やることに合わせたアーキテクチャを使い分けれるようにデーモ
    ン化して切り出した
    一つのことをうまくやる 土管に徹する
    UNIX
    哲学
    HTTP API
    というどの言語でも持っているようなインターフェイス
    で触れる
    現在では というデーモンに思想は引き継がれてAndroid
    にも通知が送れるようになっています
    Gunfish

    View Slide

  87. これだ!と思ってWebSocket
    これだ!と思ってWebSocket
    でも同じことをやろうとした
    でも同じことをやろうとした

    View Slide

  88. kuiperbelt
    という名前の由来
    kuiperbelt
    という名前の由来
    もともとはロングポールをやろうと思ってComet
    って言う別名もあ
    るので、じゃあ彗星の起源ということでエッジワース・カイパーベ
    ルトから名前を取った
    同じような言葉でオールトの雲というのがありoort
    は短いから良いなあと思ったんですがこの名前のOSS
    は既に
    あった

    View Slide

  89. kuiperbelt
    はWebSocket
    の接続
    kuiperbelt
    はWebSocket
    の接続
    を維持するデーモンとしてど
    を維持するデーモンとしてど
    のような課題の解決を行って
    のような課題の解決を行って
    いるか
    いるか

    View Slide

  90. 一般的なWebSocket
    の接続を
    一般的なWebSocket
    の接続を
    考える
    考える
    1.
    クライアントから普通のHTTP
    リクエストが来る
    2.
    サーバは繋いで良いクライアントだったらUpgrade
    する
    3. WebSocket
    接続を開始する
    4.
    適宜送ったり、受け取ったりする
    5.
    切りたいときに切る

    View Slide

  91. 課題
    課題
    認証の方法
    誰に送るかの指定方法
    送る内容の形式
    切断検知

    View Slide

  92. 認証の方法
    認証の方法
    kuiperbelt
    に認証機構を実装するのも考えたが……
    ユーザDB
    に接続する? OAuth
    する?
    土管に徹したいので認証機構は他所に投げたい
    一発目は普通のHTTP
    リクエストだからこれをWebApp
    にプロキ
    シすればいいのでは?

    View Slide

  93. コールバックを投げる
    コールバックを投げる
    クライアントからのHTTP
    リクエストをそのままバックエンドの
    WebApp
    に飛ばす
    200
    だったらWebSocket
    開始
    認証はWebApp
    に委ねることでkuiperbelt
    の実装自体はコンパクト
    になり堅牢になる

    View Slide

  94. 宛先の指定方法
    宛先の指定方法
    接続ごとに識別子を指定して欲しい気持ち
    接続の識別子は認証時のWebApp
    のレスポンスのヘッダに
    という名前で送ってもらってそれを使う
    メッセージを投げるときも というヘッダ
    に入れてもらう

    View Slide

  95. Body
    はApp
    のもの
    Body
    はApp
    のもの
    WebApp
    がメッセージを投げるときに叩くHTTP API
    のリクエスト
    のBody
    の内容をそのままWebSocket
    に書き込んでいる
    を指定すればバイナリも突っ込める
    msgpack
    を使っているプロジェクトもあります

    View Slide

  96. Body
    はApp
    のもの
    Body
    はApp
    のもの
    つまり何をどう送るかはWebApp
    に委ねられている =>
    土管に徹す
    る思想から
    なので送信オプションとかはHTTP
    ヘッダを使うようにしている

    View Slide

  97. 土管に徹すると何が良いか
    土管に徹すると何が良いか
    言語非依存になる
    Perl
    専用のデーモンだとユーザが広がりにくい。OSS
    プロダク
    トにおいてユーザ数というのは力になる
    役割が明確になる
    アプリケーション非依存になる

    View Slide

  98. 切断検知
    切断検知
    WebSocket
    はTCP
    なのでお行儀よく切断すれば接続が切れたこと
    を検知することが出来る
    アプリケーションによっては切断を知ることは重要なのでクライ
    アントから接続が切れたら設定したコールバックURL

    kuiperbelt
    が叩くように設定できる

    View Slide

  99. が、お行儀よく切断されないことはモバイルの世界だと大いに有り
    得るので、すぐに切れたことを検知する事はできない

    View Slide

  100. kuiperbelt
    での検知
    kuiperbelt
    での検知
    メッセージを送ろうとして書き込めなかったら切断と認定
    無通信時間が一定秒数以上続けば切断と認定
    WebSocket
    のping
    フレームをクライアントが一定間隔で送って
    くることが前提の機能

    View Slide

  101. 無通信時間による切断検知は使用プロジェクトの要望で追加した機
    能です

    View Slide

  102. 第2
    章まとめ
    第2
    章まとめ
    WebSocket
    に必要な要素をステートレスHTTP
    に分解すると普通の
    WebApp
    でもWebSocket
    を扱えるようになる
    土管に徹することで様々なWebApp
    でも使えるようになる

    View Slide

  103. 第3

    第3

    本番投入への道
    本番投入への道

    View Slide

  104. 作り始めたときは趣味だった
    作り始めたときは趣味だった
    社内でリアルタイムゲームのプロジェクトが立ち上がったときに
    「ぼくがかんがえたさいきょうのWebSocket
    アーキテクチャ」を
    披露し、既に実装はありますと言ったら採用された

    View Slide

  105. 趣味プロダクトはそのままで
    趣味プロダクトはそのままで
    は本番投入はおそらく出来な
    は本番投入はおそらく出来な
    い……
    い……

    View Slide

  106. そもそもどこのサービスにも投入されていない
    ユースケースが妄想かもしれない
    Proof of Concept
    のつもり
    こういうの考えてみたけれどどう?っていう実装

    View Slide

  107. プロジェクトへの導入をしながら機能を追加していった
    スケールアウト用の機能
    ベンチマーク
    監視API,
    ログ

    View Slide

  108. スケールアウト(
    複数台)
    戦略
    スケールアウト(
    複数台)
    戦略
    何故複数台構成に出来るようにするか?
    WebSocket
    受ける君が1
    台だと1
    台落ちるだけでサービス全体が障
    害に陥る(HA
    目的)

    View Slide

  109. 何故複数台構成に出来るようにするか?
    1
    台のスペックが上げられる上限は決まっている
    またAWS
    だとスペックは倍倍に上がっていくのでちょうどいい
    スペックに設定できないというのもあります

    View Slide

  110. 先行事例
    先行事例
    PC
    ゲームなどのオンラインゲーム
    それぞれのインスタンスがPublic IP
    を持っていてクライアント
    に接続先IP
    を指定
    同じ部屋の人は同じインスタンスに固めるなどが出来る
    インスタンス落ちたときのことをApp
    が考えないといけない

    View Slide

  111. 先行事例
    先行事例
    Node.js
    やActionCable
    など
    Redis pub/sub
    を用いる(
    メッセージキュー)
    WebSocket
    の接続を持つサーバが部屋ごとに作られたtopic

    subscribe
    しておいて、WebApp
    側が部屋のtopic
    に対してメッセ
    ージをpublish
    する
    Redis
    さえスケールさせれば、そうRedis
    さえ落ちなければ……

    View Slide

  112. やりたくないこと
    やりたくないこと
    WebApp
    からつなぐ先を指定させたくない
    外から繋げられるPublic IP
    がそれぞれのインスタンスに必要
    負荷の偏りが生じる

    View Slide

  113. やりたくないこと
    やりたくないこと
    特定のミドルウェアに依存したくない
    Redis pub/sub
    を使うとスケール限界がRedis
    の性能になる
    インフラ構成でRedis
    が必須になる

    View Slide

  114. 必要なのは
    どの接続がどのインスタンス
    どの接続がどのインスタンス
    にあるかをクライアント
    にあるかをクライアント
    (WebApp)
    が知っておくこと
    (WebApp)
    が知っておくこと

    View Slide

  115. KVS
    のシャーディング方法を
    KVS
    のシャーディング方法を
    参考にする
    参考にする
    libketama
    とかtwemproxy
    とか
    クライアントがキーから値が入っているホストを特定して取って
    くる
    KVS
    自体は分散されていることを別に知らなくても良い

    View Slide

  116. WebSocket
    の場合
    WebSocket
    の場合
    どこにつなぐべきかを接続時に指定すれば同じようなことは出来
    るが……
    やりたくないリストに入っている
    前段にLB
    を立てる場合はどこに繋ぐかはランダムなので接続識
    別子からどこに入っているかを導き出すことは出来ない
    スティッキーセッションを使えばランダムじゃなくすることは出来ますがどこに繋ぐべきかを指定するパタ
    ーンと同じになる

    View Slide

  117. 接続コールバックにアドレス
    接続コールバックにアドレス
    を埋め込む
    を埋め込む
    callback
    のリクエストにkuiperbelt
    は以下のヘッダを付
    ける
    X-Kuiperbelt-Endpoint: 192.168.100.1:9180

    View Slide

  118. 接続コールバックにアドレス
    接続コールバックにアドレス
    を埋め込む
    を埋め込む
    これをWebApp
    側は接続識別子と一緒に入れておいてどこにつな
    がっているかをKVS
    とかに保存しておく

    View Slide

  119. Q.
    結局KVS
    使っているので
    Q.
    結局KVS
    使っているので
    は???
    は???
    A.
    ユーザID
    と接続識別子の変
    A.
    ユーザID
    と接続識別子の変
    換にKVS
    とか使うでしょ
    換にKVS
    とか使うでしょ

    View Slide

  120. Q.
    識別子=
    ユーザID
    とかにす
    Q.
    識別子=
    ユーザID
    とかにす
    ればKVS
    必要じゃなくない?
    ればKVS
    必要じゃなくない?
    A. 1
    人のユーザが複数のWS

    A. 1
    人のユーザが複数のWS

    続持つケースがあって
    続持つケースがあって

    View Slide

  121. これでスケールの上限は
    これでスケールの上限は
    WebApp
    側の識別子解決の方
    WebApp
    側の識別子解決の方
    法に依存する
    法に依存する

    View Slide

  122. もしくはネットワークトポロ
    もしくはネットワークトポロ
    ジがフルメッシュになってい
    ジがフルメッシュになってい
    るのでそこも性能限界になり
    るのでそこも性能限界になり
    うる
    うる

    View Slide

  123. メッセージ送信時の通信の流れ

    View Slide

  124. kuiperbelt
    インスタンスが持っているSend API
    への接続数は
    WebApp
    側インスタンスの数に依存する
    超大規模環境でWebApp
    が多いと階層構造にするみたいなのが
    必要そう?
    Perl
    はメモリを食わないので横の台数が少なくて済むので便利

    View Slide

  125. どこがボトルネックになりう
    どこがボトルネックになりう
    るかをちゃんと知るのが大切
    るかをちゃんと知るのが大切

    View Slide

  126. ベンチマーク
    ベンチマーク
    理論上OK
    でも実際どうなのかというのは実際に動かさないとわ
    からない....
    実際のアプリケーションで想定される負荷をかけてみる
    ミドルウェアのベンチをとっとけば他のアプリでも採用がすん
    なりいきやすい

    View Slide

  127. ベンチの条件
    ベンチの条件
    クライアントは指定した数だけWebSocket
    のコネクションを貼る
    降ってきたメッセージはログに吐く
    WebApp
    側は接続中の接続の識別子を取ってきてメッセージを送
    るここは実アプリの挙動を真似すると良い
    n
    人に同じメッセージを送るパターン
    メッセージサイズetc...

    View Slide

  128. 結構古いバージョンの結果なので今とは性能が違う場合があります サーバは2
    コア2
    台です
    clients event CPU Mem(RSS) msgs/sec
    1000 1000 13% 68MB 2666
    5000 2000 24% 366MB 5388
    5000 4000 49% 355MB 10916
    10000 10000 120% 688MB 26666
    10000 1666 22% 737MB 4466

    View Slide

  129. わかること
    わかること
    メモリ使用量はクライアント数に依存する
    CPU
    は秒間メッセージ数に依存する
    ここからチューニングポイントが分かる

    View Slide

  130. さらにクライアント数を伸ばしていくと...
    clients event CPU Mem(RSS) msgs/sec
    20000 3333 47% 1400MB 8666
    40000 6666 130% 1500MB(4a383f2) 17880

    View Slide

  131. 40000 clients
    のところでメモリ使用量が減っている?けれど
    メモリが結構辛くなったのでチューニングを行いました

    View Slide

  132. メッセージを書き込むときに確保するバッファサイズを最適化し

    use io.CopyBuffer by @fujiwara
    io.Copy makes 32KB buffer automatically, but it
    is too large for kuiperbelt use case.

    View Slide

  133. これらベンチマークはfujiwara
    さんにやっていただきました

    View Slide

  134. 監視系の機能も@fujiwara
    さんに入れてもらったものが多い

    View Slide

  135. 監視系のAPI
    監視系のAPI
    時間を返すだけのAPI
    ロードバランサーからのヘルスチェックに必要
    kuiperbelt
    固有の監視項目を返す
    生きてるコネクション数/
    エラー数
    はこれを使っている
    運用と監視は切っても切れない関係にあることがわかりますね
    mackerel-plugin-kuiperbelt

    View Slide

  136. ログはガンガン出す
    ログはガンガン出す
    ただしパフォーマンスにも影響するのでログレベルで調整できるよ
    うにする

    View Slide

  137. ログレベル
    ログレベル
    Perl
    だと がよく使われている
    CRITICAL, WARN, INFO, DEBUG
    に出力するログを分類する
    kuiperbelt
    はDEBUG
    にガンガンログを出している => WebSocket
    のデバッグがめんどくさいので開発中に躓いて出したログはだ
    いたい残している
    その代わりに運用中にはDEBUG
    やINFO
    のログは切っている
    Log::Minimal

    View Slide

  138. ログうるさすぎ問題
    ログうるさすぎ問題
    アクセスログをINFO
    で出していたけれどログ流量が半端なくて
    他のログが埋まって機能しなくなった……
    そもそも前段にnginx
    やALB
    がいるときはそっちでログが出せる
    のでは?
    導入プロジェクトの人の要望から オプ
    ションを追加した

    View Slide

  139. デバッグ時には必要だけれど運用時には必要ないログみたいなのが
    ある

    View Slide

  140. そしてプロダクションへ
    そしてプロダクションへ
    カヤックでゲームをリリースするときは事前に専用のシナリオで
    負荷試験を行っています
    kuiperbelt
    導入プロジェクトはkuiperbelt
    含めた負荷試験も行っ
    ている
    それもクリアして、本番で実際に動いています

    View Slide

  141. これで
    これで
    Production Ready
    Production Ready
    と言える
    と言える

    View Slide

  142. 第3
    章まとめ
    第3
    章まとめ
    本番運用できるミドルウェアにするためには、いくつかの機能追
    加やベンチマークなど作り始めとは違う視点が必要
    ミドルウェアは信頼されてなんぼなので、これらは必要な労力だ
    と思う

    View Slide

  143. まとめ
    まとめ
    ミドルウェア作りは産みの苦しみもあるけれど、概念作れるし小
    説の世界観設計に似ている
    俺がミドルウェアを作るからその上で面白いゲームを作ってくれ
    と日々思っています
    一回概念を提唱するとそれについてくる人がいて助かる
    Thanks for @fujiwara, @shogo82148, and Users!

    View Slide

  144. 提供
    提供
    面白法人カヤックでは攻めのゲームづくりを一緒にする仲間を募集
    しています!!!
    Perl
    とかGo
    とかUnity/C#
    とか。今やってなくても応募はタダ!!

    View Slide

  145. その他やったこと
    その他やったこと
    本当にWebSocket
    をスマホが喋れるか検証
    レースコンディション解消
    Docker
    対応
    ALB
    が半死したときにhttp2
    になっててkuiperbelt
    も巻き込まれた

    View Slide

  146. Docker
    対応
    Docker
    対応
    カヤックでもECS
    にWebApp
    を載っけるのを進めています
    WebApp
    をコンテナに押し込んだら、kuiperbelt
    もコンテナに押し
    込みたい
    が、kuiperbelt
    を複数台構成にしたときは個別のコンテナにつな
    がるホストとポートをkuiperbelt
    自身が知る必要がある

    View Slide

  147. 自分につながるホスト名の取
    自分につながるホスト名の取


    オプションにホスト名orIP
    アドレスを書くか、無ければ
    を使う
    コンテナ内ではコンテナ起動時のネットワーク設定によっては
    で正しいものが取れない

    View Slide

  148. 自分につながるホスト名の取
    自分につながるホスト名の取


    コンテナ起動時にconfig
    を書き換えてX-Kuiperbelt-Host
    を正しいも
    のにしている
    以上はECS
    の場合です。k8s
    とかの場合はまだ試行錯誤中です。誰
    か助けてくれ

    View Slide

  149. その他課題
    その他課題
    kuiperbelt
    単体でスケールアウトさせる需要の声
    ドキュメント
    日本語ドキュメントはちゃんと書いてあるのだが
    ロゴがほしい
    社外のユーザ
    現状社内しかユーザはいない

    View Slide

  150. これからの展望
    これからの展望
    ekbo-router(cluster mode)
    上り対応(
    需要があれば)
    WebSocket
    以外の対応
    WebRTC
    が個人的に有望そう
    WebSocket over HTTP/2

    View Slide

  151. Net::Kuiperbelt, Alien::Kuiperbelt
    HTTP
    喋れば良いとは言えライブラリがあったほうがみんな使
    いそう
    Plack::Middleware
    とかで実現しても良いですが
    できるのか?

    View Slide

  152. 未実装の
    未来の話: ekbo-router
    未来の話: ekbo-router
    専用デーモンをWebApp
    側に立ててプロキシする
    識別子の発行はekbo-router
    が行う

    View Slide

  153. 未来の話: ekbo-router
    未来の話: ekbo-router
    prefix base routing
    kuperbelt
    インスタンスと識別子のprefix
    を対応させる
    ここは分散KVS
    が必要......
    WebApp
    はどこに接続があるかを気にせずにローカルのekbo-
    router
    に投げれば良い

    View Slide