Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

リアルタイム通信とは リアルタイム通信とは あるユーザの操作 時間経過やゲーム内オブジェクトの状態によって引き起こされるイ ベント が 別のユーザに即座に伝わる アーキテクチャ/ ゲームシステム のことを指す

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

ポーリング ポーリング

Slide 17

Slide 17 text

サーバプッシュ サーバプッシュ

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

時代が移ろいでいきPerl 界では mod_perl perl インタプリタの起動とモジュールの読み込みは予めしてお く FastCGI Apache からの解脱, lighttpd ってのがあってだな PSGI もうperl がHTTP をしゃべっちゃう Perl ウェブ開発の中世~CGI と Plack の間~ - YAPC::Kansai 2017

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

別件: アプリケーションと密結 別件: アプリケーションと密結 合の問題 合の問題 アプリ更新したいから再起動するぞ!!!! ↓ プロセスが殺されるから全部一旦切るで~~ ↓ え?????? クライアントの再接続が一気に起こります

Slide 67

Slide 67 text

別の解法 => サーバプッシュだ 別の解法 => サーバプッシュだ け別にやる け別にやる Node.js でSocket.IO しゃべってRedis pub/sub でWebApp からつなげ る Go で似たようなことをgRPC/SSE でやる ActionCable Ruby on Rails でWebSocket をしゃべる機能 kuiperbelt <- 本日はコレの話 openfresh/plasma

Slide 68

Slide 68 text

第1 章まとめ 第1 章まとめ 同期的Prefork では大量の寿命の長い接続をさばくのは困難であ る 現実的にやるには並行プログラミングを導入するか、サーバプッ シュだけ別のサーバでやる手法が考えられる

Slide 69

Slide 69 text

第2 章 第2 章 kuiperbelt とは kuiperbelt とは

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

認証用の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!"}'],

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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, );

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

第3 章 第3 章 本番投入への道 本番投入への道

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

先行事例 先行事例 Node.js やActionCable など Redis pub/sub を用いる( メッセージキュー) WebSocket の接続を持つサーバが部屋ごとに作られたtopic を subscribe しておいて、WebApp 側が部屋のtopic に対してメッセ ージをpublish する Redis さえスケールさせれば、そうRedis さえ落ちなければ……

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

Q. 識別子= ユーザID とかにす Q. 識別子= ユーザID とかにす ればKVS 必要じゃなくない? ればKVS 必要じゃなくない? A. 1 人のユーザが複数のWS 接 A. 1 人のユーザが複数のWS 接 続持つケースがあって 続持つケースがあって

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

結構古いバージョンの結果なので今とは性能が違う場合があります サーバは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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

メッセージを書き込むときに確保するバッファサイズを最適化し た use io.CopyBuffer by @fujiwara io.Copy makes 32KB buffer automatically, but it is too large for kuiperbelt use case.

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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