1. Go로 새 프로젝트 시작하기
Go로 모바일 게임 서버에 도전하면서 겪었던 고난과 역경의 분투기
DEVSISTERS 이준성
1
2. 이준성
joonsung@devsisters.com
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
18. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
18
19. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
• 가벼운 고루틴!
19
일회용으로 마구 만들어 써도 충분한 성능의 고루틴
→ 각 유저마다 각각 하나의 고루틴이 로직 담당
→ 자연스럽게 순차적인 요청 처리가 보장됨
20. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
• 가벼운 고루틴!
• 간결한 코드 사용으로 인한 생산성 향상
20
21. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
• 가벼운 고루틴!
• 간결한 코드 사용으로 인한 생산성 향상
• Interface의 편리성
21
특히 Cap’n proto나 protobuf와 같은 serialization library와의
궁합이 좋음
22. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
• 가벼운 고루틴!
• 간결한 코드 사용으로 인한 생산성 향상
• Interface의 편리성
• 빠른 컴파일 속도와 실행 속도 덕분에 테스트가 빠르고 쉬움
22
23. 왜 Go인가요?
Go로 새 프로젝트 시작하기
Go를 선택해서 좋았던 점
• Channel을 활용한 손쉬운 Concurrency 구현
• 가벼운 고루틴!
• 간결한 코드 사용으로 인한 생산성 향상
• Interface의 편리성
• 빠른 컴파일 속도와 실행 속도 덕분에 테스트가 빠르고 쉬움
• 코딩 스타일을 알아서 맞춰줌
23
25. 왜 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 gdb
support is not a priority for the team.
- Rob Pike, on March 2014
대부분의 Debugger (gdb, lldb, delve, godebug, …) 가
• 복잡한 상황에서 Go 객체 내부를 제대로 print하지 못하거나
• Function call 을 지원하지 않거나
• Goroutine이 여러 개일 때 제대로 동작하지 않는다
25
26. 왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을 만하면 튀어나오는 서드파티 라이브러리 버그
26
28. 왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을 만하면 튀어나오는 서드파티 라이브러리 버그
• 상속 없는 것까진 괜찮은데…
Simple Typing이 언어 철학인 건 알겠는데…
그래도 Generic은 필요하다
Int8Contains, Int16Contains, Int32Contains, Int64Contains,
Int8Random, Int16Random, …을 하나하나 만드는건 좀…
28
29. 왜 Go인가요?
Go로 새 프로젝트 시작하기
고통받았던 부분
• 쓸만한 Full-featured Debugger가 없다
• 잊을 만하면 튀어나오는 서드파티 라이브러리 버그
• 그래도 Generic은 필요하다
• 코드의 많은 부분이 에러 처리 코드다
if err := (…); err != nil { return err }
에러 처리 제대로 하다보면 코드가 생각보다 이쁘진 않다
29
32. 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] = actor
return actor, pid
}
panic: runtime error: invalid memory address or nil pointer dereference
33. Concurrent Map
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 33
type Director struct {
sync.RWMutex
pidMap map[int]*Actor
}
func (d *Director) Start() (*Actor, Pid) {
pid := d.createPid()
actor := NewActor(pid)
d.Lock()
defer d.Unlock()
d.pidMap[pid] = actor
return actor, pid
}
34. 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)
}
}
35. 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)
}
}
36. 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: *MathError
Value: nil
type: error (interface)
Value: typed nil >.<
Error interface로 캐스팅될 때
값은 nil이지만 type이 nil이 아니므로
Interface nil과 같지 않다
37. 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)
}
}
38. 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)
}
}
39. 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)
}
}
40. Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 40
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
(X) if err == nil { return response, err }
(O) if err == nil { return response, nil }
41. Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 41
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
서로 다른 타입의 err 변수를 가급적 재사용하지 말 것
if err := Something(); err != nil { ... }
이 구문을 이용하면 if문 안에서 새로운 Scope가 생성되므로 보다 안전
42. Typed nil
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 42
Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것
서로 다른 타입의 err 변수를 가급적 재사용하지 말 것
특별한 이유가 없다면 모든 함수의 return type을 “error” 인터페이스로 통일할 것
44. 코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 44
//go:generate ./some_runnable_command_here.sh –opt1 --opt2=foobar
$ go generate
$ go build
$ go test
45. 코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 45
cat <<< "
package generated
var BaseDir = "$BASEDIR"
var Version = struct {
ServerRevision string
ProtocolRevision string
DeployDate string
}{
"$SERVER_HASH",
"$PROTOCOL_HASH",
"$DEPLOY_DATE",
}
" > generated/version.go
46. 코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 46
type Ints []int
func (a Ints) Val() []int
func (a Ints) Copy() Ints
func (a Ints) Cut(i int, j int)
func (a Ints) Insert(i int, x int) Ints
func (a Ints) Append(x ...int) Ints
func (a Ints) Reverse()
func (a Ints) Filter(f func(i int, value interface{}) bool) Ints
func (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
47. 코드로 코드 만들기
삽질, 삽질, 삽질…
Go로 새 프로젝트 시작하기 47
• shell script로 만들기
• 빌드 시점의 환경에 대한 정보가 필요할 때
• Go, Python 등을 이용해 프로그래밍하기
• 언어의 한계를 극복! 하거나, 실수하기 쉬운 단순 작업을 여
러 번 해야 할 때
• Go의 ast, parser 모듈이 유용함
• 내가 짠 코드를 ast, parser로 읽어서 적당한 기계 코드
를 패키지에 추가로 삽입
• 용도에 따라 외부 툴 이용하기 등등…
55. 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 function
Inject random
valid parameter
Check if target function
is working well
55
57. 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
58. Cine
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/cine
type Phonebook struct {
cine.Actor
book 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 call
ret, _ := cine.Call(pid, (*Phonebook).Lookup, "Jane")
number := ret[0].(int)
58
59. GoQuic
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/goquic
https://www.youtube.com/watch?v=hQZ-0mXFmk8
59
60. 우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
QUIC vs TCP+TLS+SPDY/HTTP2
• 0-RTT support
• Multiplexing without Head-of-line blocking
• Forward error correction
• Connection Migration
60
61. GoQuic
우리가 공개한 라이브러리들
Go로 새 프로젝트 시작하기
github.com/devsisters/goquic
Chromium
Extract QUIC core devsisters/
libquic
Go binding w/ cgo devsisters/
goquic
devsisters.github.io/goquic
// server
goquic.ListenAndServe(":8080", 1, nil)
// client
client := &http.Client{
Transport: goquic.NewRoundTripper(false),
}
resp, err := client.Get("http://example.com/")
61