[ndc2017 : 박준철] python 게임 서버...

78
스마트스터디 박준철 몬스터 슈퍼리그 게임 서버 Python 게임 서버 안녕하십니까?

Upload: -

Post on 21-Jan-2018

8.812 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

스마트스터디

박준철

몬스터 슈퍼리그 게임 서버

Python 게임 서버 안녕하십니까?

Page 2: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

목표

• 몬슈리의 Python 게임 서버에 사용된 각종 라이브러리와 서비스 공유

• 몬슈리의 사례를 통해서 Python 으로 게임 서버를 만들 때 개발자의 의사결정에

도움이 되는 것

• 경험의 공유를 통한 “Python 은 느리지만 빨라”를 전파

몬스터 슈퍼 리그를 개발하면서 빠른 개발 진행을 위해 선택했던 Python 게임

서버, '잘 되면 다시 만들지 뭐'라는 생각에서 시작했지만 다시 만들 일은 영원히

오지 않았습니다... Python으로 게임 서버를 만들었을 때 사용한 것은 무엇인지

또 실제 오픈 했을 때 서버는 안녕했는지 알아봅니다.

from https://ndc.nexon.com

Page 3: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

목차1. 몬슈리 Python 게임 서버는 안녕하십니까?

2. 몬슈리 Python 게임 서버

3. 안녕하기 위한 노력

4. 몬슈리 Python 게임 서버는 안녕했나요?

5. Python 게임 서버

Page 4: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

몬슈리 Python 게임 서버는

안녕하십니까?

Page 5: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

현재

• DAU : 10 만 명

• CCU : 2 만 명

• Request / day = 6,000 만

• Sales in 6 months = $ 2,000 만 +

Page 6: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

몬슈리 Python 게임 서버

Page 7: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python 2.7.11

• 개발 및 유지 보수가 편해야 함

• 다양한 라이브러리와 클라우드 서비스를 지원

• 패키지 관리가 잘 되어야 하고 의존성 문제가 없어야 함

• 서버의 퍼포먼스보다 개발자의 퍼포먼스가 중요

• 이미 사내 backend 개발에 Python 을 사용하고 있음

• 몬슈리 개발팀에 적합한 언어

Page 8: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python 2.7.11

• Blinker

• GData

• Celery

• Jinja2, bootstrap3

• NewRelic, Sentry

• …

• Flask (nginx + uwsgi)

• SQLAlchemy

• Protobuf (Protocol Buffers)

• Redis

• NumPy

• Requests

Page 9: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

졸음 주의

• 몬슈리 게임 서버에 사용된 각종 라이브러리 나열식 설명

Page 10: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

기본적인 선택들

• Lightweight Web Application Framework - Flask

• ORM - SQLAlchemy

• KeyValue DB - Redis

• Asynchronous Tasks - Celery

Page 11: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

게임 서버는 Flask

• Protobuf message 기준으로 decorator 를 통한 routing@route('ReqUserLogin')def userLogin(reqUserLogin):

@app.route('/api', methods=['POST'])req = request_pb2.Request.FromString(reqBody)…db_begin()try:

rsp = handle(req)db_commit()

except:db_rollback()

finally:db_end()

return rsp

Page 12: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

게임 서버는 Flask

• Error 처리

• Status code=500

• 명백한 서버 오류, 클라이언트에서 “재시도” or “재접속” 팝업

• Status code=200 & Error Code 를 나누어 사용

• 서버와 클라이언트의 데이터가 맞지 않는 상황으로 판단

• 오류 메시지 노출 후 계속 진행 or “재접속”

Page 13: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

게임 서버는 Flask

• 클라이언트의 Retry 대비

• Response 를 Redis 에 보관 후 동일한 Request 에 대해서 Response 를

바로 Return

• 2 회 이상 발생시 Status Code=200 + Error Code return

Page 14: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

DB 는 SQLAlchemy

• Alembic 을 이용한 DB Migration 지원

• Multi DB 지원

• Auto Commit 사용하지 않음

• 모든 User Request 에 대해서 Transaction 사용

• Request 처리 중 서버 오류나 데이터 오류의 경우 Rollback

• 오류 없이 로직이 처리된 경우 Commit

Page 15: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

데이터는 Protocol Buffers (ProtoBuf)

• Message 파일을 정의하고 Compile

• Python , 3rd party c# 지원

• optional

• enum

• type checker

• python 을 위한 C++ 구현체 지원

message MsgUserItem{optional fixed32 item_uid = 1;optional uint32 item_count = 2;

}

enum MonsterStatType {MS_None = 0;MS_Attack = 1;MS_Defence = 2;MS_Heal = 3;MS_Balance = 4;MS_Hp = 5;

}

Page 16: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

랜덤은 NumPy

• Python Random 보다 빠르고 더 좋은 결과를 주지 않을까?

• 막연한 기대로 선택

• 그러나 실제로는…

Page 17: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

랜덤은 NumPy

• Random seed : 1• 10, 1000, 1,000,000 개 random 비교

Page 18: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

랜덤은 NumPy

• Random seed : 1• 10, 1000, 1,000,000 개 random 비교

Page 19: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

랜덤은 NumPy

• Random seed : 1• 10, 1000, 1,000,000 개 random 비교

Page 20: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

랜덤은 NumPy

• randint 는 성능 차이가 10배 정도 나지만 random 은 차이 없음

• python.random.choice vs numpy.random.choice>>> import random, numpy>>> a = ['Miho', 'Anu', 'Leo', 'Lily', 'Seiren', 'Sura', 'Persephone', 'Nightmare']>>> random.choice(a)'Anu'>>> numpy.random.choice(a, 11, p=[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.2]).tolist()['Lily', 'Anu', 'Persephone', 'Sura', 'Seiren', 'Sura', 'Sura', 'Persephone', 'Anu', 'Lily', 'Anu']

random Time (s)

random.random 0.077058

numpy.random 0.078524

python.randint 1.091723

numpy.randint 0.102347

Page 21: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

서버간 연결은 requests

• Python 용 HTTP 라이브러리, 주로 서버간 통신에 사용

• Timeout 설정

• Timeout 설정하지 않으면 exception 없는 한 무한 대기

“Without a timeout, your code may hang for minutes or more.”

• User request 에서 서버간 request timeout : 3~9s

• Scheduler 에서 서버간 request timeout : 30s

>>> r = requests.post(url, json=json, timeout=3, headers=headers)

Page 22: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Quest 는 Blinker 로

• Blinker : Signal(Event) Broadcasting 라이브러리

• Signal(Event) 을 send 하는 즉시 Receiver 에게 데이터 전달

• Quest 에서 사용한 방법

• Quest 종류 별로 미리 Custom Name Signal(Event) 정의

• Signal(Event) 별로 1개의 Receiver 를 connect

• 서버 로직에서 Quest 체크가 필요한 경우 Signal 을 send

Page 23: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Quest 는 Blinker 로# signals.pyimport blinkernamespace = blinker.Namespace()summon = namespace.signal('summon')…

# main.py - subscribe summon signal (event)import signals…@signals.summon.connectdef check_summon(sender, user, **kwargs):

quests = find_quests(user, main_condi=data_pb2.MsgQuest.Summon, **kwargs)if quests:

add_extra_response(quests_inc_count(user, quests))…# summon 에 대한 각종 조건 체크 후 퀘스트 진행상황 업데이트signals.summon.send(user=user, monster=new_monster, summon_mon_egg=True)…

Page 24: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

게임 데이터는 gdata 로

• 기획팀에서 사용하는 data 는 google sheet 로 관리

• data sheet 를 protobuf serializing 하여 file 로 저장

• 116 google sheets (11 files) serializing = 15s

$ ls joongom staff 644797 3 16 13:37 string.zh-tw.pbjoongom staff 660737 3 16 13:37 string.zh-cn.pbjoongom staff 1676594 3 16 13:37 string.th.pbjoongom staff 739756 3 16 13:37 string.pt.pbjoongom staff 807264 3 16 13:37 string.ko.pbjoongom staff 881384 3 16 13:37 string.ja.pbjoongom staff 763874 3 16 13:37 string.it.pbjoongom staff 794710 3 16 13:37 string.fr.pbjoongom staff 764741 3 16 13:37 string.es.pbjoongom staff 707415 3 16 13:37 string.en.pbjoongom staff 757907 3 16 13:37 string.de.pbjoongom staff 3413470 3 16 13:37 gamedata.pb

Page 25: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

게임 데이터는 gdata 로

• 수정 전 데이터와의 diff 를 확인하는 별도의 툴이 필요

• 데이터 배포 전 반드시 확인하는 절차를 진행

• protobuf 를 dict 으로 변경하여 diff

• protobuf-to-dict : https://github.com/benhodgson/protobuf-to-dict

$ data_diff.py 610 620removed mon.cocomaru.tree.1 substages uid:stage.01.05 normal_display_mons index:2removed mon.slimeb.water.1 substages uid:stage.01.05 normal_display_mons index:3modified mon.jackolittlew.light.3 --> mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:0modified mon.jackolittle.dark.3 --> mon.slimeb.water.3 substages uid:stage.01.02 hell_display_mons index:1modified mon.jackolittle.dark.3 --> mon.squir.tree.3 substages uid:stage.01.02 hell_display_mons index:2added mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:3

Page 26: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python Exception 은 Sentry

• Sentry is a modern error logging and aggregation platform.

server.py...sentry = Sentry(dsn='http://public_key:[email protected]/1')

def create_app():app = Flask(__name__)sentry.init_app(app)return app

...

try:...

except:sentry.captureException(extra={’User’: userData, ‘Request’: requestData})

Page 27: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python Exception 은 Sentry

Page 28: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

nginx + uWSGI + supervisor

• Flask

• WSGI 표준 지원, uWSGI 로 nginx 연결

• nginx

• 각종 성능에 도움이 될 수 있는 옵션들 적용

• 배포 서버에서만 사용 (개발할 때는 flask run server)

• Supervisor

• Process 컨트롤 시스템

Page 29: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

운영툴 은 flask + Jinja2 + bootstrap3

Page 30: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

서버는 AWS ECS

• AWS ECS 로 서버 구축

• 게임 서버를 docker registry 를 통해 배포

• AWS ECS, RDS, ElasticCache (Redis), ELB, EC2 사용

• 강력한 AWS CLI

$ aws ecs register-task-definition --family qa-tool --container-definitions file://./qa-tool.json

$ aws ecs update-service --cluster qa-gs --service msl-tool --task-definition qa-tool

$ aws ecs update-service --cluster qa-gs --service msl-tool --desired-count 1 --deployment-configuration

"maximumPercent=100,minimumHealthyPercent=0”

$ aws ecs update-service --cluster qa-gs --service msl-game --desired-count 0

$ aws ecs update-service --cluster qa-gs --service msl-game --desired-count 1

Page 31: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

CBT #1 기본 서버 구성

• Game Server & Game Tool Server

• Flask app x n

• Nginx worker x m

• Redis x 1

• Celery x 1

• AWS RDS(MySQL) x 1

• AWS ElasticCache(Redis) x 1

• AWS ELB x 2

• AWS EC2 x 3

• AWS ECS

• Game server service (2 task)

• Game tool service (1 task)

Page 32: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

CBT #1

Page 33: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

안녕하기 위한 노력

안녕하세요. 개발자님.

전 퍼블리셔라고 합니다.

Page 34: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

안녕하세요.

• 퍼블리셔 : 안녕하세요. “퍼블리셔” 입니다.

• 개발자 : 안녕하세요.

준곰이라고 불러 주세요. ☺

2003 년부터 NEXON, NCSOFT, NEOWIZ 에서 게임을 만들었습니다.

대부분 서비스를 종료했습니다만… 왠지 이번에는 느낌이 좋네요.

그리고 2010년 부터 스마트스터디에서 핑크퐁 앱과 각종 게임들을 만들었습니다.

CTO 를 하고 있는 몬슈리 개발자 박준철 입니다.

Page 35: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Sharding 해주실 수 있나요?

• 개 : 네, Table 마다 다른 DB 를 지정할 수 있습니다.

• 퍼 : 1개의 Table 을 n개의 DB에 저장하는 Sharding 을 해주셔야…

• 개 : ORM 쓰고 foreign key도 썼는데… T_T

Page 36: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

DB Sharding

• CBT #1 에서 DB CPU 사용률이 높은 것을 확인

• ‘퍼’ 님이 100배 많이 유저님들을 모시고 올 것이니…

A. Foreign key 를 삭제

B. Flask-SQLAlchemy 코드를 수정 Sharding 구현

a. 1 DB = Shard DB x n

b. User 기준으로 GameDB sharding

c. User 별 Shard DB 정보 - 1 개의 CommonDB

Page 37: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

DB ShardingSQLALCHEMY_BINDS = {

'common': 'mysql://msl:msl@localhost/MSLCommonDB','game': 'mysql://msl:msl@localhost/MSLGameDB1','log': 'mysql://msl:msl@localhost/MSLLogDB1','clan': 'mysql://msl:msl@localhost/MSLClanDB',

}

SQLALCHEMY_BINDS = {'common': 'mysql://msl:msl@localhost/MSLCommonDB','game': ['mysql://msl:msl@localhost/MSLGameDB1',

'mysql://msl:msl@localhost/MSLGameDB2'],'log': ['mysql://msl:msl@localhost/MSLLogDB1',

'mysql://msl:msl@localhost/MSLLogDB2'],'clan': 'mysql://msl:msl@localhost/MSLClanDB',

}

use commondb;insert into db (db_idx, host, db_name, db_type, world_idx, shard_no, use_yn, reg_date) \

values (11, 'localhost', 'gdb1', 'U', 1, 1, 'Y', now());insert into db (db_idx, host, db_name, db_type, world_idx, shard_no, use_yn, reg_date) \

values (12, 'localhost', 'gdb2', 'U', 1, 2, 'Y', now());insert into db (db_idx, host, db_name, db_type, world_idx, shard_no, use_yn, reg_date) \

values (21, 'localhost', 'logdb1', 'L', 1, 1, 'Y', now());insert into db (db_idx, host, db_name, db_type, world_idx, shard_no, use_yn, reg_date) \

values (22, 'localhost', 'logdb2', 'L', 1, 2, 'Y', now());

• Shard DB 정보는 commondb.db 에서 관리

Page 38: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

DB Sharding

> show create table commondb.account;-----------------------------------------------------…`user_id` bigint(20) NOT NULL,`gamedb_shard_no` smallint(6) NOT NULL DEFAULT `1`,`logdb_shard_no` smallint(6) NOT NULL DEFAULT `1`,…------------------------------------------------------

• user 의 game, log db 를 sharding

• gamedb, logdb 의 sharding 을 구성하는 DB 개수가 다를 수 있음

• gamedb_shard_no, logdb_shard_no 정보를 account 에 추가

Page 39: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

DB Sharding

db.set_default_shard(current_app, 'game', gamedb_shard_no)db.set_default_shard(current_app, 'log', logdb_shard_no)q = User.query.set_shard(gamedb_shard_no).filter_by(id=user_id)

• User Request 처리는 대체로 user 의 data 에 접근하므로 shard_no 고정적

• default_shard_no 지정 기능 추가

• 친구 data에 접근하는 경우 다른 shard_no 를 사용하는 경우 발생

• Query 에 shard 지정 기능 추가

Page 40: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Redis CPU 사용률이 너무 높은데요?

• 개 : 성능 좋은 인스턴스로 바꿔서 서비스 해줘요.

• 퍼 : redis 는 싱글 스레드라…

• 개 : 아…

• 퍼 : redis 도 Sharding 해주시면 좋겠습니다 ☺

• 개 : 아…

Page 41: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Redis Sharding

• https://redis.io/topics/benchmarks

• “Redis runs slower on a VM compared to running without virtualization using the same hardware. If you have the chance to run Redis on a physical machine this is preferred. …”

• virtualized vs bare-metal servers (without pipeline)

Intel(R) Xeon(R) CPU E5520 @ 2.27GHz Linode 2048 instance

SET 122556.53 req/sec 35001.75 req/sec

GET 123601.76 req/sec 37481.26 req/sec

$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q

Page 42: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Redis Sharding

• Redis 저장 데이터

• User Session Token & Data ( expire=3600 )

• User Response Cache Data ( expire=3600 )

• Recommended Friends

• Friend Dungeon Data ( expire=3600 )

• Server Cache Data

Page 43: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Redis Sharding

• 1 User Request

• 1 Read User Session Token + 1 Read User Session Data

• 1 Write User Session Token + 1 Write User Session Data

• 1 Read and 1 Write Serialized Response Cache Data

• CCU 100,000 : 0.07 TPS * 100,000 * 6 = 42,000 / sec

• User data 만 User Session Token 으로 Sharding

redis_shard_no = Hash(User Session Token) % session_redis_count

Page 44: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Monitoring은 뭘로 하나요?

• 퍼 : 서버 Monitoring은 뭘로 하나요?

• 개 : Exception 나면 Sentry 가 와요. Sentry 짱

• 퍼 : Exception 안 날 때는 뭘로 봐요?

• 개 : 잘 돌고 있겠죠…

• 퍼 : …

Page 45: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

New Relic

• APM(Application Performance Monitoring)

• 24 시간 이내 Request 의 Transaction 분석 무료

• Database Transaction 분석 유료

• Transaction 별 분석 가능 (set_transaction_name)

…app = newrelic.agent.wsgi_application()(app)…def handle(api_name, req):

…newrelic.agent.set_transaction_name(api_name)…

Page 46: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

New Relic

Page 47: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

New Relic• 대략 많이 비쌈 (c4.4xlarge x 25 = essentials $7,500/month)

Page 48: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test 해주세요

• 퍼 : 서버 준비할 수 있도록 Performance Test 해주세요.

• 개 : 그냥 AWS 인데 100대로 시작하면 안되나요?

• 퍼 : 네 =_=

• 개 : 아…

Page 49: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• nGrinder, Locust, New Relic

• Test Server

• DB : AWS RDS db.r3.large x 1 (2 Core, 16 GiB)

• Game Server : AWS ec2 c4.xlarge x 3 (4 Core, 7.5 GiB)

• Test Client

• Master : AWS ec2 c4.xlarge x 1 (4 Core, 7.5 GiB)

• Slave : AWS ec2 c4.xlarge x 6 (4 Core, 7.5 GiB)

Page 50: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• nGrinder

( https://naver.github.io/ngrinder )

• Java 기반, python 지원 안 함

• Master x 1, Slave x 6

• Test Request : Connection Info

• 700 TPS 까지 안정적(233 TPS/server)

• 700 TPS 이상에서는 request 가 쌓이면서

latency 급격히 증가

Page 51: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• Locust ( http://locust.io )

• Python 지원

• Master x 1, Slave x 6

Page 52: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• Locust Bot (15 가지 주요 행동 )

• User Register, Login, Gift, Friend, Quest, Battle 등 구현

• 특정 대기 시간 후 지정된 확률로 행동(Behavior) 을 선택 후 실행

• 이전 Response 정보 반영하여 행동

Page 53: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• 110 TPS (36 TPS/server)

• CPU 사용률은 최대 80%

• 1 Request = 1 core 를 100ms

점유 , 4 core 는 40 TPS

Page 54: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test

• CBT 를 기준으로 1 user = 0.07 TPS

예측 동접 TPS / User TPS40 TPS(4 core)

80 TPS(8 core)

10,000 0.07 700 17 대 9 대

30,000 0.07 2100 52 대 26 대

50,000 0.07 3500 87 대 43 대

100,000 0.07 7000 174 대 86 대

Page 55: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Test 후

• 개 : 10 만 동접을 위해 8 core 서버를 86 대면 충분하네요.

• 퍼 : …

• 개 : 다들 이정도 쓰시죠? …

• 퍼 : …

Page 56: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - Protobuf

Dockerfile

ADD protobuf-2.6.1.tar.gz /app/WORKDIR /appRUN \

mv protobuf-2.6.1 protobuf &&\cd protobuf &&\export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp &&\export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION_VERSION=2 &&\./autogen.sh &&\./configure CXXFLAGS="-DNDEBUG -O2" CFLAGS="-DNDEBUG -O2" &&\make &&\make install &&\cd python &&\python setup.py build &&\python setup.py build --cpp_implementation &&\python setup.py install --cpp_implementation &&\

• Protobuf C++ 구현체 사용

• 메모리 사용량 90% 이상 절감

• 연산 속도 30배 이상 향상

• 단, float 사용 주의

Page 57: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - Protobuf

• 21,724 bytes 의 login response parsing time & peak memory

protobuf Time (1,000) Time(10,000)

python 16.143264 2:24.392930

python protobuf C++ 0.451125 4.211471

pypy 4.446425 28.042325

Python3.6.0 17.954635 3:04.946336

protobuf Memory (1,000) Memory (10,000)

python 1,186 MB 10,089 MB

python protobuf C++ 115 MB 824 MB

pypy 599 MB 5,421 MB

Python3.6.0 781 MB 7,331 MB

Page 58: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - Redis

with redis_store.pipeline() as pipeline:try:

pipeline.hmset(name, mapping)pipeline.expire(name, expire_time)pipeline.execute()

• Pipeline 적용

• Avg calls 93 5

Page 59: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - DB

q = db.session.query(User, UserMonster). \set_shard(gamedb_shard_no). \filter(User.id.in_(ids)). \filter(User.monster_id==UserMonster.id). \all()

for u, m in q:…

• ORM 에 의지, Sharding

• QuerySet loop .all()

• Foreign key join

• Avg calls 23 3

Page 60: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - DB

• 기존의 경우 Friend 를 1회 select 하여 n개의 data를 가져옴

• n 번 commondb 의 Account 를 select

• n 번 gamedb 의 User 를 select

• n 번 gamedb 의 UserMonster 를 select

Page 61: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - DB

• select 를 줄이기 위해 pk 를 확보하여 in_ 으로 filter

• Shard 별 pk 를 확보하여 loop

Page 62: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

• Pure Python 으로 Python 작성하여 jit 으로 실행 시간에 성능을 끌어 올림

• Dockerfile 만 변경하여 PyPy 설치, PyPy 로 uwsgi 실행

Dockerfile

...ADD pypy2-v5.3.1-linux64.tar.bz2 /app/WORKDIR /app...RUN \mv pypy2-v5.3.1-linux64 pypy &&\./pypy/bin/pypy -m ensurepip &&\./pypy/bin/pip install numpy &&\./pypy/bin/pip install -e ....

uwsgi.ini

...plugins = pypypypy-lib = /app/pypy/bin/libpypy-c.sopypy-home = /app/pypy/binpypy-wsgi = docker-uwsgi:app...

Page 63: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

CPython PyPy

Page 64: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

CPython PyPy

Page 65: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

• 동일한 코드가 동작하는 경우로 테스트

• Process 당 약 10,000 개 이상의 요청 처리 후 성능 향상

• 향상된 성능은 측정하기 힘들 정도 (성능이 너무 좋아짐)

Page 66: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

그러나

Page 67: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음

• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가

• Protobuf C++ 구현체를 사용할 수 없음

• 높은 메모리 사용량

Page 68: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - PyPy

• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음

• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가

• Protobuf C++ 구현체를 사용할 수 없음

• 높은 메모리 사용량

• 사용 포기

Page 69: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Performance Improvement - Result

• Protobuf C++ 구현체로 변경

• Redis pipeline 사용

• Database Query 최적화

• 8 Core 15 GiB x 50• Python CPU 점유 시간

• 100ms 50ms

• 4 Core 7.5 GiB x 174

Page 70: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

몬슈리 Python 게임 서버는

안녕했나요?

Page 71: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

네, 오픈 직후에도 안녕했습니다.

• DAU : 50 만

• CCU : 7 만

• 30만 CCU 이상 가능

Page 72: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

네, 오픈 직후에도 안녕했습니다.

Page 73: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

그리고 지금도 안녕합니다.

• DAU : 10 만

• CCU : 2 만

• 1 User : 0.06 TPS

• Daily Requests = 6,000 만

• DB CPU 사용률 : 5%

• 6개월 동안 서비스 중

Page 74: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python 게임 서버

Page 75: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

생각해본 것

• 처음부터 PyPy 를 고려한 설계를 했다면?

• 처음부터 Sharding 을 고려했다면?

• MySQL 이 아닌 다른 DB 를 사용했다면?

• Cython 으로 일부를 정적 컴파일하여 사용했다면?

• Protobuf 보다 빠른 data serialization 이 있다면?

Page 76: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

Python 게임 서버

• 각종 라이브러리와 클라우드 서비스를 쉽게 이용할 수 있다.

• 느리지만 빠르다.

• Python 게임 서버 안녕합니다. 걱정 안하셔도 됩니다.

Page 77: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

• 글로벌 콘텐츠 기업

• 창업 8년차

• 직원 수 143명

• 대표 콘텐츠

• 핑크퐁

• 상어 가족

• 몬스터 슈퍼리그

Page 78: [NDC2017 : 박준철] Python 게임 서버 안녕하십니까 - 몬스터 슈퍼리그 게임 서버

함께해요http://www.smartstudy.co.kr/withyou/