Go言語でサーバのGraceful Shutdown

Go言語のサーバで Graceful Restart をするにあたり [go-server-starter](https://github.com/lestrrat/go-server-starter) を使ってみようかなと思ったので、そのために必要なライブラリを調査してみました。

## Graceful Restart

今回の主題とは別件ですが、Go言語の Graceful Restart のためのライブラリでは、`os.StartProcess` や `exec.Command` によって自分自身の子プロセスを起動し、ファイルディスクリプタ経由でソケットを渡して、再起動時は子プロセスのみを停止・起動するものがあります。

- [github.com/facebookgo/grace](https://github.com/facebookgo/grace)
- [github.com/jpillora/overseer](https://github.com/jpillora/overseer)
- [github.com/naoina/miyabi](https://github.com/naoina/miyabi)

今回はこれらのライブラリについては触れません。

## Graceful Shutdown

もう一方で、Graceful Shutdown の機能だけを提供するものがあります。

- [github.com/tylerb/graceful](https://github.com/tylerb/graceful)
- [github.com/braintree/manners](https://github.com/braintree/manners)
- [github.com/shogo82148/go-gracedown](https://github.com/shogo82148/go-gracedown)

これらの場合では [go-server-starter](https://github.com/lestrrat/go-server-starter) や [circus](https://circus.readthedocs.io/en/latest/) などがソケットを保持して子プロセスを起動し、ファイルディスクリプタ経由で子プロセスにソケットを渡すことになります。

## go-server-starter

go-server-starter を使って Graceful Shutdown の各ライブラリを使ったサンプルを実装してみました。ソースコード全体は [github.com/najeira/grace_samples](https://github.com/najeira/grace_samples) をご覧ください。

※ 前述のリポジトリ内にある facebookgo_httpdown のみ、リスナーを外部から渡せないので go-server-starter に対応したものではなく、単独でのサンプルです。今回は関係ありません。

## 試してみる

### go get

```
go get github.com/lestrrat/go-server-starter/cmd/start_server
go get github.com/najeira/grace_samples/braintree_manners
go get github.com/najeira/grace_samples/shogo_gracedown
go get github.com/najeira/grace_samples/tylerb_graceful
```

### go build

braintree_manners を試す場合。

```
cd $GOPATH/src/github.com/najeira/grace_samples/braintree_manners
go build
```

### run

サーバを起動します。

```
$GOPATH/bin/start_server --port 8080 --pid-file app.pid -- ./braintree_manners &
```

HTTPリクエストを送ってみます。

```
curl http://localhost:8080/

going to sleep 100ms with pid 2880
slept 100ms with pid 2880
```

### restart

サーバの再起動は `HUP` です。

```
kill -HUP `cat app.pid`
```

```
received HUP (num_old_workers=TODO)
spawning a new worker (num_old_workers=TODO)
starting new worker 2899
new worker is now running, sending TERM to old workers:2880
sleep 0 secs
killing old workers
old worker 2880 died, status:0
```

### stop

サーバの停止は `TERM` です。

```
kill -TERM `cat app.pid`
```

## 負荷をかけてみる

シグナル `HUP` を送り続けて再起動させ続けます。

```
while true; do kill -HUP `cat app.pid`; sleep 1; done
```

### ab

別のセッションで `ab` で負荷をかけてみます。

```
ab -r -n 100000 -c 1000 http://localhost:8080/
```

### Results

#### braintree_manners

```
Concurrency Level:      1000
Time taken for tests:   12.580 seconds
Complete requests:      100000
Failed requests:        22
   (Connect: 0, Receive: 0, Length: 22, Exceptions: 0)
Write errors:           0
Total transferred:      15798424 bytes
HTML transferred:       6099200 bytes
Requests per second:    7949.39 [#/sec] (mean)
Time per request:       125.796 [ms] (mean)
Time per request:       0.126 [ms] (mean, across all concurrent requests)
Transfer rate:          1226.44 [Kbytes/sec] received
```

#### shogo_gracedown

```
Concurrency Level:      1000
Time taken for tests:   12.898 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Total transferred:      15801082 bytes
HTML transferred:       6100306 bytes
Requests per second:    7753.17 [#/sec] (mean)
Time per request:       128.980 [ms] (mean)
Time per request:       0.129 [ms] (mean, across all concurrent requests)
Transfer rate:          1196.37 [Kbytes/sec] received
```

#### tylerb_graceful

```
Concurrency Level:      1000
Time taken for tests:   12.472 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Total transferred:      15800474 bytes
HTML transferred:       6100183 bytes
Requests per second:    8017.95 [#/sec] (mean)
Time per request:       124.720 [ms] (mean)
Time per request:       0.125 [ms] (mean, across all concurrent requests)
Transfer rate:          1237.18 [Kbytes/sec] received
```

### Environment

- go version go1.6.2 linux/amd64
- Amazon EC2 c4.large
- Linux version 4.4.11-23.53.amzn1.x86_64

## Conclusion

braintree_manners で取りこぼしがあるようです。

他、おすすめの Shutdown ライブラリがあれば、ぜひ教えてください！

## 参考

See: https://shogo82148.github.io/blog/2015/11/23/golang-graceful-restart-2nd/