SlideShare uma empresa Scribd logo
1 de 78
Baixar para ler offline
스마트스터디
박준철
몬스터 슈퍼리그 게임 서버
Python 게임 서버 안녕하십니까?
목표
• 몬슈리의 Python 게임 서버에 사용된 각종 라이브러리와 서비스 공유
• 몬슈리의 사례를 통해서 Python 으로 게임 서버를 만들 때 개발자의 의사결정에
도움이 되는 것
• 경험의 공유를 통한 “Python 은 느리지만 빨라”를 전파
몬스터 슈퍼 리그를 개발하면서 빠른 개발 진행을 위해 선택했던 Python 게임
서버, '잘 되면 다시 만들지 뭐'라는 생각에서 시작했지만 다시 만들 일은 영원히
오지 않았습니다... Python으로 게임 서버를 만들었을 때 사용한 것은 무엇인지
또 실제 오픈 했을 때 서버는 안녕했는지 알아봅니다.
from https://ndc.nexon.com
목차
1. 몬슈리 Python 게임 서버는 안녕하십니까?
2. 몬슈리 Python 게임 서버
3. 안녕하기 위한 노력
4. 몬슈리 Python 게임 서버는 안녕했나요?
5. Python 게임 서버
몬슈리 Python 게임 서버는
안녕하십니까?
현재
• DAU : 10 만 명
• CCU : 2 만 명
• Request / day = 6,000 만
• Sales in 6 months = $ 2,000 만 +
몬슈리 Python 게임 서버
Python 2.7.11
• 개발 및 유지 보수가 편해야 함
• 다양한 라이브러리와 클라우드 서비스를 지원
• 패키지 관리가 잘 되어야 하고 의존성 문제가 없어야 함
• 서버의 퍼포먼스보다 개발자의 퍼포먼스가 중요
• 이미 사내 backend 개발에 Python 을 사용하고 있음
• 몬슈리 개발팀에 적합한 언어
Python 2.7.11
• Blinker
• GData
• Celery
• Jinja2, bootstrap3
• NewRelic, Sentry
• …
• Flask (nginx + uwsgi)
• SQLAlchemy
• Protobuf (Protocol Buffers)
• Redis
• NumPy
• Requests
졸음 주의
• 몬슈리 게임 서버에 사용된 각종 라이브러리 나열식 설명
기본적인 선택들
• Lightweight Web Application Framework - Flask
• ORM - SQLAlchemy
• KeyValue DB - Redis
• Asynchronous Tasks - Celery
게임 서버는 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
게임 서버는 Flask
• Error 처리
• Status code=500
• 명백한 서버 오류, 클라이언트에서 “재시도” or “재접속” 팝업
• Status code=200 & Error Code 를 나누어 사용
• 서버와 클라이언트의 데이터가 맞지 않는 상황으로 판단
• 오류 메시지 노출 후 계속 진행 or “재접속”
게임 서버는 Flask
• 클라이언트의 Retry 대비
• Response 를 Redis 에 보관 후 동일한 Request 에 대해서 Response 를
바로 Return
• 2 회 이상 발생시 Status Code=200 + Error Code return
DB 는 SQLAlchemy
• Alembic 을 이용한 DB Migration 지원
• Multi DB 지원
• Auto Commit 사용하지 않음
• 모든 User Request 에 대해서 Transaction 사용
• Request 처리 중 서버 오류나 데이터 오류의 경우 Rollback
• 오류 없이 로직이 처리된 경우 Commit
데이터는 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;
}
랜덤은 NumPy
• Python Random 보다 빠르고 더 좋은 결과를 주지 않을까?
• 막연한 기대로 선택
• 그러나 실제로는…
랜덤은 NumPy
• Random seed : 1
• 10, 1000, 1,000,000 개 random 비교
랜덤은 NumPy
• Random seed : 1
• 10, 1000, 1,000,000 개 random 비교
랜덤은 NumPy
• Random seed : 1
• 10, 1000, 1,000,000 개 random 비교
랜덤은 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
서버간 연결은 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)
Quest 는 Blinker 로
• Blinker : Signal(Event) Broadcasting 라이브러리
• Signal(Event) 을 send 하는 즉시 Receiver 에게 데이터 전달
• Quest 에서 사용한 방법
• Quest 종류 별로 미리 Custom Name Signal(Event) 정의
• Signal(Event) 별로 1개의 Receiver 를 connect
• 서버 로직에서 Quest 체크가 필요한 경우 Signal 을 send
Quest 는 Blinker 로
# signals.py
import blinker
namespace = blinker.Namespace()
summon = namespace.signal('summon')
…
# main.py - subscribe summon signal (event)
import signals
…
@signals.summon.connect
def 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)
…
게임 데이터는 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.pb
joongom staff 660737 3 16 13:37 string.zh-cn.pb
joongom staff 1676594 3 16 13:37 string.th.pb
joongom staff 739756 3 16 13:37 string.pt.pb
joongom staff 807264 3 16 13:37 string.ko.pb
joongom staff 881384 3 16 13:37 string.ja.pb
joongom staff 763874 3 16 13:37 string.it.pb
joongom staff 794710 3 16 13:37 string.fr.pb
joongom staff 764741 3 16 13:37 string.es.pb
joongom staff 707415 3 16 13:37 string.en.pb
joongom staff 757907 3 16 13:37 string.de.pb
joongom staff 3413470 3 16 13:37 gamedata.pb
게임 데이터는 gdata 로
• 수정 전 데이터와의 diff 를 확인하는 별도의 툴이 필요
• 데이터 배포 전 반드시 확인하는 절차를 진행
• protobuf 를 dict 으로 변경하여 diff
• protobuf-to-dict : https://github.com/benhodgson/protobuf-to-dict
$ data_diff.py 610 620
removed mon.cocomaru.tree.1 substages uid:stage.01.05 normal_display_mons index:2
removed mon.slimeb.water.1 substages uid:stage.01.05 normal_display_mons index:3
modified mon.jackolittlew.light.3 --> mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:0
modified mon.jackolittle.dark.3 --> mon.slimeb.water.3 substages uid:stage.01.02 hell_display_mons index:1
modified mon.jackolittle.dark.3 --> mon.squir.tree.3 substages uid:stage.01.02 hell_display_mons index:2
added mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:3
Python Exception 은 Sentry
• Sentry is a modern error logging and aggregation platform.
server.py
...
sentry = Sentry(dsn='http://public_key:secret_key@example.com/1')
def create_app():
app = Flask(__name__)
sentry.init_app(app)
return app
...
try:
...
except:
sentry.captureException(extra={’User’: userData, ‘Request’: requestData})
Python Exception 은 Sentry
nginx + uWSGI + supervisor
• Flask
• WSGI 표준 지원, uWSGI 로 nginx 연결
• nginx
• 각종 성능에 도움이 될 수 있는 옵션들 적용
• 배포 서버에서만 사용 (개발할 때는 flask run server)
• Supervisor
• Process 컨트롤 시스템
운영툴 은 flask + Jinja2 + bootstrap3
서버는 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
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)
CBT #1
안녕하기 위한 노력
안녕하세요. 개발자님.
전 퍼블리셔라고 합니다.
안녕하세요.
• 퍼블리셔 : 안녕하세요. “퍼블리셔” 입니다.
• 개발자 : 안녕하세요.
준곰이라고 불러 주세요. ☺
2003 년부터 NEXON, NCSOFT, NEOWIZ 에서 게임을 만들었습니다.
대부분 서비스를 종료했습니다만… 왠지 이번에는 느낌이 좋네요.
그리고 2010년 부터 스마트스터디에서 핑크퐁 앱과 각종 게임들을 만들었습니다.
CTO 를 하고 있는 몬슈리 개발자 박준철 입니다.
Sharding 해주실 수 있나요?
• 개 : 네, Table 마다 다른 DB 를 지정할 수 있습니다.
• 퍼 : 1개의 Table 을 n개의 DB에 저장하는 Sharding 을 해주셔야…
• 개 : ORM 쓰고 foreign key도 썼는데… T_T
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
DB Sharding
SQLALCHEMY_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 에서 관리
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 에 추가
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 지정 기능 추가
Redis CPU 사용률이 너무 높은데요?
• 개 : 성능 좋은 인스턴스로 바꿔서 서비스 해줘요.
• 퍼 : redis 는 싱글 스레드라…
• 개 : 아…
• 퍼 : redis 도 Sharding 해주시면 좋겠습니다 ☺
• 개 : 아…
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
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
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
Monitoring은 뭘로 하나요?
• 퍼 : 서버 Monitoring은 뭘로 하나요?
• 개 : Exception 나면 Sentry 가 와요. Sentry 짱
• 퍼 : Exception 안 날 때는 뭘로 봐요?
• 개 : 잘 돌고 있겠죠…
• 퍼 : …
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)
…
New Relic
New Relic
• 대략 많이 비쌈 (c4.4xlarge x 25 = essentials $7,500/month)
Performance Test 해주세요
• 퍼 : 서버 준비할 수 있도록 Performance Test 해주세요.
• 개 : 그냥 AWS 인데 100대로 시작하면 안되나요?
• 퍼 : 네 =_=
• 개 : 아…
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)
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 급격히 증가
Performance Test
• Locust ( http://locust.io )
• Python 지원
• Master x 1, Slave x 6
Performance Test
• Locust Bot (15 가지 주요 행동 )
• User Register, Login, Gift, Friend, Quest, Battle 등 구현
• 특정 대기 시간 후 지정된 확률로 행동(Behavior) 을 선택 후 실행
• 이전 Response 정보 반영하여 행동
Performance Test
• 110 TPS (36 TPS/server)
• CPU 사용률은 최대 80%
• 1 Request = 1 core 를 100ms
점유 , 4 core 는 40 TPS
Performance Test
• CBT 를 기준으로 1 user = 0.07 TPS
예측 동접 TPS / User TPS
40 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 대
Performance Test 후
• 개 : 10 만 동접을 위해 8 core 서버를 86 대면 충분하네요.
• 퍼 : …
• 개 : 다들 이정도 쓰시죠? …
• 퍼 : …
Performance Improvement - Protobuf
Dockerfile
ADD protobuf-2.6.1.tar.gz /app/
WORKDIR /app
RUN 
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 사용 주의
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
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
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
Performance Improvement - DB
• 기존의 경우 Friend 를 1회 select 하여 n개의 data를 가져옴
• n 번 commondb 의 Account 를 select
• n 번 gamedb 의 User 를 select
• n 번 gamedb 의 UserMonster 를 select
Performance Improvement - DB
• select 를 줄이기 위해 pk 를 확보하여 in_ 으로 filter
• Shard 별 pk 를 확보하여 loop
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 = pypy
pypy-lib = /app/pypy/bin/libpypy-c.so
pypy-home = /app/pypy/bin
pypy-wsgi = docker-uwsgi:app
...
Performance Improvement - PyPy
CPython PyPy
Performance Improvement - PyPy
CPython PyPy
Performance Improvement - PyPy
• 동일한 코드가 동작하는 경우로 테스트
• Process 당 약 10,000 개 이상의 요청 처리 후 성능 향상
• 향상된 성능은 측정하기 힘들 정도 (성능이 너무 좋아짐)
그러나
Performance Improvement - PyPy
• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음
• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가
• Protobuf C++ 구현체를 사용할 수 없음
• 높은 메모리 사용량
Performance Improvement - PyPy
• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음
• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가
• Protobuf C++ 구현체를 사용할 수 없음
• 높은 메모리 사용량
• 사용 포기
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
몬슈리 Python 게임 서버는
안녕했나요?
네, 오픈 직후에도 안녕했습니다.
• DAU : 50 만
• CCU : 7 만
• 30만 CCU 이상 가능
네, 오픈 직후에도 안녕했습니다.
그리고 지금도 안녕합니다.
• DAU : 10 만
• CCU : 2 만
• 1 User : 0.06 TPS
• Daily Requests = 6,000 만
• DB CPU 사용률 : 5%
• 6개월 동안 서비스 중
Python 게임 서버
생각해본 것
• 처음부터 PyPy 를 고려한 설계를 했다면?
• 처음부터 Sharding 을 고려했다면?
• MySQL 이 아닌 다른 DB 를 사용했다면?
• Cython 으로 일부를 정적 컴파일하여 사용했다면?
• Protobuf 보다 빠른 data serialization 이 있다면?
Python 게임 서버
• 각종 라이브러리와 클라우드 서비스를 쉽게 이용할 수 있다.
• 느리지만 빠르다.
• Python 게임 서버 안녕합니다. 걱정 안하셔도 됩니다.
• 글로벌 콘텐츠 기업
• 창업 8년차
• 직원 수 143명
• 대표 콘텐츠
• 핑크퐁
• 상어 가족
• 몬스터 슈퍼리그
함께해요
http://www.smartstudy.co.kr/withyou/

Mais conteúdo relacionado

Mais procurados

임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013devCAT Studio, NEXON
 
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019devCAT Studio, NEXON
 
임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012devCAT Studio, NEXON
 
송창규, unity build로 빌드타임 반토막내기, NDC2010
송창규, unity build로 빌드타임 반토막내기, NDC2010송창규, unity build로 빌드타임 반토막내기, NDC2010
송창규, unity build로 빌드타임 반토막내기, NDC2010devCAT Studio, NEXON
 
Python 게임서버 안녕하십니까 : RPC framework 편
Python 게임서버 안녕하십니까 : RPC framework 편Python 게임서버 안녕하십니까 : RPC framework 편
Python 게임서버 안녕하십니까 : RPC framework 편준철 박
 
Next-generation MMORPG service architecture
Next-generation MMORPG service architectureNext-generation MMORPG service architecture
Next-generation MMORPG service architectureJongwon Kim
 
Windows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCPWindows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCPSeungmo Koo
 
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019devCAT Studio, NEXON
 
게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가Seungmo Koo
 
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
 
NDC 11 자이언트 서버의 비밀
NDC 11 자이언트 서버의 비밀NDC 11 자이언트 서버의 비밀
NDC 11 자이언트 서버의 비밀승명 양
 
Multiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theoremMultiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theoremSeungmo Koo
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략YEONG-CHEON YOU
 
Ndc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABCNdc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABCHo Gyu Lee
 
게임 분산 서버 구조
게임 분산 서버 구조게임 분산 서버 구조
게임 분산 서버 구조Hyunjik Bae
 
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014devCAT Studio, NEXON
 
[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기Sang Heon Lee
 
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기MinGeun Park
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012devCAT Studio, NEXON
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템QooJuice
 

Mais procurados (20)

임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013
 
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
 
임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012
 
송창규, unity build로 빌드타임 반토막내기, NDC2010
송창규, unity build로 빌드타임 반토막내기, NDC2010송창규, unity build로 빌드타임 반토막내기, NDC2010
송창규, unity build로 빌드타임 반토막내기, NDC2010
 
Python 게임서버 안녕하십니까 : RPC framework 편
Python 게임서버 안녕하십니까 : RPC framework 편Python 게임서버 안녕하십니까 : RPC framework 편
Python 게임서버 안녕하십니까 : RPC framework 편
 
Next-generation MMORPG service architecture
Next-generation MMORPG service architectureNext-generation MMORPG service architecture
Next-generation MMORPG service architecture
 
Windows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCPWindows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCP
 
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019
이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019
 
게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가
 
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
 
NDC 11 자이언트 서버의 비밀
NDC 11 자이언트 서버의 비밀NDC 11 자이언트 서버의 비밀
NDC 11 자이언트 서버의 비밀
 
Multiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theoremMultiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theorem
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략
 
Ndc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABCNdc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABC
 
게임 분산 서버 구조
게임 분산 서버 구조게임 분산 서버 구조
게임 분산 서버 구조
 
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
 
[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기
 
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기
[150124 박민근] 모바일 게임 개발에서 루아 스크립트 활용하기
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템
 

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

Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기OnGameServer
 
Node.js를 사용한 Big Data 사례연구
Node.js를 사용한 Big Data 사례연구Node.js를 사용한 Big Data 사례연구
Node.js를 사용한 Big Data 사례연구ByungJoon Lee
 
TenforFlow Internals
TenforFlow InternalsTenforFlow Internals
TenforFlow InternalsKiho Hong
 
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018devCAT Studio, NEXON
 
Rhea_MMO_SNG_Convergence_Server_Architecture
Rhea_MMO_SNG_Convergence_Server_ArchitectureRhea_MMO_SNG_Convergence_Server_Architecture
Rhea_MMO_SNG_Convergence_Server_ArchitectureRhea Strike
 
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드cranbe95
 
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptxYeongKiKim1
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기SeungYong Oh
 
Python server-101
Python server-101Python server-101
Python server-101Huey Park
 
Pgday bdr 천정대
Pgday bdr 천정대Pgday bdr 천정대
Pgday bdr 천정대PgDay.Seoul
 
Pgday bdr gt1000
Pgday bdr gt1000Pgday bdr gt1000
Pgday bdr gt1000정대 천
 
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영)
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영) 파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영)
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영) Tae Young Lee
 
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석smartstudy_official
 
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)Tae Young Lee
 
[NDC 2017] 이카루스 북미 : 베타서비스 활용법
[NDC 2017] 이카루스 북미 : 베타서비스 활용법[NDC 2017] 이카루스 북미 : 베타서비스 활용법
[NDC 2017] 이카루스 북미 : 베타서비스 활용법David Kim
 
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅Youngmin Koo
 
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현NAVER D2
 
[E-commerce & Retail Day] 인공지능서비스 활용방안
[E-commerce & Retail Day] 인공지능서비스 활용방안[E-commerce & Retail Day] 인공지능서비스 활용방안
[E-commerce & Retail Day] 인공지능서비스 활용방안Amazon Web Services Korea
 

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

Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기
 
Node.js를 사용한 Big Data 사례연구
Node.js를 사용한 Big Data 사례연구Node.js를 사용한 Big Data 사례연구
Node.js를 사용한 Big Data 사례연구
 
TenforFlow Internals
TenforFlow InternalsTenforFlow Internals
TenforFlow Internals
 
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
 
Rhea_MMO_SNG_Convergence_Server_Architecture
Rhea_MMO_SNG_Convergence_Server_ArchitectureRhea_MMO_SNG_Convergence_Server_Architecture
Rhea_MMO_SNG_Convergence_Server_Architecture
 
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드
Ndc2011 성능 향상을_위한_데이터베이스_아키텍쳐_구축_및_개발_가이드
 
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
 
Meteor IoT
Meteor IoTMeteor IoT
Meteor IoT
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
 
Python server-101
Python server-101Python server-101
Python server-101
 
Pgday bdr 천정대
Pgday bdr 천정대Pgday bdr 천정대
Pgday bdr 천정대
 
Pgday bdr gt1000
Pgday bdr gt1000Pgday bdr gt1000
Pgday bdr gt1000
 
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영)
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영) 파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영)
파이썬 데이터과학 레벨1 - 초보자를 위한 데이터분석, 데이터시각화 (2020년 이태영)
 
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석
[스마트스터디]모바일 애플리케이션 서비스에서의 로그 수집과 분석
 
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)
파이썬 데이터과학 1일차 - 초보자를 위한 데이터분석, 데이터시각화 (이태영)
 
파이썬으로 익히는 딥러닝
파이썬으로 익히는 딥러닝파이썬으로 익히는 딥러닝
파이썬으로 익히는 딥러닝
 
[NDC 2017] 이카루스 북미 : 베타서비스 활용법
[NDC 2017] 이카루스 북미 : 베타서비스 활용법[NDC 2017] 이카루스 북미 : 베타서비스 활용법
[NDC 2017] 이카루스 북미 : 베타서비스 활용법
 
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
 
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
 
[E-commerce & Retail Day] 인공지능서비스 활용방안
[E-commerce & Retail Day] 인공지능서비스 활용방안[E-commerce & Retail Day] 인공지능서비스 활용방안
[E-commerce & Retail Day] 인공지능서비스 활용방안
 

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

  • 1. 스마트스터디 박준철 몬스터 슈퍼리그 게임 서버 Python 게임 서버 안녕하십니까?
  • 2. 목표 • 몬슈리의 Python 게임 서버에 사용된 각종 라이브러리와 서비스 공유 • 몬슈리의 사례를 통해서 Python 으로 게임 서버를 만들 때 개발자의 의사결정에 도움이 되는 것 • 경험의 공유를 통한 “Python 은 느리지만 빨라”를 전파 몬스터 슈퍼 리그를 개발하면서 빠른 개발 진행을 위해 선택했던 Python 게임 서버, '잘 되면 다시 만들지 뭐'라는 생각에서 시작했지만 다시 만들 일은 영원히 오지 않았습니다... Python으로 게임 서버를 만들었을 때 사용한 것은 무엇인지 또 실제 오픈 했을 때 서버는 안녕했는지 알아봅니다. from https://ndc.nexon.com
  • 3. 목차 1. 몬슈리 Python 게임 서버는 안녕하십니까? 2. 몬슈리 Python 게임 서버 3. 안녕하기 위한 노력 4. 몬슈리 Python 게임 서버는 안녕했나요? 5. Python 게임 서버
  • 4. 몬슈리 Python 게임 서버는 안녕하십니까?
  • 5. 현재 • DAU : 10 만 명 • CCU : 2 만 명 • Request / day = 6,000 만 • Sales in 6 months = $ 2,000 만 +
  • 7. Python 2.7.11 • 개발 및 유지 보수가 편해야 함 • 다양한 라이브러리와 클라우드 서비스를 지원 • 패키지 관리가 잘 되어야 하고 의존성 문제가 없어야 함 • 서버의 퍼포먼스보다 개발자의 퍼포먼스가 중요 • 이미 사내 backend 개발에 Python 을 사용하고 있음 • 몬슈리 개발팀에 적합한 언어
  • 8. Python 2.7.11 • Blinker • GData • Celery • Jinja2, bootstrap3 • NewRelic, Sentry • … • Flask (nginx + uwsgi) • SQLAlchemy • Protobuf (Protocol Buffers) • Redis • NumPy • Requests
  • 9. 졸음 주의 • 몬슈리 게임 서버에 사용된 각종 라이브러리 나열식 설명
  • 10. 기본적인 선택들 • Lightweight Web Application Framework - Flask • ORM - SQLAlchemy • KeyValue DB - Redis • Asynchronous Tasks - Celery
  • 11. 게임 서버는 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
  • 12. 게임 서버는 Flask • Error 처리 • Status code=500 • 명백한 서버 오류, 클라이언트에서 “재시도” or “재접속” 팝업 • Status code=200 & Error Code 를 나누어 사용 • 서버와 클라이언트의 데이터가 맞지 않는 상황으로 판단 • 오류 메시지 노출 후 계속 진행 or “재접속”
  • 13. 게임 서버는 Flask • 클라이언트의 Retry 대비 • Response 를 Redis 에 보관 후 동일한 Request 에 대해서 Response 를 바로 Return • 2 회 이상 발생시 Status Code=200 + Error Code return
  • 14. DB 는 SQLAlchemy • Alembic 을 이용한 DB Migration 지원 • Multi DB 지원 • Auto Commit 사용하지 않음 • 모든 User Request 에 대해서 Transaction 사용 • Request 처리 중 서버 오류나 데이터 오류의 경우 Rollback • 오류 없이 로직이 처리된 경우 Commit
  • 15. 데이터는 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; }
  • 16. 랜덤은 NumPy • Python Random 보다 빠르고 더 좋은 결과를 주지 않을까? • 막연한 기대로 선택 • 그러나 실제로는…
  • 17. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  • 18. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  • 19. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  • 20. 랜덤은 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
  • 21. 서버간 연결은 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)
  • 22. Quest 는 Blinker 로 • Blinker : Signal(Event) Broadcasting 라이브러리 • Signal(Event) 을 send 하는 즉시 Receiver 에게 데이터 전달 • Quest 에서 사용한 방법 • Quest 종류 별로 미리 Custom Name Signal(Event) 정의 • Signal(Event) 별로 1개의 Receiver 를 connect • 서버 로직에서 Quest 체크가 필요한 경우 Signal 을 send
  • 23. Quest 는 Blinker 로 # signals.py import blinker namespace = blinker.Namespace() summon = namespace.signal('summon') … # main.py - subscribe summon signal (event) import signals … @signals.summon.connect def 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) …
  • 24. 게임 데이터는 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.pb joongom staff 660737 3 16 13:37 string.zh-cn.pb joongom staff 1676594 3 16 13:37 string.th.pb joongom staff 739756 3 16 13:37 string.pt.pb joongom staff 807264 3 16 13:37 string.ko.pb joongom staff 881384 3 16 13:37 string.ja.pb joongom staff 763874 3 16 13:37 string.it.pb joongom staff 794710 3 16 13:37 string.fr.pb joongom staff 764741 3 16 13:37 string.es.pb joongom staff 707415 3 16 13:37 string.en.pb joongom staff 757907 3 16 13:37 string.de.pb joongom staff 3413470 3 16 13:37 gamedata.pb
  • 25. 게임 데이터는 gdata 로 • 수정 전 데이터와의 diff 를 확인하는 별도의 툴이 필요 • 데이터 배포 전 반드시 확인하는 절차를 진행 • protobuf 를 dict 으로 변경하여 diff • protobuf-to-dict : https://github.com/benhodgson/protobuf-to-dict $ data_diff.py 610 620 removed mon.cocomaru.tree.1 substages uid:stage.01.05 normal_display_mons index:2 removed mon.slimeb.water.1 substages uid:stage.01.05 normal_display_mons index:3 modified mon.jackolittlew.light.3 --> mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:0 modified mon.jackolittle.dark.3 --> mon.slimeb.water.3 substages uid:stage.01.02 hell_display_mons index:1 modified mon.jackolittle.dark.3 --> mon.squir.tree.3 substages uid:stage.01.02 hell_display_mons index:2 added mon.slime.tree.3 substages uid:stage.01.02 hell_display_mons index:3
  • 26. Python Exception 은 Sentry • Sentry is a modern error logging and aggregation platform. server.py ... sentry = Sentry(dsn='http://public_key:secret_key@example.com/1') def create_app(): app = Flask(__name__) sentry.init_app(app) return app ... try: ... except: sentry.captureException(extra={’User’: userData, ‘Request’: requestData})
  • 28. nginx + uWSGI + supervisor • Flask • WSGI 표준 지원, uWSGI 로 nginx 연결 • nginx • 각종 성능에 도움이 될 수 있는 옵션들 적용 • 배포 서버에서만 사용 (개발할 때는 flask run server) • Supervisor • Process 컨트롤 시스템
  • 29. 운영툴 은 flask + Jinja2 + bootstrap3
  • 30. 서버는 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
  • 31. 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)
  • 33. 안녕하기 위한 노력 안녕하세요. 개발자님. 전 퍼블리셔라고 합니다.
  • 34. 안녕하세요. • 퍼블리셔 : 안녕하세요. “퍼블리셔” 입니다. • 개발자 : 안녕하세요. 준곰이라고 불러 주세요. ☺ 2003 년부터 NEXON, NCSOFT, NEOWIZ 에서 게임을 만들었습니다. 대부분 서비스를 종료했습니다만… 왠지 이번에는 느낌이 좋네요. 그리고 2010년 부터 스마트스터디에서 핑크퐁 앱과 각종 게임들을 만들었습니다. CTO 를 하고 있는 몬슈리 개발자 박준철 입니다.
  • 35. Sharding 해주실 수 있나요? • 개 : 네, Table 마다 다른 DB 를 지정할 수 있습니다. • 퍼 : 1개의 Table 을 n개의 DB에 저장하는 Sharding 을 해주셔야… • 개 : ORM 쓰고 foreign key도 썼는데… T_T
  • 36. 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
  • 37. DB Sharding SQLALCHEMY_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 에서 관리
  • 38. 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 에 추가
  • 39. 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 지정 기능 추가
  • 40. Redis CPU 사용률이 너무 높은데요? • 개 : 성능 좋은 인스턴스로 바꿔서 서비스 해줘요. • 퍼 : redis 는 싱글 스레드라… • 개 : 아… • 퍼 : redis 도 Sharding 해주시면 좋겠습니다 ☺ • 개 : 아…
  • 41. 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
  • 42. 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
  • 43. 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
  • 44. Monitoring은 뭘로 하나요? • 퍼 : 서버 Monitoring은 뭘로 하나요? • 개 : Exception 나면 Sentry 가 와요. Sentry 짱 • 퍼 : Exception 안 날 때는 뭘로 봐요? • 개 : 잘 돌고 있겠죠… • 퍼 : …
  • 45. 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) …
  • 47. New Relic • 대략 많이 비쌈 (c4.4xlarge x 25 = essentials $7,500/month)
  • 48. Performance Test 해주세요 • 퍼 : 서버 준비할 수 있도록 Performance Test 해주세요. • 개 : 그냥 AWS 인데 100대로 시작하면 안되나요? • 퍼 : 네 =_= • 개 : 아…
  • 49. 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)
  • 50. 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 급격히 증가
  • 51. Performance Test • Locust ( http://locust.io ) • Python 지원 • Master x 1, Slave x 6
  • 52. Performance Test • Locust Bot (15 가지 주요 행동 ) • User Register, Login, Gift, Friend, Quest, Battle 등 구현 • 특정 대기 시간 후 지정된 확률로 행동(Behavior) 을 선택 후 실행 • 이전 Response 정보 반영하여 행동
  • 53. Performance Test • 110 TPS (36 TPS/server) • CPU 사용률은 최대 80% • 1 Request = 1 core 를 100ms 점유 , 4 core 는 40 TPS
  • 54. Performance Test • CBT 를 기준으로 1 user = 0.07 TPS 예측 동접 TPS / User TPS 40 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 대
  • 55. Performance Test 후 • 개 : 10 만 동접을 위해 8 core 서버를 86 대면 충분하네요. • 퍼 : … • 개 : 다들 이정도 쓰시죠? … • 퍼 : …
  • 56. Performance Improvement - Protobuf Dockerfile ADD protobuf-2.6.1.tar.gz /app/ WORKDIR /app RUN 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 사용 주의
  • 57. 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
  • 58. 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
  • 59. 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
  • 60. Performance Improvement - DB • 기존의 경우 Friend 를 1회 select 하여 n개의 data를 가져옴 • n 번 commondb 의 Account 를 select • n 번 gamedb 의 User 를 select • n 번 gamedb 의 UserMonster 를 select
  • 61. Performance Improvement - DB • select 를 줄이기 위해 pk 를 확보하여 in_ 으로 filter • Shard 별 pk 를 확보하여 loop
  • 62. 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 = pypy pypy-lib = /app/pypy/bin/libpypy-c.so pypy-home = /app/pypy/bin pypy-wsgi = docker-uwsgi:app ...
  • 63. Performance Improvement - PyPy CPython PyPy
  • 64. Performance Improvement - PyPy CPython PyPy
  • 65. Performance Improvement - PyPy • 동일한 코드가 동작하는 경우로 테스트 • Process 당 약 10,000 개 이상의 요청 처리 후 성능 향상 • 향상된 성능은 측정하기 힘들 정도 (성능이 너무 좋아짐)
  • 67. Performance Improvement - PyPy • 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음 • 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가 • Protobuf C++ 구현체를 사용할 수 없음 • 높은 메모리 사용량
  • 68. Performance Improvement - PyPy • 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음 • 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가 • Protobuf C++ 구현체를 사용할 수 없음 • 높은 메모리 사용량 • 사용 포기
  • 69. 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
  • 70. 몬슈리 Python 게임 서버는 안녕했나요?
  • 71. 네, 오픈 직후에도 안녕했습니다. • DAU : 50 만 • CCU : 7 만 • 30만 CCU 이상 가능
  • 72. 네, 오픈 직후에도 안녕했습니다.
  • 73. 그리고 지금도 안녕합니다. • DAU : 10 만 • CCU : 2 만 • 1 User : 0.06 TPS • Daily Requests = 6,000 만 • DB CPU 사용률 : 5% • 6개월 동안 서비스 중
  • 75. 생각해본 것 • 처음부터 PyPy 를 고려한 설계를 했다면? • 처음부터 Sharding 을 고려했다면? • MySQL 이 아닌 다른 DB 를 사용했다면? • Cython 으로 일부를 정적 컴파일하여 사용했다면? • Protobuf 보다 빠른 data serialization 이 있다면?
  • 76. Python 게임 서버 • 각종 라이브러리와 클라우드 서비스를 쉽게 이용할 수 있다. • 느리지만 빠르다. • Python 게임 서버 안녕합니다. 걱정 안하셔도 됩니다.
  • 77. • 글로벌 콘텐츠 기업 • 창업 8년차 • 직원 수 143명 • 대표 콘텐츠 • 핑크퐁 • 상어 가족 • 몬스터 슈퍼리그