Slide 1

Slide 1 text

ステートフルで 大規模アクセスのある soft-realtimeな ゲームサーバーを easyにつくる

Slide 2

Slide 2 text

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆) 株式会社ドリコム ソフトウェアエンジニア (サーバーサイド)

Slide 5

Slide 5 text

本日は知見を共有しに来ました。 皆さんが同じ苦労を味ははない為にrailを敷いてゆきたい。

Slide 6

Slide 6 text

AWSのKubernetes上で、WebSocketを使ふElixir serverを運用するまで。

Slide 7

Slide 7 text

HTML5 (ほぼWebGL) のスマホ向け2Dゲーム TCG。Real time PvP

Slide 8

Slide 8 text

Raugh system architecture REST API WebSocket REST API PubSub PubSub 一部metrics収集 Matching 対戦

Slide 9

Slide 9 text

開発teamを立ち上げる

Slide 10

Slide 10 text

開発teamを立ち上げる 前提 : 資産が無い。 ● 新規開発である。 ● Real time対戦serverの知見があまり無い。 ● HTML5 game applicationのserver側の知見が無い。 Elixirの運用経験が在る。(Elixirを使ってSidekiqを操作する https://qiita.com/ohr486/items9/db88866786ee8bb89d9) Prototype開発期間であり、失敗したら作り直せる。

Slide 11

Slide 11 text

どのやうに開発を始めたか アプリを作る前にrailを敷く心。 ● Credo ● Dialyzer ● mix test ● eye_drops ● docker-compose.yml ● PULL_REQUEST_TAMPLATE.md ● CODE_OF_CONDUCT.md

Slide 12

Slide 12 text

どのやうに開発を始めたか アプリを作る前にrailを敷く心。 ● Credo ● Dialyzer ● mix test ● eye_drops ● docker-compose.yml ● PULL_REQUEST_TAMPLATE.md ● PULL_REQUEST_TAMPLATE.md ● CODE_OF_CONDUCT.md https://hex.pm/packages/inner_cotton

Slide 13

Slide 13 text

どのやうに開発を始めたか アプリを作る前にrailを敷く心。 ● CI/CD (Digdag) ● ChatOps (Hubot) Teamが小さい & アプリが小さい内にやる。

Slide 14

Slide 14 text

どのやうにElixirを学んだか (私個人) 前提 : Elixirの開発 & 運用経験は在る。 Erlang processを使って設計した開発は無い。つまりErlang/Elixirは素人。 関数型言語は好き。趣味ではHaskellを使ふ。

Slide 15

Slide 15 text

どのやうにElixirを学んだか (私個人) Elixirの3大特徴 : ● 関数型言語 ● GenServer (状態としてのprocess) ● Supervisor (監視tree)

Slide 16

Slide 16 text

どのやうにElixirを学んだか (私個人) 関数型言語 : 知ってた。好き。 HaskellとClojureをやってゐる。好き。

Slide 17

Slide 17 text

どのやうにElixirを学んだか (私個人) GenServer (状態としてのprocess) : 飛行機本にはいっぱい書いてあるがあれはオレオレGenServerの作り方であって使ひ方 ではない。Frameworkは使へなければいけない。 GenServerを使ふlibraryを作って学んだ。 (https://hex.pm/packages/holiday_jp) Parallel計算の不具合は登場人物2人で大体は再現する。1人ではなく2人で考へる。

Slide 18

Slide 18 text

どのやうにElixirを学んだか (私個人) Supervisor (監視tree) : ApplicationとGenServerを使ってゐたらだいたい使へる。 色々脳内simulationして作ってゐる。もっといい設計方法を作りたい。 Hot code reloadを実践利用してゐないのでその辺り曖昧…。

Slide 19

Slide 19 text

どのやうにElixirを学んだか (私個人) Q. 本読んだ? A. 読んでない。(後になってから読んだ。) 一番読んでるのはElixirとErlang/OTPの公式document。 ● https://elixir-lang.org/docs.html ● http://erlang.org/doc/

Slide 20

Slide 20 text

どのやうにElixirを学んだか (team) 実は何もやってない。 皆が学んだ事を常時会話してゐる。(わりと結構常時。) 余計な事を喋っても、余計な事をやっても、責めない。

Slide 21

Slide 21 text

Real time対戦の設計

Slide 22

Slide 22 text

Real time対戦の設計 Matching用のchannelと対戦用のchannelを分けた。 Matchingには色々なlogicが在る為、複数種の matching channelを使ひ分けてゐる。

Slide 23

Slide 23 text

Matchingの設計 独立したmicro serviceをElixirで作った。 近いratingのUserが来るまで待ちつつ、rating幅を広げてゆく。 1台のserverで、Redisのsorted setに対して無限loopしてゐるだけ。 対戦serverとはPhoenix.PubSubで通信する。

Slide 24

Slide 24 text

対戦のprocess設計 Userを特定のserverにroutingしない。 serverの前でroutingする代りに、後ろでPubSubする。 何も考へずにserverを増減させられる。 LBはALB (coreos/alb-ingress-controller)。

Slide 25

Slide 25 text

対戦のprocess設計 1つの対戦を管轄する唯一のprocessは存在 せず、channel毎に計算する。 DBにlockを取得して排他制御する。 (Distributed locks with Redis https://redis.io/topics/distlock) ChannelとRedisだけなので構成が簡単に できたが、複数のchannelで1つの対戦を管 理する故に時間経過管理が複雑になってし まった。

Slide 26

Slide 26 text

Serverを終了する Deployに関はるところ。 K8sのPreStopで、接続してゐるchannelが 無くなるまで待つ。

Slide 27

Slide 27 text

対戦のlogic設計 logic計算は、対戦状態を入力し、対戦状態と計算履歴を出力する純粋関数。

Slide 28

Slide 28 text

対戦のlogic設計 Elm (Redux) architectureを基に設計した。 優先度付きqueueのちゃんとしたlibraryが無かったので、 作った。(https://hex.pm/packages/pqueue2)

Slide 29

Slide 29 text

Master dataの管理 https://hex.pm/packages/mnemonics ● 読み取り専用。 ● on-memoryで高速。 ● 再起動せずに新しいver.のdataへ入れ 替へられる。 ● 古いver.で処理してゐた計算はそのまま 古いver.を読み出し続けられる。 ● Heap領域にcacheできる。読み出した dataをsnapshotとしてsystem外に持 ち運べる。 ● Parallel。

Slide 30

Slide 30 text

Elixir runs on Kubernetes

Slide 31

Slide 31 text

Elixir run on Docker Local環境ではDocker Composeで開発してゐる。 Erlang/OTPとElixirの両方のver.を固定したいので、base imageを作った。 https://hub.docker.com/r/nesachirou/elixir/

Slide 32

Slide 32 text

Build for release Distilleryでmix releaseする。 Docker multi stage buildでimageを軽くする。 Umbrellaでmicro serviceを開発し、Distilleryで別々にbuildしてゐる。 Stagingと本番で全く同じimageを使ふ (可搬性) 為に、config.exsではなく環境変数 で設定する。(https://hexdocs.pm/distillery/runtime-configuration.html) 環境変数はK8sのConfigMapから設定する。

Slide 33

Slide 33 text

Phoenixの起動を待つ K8sのReadiness probeで、HTTPを受け付けられるようになるまで待つ。 https://hex.pm/packages/komachi_heartbeat

Slide 34

Slide 34 text

Kubernetes on AWS まずAWSである事が前提。

Slide 35

Slide 35 text

Kubernetes on AWS K8sを選んだのは : ● Game logicが大量に載ってゐる & PvPなので、hot deployは考へたくなかっ た。 ● Elixirの標準のdeploy & scale方法が無かったので、containerに入れて何も 考へたくなかった。 ● Dockerに含まれる等、de facto standardである。 ● GCPでもAzureでも使へる。(当時はAWSでのみ使へなかった。) ● 将来の為の知見としても有用。

Slide 36

Slide 36 text

Kubernetes on AWS 当時EKSは無かった。のでkopsでclusterを作ってゐる。 社内標準のCentOS with itamaeをkopsで使へるやうに改造していただき、インフラ teamにclusterを運用してもらってゐる。 みなさんは迷はずEKSを使ってください。

Slide 37

Slide 37 text

Deploy to Kubernetes 全てをcodeに記載する。 itamaeでDigdagを構築する。 Digdagでimageをbuildし、kubectl set imageする。 cluster設定はchartとして書き、Helmで適用する。

Slide 38

Slide 38 text

監視 監視は社内標準のZabbix。 Node (インスタンス) のmetricsは社内標準のmackerel。 APMはAppSignal + ReconEx。error通知はSentryで行ふのでerror_loggerは 外す。 error通知は社内標準のSentry。logger_sentryを使ってゐたが追加情報を送れな いのでやめた。Sentry付属のerror_logger + 自作のErrorLogger macroを丁寧 に埋め込んである。 Logは、DaemonSetでfluentdを立て、CloudWatch + S3に送る標準的なやり方 をしてゐる。

Slide 39

Slide 39 text

Performance

Slide 40

Slide 40 text

最適化 実は最適化はしてゐない。 最適化より、安定し、scaleさせる事に力を割いてきた為。 ERLANG IN ANGERに書いてある事くらいは注意する。

Slide 41

Slide 41 text

負荷試験 GatlingからWebSocketで繋いで対戦する。 Gatling実行インスタンスを複数用意して負荷を増やす。

Slide 42

Slide 42 text

負荷試験 問題 : Logger。 Stagingでdebug logを見たい & stagingと本番で同じDocker imageを使ひたい → Loggerのlevelだけ設定し、compile_time_purge_levelを設定してゐなかっ た。 Loggerに大量のmessageが詰まり、常にsync_thresholdを超える。全ての処理が IO待ちになる。 Stagingでのdebug logを諦め、compile_time_purge_levelを設定した。 Loggerの各種thresholdを1桁くらい上げた。

Slide 43

Slide 43 text

負荷試験 問題 : 対戦dataがmemoryを使ひ切る。 対戦dataをRedisに一時保存してあり、計算時に取り出す。取り出したところで memory不足でprocessがcrash (process毎にmemory制限をかけてある)。 Erlangの一部の外部表現は大きくなる。特にNEW_FUN_EXTとか (External Term Format http://erlang.org/doc/apps/erts/erl_ext_dist.html)。 Master dataと共通するdata (Mnemonics.Snap) を保存前に削除し、master dataから毎回取り直す。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する。

Slide 44

Slide 44 text

負荷試験 問題 : Master dataがmemoryを使ひ切る。 前頁でmaster dataのETSから頻繁にdataをcopyする事になり、GCが追い付かず memory不足でprocessがcrash。 頻繁にcopyするmaster dataを計算の初めにMnemonics.Snapに載せる。(そし てRedisに保存する前に空にする。)

Slide 45

Slide 45 text

負荷試験 問題 : Redixに処理が詰まる Redix Connectionのmessage queueが詰まる。 そら、そう(〃l _ l) Connection poolを実装した。後にRedisZとなる (後述)。

Slide 46

Slide 46 text

負荷試験 問題 : Redis (KVS) へのnetworkを使ひ切る。 当時PubSubもKVSもnetworkがbottleneckになってゐた。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する (PubSubでも工夫すれ ばできる)。 Redisをshardingする (後述)。

Slide 47

Slide 47 text

負荷試験 問題 : Redis (PubSub) へのnetworkを使ひ切る。 そもそもpublishを減らす。Publish先を抽象化するstructを作り、Channel topic ではなくstructに向けてpublishする。Publishが不要だと判定したら、send/2で済ま す。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する。但しmapしかpublish できないのでvalueだけ圧縮する等dirty hack。 Dataを直接にpublishせずKVSに置き、keyだけをpublishする。 Redisをshardingする (後述)。

Slide 48

Slide 48 text

負荷試験 問題 : CPUを使ひ切ってくれない。 不明。 K8s側で対処する。Podに割り当てるCPU量を少なくし、Nodeに詰めるPod数を増や す。 Server台数で補ふ。

Slide 49

Slide 49 text

負荷試験 問題 : PubSubがscaleしない。 させた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でこれについてのLTが在ります。

Slide 50

Slide 50 text

負荷試験 問題 : Redixがscaleしない。 させた (https://hex.pm/packages/redis_z)。 後述。

Slide 51

Slide 51 text

Serverをscale in/outさせる Nodeさえ確保できれば、scale outは簡単。 前述のAppTerminatorに対戦終了を待たせる事で、serverを突然死させずにscale inする。

Slide 52

Slide 52 text

Redisをscaleさせる PubSubにもDBにもRedisを使ってゐる。 Redisをscale outさせる。 PubSub KVS

Slide 53

Slide 53 text

Redis (PubSub)をscaleさせる Phoenix.PubSub.Redisを捨て、自作のPubSub adapterに差し替へた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でLTが在ります。

Slide 54

Slide 54 text

Redis (KVS)をscaleさせる Redis clusterは運用しない。 ShardingするRedixのwrapperを作った。

Slide 55

Slide 55 text

Redis (KVS)をscaleさせる https://hex.pm/packages/redis_z ● No downgrade from Redix: pipeline concurrency & auto reconnection. (https://hexdocs.pm/redix/real-world-usage.html) ● Parallel connection pooling. ● Sharding support. ● Auto reconnect at Amazon ElastiCache Multi-AZ failover. (https://rubygems.org/gems/redis-elasticache)

Slide 56

Slide 56 text

Let’s share 知見. Let’s `mix hex.publish`.