go로 새 프로젝트 시작하기
TRANSCRIPT
Go로 새 프로젝트 시작하기Go로 모바일 게임 서버에 도전하면서 겪었던 고난과 역경의 분투기
DEVSISTERS 이준성
1
github.com/hodduc
2010~ | KAIST CS
| 개발 동아리 SPARCS
2012년 | ACM-ICPC 2012 Daejeon 1st place
2013년 6월 | ACM-ICPC 2013 World Final 48th place
2013~2014년 | 파이오링크
2014년 8월 | 데브시스터즈 입사, 쿠키런 서버 개발.
현재 | Go를 이용한 신규 프로젝트 진행 중
발표자 소개
Go로 새 프로젝트 시작하기 2
왜 Go인가요?
1
3
왜 Go인가요?
Go로 새 프로젝트 시작하기
기존 쿠키런 서버
and
4
왜 Go인가요?
Go로 새 프로젝트 시작하기
목표
복잡하지 않은 서버구조Spring 부들부들…
모든 서버 컴포넌트를 Scalable하게 만들기== MySQL 안 쓰거나 의존하지 않기
다른 언어와 잘 결합할 것C++ (quic), lua (게임 로직), …
5
왜 Go인가요?
Go로 새 프로젝트 시작하기
후보군
6
왜 Go인가요?
Go로 새 프로젝트 시작하기
Type safety
7
왜 Go인가요?
Go로 새 프로젝트 시작하기
Type safety 멀티코어 활용
8
왜 Go인가요?
Go로 새 프로젝트 시작하기
Type safety 멀티코어 활용 IDE support
9
왜 Go인가요?
Go로 새 프로젝트 시작하기
Type safety 멀티코어 활용 IDE support 서드파티 완성도
10
왜 Go인가요?
Go로 새 프로젝트 시작하기
자…바를 써야하나!?
11
왜 Go인가요?
Go로 새 프로젝트 시작하기
개발하기 쉬운가?
어렵다
매우 쉽다
쉽다
12
왜 Go인가요?
Go로 새 프로젝트 시작하기
개발하기 쉬운가? 짜야 하는 코드의 양
어렵다
매우 쉽다
쉽다
매우 많다
매우 적다
적다고 생각했다
13
왜 Go인가요?
Go로 새 프로젝트 시작하기
개발하기 쉬운가? 짜야 하는 코드의 양 Deploy하기 쉬운가?
어렵다
매우 쉽다
쉽다
매우 많다
매우 적다
적다
어렵다
어렵다
매우 쉽다고 생각했다
14
왜 Go인가요?
Go로 새 프로젝트 시작하기
하고 싶은가?
15
왜 Go인가요?
Go로 새 프로젝트 시작하기 16
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
17
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
18
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
• 가벼운고루틴!
19
일회용으로 마구 만들어 써도 충분한 성능의 고루틴
→ 각 유저마다 각각 하나의 고루틴이 로직 담당
→ 자연스럽게 순차적인 요청 처리가 보장됨
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
• 가벼운고루틴!
• 간결한코드사용으로인한 생산성향상
20
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
• 가벼운고루틴!
• 간결한코드사용으로인한 생산성향상
• Interface의 편리성
21
특히 Cap’n proto나 protobuf와 같은 serialization library와의
궁합이 좋음
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
• 가벼운고루틴!
• 간결한코드사용으로인한 생산성향상
• Interface의 편리성
• 빠른컴파일속도와실행속도덕분에테스트가빠르고쉬움
22
왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한손쉬운 Concurrency 구현
• 가벼운고루틴!
• 간결한코드사용으로인한 생산성향상
• Interface의 편리성
• 빠른컴파일속도와실행속도덕분에테스트가빠르고쉬움
• 코딩스타일을알아서맞춰줌
23
왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분들
24
왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
the ability to use the debugger to understand a Go program's full environment will likely never work, and improving gdbsupport is not a priority for the team.
- Rob Pike, on March 2014
대부분의 Debugger (gdb, lldb, delve, godebug, …) 가
• 복잡한 상황에서 Go 객체 내부를 제대로 print하지 못하거나
• Function call 을 지원하지 않거나
• Goroutine이 여러 개일 때 제대로 동작하지 않는다
25
왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을만하면튀어나오는서드파티라이브러리버그
26
왜 Go인가요?
Go로 새 프로젝트 시작하기 27
왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을만하면튀어나오는서드파티라이브러리버그
• 상속없는것까진괜찮은데…Simple Typing이언어 철학인건 알겠는데…그래도 Generic은 필요하다
Int8Contains, Int16Contains, Int32Contains, Int64Contains,
Int8Random, Int16Random, …을 하나하나 만드는건 좀…
28
왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을만하면튀어나오는서드파티라이브러리버그
• 그래도 Generic은 필요하다
• 코드의많은부분이에러처리코드다
if err := (…); err != nil { return err }
에러 처리 제대로 하다보면 코드가 생각보다 이쁘진 않다
29
삽질, 삽질, 삽질…
2
30
Concurrent Map
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 31
Go의 map은 thread-safe 하지 않다
Concurrent Map
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 32
type Director struct {pidMap map[int]*Actor
}
func (d *Director) Start() (*Actor, Pid) {pid := d.createPid()actor := NewActor(pid)
d.pidMap[pid] = actorreturn actor, pid
}
panic: runtime error: invalid memory address or nil pointer dereference
Concurrent Map
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 33
type Director struct {sync.RWMutexpidMap map[int]*Actor
}
func (d *Director) Start() (*Actor, Pid) {pid := d.createPid()actor := NewActor(pid)
d.Lock()defer d.Unlock()d.pidMap[pid] = actorreturn actor, pid
}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 34
func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}
}return nil
}
func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), err
}
func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 35
func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}
}return nil
}
func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), err
}
func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 36
func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}
}return nil
}
func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), err
}
func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
type: *MathErrorValue: nil
type: error (interface)Value: typed nil >.<
Error interface로 캐스팅될 때
값은 nil이지만 type이 nil이 아니므로
Interface nil과 같지 않다
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 37
func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}
}return nil
}
func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), nil
}
func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 38
func RootStr(s string) (int, error) {i, err := strconv.Atoi(s)if err != nil {return 0, err
}
err = AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), nil
}
func main() {sqrt, err := RootStr("81")if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 39
func RootStr(s string) (int, error) {i, err := strconv.Atoi(s)if err != nil {return 0, err
}
err = AssertPositive(i)if err != nil {return 0, err
}return int(math.Sqrt(float64(i))), nil
}
func main() {sqrt, err := RootStr("81")if err != nil {fmt.Println("error is not nil:", err)
} else {fmt.Println(sqrt)
}}
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 40
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
(X) if err == nil { return response, err }(O) if err == nil { return response, nil }
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 41
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
서로 다른 타입의 err 변수를 가급적 재사용하지 말 것
if err := Something(); err != nil { ... }
이 구문을 이용하면 if문 안에서 새로운 Scope가 생성되므로 보다 안전
Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 42
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
서로 다른 타입의 err 변수를 가급적 재사용하지 말 것
특별한 이유가 없다면 모든 함수의 return type을 “error” 인터페이스로 통일할 것
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 43
코드로 코드 만들기
코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 44
//go:generate ./some_runnable_command_here.sh –opt1 --opt2=foobar
$ go generate$ go build$ go test
코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 45
cat <<< "package generatedvar BaseDir = \"$BASEDIR\"var Version = struct {
ServerRevision stringProtocolRevision stringDeployDate string
}{\"$SERVER_HASH\",\"$PROTOCOL_HASH\",\"$DEPLOY_DATE\",
}" > generated/version.go
코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 46
type Ints []int
func (a Ints) Val() []intfunc (a Ints) Copy() Intsfunc (a Ints) Cut(i int, j int)func (a Ints) Insert(i int, x int) Intsfunc (a Ints) Append(x ...int) Intsfunc (a Ints) Reverse() func (a Ints) Filter(f func(i int, value interface{}) bool) Intsfunc (a Ints) Each(f func(i int, value interface{})) func (a Ints) Map(f func(i int, value interface{}) int) Ints
Type Int32s []int32......
Type Int64s []int64
코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 47
• shell script로만들기• 빌드 시점의 환경에 대한 정보가 필요할 때
• Go, Python 등을이용해프로그래밍하기• 언어의 한계를 극복! 하거나, 실수하기 쉬운 단순 작업을 여
러 번 해야 할 때
• Go의 ast, parser 모듈이 유용함• 내가 짠 코드를 ast, parser로 읽어서 적당한 기계 코드
를 패키지에 추가로 삽입
• 용도에따라외부툴 이용하기등등…
뭐뭐 쓰셨어요?
3
48
vim-go
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기 49
Godeps
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기 50
• Dependency Manager & Vendoring tool
• Copy all dependencies into Workspace directory ( ~= virtualenv in Python )
• Fix Go version, Library version, …
{"ImportPath": "github.com/devsisters/gb-server","GoVersion": "go1.4.2","Deps": [
{"ImportPath": "golang.org/x/net/context","Rev": "0b492c5a9642fa1365f77ad20bbb259a25572507"
},{
"ImportPath": "golang.org/x/oauth2","Rev": "ce5ea7da934b76b1066c527632359e2b8f65db97"
},{"ImportPath": "google.golang.org/api/googleapi","Rev": "c34364630fd76916db716b46fd3a75403b161768"
},....
go-errors
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기
var Crashed = errors.Errorf("oh dear")
func Crash() error {return errors.New(Crashed)}
if err != nil {if errors.Is(err, crashy.Crashed) {fmt.Println(err.(*errors.Error).ErrorStack())
} else {panic(err)
}}
51
GoConvey
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기
http://goconvey.co/
52
GoConvey
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기
http://goconvey.co/
53
GoConvey
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기
http://goconvey.co/
54
Test function
testing/quick
우리가 사용한 라이브러리들
Go로 새 프로젝트 시작하기
func TestOddMultipleOfThree(t *testing.T) {f := func(x int) bool {y := OddMultipleOfThree(x)return y%2 == 1 && y%3 == 0
}if err := quick.Check(f, nil); err != nil { t.Error(err)
}}
Target functionInject randomvalid parameter
Check if target functionis working well
55
우리가 공개한 라이브러리들(자랑) (뿌듯)
4
56
Cine
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/cine
• Actor model for Go (like erlang)
• All actor is identified by unique PID
• Remote actor is treated same as local one
• Supports synchronous / asynchronous function call
Cine
Host 1 Host 2 Host 3
Actor Actor Actor Actor Actor
57
Cine
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/cine
type Phonebook struct {cine.Actorbook map[string]int
}
cine.Init("127.0.0.1:8000")phonebook := Phonebook{cine.Actor{}, make(map[string]int)}pid := cine.StartActor(&phonebook)
// For asynchronous call (ignore all errors)cine.Cast(pid, nil, (*Phonebook).Add, "Jane", 1234)
// For synchronous callret, _ := cine.Call(pid, (*Phonebook).Lookup, "Jane")number := ret[0].(int)
58
GoQuic
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/goquic
https://www.youtube.com/watch?v=hQZ-0mXFmk8
59
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
QUIC vs TCP+TLS+SPDY/HTTP2
• 0-RTT support
• Multiplexing without Head-of-line blocking
• Forward error correction
• Connection Migration
60
GoQuic
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/goquic
Chromium
Extract QUIC core devsisters/
libquicGo binding w/ cgo devsisters/
goquic
devsisters.github.io/goquic
// servergoquic.ListenAndServe(":8080", 1, nil)
// clientclient := &http.Client{
Transport: goquic.NewRoundTripper(false),}resp, err := client.Get("http://example.com/")
61
그래서……?
5
62
Q. 그래서 Go를 새 프로젝트에 써도 될까요?
그래서……?
Go로 새 프로젝트 시작하기 63
Q. 그래서 Go를 새 프로젝트에 써도 될까요?
그래서……?
Go로 새 프로젝트 시작하기
A. 여러가지 난관이 있었지만 데브시스터즈에서는전체적으로 만족스럽게 개발 중
단, 프로젝트 셋업 초기에시간 투자와 삽질은 각오할 것
64
Q. 그래서 쿠키런2 언제 나와요?
그래서……?
Go로 새 프로젝트 시작하기 65
Q. 그래서 쿠키런2 언제 나와요?
그래서……?
Go로 새 프로젝트 시작하기
A. 비밀입니다 (웃음)
66
Q. 그래서 Go 개발자 뽑나요?
그래서……?
Go로 새 프로젝트 시작하기 67
Go 개발자 모십니다
감사합니다