Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ステートフルで大規模アクセスのあるsoft-realtimeなゲームサーバーをeasyにつくる
Search
さっちゃん
June 16, 2018
Programming
4
3.4k
ステートフルで大規模アクセスのあるsoft-realtimeなゲームサーバーをeasyにつくる
#elixirfest #elixirfestjp
さっちゃん
June 16, 2018
Tweet
Share
More Decks by さっちゃん
See All by さっちゃん
みんなのオブザーバビリティプラットフォームを作ってるんだがパフォーマンスがやばい #mackerelio #srenext
ne_sachirou
0
1.2k
作ってよかったgraceful shutdownライブラリ #kyotogo
ne_sachirou
0
1.1k
path 依存型って何?
ne_sachirou
0
480
野生の onbording と onbording 設計 #kyototechtalk
ne_sachirou
0
570
メトリックはいかにして見え續ける樣になったか #devio2022
ne_sachirou
0
69
名實一致
ne_sachirou
0
610
まかれるあなとみあ ―Mackerel のしくみを理解する 30 分― @ Hatena Engineer Seminar #16
ne_sachirou
0
3k
tacit programming : Point-free, Concatenatives & J
ne_sachirou
0
830
Monitoring Containerized Elixir
ne_sachirou
1
910
Other Decks in Programming
See All in Programming
OnlineTestConf: Test Automation Friend or Foe
maaretp
0
120
Jakarta EE meets AI
ivargrimstad
0
230
flutterkaigi_2024.pdf
kyoheig3
0
150
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
250
Outline View in SwiftUI
1024jp
1
330
Why Jakarta EE Matters to Spring - and Vice Versa
ivargrimstad
0
1.2k
2024/11/8 関西Kaggler会 2024 #3 / Kaggle Kernel で Gemma 2 × vLLM を動かす。
kohecchi
5
940
見せてあげますよ、「本物のLaravel批判」ってやつを。
77web
7
7.8k
Enabling DevOps and Team Topologies Through Architecture: Architecting for Fast Flow
cer
PRO
0
340
Duckdb-Wasmでローカルダッシュボードを作ってみた
nkforwork
0
130
イベント駆動で成長して委員会
happymana
1
340
AWS Lambdaから始まった Serverlessの「熱」とキャリアパス / It started with AWS Lambda Serverless “fever” and career path
seike460
PRO
1
260
Featured
See All Featured
Music & Morning Musume
bryan
46
6.2k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
665
120k
The Cost Of JavaScript in 2023
addyosmani
45
6.8k
Automating Front-end Workflow
addyosmani
1366
200k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
42
9.2k
Become a Pro
speakerdeck
PRO
25
5k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
33
1.9k
Designing for Performance
lara
604
68k
Happy Clients
brianwarren
98
6.7k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Designing for humans not robots
tammielis
250
25k
Transcript
ステートフルで 大規模アクセスのある soft-realtimeな ゲームサーバーを easyにつくる
.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)
None
.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆) 株式会社ドリコム ソフトウェアエンジニア (サーバーサイド)
本日は知見を共有しに来ました。 皆さんが同じ苦労を味ははない為にrailを敷いてゆきたい。
AWSのKubernetes上で、WebSocketを使ふElixir serverを運用するまで。
HTML5 (ほぼWebGL) のスマホ向け2Dゲーム TCG。Real time PvP
Raugh system architecture REST API WebSocket REST API PubSub PubSub
一部metrics収集 Matching 対戦
開発teamを立ち上げる
開発teamを立ち上げる 前提 : 資産が無い。 • 新規開発である。 • Real time対戦serverの知見があまり無い。 •
HTML5 game applicationのserver側の知見が無い。 Elixirの運用経験が在る。(Elixirを使ってSidekiqを操作する https://qiita.com/ohr486/items9/db88866786ee8bb89d9) Prototype開発期間であり、失敗したら作り直せる。
どのやうに開発を始めたか アプリを作る前にrailを敷く心。 • Credo • Dialyzer • mix test •
eye_drops • docker-compose.yml • PULL_REQUEST_TAMPLATE.md • CODE_OF_CONDUCT.md
どのやうに開発を始めたか アプリを作る前に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
どのやうに開発を始めたか アプリを作る前にrailを敷く心。 • CI/CD (Digdag) • ChatOps (Hubot) Teamが小さい &
アプリが小さい内にやる。
どのやうにElixirを学んだか (私個人) 前提 : Elixirの開発 & 運用経験は在る。 Erlang processを使って設計した開発は無い。つまりErlang/Elixirは素人。 関数型言語は好き。趣味ではHaskellを使ふ。
どのやうにElixirを学んだか (私個人) Elixirの3大特徴 : • 関数型言語 • GenServer (状態としてのprocess) •
Supervisor (監視tree)
どのやうにElixirを学んだか (私個人) 関数型言語 : 知ってた。好き。 HaskellとClojureをやってゐる。好き。
どのやうにElixirを学んだか (私個人) GenServer (状態としてのprocess) : 飛行機本にはいっぱい書いてあるがあれはオレオレGenServerの作り方であって使ひ方 ではない。Frameworkは使へなければいけない。 GenServerを使ふlibraryを作って学んだ。 (https://hex.pm/packages/holiday_jp) Parallel計算の不具合は登場人物2人で大体は再現する。1人ではなく2人で考へる。
どのやうにElixirを学んだか (私個人) Supervisor (監視tree) : ApplicationとGenServerを使ってゐたらだいたい使へる。 色々脳内simulationして作ってゐる。もっといい設計方法を作りたい。 Hot code reloadを実践利用してゐないのでその辺り曖昧…。
どのやうにElixirを学んだか (私個人) Q. 本読んだ? A. 読んでない。(後になってから読んだ。) 一番読んでるのはElixirとErlang/OTPの公式document。 • https://elixir-lang.org/docs.html •
http://erlang.org/doc/
どのやうにElixirを学んだか (team) 実は何もやってない。 皆が学んだ事を常時会話してゐる。(わりと結構常時。) 余計な事を喋っても、余計な事をやっても、責めない。
Real time対戦の設計
Real time対戦の設計 Matching用のchannelと対戦用のchannelを分けた。 Matchingには色々なlogicが在る為、複数種の matching channelを使ひ分けてゐる。
Matchingの設計 独立したmicro serviceをElixirで作った。 近いratingのUserが来るまで待ちつつ、rating幅を広げてゆく。 1台のserverで、Redisのsorted setに対して無限loopしてゐるだけ。 対戦serverとはPhoenix.PubSubで通信する。
対戦のprocess設計 Userを特定のserverにroutingしない。 serverの前でroutingする代りに、後ろでPubSubする。 何も考へずにserverを増減させられる。 LBはALB (coreos/alb-ingress-controller)。
対戦のprocess設計 1つの対戦を管轄する唯一のprocessは存在 せず、channel毎に計算する。 DBにlockを取得して排他制御する。 (Distributed locks with Redis https://redis.io/topics/distlock) ChannelとRedisだけなので構成が簡単に
できたが、複数のchannelで1つの対戦を管 理する故に時間経過管理が複雑になってし まった。
Serverを終了する Deployに関はるところ。 K8sのPreStopで、接続してゐるchannelが 無くなるまで待つ。
対戦のlogic設計 logic計算は、対戦状態を入力し、対戦状態と計算履歴を出力する純粋関数。
対戦のlogic設計 Elm (Redux) architectureを基に設計した。 優先度付きqueueのちゃんとしたlibraryが無かったので、 作った。(https://hex.pm/packages/pqueue2)
Master dataの管理 https://hex.pm/packages/mnemonics • 読み取り専用。 • on-memoryで高速。 • 再起動せずに新しいver.のdataへ入れ 替へられる。
• 古いver.で処理してゐた計算はそのまま 古いver.を読み出し続けられる。 • Heap領域にcacheできる。読み出した dataをsnapshotとしてsystem外に持 ち運べる。 • Parallel。
Elixir runs on Kubernetes
Elixir run on Docker Local環境ではDocker Composeで開発してゐる。 Erlang/OTPとElixirの両方のver.を固定したいので、base imageを作った。 https://hub.docker.com/r/nesachirou/elixir/
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から設定する。
Phoenixの起動を待つ K8sのReadiness probeで、HTTPを受け付けられるようになるまで待つ。 https://hex.pm/packages/komachi_heartbeat
Kubernetes on AWS まずAWSである事が前提。
Kubernetes on AWS K8sを選んだのは : • Game logicが大量に載ってゐる & PvPなので、hot
deployは考へたくなかっ た。 • Elixirの標準のdeploy & scale方法が無かったので、containerに入れて何も 考へたくなかった。 • Dockerに含まれる等、de facto standardである。 • GCPでもAzureでも使へる。(当時はAWSでのみ使へなかった。) • 将来の為の知見としても有用。
Kubernetes on AWS 当時EKSは無かった。のでkopsでclusterを作ってゐる。 社内標準のCentOS with itamaeをkopsで使へるやうに改造していただき、インフラ teamにclusterを運用してもらってゐる。 みなさんは迷はずEKSを使ってください。
Deploy to Kubernetes 全てをcodeに記載する。 itamaeでDigdagを構築する。 Digdagでimageをbuildし、kubectl set imageする。 cluster設定はchartとして書き、Helmで適用する。
監視 監視は社内標準の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に送る標準的なやり方 をしてゐる。
Performance
最適化 実は最適化はしてゐない。 最適化より、安定し、scaleさせる事に力を割いてきた為。 ERLANG IN ANGERに書いてある事くらいは注意する。
負荷試験 GatlingからWebSocketで繋いで対戦する。 Gatling実行インスタンスを複数用意して負荷を増やす。
負荷試験 問題 : 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桁くらい上げた。
負荷試験 問題 : 対戦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])`で圧縮する。
負荷試験 問題 : Master dataがmemoryを使ひ切る。 前頁でmaster dataのETSから頻繁にdataをcopyする事になり、GCが追い付かず memory不足でprocessがcrash。 頻繁にcopyするmaster dataを計算の初めにMnemonics.Snapに載せる。(そし
てRedisに保存する前に空にする。)
負荷試験 問題 : Redixに処理が詰まる Redix Connectionのmessage queueが詰まる。 そら、そう(〃l _ l)
Connection poolを実装した。後にRedisZとなる (後述)。
負荷試験 問題 : Redis (KVS) へのnetworkを使ひ切る。 当時PubSubもKVSもnetworkがbottleneckになってゐた。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する (PubSubでも工夫すれ ばできる)。
Redisをshardingする (後述)。
負荷試験 問題 : 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する (後述)。
負荷試験 問題 : CPUを使ひ切ってくれない。 不明。 K8s側で対処する。Podに割り当てるCPU量を少なくし、Nodeに詰めるPod数を増や す。 Server台数で補ふ。
負荷試験 問題 : PubSubがscaleしない。 させた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でこれについてのLTが在ります。
負荷試験 問題 : Redixがscaleしない。 させた (https://hex.pm/packages/redis_z)。 後述。
Serverをscale in/outさせる Nodeさえ確保できれば、scale outは簡単。 前述のAppTerminatorに対戦終了を待たせる事で、serverを突然死させずにscale inする。
Redisをscaleさせる PubSubにもDBにもRedisを使ってゐる。 Redisをscale outさせる。 PubSub KVS
Redis (PubSub)をscaleさせる Phoenix.PubSub.Redisを捨て、自作のPubSub adapterに差し替へた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でLTが在ります。
Redis (KVS)をscaleさせる Redis clusterは運用しない。 ShardingするRedixのwrapperを作った。
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)
Let’s share 知見. Let’s `mix hex.publish`.