Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
스마트스터디
박준철
몬스터 슈퍼리그 게임 서버
Python 게임 서버 안녕하십니까?
목표
• 몬슈리의 Python 게임 서버에 사용된 각종 라이브러리와 서비스 공유
• 몬슈리의 사례를 통해서 Python 으로 게임 서버를 만들 때 개발자의 의사결정에
도움이 되는 것
• 경험의 공유를 통한 “Python...
목차
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
• 개발 및 유지 보수가 편해야 함
• 다양한 라이브러리와 클라우드 서비스를 지원
• 패키지 관리가 잘 되어야 하고 의존성 문제가 없어야 함
• 서버의 퍼포먼스보다 개발자의 퍼포먼스가 중요
• ...
Python 2.7.11
• Blinker
• GData
• Celery
• Jinja2, bootstrap3
• NewRelic, Sentry
• …
• Flask (nginx + uwsgi)
• SQLAlchemy
...
졸음 주의
• 몬슈리 게임 서버에 사용된 각종 라이브러리 나열식 설명
기본적인 선택들
• Lightweight Web Application Framework - Flask
• ORM - SQLAlchemy
• KeyValue DB - Redis
• Asynchronous Tasks - C...
게임 서버는 Flask
• Protobuf message 기준으로 decorator 를 통한 routing
@route('ReqUserLogin')
def userLogin(reqUserLogin):
…
@app.rou...
게임 서버는 Flask
• Error 처리
• Status code=500
• 명백한 서버 오류, 클라이언트에서 “재시도” or “재접속” 팝업
• Status code=200 & Error Code 를 나누어 사용
•...
게임 서버는 Flask
• 클라이언트의 Retry 대비
• Response 를 Redis 에 보관 후 동일한 Request 에 대해서 Response 를
바로 Return
• 2 회 이상 발생시 Status Code=2...
DB 는 SQLAlchemy
• Alembic 을 이용한 DB Migration 지원
• Multi DB 지원
• Auto Commit 사용하지 않음
• 모든 User Request 에 대해서 Transaction 사용...
데이터는 Protocol Buffers (ProtoBuf)
• Message 파일을 정의하고 Compile
• Python , 3rd party c# 지원
• optional
• enum
• type checker
• ...
랜덤은 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, nu...
서버간 연결은 requests
• Python 용 HTTP 라이브러리, 주로 서버간 통신에 사용
• Timeout 설정
• Timeout 설정하지 않으면 exception 없는 한 무한 대기
“Without a time...
Quest 는 Blinker 로
• Blinker : Signal(Event) Broadcasting 라이브러리
• Signal(Event) 을 send 하는 즉시 Receiver 에게 데이터 전달
• Quest 에서 ...
Quest 는 Blinker 로
# signals.py
import blinker
namespace = blinker.Namespace()
summon = namespace.signal('summon')
…
# main...
게임 데이터는 gdata 로
• 기획팀에서 사용하는 data 는 google sheet 로 관리
• data sheet 를 protobuf serializing 하여 file 로 저장
• 116 google sheets...
게임 데이터는 gdata 로
• 수정 전 데이터와의 diff 를 확인하는 별도의 툴이 필요
• 데이터 배포 전 반드시 확인하는 절차를 진행
• protobuf 를 dict 으로 변경하여 diff
• protobuf-to...
Python Exception 은 Sentry
• Sentry is a modern error logging and aggregation platform.
server.py
...
sentry = Sentry(dsn='...
Python Exception 은 Sentry
nginx + uWSGI + supervisor
• Flask
• WSGI 표준 지원, uWSGI 로 nginx 연결
• nginx
• 각종 성능에 도움이 될 수 있는 옵션들 적용
• 배포 서버에서만 사용 (개발할 때는...
운영툴 은 flask + Jinja2 + bootstrap3
서버는 AWS ECS
• AWS ECS 로 서버 구축
• 게임 서버를 docker registry 를 통해 배포
• AWS ECS, RDS, ElasticCache (Redis), ELB, EC2 사용
• 강력한 AWS...
CBT #1 기본 서버 구성
• Game Server & Game Tool Server
• Flask app x n
• Nginx worker x m
• Redis x 1
• Celery x 1
• AWS RDS(MyS...
CBT #1
안녕하기 위한 노력
안녕하세요. 개발자님.
전 퍼블리셔라고 합니다.
안녕하세요.
• 퍼블리셔 : 안녕하세요. “퍼블리셔” 입니다.
• 개발자 : 안녕하세요.
준곰이라고 불러 주세요. ☺
2003 년부터 NEXON, NCSOFT, NEOWIZ 에서 게임을 만들었습니다.
대부분 서비스를 종...
Sharding 해주실 수 있나요?
• 개 : 네, Table 마다 다른 DB 를 지정할 수 있습니다.
• 퍼 : 1개의 Table 을 n개의 DB에 저장하는 Sharding 을 해주셔야…
• 개 : ORM 쓰고 for...
DB Sharding
• CBT #1 에서 DB CPU 사용률이 높은 것을 확인
• ‘퍼’ 님이 100배 많이 유저님들을 모시고 올 것이니…
A. Foreign key 를 삭제
B. Flask-SQLAlchemy 코드를...
DB Sharding
SQLALCHEMY_BINDS = {
'common': 'mysql://msl:msl@localhost/MSLCommonDB',
'game': 'mysql://msl:msl@localhost/MSL...
DB Sharding
> show create table commondb.account;
-----------------------------------------------------
…
`user_id` bigint...
DB Sharding
db.set_default_shard(current_app, 'game', gamedb_shard_no)
db.set_default_shard(current_app, 'log', logdb_shar...
Redis CPU 사용률이 너무 높은데요?
• 개 : 성능 좋은 인스턴스로 바꿔서 서비스 해줘요.
• 퍼 : redis 는 싱글 스레드라…
• 개 : 아…
• 퍼 : redis 도 Sharding 해주시면 좋겠습니다 ☺...
Redis Sharding
• https://redis.io/topics/benchmarks
• “Redis runs slower on a VM compared to running without virtualizatio...
Redis Sharding
• Redis 저장 데이터
• User Session Token & Data ( expire=3600 )
• User Response Cache Data ( expire=3600 )
• Rec...
Redis Sharding
• 1 User Request
• 1 Read User Session Token + 1 Read User Session Data
• 1 Write User Session Token + 1 Wr...
Monitoring은 뭘로 하나요?
• 퍼 : 서버 Monitoring은 뭘로 하나요?
• 개 : Exception 나면 Sentry 가 와요. Sentry 짱
• 퍼 : Exception 안 날 때는 뭘로 봐요?
• ...
New Relic
• APM(Application Performance Monitoring)
• 24 시간 이내 Request 의 Transaction 분석 무료
• Database Transaction 분석 유료
• ...
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...
Performance Test
• nGrinder
( https://naver.github.io/ngrinder )
• Java 기반, python 지원 안 함
• Master x 1, Slave x 6
• Test R...
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 등 구현
• 특정 대기 시간 후 지정된 확률로...
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 대 ...
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...
Performance Improvement - Protobuf
• 21,724 bytes 의 login response parsing time & peak memory
protobuf Time (1,000) Time(1...
Performance Improvement - Redis
with redis_store.pipeline() as pipeline:
try:
pipeline.hmset(name, mapping)
pipeline.expir...
Performance Improvement - DB
q = db.session.query(User, UserMonster). 
set_shard(gamedb_shard_no). 
filter(User.id.in_(ids...
Performance Improvement - DB
• 기존의 경우 Friend 를 1회 select 하여 n개의 data를 가져옴
• n 번 commondb 의 Account 를 select
• n 번 gamedb 의...
Performance Improvement - DB
• select 를 줄이기 위해 pk 를 확보하여 in_ 으로 filter
• Shard 별 pk 를 확보하여 loop
Performance Improvement - PyPy
• Pure Python 으로 Python 작성하여 jit 으로 실행 시간에 성능을 끌어 올림
• Dockerfile 만 변경하여 PyPy 설치, PyPy 로 uw...
Performance Improvement - PyPy
CPython PyPy
Performance Improvement - PyPy
CPython PyPy
Performance Improvement - PyPy
• 동일한 코드가 동작하는 경우로 테스트
• Process 당 약 10,000 개 이상의 요청 처리 후 성능 향상
• 향상된 성능은 측정하기 힘들 정도 (성능이 너...
그러나
Performance Improvement - PyPy
• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음
• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가
...
Performance Improvement - PyPy
• 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음
• 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가
...
Performance Improvement - Result
• Protobuf C++ 구현체로 변경
• Redis pipeline 사용
• Database Query 최적화
• 8 Core 15 GiB x 50• Pyt...
몬슈리 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 으로 일부를 정적 컴파일하여 사용했다면?
• P...
Python 게임 서버
• 각종 라이브러리와 클라우드 서비스를 쉽게 이용할 수 있다.
• 느리지만 빠르다.
• Python 게임 서버 안녕합니다. 걱정 안하셔도 됩니다.
• 글로벌 콘텐츠 기업
• 창업 8년차
• 직원 수 143명
• 대표 콘텐츠
• 핑크퐁
• 상어 가족
• 몬스터 슈퍼리그
함께해요
http://www.smartstudy.co.kr/withyou/
Upcoming SlideShare
Loading in …5
×

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

13,116 views

Published on

SMARTSTUDY 에서 몬스터 슈퍼 리그를 개발하면서 빠른 개발 진행을 위해 선택했던 Python 게임 서버, '잘 되면 다시 만들지 뭐'라는 생각에서 시작했지만 다시 만들 일은 영원히 오지 않았습니다... Python으로 게임 서버를 만들었을 때 사용한 것은 무엇인지 또 실제 오픈 했을 때 서버는 안녕했는지 알아봅니다.

Published in: Technology
  • Visit this site: tinyurl.com/sexinarea and find sex in your area for one night)) You can find me on this site too)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 좋은 정보 감사합니다. 파이썬 공부에 많은 도움이 되었습니다
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Sex in your area for one night is there tinyurl.com/hotsexinarea Copy and paste link in your browser to visit a site)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Girls for sex are waiting for you https://bit.ly/2TQ8UAY
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Meetings for sex in your area are there: https://bit.ly/2TQ8UAY
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

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

  1. 1. 스마트스터디 박준철 몬스터 슈퍼리그 게임 서버 Python 게임 서버 안녕하십니까?
  2. 2. 목표 • 몬슈리의 Python 게임 서버에 사용된 각종 라이브러리와 서비스 공유 • 몬슈리의 사례를 통해서 Python 으로 게임 서버를 만들 때 개발자의 의사결정에 도움이 되는 것 • 경험의 공유를 통한 “Python 은 느리지만 빨라”를 전파 몬스터 슈퍼 리그를 개발하면서 빠른 개발 진행을 위해 선택했던 Python 게임 서버, '잘 되면 다시 만들지 뭐'라는 생각에서 시작했지만 다시 만들 일은 영원히 오지 않았습니다... Python으로 게임 서버를 만들었을 때 사용한 것은 무엇인지 또 실제 오픈 했을 때 서버는 안녕했는지 알아봅니다. from https://ndc.nexon.com
  3. 3. 목차 1. 몬슈리 Python 게임 서버는 안녕하십니까? 2. 몬슈리 Python 게임 서버 3. 안녕하기 위한 노력 4. 몬슈리 Python 게임 서버는 안녕했나요? 5. Python 게임 서버
  4. 4. 몬슈리 Python 게임 서버는 안녕하십니까?
  5. 5. 현재 • DAU : 10 만 명 • CCU : 2 만 명 • Request / day = 6,000 만 • Sales in 6 months = $ 2,000 만 +
  6. 6. 몬슈리 Python 게임 서버
  7. 7. Python 2.7.11 • 개발 및 유지 보수가 편해야 함 • 다양한 라이브러리와 클라우드 서비스를 지원 • 패키지 관리가 잘 되어야 하고 의존성 문제가 없어야 함 • 서버의 퍼포먼스보다 개발자의 퍼포먼스가 중요 • 이미 사내 backend 개발에 Python 을 사용하고 있음 • 몬슈리 개발팀에 적합한 언어
  8. 8. Python 2.7.11 • Blinker • GData • Celery • Jinja2, bootstrap3 • NewRelic, Sentry • … • Flask (nginx + uwsgi) • SQLAlchemy • Protobuf (Protocol Buffers) • Redis • NumPy • Requests
  9. 9. 졸음 주의 • 몬슈리 게임 서버에 사용된 각종 라이브러리 나열식 설명
  10. 10. 기본적인 선택들 • Lightweight Web Application Framework - Flask • ORM - SQLAlchemy • KeyValue DB - Redis • Asynchronous Tasks - Celery
  11. 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. 12. 게임 서버는 Flask • Error 처리 • Status code=500 • 명백한 서버 오류, 클라이언트에서 “재시도” or “재접속” 팝업 • Status code=200 & Error Code 를 나누어 사용 • 서버와 클라이언트의 데이터가 맞지 않는 상황으로 판단 • 오류 메시지 노출 후 계속 진행 or “재접속”
  13. 13. 게임 서버는 Flask • 클라이언트의 Retry 대비 • Response 를 Redis 에 보관 후 동일한 Request 에 대해서 Response 를 바로 Return • 2 회 이상 발생시 Status Code=200 + Error Code return
  14. 14. DB 는 SQLAlchemy • Alembic 을 이용한 DB Migration 지원 • Multi DB 지원 • Auto Commit 사용하지 않음 • 모든 User Request 에 대해서 Transaction 사용 • Request 처리 중 서버 오류나 데이터 오류의 경우 Rollback • 오류 없이 로직이 처리된 경우 Commit
  15. 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. 16. 랜덤은 NumPy • Python Random 보다 빠르고 더 좋은 결과를 주지 않을까? • 막연한 기대로 선택 • 그러나 실제로는…
  17. 17. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  18. 18. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  19. 19. 랜덤은 NumPy • Random seed : 1 • 10, 1000, 1,000,000 개 random 비교
  20. 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. 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. 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. 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. 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. 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. 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})
  27. 27. Python Exception 은 Sentry
  28. 28. nginx + uWSGI + supervisor • Flask • WSGI 표준 지원, uWSGI 로 nginx 연결 • nginx • 각종 성능에 도움이 될 수 있는 옵션들 적용 • 배포 서버에서만 사용 (개발할 때는 flask run server) • Supervisor • Process 컨트롤 시스템
  29. 29. 운영툴 은 flask + Jinja2 + bootstrap3
  30. 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. 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)
  32. 32. CBT #1
  33. 33. 안녕하기 위한 노력 안녕하세요. 개발자님. 전 퍼블리셔라고 합니다.
  34. 34. 안녕하세요. • 퍼블리셔 : 안녕하세요. “퍼블리셔” 입니다. • 개발자 : 안녕하세요. 준곰이라고 불러 주세요. ☺ 2003 년부터 NEXON, NCSOFT, NEOWIZ 에서 게임을 만들었습니다. 대부분 서비스를 종료했습니다만… 왠지 이번에는 느낌이 좋네요. 그리고 2010년 부터 스마트스터디에서 핑크퐁 앱과 각종 게임들을 만들었습니다. CTO 를 하고 있는 몬슈리 개발자 박준철 입니다.
  35. 35. Sharding 해주실 수 있나요? • 개 : 네, Table 마다 다른 DB 를 지정할 수 있습니다. • 퍼 : 1개의 Table 을 n개의 DB에 저장하는 Sharding 을 해주셔야… • 개 : ORM 쓰고 foreign key도 썼는데… T_T
  36. 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. 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. 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. 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. 40. Redis CPU 사용률이 너무 높은데요? • 개 : 성능 좋은 인스턴스로 바꿔서 서비스 해줘요. • 퍼 : redis 는 싱글 스레드라… • 개 : 아… • 퍼 : redis 도 Sharding 해주시면 좋겠습니다 ☺ • 개 : 아…
  41. 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. 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. 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. 44. Monitoring은 뭘로 하나요? • 퍼 : 서버 Monitoring은 뭘로 하나요? • 개 : Exception 나면 Sentry 가 와요. Sentry 짱 • 퍼 : Exception 안 날 때는 뭘로 봐요? • 개 : 잘 돌고 있겠죠… • 퍼 : …
  45. 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) …
  46. 46. New Relic
  47. 47. New Relic • 대략 많이 비쌈 (c4.4xlarge x 25 = essentials $7,500/month)
  48. 48. Performance Test 해주세요 • 퍼 : 서버 준비할 수 있도록 Performance Test 해주세요. • 개 : 그냥 AWS 인데 100대로 시작하면 안되나요? • 퍼 : 네 =_= • 개 : 아…
  49. 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. 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. 51. Performance Test • Locust ( http://locust.io ) • Python 지원 • Master x 1, Slave x 6
  52. 52. Performance Test • Locust Bot (15 가지 주요 행동 ) • User Register, Login, Gift, Friend, Quest, Battle 등 구현 • 특정 대기 시간 후 지정된 확률로 행동(Behavior) 을 선택 후 실행 • 이전 Response 정보 반영하여 행동
  53. 53. Performance Test • 110 TPS (36 TPS/server) • CPU 사용률은 최대 80% • 1 Request = 1 core 를 100ms 점유 , 4 core 는 40 TPS
  54. 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. 55. Performance Test 후 • 개 : 10 만 동접을 위해 8 core 서버를 86 대면 충분하네요. • 퍼 : … • 개 : 다들 이정도 쓰시죠? … • 퍼 : …
  56. 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. 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. 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. 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. 60. Performance Improvement - DB • 기존의 경우 Friend 를 1회 select 하여 n개의 data를 가져옴 • n 번 commondb 의 Account 를 select • n 번 gamedb 의 User 를 select • n 번 gamedb 의 UserMonster 를 select
  61. 61. Performance Improvement - DB • select 를 줄이기 위해 pk 를 확보하여 in_ 으로 filter • Shard 별 pk 를 확보하여 loop
  62. 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. 63. Performance Improvement - PyPy CPython PyPy
  64. 64. Performance Improvement - PyPy CPython PyPy
  65. 65. Performance Improvement - PyPy • 동일한 코드가 동작하는 경우로 테스트 • Process 당 약 10,000 개 이상의 요청 처리 후 성능 향상 • 향상된 성능은 측정하기 힘들 정도 (성능이 너무 좋아짐)
  66. 66. 그러나
  67. 67. Performance Improvement - PyPy • 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음 • 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가 • Protobuf C++ 구현체를 사용할 수 없음 • 높은 메모리 사용량
  68. 68. Performance Improvement - PyPy • 다양한 패턴의 request 처리에 대해서는 성능이 빠르게 올라가지 않음 • 초반 느린 부트스트래핑(Bootstrapping) 단계로 인해 Latency 증가 • Protobuf C++ 구현체를 사용할 수 없음 • 높은 메모리 사용량 • 사용 포기
  69. 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. 70. 몬슈리 Python 게임 서버는 안녕했나요?
  71. 71. 네, 오픈 직후에도 안녕했습니다. • DAU : 50 만 • CCU : 7 만 • 30만 CCU 이상 가능
  72. 72. 네, 오픈 직후에도 안녕했습니다.
  73. 73. 그리고 지금도 안녕합니다. • DAU : 10 만 • CCU : 2 만 • 1 User : 0.06 TPS • Daily Requests = 6,000 만 • DB CPU 사용률 : 5% • 6개월 동안 서비스 중
  74. 74. Python 게임 서버
  75. 75. 생각해본 것 • 처음부터 PyPy 를 고려한 설계를 했다면? • 처음부터 Sharding 을 고려했다면? • MySQL 이 아닌 다른 DB 를 사용했다면? • Cython 으로 일부를 정적 컴파일하여 사용했다면? • Protobuf 보다 빠른 data serialization 이 있다면?
  76. 76. Python 게임 서버 • 각종 라이브러리와 클라우드 서비스를 쉽게 이용할 수 있다. • 느리지만 빠르다. • Python 게임 서버 안녕합니다. 걱정 안하셔도 됩니다.
  77. 77. • 글로벌 콘텐츠 기업 • 창업 8년차 • 직원 수 143명 • 대표 콘텐츠 • 핑크퐁 • 상어 가족 • 몬스터 슈퍼리그
  78. 78. 함께해요 http://www.smartstudy.co.kr/withyou/

×