왜 레진코믹스는 구글앱엔진을 선택했나

77

description

프리미엄 웹툰서비스, 레진코믹스는 구글 앱 엔진 위에서 서비스 되고 있습니다. 레진코믹스가 왜 구글 앱 엔진을 선택했는지, 주로 쓰고 있는 기능들과 함께 개발하면서 생긴 다양한 이슈와 팁들에 대해 이야기합니다. (2014.01.22 GDG Seoul Meetup)

Transcript of 왜 레진코믹스는 구글앱엔진을 선택했나

Page 1: 왜 레진코믹스는 구글앱엔진을 선택했나
Page 2: 왜 레진코믹스는 구글앱엔진을 선택했나

왜 레진코믹스는

구글 앱엔진을 선택했나

사용자, 즉 개발자는

Page 3: 왜 레진코믹스는 구글앱엔진을 선택했나

개발자 누구

•@curioe , http://curioe.com // 궁금한, 이상한

•레진엔터테인먼트 서버 개발자 1명 (2014.01)

•만명 기업 > 천명 기업 > 600명 기업 > 9명 기업에 코드를 남김

•할 일과 < 역할이 < 점점 더 < 많아짐

Page 4: 왜 레진코믹스는 구글앱엔진을 선택했나

다이나믹 타임트리2013년 2월 3월 4월 말

Open PaaS! 백수...

No! 락 인

레진 합류Cloud Foundry

Page 5: 왜 레진코믹스는 구글앱엔진을 선택했나

다이나믹 타임트리2013년 2월 3월 4월 말 6월 초

Open PaaS! 백수... 레진 합류

ㅠ.ㅠ

서비스 오픈 40일 후! !

서버 개발자 오직 나 하나... !

기획/요구사항은 개발하면서 진행됨 orz

Page 6: 왜 레진코믹스는 구글앱엔진을 선택했나

다이나믹 타임트리

2013/04

레진 합류

인프라는 인프라팀이!!

데이터베이스는 DBA가!!

개발은 분담

Before

Page 7: 왜 레진코믹스는 구글앱엔진을 선택했나

다이나믹 타임트리

2013/04

레진 합류

인프라는 인프라팀이!!

데이터베이스는 DBA가!!

개발은 분담

인프라 내가 해야 함!!

데이터베이스 내가 해야 함!!

개발 나 혼자 해야 함

Before After

Page 8: 왜 레진코믹스는 구글앱엔진을 선택했나

“앱 엔진 씁시다”

@xguru

Page 9: 왜 레진코믹스는 구글앱엔진을 선택했나

구글 앱 엔진

2013/04

레진 합류

인프라는 인프라팀이!!

데이터베이스는 DBA가!!

개발은 분담

PaaS 앱엔진으로

Datastore 앱엔진으로

개발 개발만 하면 됨

Before After

Page 10: 왜 레진코믹스는 구글앱엔진을 선택했나

구글 앱 엔진4월 말

레진 합류

PaaS 앱엔진으로!!

Datastore 앱엔진으로!!

개발 개발만 하면 됨

I don’ care!! 락 인

6월 초

일정 / 리소스 해결

미션 컴플리티드!

Page 11: 왜 레진코믹스는 구글앱엔진을 선택했나

왜 레진코믹스는

구글 앱엔진을 선택했나

서비스는

Page 12: 왜 레진코믹스는 구글앱엔진을 선택했나

레진코믹스 - 프리미엄 웹툰

Page 13: 왜 레진코믹스는 구글앱엔진을 선택했나

레진코믹스 - 부분유료화

D - 21

D - 14

D - 7

D - 21

D - 77일후 무료공개 예정

D - 7

“ “7일이나 기다려?? 먼저보는 즐거움!

Page 14: 왜 레진코믹스는 구글앱엔진을 선택했나

Auto Scaling

Page 15: 왜 레진코믹스는 구글앱엔진을 선택했나

누적 회원 증가

6

12

78

9

10

11

Page 16: 왜 레진코믹스는 구글앱엔진을 선택했나

트래픽 - 시간대별 특성

Page 17: 왜 레진코믹스는 구글앱엔진을 선택했나

Instance

샘플이미지

Page 18: 왜 레진코믹스는 구글앱엔진을 선택했나

Images Service

Page 19: 왜 레진코믹스는 구글앱엔진을 선택했나

이미지 서비스

CMS

app engine

blobstore

images service

업로드

일반화질

고화질변환

저장

Page 20: 왜 레진코믹스는 구글앱엔진을 선택했나

이미지 서비스 비용

app engine

blobstore

images service

업로드

일반화질

고화질

incoming bandwidth

storage

bandwidth out

image manipulation api

변환

저장

무료유료

CMS

Page 21: 왜 레진코믹스는 구글앱엔진을 선택했나

서비스는

구글 앱엔진을 선택했나

왜 레진코믹스는

아마존 EC2 가 아닌,

Page 22: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 vs EC2

PaaS 구글 앱 엔진

IaaS EC2

내가 질 책임 애플리케이션애플리케이션, 데이터베이스, 웹서버, 운영체제, 모니터링, 로드밸런싱, 업

그레이드

sysadmin Google 필요

(상대적) 비용 비쌈 쌈

Google Compute Engine vs EC2

Page 23: 왜 레진코믹스는 구글앱엔진을 선택했나

Platform as a Service

Frontend Instance Blobstore

Images Services Datastore Memcache

Backend Instance Mail Cron

Task Queue Admin Console

Page 24: 왜 레진코믹스는 구글앱엔진을 선택했나

왜 레진코믹스는

구글 앱엔진을 선택했나

해외에서는

Page 25: 왜 레진코믹스는 구글앱엔진을 선택했나

Snapchat

http://www.quora.com/Google-App-Engine/What-is-the-highest-traffic-website-built-on-top-of-Google-App-Engine-Python http://gigaom.com/2013/05/07/snapchats-act-of-faith-in-building-on-google-compute-engine/

월 활성 사용자(MAU) 3천만 55% 가 매일 사용함 - 1천6백만 하루에 1억 5천만 사진 업로드

“앱엔진은 우리가 애플리케이션을 개발하는데 집중할 수 있게 해줬어요. 앱엔진이 우리에게 제공해준 개발의 편의없이 이렇게까지 못했을 꺼에요.”

- 바비 머피, CTO 이며 co-Founder

Page 26: 왜 레진코믹스는 구글앱엔진을 선택했나

Khan Academy

https://cloud.google.com/files/KhanAcademy.pdf

380만 월 UV 수업일에 150만 프랙티스가 제공 2000 개 이상의 동영상

“만약 구글 앱엔진이 없었다면, 서버를 셋업이나 라우터 설정에 정말 많은 시간을 보냈을꺼에요. 실 서비스에 집중할 수 있게 한 건 구글 앱엔진의 장점입니다.”

- 벤 카멘스, 리드 개발자

“개발 프로세스가 쉬워서 평균 하루에 한번, 많게는 10번도 배포를 합니다.”

Page 27: 왜 레진코믹스는 구글앱엔진을 선택했나

Rovio

https://cloud.google.com/files/Rovio.pdf

Angry bird friend for facebook MAU 1천 3백만

“구글 앱엔진은 우리가 게임당 한명 또는 두명의 개발자로만 매우 빠르게 게임을 런칭하도록 해줬어요. 구글이 모든 서버들을 관리해주기 때문에, 유지보수에 있어 우리가 할 일이 거의 없었어요.”

- 스테판 호크, 웹 게임 리드 서버 개발자

Page 28: 왜 레진코믹스는 구글앱엔진을 선택했나

왜 레진코믹스는 구글 앱엔진을 선택했나

• 개발이 빠르고 쉬움

• 자동으로 스케일링해줌

• 개발에만 집중

• 유용한 빌트인 기능들 제공

• 구글 노하우가 녹아있는 인프라 위에서 구동 (비즈니스)

Page 29: 왜 레진코믹스는 구글앱엔진을 선택했나

좋다고 생각없이 쓰면 패가망신

Page 30: 왜 레진코믹스는 구글앱엔진을 선택했나

Generally available features

Language & Runtime

Communications Data Storage, Retrieval, & Search

Java Python PHPpreview

Dedicated Memcache

Datastore

Blobstore

Search

Memcache

Goexperimental

Logs

HRD Migration Tool

Channel

Mail

URL Fetch

XMPP

Google Cloud Endpoints

Process Management

Task Queue

Scheduled Tasks

Computation

Backends

Images

App Configuration & Management

App Identity

Capabilities

Traffic Splitting

Multitenancy

Remote

SSL for Custom Domains

Users

Preview features

Modules

Sockets

Cloud SQL

Google Cloud Storage Client Library

Experimental features

OpenID

OAuth

MapReduce

Datastore Admin/Backup/Restore

PageSpeed

Prospective Search

Task Queue REST API

Task Queue Tagging

Appstats

Page 31: 왜 레진코믹스는 구글앱엔진을 선택했나

Generally available features

Language & Runtime

Communications Data Storage, Retrieval, & Search

Java Python PHPpreview

Dedicated Memcache

Datastore

Blobstore

Search

Memcache

Goexperimental

Logs

HRD Migration Tool

Channel

Mail

URL Fetch

XMPP

Google Cloud Endpoints

Process Management

Task Queue

Scheduled Tasks

Computation

Backends

Images

App Configuration & Management

App Identity

Capabilities

Traffic Splitting

Multitenancy

Remote

SSL for Custom Domains

Users

Preview features

Modules

Sockets

Cloud SQL

Google Cloud Storage Client Library

Experimental features

OpenID

OAuth

MapReduce

Datastore Admin/Backup/Restore

PageSpeed

Prospective Search

Task Queue REST API

Task Queue Tagging

Appstats

Page 32: 왜 레진코믹스는 구글앱엔진을 선택했나

드리고 싶은 이야기

참고사항

그 다음

북마크

배포/관리

경험한 기능들정말 팁

App Engine API

Appstats

Blobstore

Logs

URL Fetch

Images

Scheduled Tasks

Dedicated Memcache

Datastore

MailTask Queue

My App

Page 33: 왜 레진코믹스는 구글앱엔진을 선택했나

배포/관리

Page 34: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 애플리케이션

Application id: gdgseoulmeetup

Page 35: 왜 레진코믹스는 구글앱엔진을 선택했나

요청

Application id: gdgseoulmeetup

http://gdgseoulmeetup.appspot.com

Page 36: 왜 레진코믹스는 구글앱엔진을 선택했나

Dynamic 인스턴스

Application id: gdgseoulmeetup

Instance

http://gdgseoulmeetup.appspot.com

dynamic

Page 37: 왜 레진코믹스는 구글앱엔진을 선택했나

Resident 인스턴스

Application

Instance

id: gdgseoulmeetup

Resident

늘 떠있음

Page 38: 왜 레진코믹스는 구글앱엔진을 선택했나

요청

Application

Instance

http://gdgseoulmeetup.appspot.com

id: gdgseoulmeetup

Resident

Page 39: 왜 레진코믹스는 구글앱엔진을 선택했나

Auto Scaling

Application

Instance Instance

http://gdgseoulmeetup.appspot.com

id: gdgseoulmeetup

Resident dynamic

Frontend Instance Auto Scaling 돈 먹는 하마

Page 40: 왜 레진코믹스는 구글앱엔진을 선택했나

Auto Scaling

Application

Instance Instance Instance Instance Instance Instance

http://gdgseoulmeetup.appspot.com

id: gdgseoulmeetup

Resident dynamic dynamic dynamic dynamic dynamic

Page 41: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 애플리케이션

Application

Version Version

Module

http://gdgseoulmeetup.appspot.com

id: gdgseoulmeetup

Instance Instance Instance Instance Instance Instance

Resident dynamic dynamic dynamic dynamic dynamic

Version

Module

Page 42: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 애플리케이션

Application

Version Version

Module

http://gdgseoulmeetup.appspot.com

id: gdgseoulmeetup

Instance Instance Instance Instance Instance Instance

Resident dynamic dynamic dynamic dynamic dynamic

Version

Module

Default

Page 43: 왜 레진코믹스는 구글앱엔진을 선택했나

배포 - 버전

http://gdgseoulmeetup.appspot.com

http://20140122.gdgseoulmeetup.appspot.com

Page 44: 왜 레진코믹스는 구글앱엔진을 선택했나

local

dev server

production

•앱엔진 샌드박스 제공 •/WEB-INF/appengine-generated/local_db.bin

•개발용 app id •개발자 별로 버전 배포 •노출 안되게 앱엔진 admin 권한 처리

•새 버전 배포 -> 확인 -> 디폴트 변경

앱/버전 property 로 관리 - 빌드타임에 정해지도록 함appengine'web.xml.

!<application>${google.app.id}</application>.

<version>${google.app.version}</version>

Page 45: 왜 레진코믹스는 구글앱엔진을 선택했나

Datastore

Key Ancestor Index

Page 46: 왜 레진코믹스는 구글앱엔진을 선택했나

데이터스토어 - KeyComic

kindidentifierancestor path (optional)

Key - entity 의 ID / 구글앱엔진에서의 유일한 키 값String comicId * String title String artist

UserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime Long userId

Entity

Page 47: 왜 레진코믹스는 구글앱엔진을 선택했나

데이터스토어 - KeyComic

kindidentifierancestor path (optional)

Key - entity 의 ID / 구글앱엔진에서의 유일한 키 값String comicId * String title String artist

UserLong userId * String username boolean adult int coinBalance

identifier

PurchaseLong purchaseId * String episodeId int coin long purchaseTime Long userId

자동할당 - 실행시 생성되는 객체들 (long)

지정 - 미리 아는 객체들 (string, long)

Entity

Page 48: 왜 레진코믹스는 구글앱엔진을 선택했나

데이터스토어 - KeyComic

kindidentifierancestor path (optional)

Key - entity 의 ID / 구글앱엔진에서의 유일한 키 값String comicId * String title String artist

UserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime Long userId

SELECT.*.FROM.Comic.WHERE.__key__.=.Key('Comic',.'badboss')

SELECT.*.FROM.User.WHERE.__key__.=.Key('User',.1234123412341234)

Entity

Page 49: 왜 레진코믹스는 구글앱엔진을 선택했나

Global Query

UserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime Long userId

SELECT.*.FROM.Purchase.WHERE.userId.=.1234123412341234.

AND.episodeId.=.‘badboss'''13’

Page 50: 왜 레진코믹스는 구글앱엔진을 선택했나

Eventual Consistency

https://cloud.google.com/developers/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore

Page 51: 왜 레진코믹스는 구글앱엔진을 선택했나

Eventual Consistency

https://cloud.google.com/developers/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore

Purchase

SELECT.*.FROM.Purchase.WHERE.userId.=.1234123412341234.

AND.episodeId.=.‘badboss'''13’

Page 52: 왜 레진코믹스는 구글앱엔진을 선택했나

Strong Consistency

https://cloud.google.com/developers/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore

Page 53: 왜 레진코믹스는 구글앱엔진을 선택했나

Ancestor

UserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime

SELECT.*.FROM.Purchase.WHERE.ancestor)is.Key(‘User’,.1234123412341234).AND.episodeId.=.‘badboss'''13’

Long userId

Page 54: 왜 레진코믹스는 구글앱엔진을 선택했나

Entity Group

Ancestor

kindidentifierancestor path

Key - entity 의 IDUserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime

SELECT.*.FROM.Purchase.WHERE.ancestor)is.Key(‘User’,.1234123412341234).AND.episodeId.=.‘badboss'''13’

<Parent>Long userId

Page 55: 왜 레진코믹스는 구글앱엔진을 선택했나

데이터스토어 인덱스

UserLong userId * String username boolean adult int coinBalance

PurchaseLong purchaseId * String episodeId int coin long purchaseTime <Parent>Long userId

SELECT.*.FROM.Purchase.WHERE.ancestor)is.Key(‘User’,.1234123412341234).AND.episodeId.=.‘badboss'''13’

index

주의 index 는 entity 를 update 할 때 생김 나중에 추가하려면 entity 를 새로update 해야 함

Page 56: 왜 레진코믹스는 구글앱엔진을 선택했나

composite 인덱스 추가

주의 운영 중에 index 조합을 추가한 경우, Serving 상태를 확인한 후에 default 로 배포할 것

Page 57: 왜 레진코믹스는 구글앱엔진을 선택했나

카운트1. 카운트하는 엔터티를 둔다. - 동시에 엔터티를 수정하게 되면 Datastore Contention 문제 - 동시에 못하도록 한다면 한번에 한 카운트만 처리 문제 !2. Sharding Counter https://developers.google.com/appengine/articles/sharding_counters - 랜덤으로 카운터를 여러개 두고, 사용하지 않은 카운터를 읽고 씀 - 나중에 합쳐 확인하는 방법 - 정확 - 매 요청마다 데이터스토어 써 느린데다 데이터 엔터티가 무수히 많이 생김 !3. Deferred Task Queue 활용 - 데이터스토어에 permanent 카운트 - Memcache 에 current 카운트 - 주기적으로 Memcache 에서 데이터스토어로 씀 - 성능은 좋지만, 정확하지 않을 수 있다.

Page 58: 왜 레진코믹스는 구글앱엔진을 선택했나

비용 줄이기

• Appstats 써서 요청에 대한 비용이 어느정도인지 검토

• Memcache 를 되도록 많이 쓰기

• Urlfetch 는 될 수 있는 한 앱에서 할 것

Page 59: 왜 레진코믹스는 구글앱엔진을 선택했나

Appstats

Page 60: 왜 레진코믹스는 구글앱엔진을 선택했나

Memcache

•Memcache 쓰면 빠르고 비용도 줄어듬

•제한 1MB 이하 - Object 의 List 를 통째로 넣으려면 주의 (json 변환)

•자바의 경우 Objectify 사용 추천 (Datastore + 기본 Memcache)

Page 61: 왜 레진코믹스는 구글앱엔진을 선택했나

Page 62: 왜 레진코믹스는 구글앱엔진을 선택했나

메일 Quota 요청 미리미리

Page 63: 왜 레진코믹스는 구글앱엔진을 선택했나

세션 삭제, 앱엔진이 안해줌

!/_ah/sessioncleanup com.google.apphosting.utils.servlet.SessionCleanupServlet !cron 으로 주기적으로 호출할 것

http://www.radomirml.com/blog/2011/03/26/cleaning-up-expired-sessions-from-app-engine-datastore/

Page 64: 왜 레진코믹스는 구글앱엔진을 선택했나

Cron 문법 주의

•매시간마다 ===> every 1 hours

•매시간인데 15분에 실행되면 좋겠음

===>

!

every 1 hours from 00:15 to 00:14

every 1 hours from 00:15 to 00:15영원히 실행되지 않음

Page 65: 왜 레진코믹스는 구글앱엔진을 선택했나

static 파일 만료 시간 조정

•Purge 기능 없음.

•사용자 브라우저에 의존하기 때문에 (자주 바뀐다면) 시간을 조정해둘 것

Page 66: 왜 레진코믹스는 구글앱엔진을 선택했나

SSL URL

•http://1.app-id.appspot.com

•https://1-dot-app-id.appspot.com

Page 67: 왜 레진코믹스는 구글앱엔진을 선택했나

참고사항

Page 68: 왜 레진코믹스는 구글앱엔진을 선택했나

Latency

• 데이터센터 저 멀리

• cdn 이용

• dynamic 데이터는 어쩔 수 없음

• 좋은 소식

• 아시아에 데이터센터 들어감http://googleasiapacific.blogspot.kr/2013/12/our-first-data-centers-in-asia-are-up.html

Page 69: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 + 스프링 최적화

• Component Scanning 줄이기/피하기

• Relationship Autowiring 줄이기/피하기

• 상용에서 XML Validation 하지 않기

• Lazy-Initialized Bean 사용하기

• Constructor Injection by Name 피하기

• https://developers.google.com/appengine/articles/spring_optimization

Page 70: 왜 레진코믹스는 구글앱엔진을 선택했나

로그 / 파일

• 로그는 좀 많이 불편

• File API 안됨

• PG사 모듈 File 쓰기 안되어서 제외

Page 71: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 그 다음

Page 72: 왜 레진코믹스는 구글앱엔진을 선택했나

AppScale

http://www.appscale.com/

구글 앱엔진 호환되는 오픈소스 PaaS

Page 73: 왜 레진코믹스는 구글앱엔진을 선택했나

AppScale Architecture

Page 74: 왜 레진코믹스는 구글앱엔진을 선택했나

북마크

Page 75: 왜 레진코믹스는 구글앱엔진을 선택했나

모니터링/커뮤니티• 시스템 상태 페이지https://code.google.com/status/appengine

• 다운타임 알림https://groups.google.com/forum/#!forum/google-appengine-downtime-notify

• issue trackerhttps://code.google.com/p/googleappengine/issues/list

• 토론https://groups.google.com/forum/#!forum/google-appengine

• IRChttp://webchat.freenode.net/ #appengine

Page 76: 왜 레진코믹스는 구글앱엔진을 선택했나

앱엔진 시작하기

• 입문자를 위한 앱엔진 튜토리얼http://googcloudlabs.appspot.com/codelabexercise1.html

Page 77: 왜 레진코믹스는 구글앱엔진을 선택했나