Go Conference 2018 Autumn 大規模ウェブサービスにおけるコード レビュー観点

F8e7a1a5f90a13a1dd9fb57ce65cab77?s=47 avvmoto
November 28, 2018

Go Conference 2018 Autumn 大規模ウェブサービスにおけるコード レビュー観点

DeNA では Google App Engine Standard Environment (GAE SE) を用い、マイクロサービスとして大規模なトラフィックをさばくウェブサービスを開発・運用しています。 GAE SE は強力なPaaSですが、1インスタンスあたりのメモリ使用量が限られるという制約もあります。本発表では、GAE SE 上で高ハイパフォーマンスを実現するために、日々開発現場で用いているメモリ最適化手法を中心に、コードレビュー観点をご紹介します。

F8e7a1a5f90a13a1dd9fb57ce65cab77?s=128

avvmoto

November 28, 2018
Tweet

Transcript

  1. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. Copyright (C)

    2018 DeNA Co.,Ltd. All Rights Reserved. 1 大規模ウェブサービスにおけるコード レビュー観点 Go Conference 2018 Autumn Sponsor Session #4 Nov 25, 2018 井本 裕 ゲーム・エンターテインメント事業本部DeNA Co., Ltd.
  2. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. Google App

    Engine (SE) + golang によるマイクロサービス開発 2
  3. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. Google App

    Engine (SE) + golang によるマイクロサービス開発 - Google App Engine - GCP にて提供される PaaS - インフラまわりの負担少なく、大規模なトラフィックをさばくことができる - Standard Environment (SE) と Flexible Environment(FE) があるが、弊社では もっぱらインスタンスの起動速度の早いSEを多用 - 特徴 - インスタンス数のオートスケール - 便利なクラウドロギング機能 - DeNA でも導入事例多数 3
  4. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. GAE SE

    + go 1.9 の hello world main.go app.yaml package main import ( "fmt" "net/http" ) func init() { http.HandleFunc("/", handle) } func handle(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, world!") } runtime: go api_version: go1.9 handlers: - url: /.* script: _go_app automatic_scaling: target_cpu_utilization: 0.65 min_instances: 5 max_instances: 100 min_pending_latency: 30ms max_pending_latency: automatic max_concurrent_requests: 50
  5. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. DeNA AndApp

    での事例 source: AndAppにおけるGCP活用事例 https://www.slideshare.net/dena_tech/andappgcp-88366004
  6. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. 実装上の考慮点 6

  7. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. GAE SE

    のメモリ事情 - 1 インスタンスあたりの性能は - メモリによってスケールすることはできない - max_concurrent_requests によってよしなに調整する - シナリオベースの負荷試験を実施して、 - 実験的に値を調整 7 automatic_scaling: target_cpu_utilization: 0.65 min_instances: 5 max_instances: 100 min_pending_latency: 30ms max_pending_latency: automatic max_concurrent_requests: 50
  8. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. メモリのアロケートを抑える実装をしよう -

    GAE SE のインスタンスのメモリサイズは大きくない - そもそもメモリのアロケートはコストが高い - 高コストなGCの走る頻度も高まる - goroutine の停止期間 (stop-the-world) も発生する 8
  9. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. go scheduler

    design - MPG model - M: Machines - P: Processors - G: goroutine 9 source: https://morsmachine.dk/go-scheduler
  10. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. checkpoint -

    ランタイムはプリエンプティブではない - スケジューラーが利用するチェックポイントは、コンパイル時に作られる - チェックポイントはコンパイル中に透過的に加わるコードである - goroutineは実行してしばらく立ったら、チェックポイントに達し次第すぐ、実行のコントロールはスケジュー ラーに戻す。 - つまり goroutine は、goroutine 自身が終わった!と言うまで終わらない - GC のタイミングで、全 goroutine がチェックポイントに達し、実行をプロセッサーに明渡すのを待つ。 10
  11. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. 11 func

    main() { var b []byte = []byte{1} h := fnv.New32() go func() { for { h.Write(b) } }() // Gosched yields the processor, allowing other goroutines to run runtime.Gosched() fmt.Println("GC Start") runtime.GC() fmt.Println("GC Done") } $ go run main.go GC Start - GC は全 goroutine がプロセッサーを明け渡 すまで、ブロックする
  12. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. goroutine -

    詳しくは Goroutines: the dark side of the runtime - Roberto Clapis - https://talks.godoc.org/github.com/empijei/gotalks/gophercon.slide#1 - (弊社の国際学会派遣制度を利用し、golab.io 2018 の現地で参加して聞いていました) 12
  13. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. メモリのアロケートを避ける実装をしよう(再掲) -

    GAE SE はメモリサイズは大きくない - そもそもメモリのアロケートはコストが高い - 高コストなGCの走る頻度も高まる - stop-the-world も発生する 13
  14. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. メモリのアロケートを減らす手法 14

  15. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.Copy -

    io.Reader からデータをすべて読みとり、 io.Writer へデータを全て書き込む例を 考えていきましょう。 - 受け取った Body をそのまま表示する handler - // handleShowBody は受け取った Body をそのまま表示します。 func handleShowBody(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { panic(err) } defer r.Body.Close() _, err = w.Write(b) if err != nil { panic(err) } }
  16. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.Copy -

    受け取った Body をそのまま表示する handler - 受け取った Body を一度メモリ上に展開 している // handleShowBody は受け取った Body をそのまま表示します。 func handleShowBody(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { panic(err) } defer r.Body.Close() _, err = w.Write(b) if err != nil { panic(err) } }
  17. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.Copy -

    受け取った Body をそのまま表示する handler - io.Copy で一度に全てメモリに展開する ことを防げた // handleShowBody は受け取った Body をそのまま表示します。 func handleShowBody(w http.ResponseWriter, r *http.Request) { _, err := io.Copy(w, r.Body) if err != nil { panic(err) } defer r.Body.Close() }
  18. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.Copy -

    io.Copy の内部で 32 kB のバッファを作成し、 32kBずつ読み取り&書き込 みを行っている - https://golang.org/src/io/io.go?s=12784:12844#L353 18
  19. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.CopyBuffer -

    明示的にコピーに利用するバッファーを 指定する io.CopyBuffer もある - バッファーのサイズを変更できる // handleShowBody は受け取った Body をそのまま表示します。 func handleShowBody(w http.ResponseWriter, r *http.Request) { buf := make([]byte, 8) _, err := io.CopyBuffer(w, r.Body, buf) if err != nil { panic(err) } defer r.Body.Close() }
  20. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. sync.Pool -

    io.CopyBuffer で用いるバッファーを キャッシュしておく - sync.Pool はバッファーをいくつかプール してくれ、プール内にバッファーがなけれ ば新規作成、あればプールされたバッ ファを返す - バッファの最大作成数は最大同時リクエ スト数となる - GAE SE の場合 max_concurrent_requests - バッファのサイズが大きいときに特に有 効 - GCS への書き込み等、ネットワークIO越しに なる場合など var pool = sync.Pool{ New: func() interface{} { return make([]byte, 8) }, } // handleShowBody は受け取った Body をそのまま表示します。 func handleShowBody(w http.ResponseWriter, r *http.Request){ buf := pool.Get().([]byte) _, err := io.CopyBuffer(w, r.Body, buf) if err != nil { panic(err) } defer r.Body.Close() pool.Put(buf) }
  21. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.ReaderFrom -

    io.ReaderFrom は、受け取った io.Reader をio.EOF またはエラーが返る までデータを読み取る - io.Copy は、dst である io.Writer が io.ReaderFrom を実装していた場合、 ReadFrom を用いてデータコピーしてくれる - データコピーの効率的な方法を知っていた場合、自分で io.ReaderFrom を実装すると効率化が見込める 21 https://golang.org/src/io/io.go#L386
  22. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. io.ReadFrom 実装例

    (net.TCPConn) - go1.11でのアップデート - net.TCPConn - “The net package now automatically uses the splice system call on Linux when copying data between TCP connections in TCPConn.ReadFrom, as called by io.Copy. The result is faster, more efficient TCP proxying.” - https://golang.org/doc/go1.11 - https://github.com/golang/go/issues/10948 - “splice(2) は、カーネルアドレス空間とユーザーアドレス空間との間のコピーを伴わずに、 2 つのファ イルディスクリプター間でデータの移動を行う。” - https://linuxjm.osdn.jp/html/LDP_man-pages/man2/splice.2.html - TCP プロキシ がより早く、効率的になる 22 func proxy(upstream, downstream net.TCPConn) { defer downstream.Close() defer upstream.Close() go io.Copy(upstream, downstream) io.Copy(downstream, upstream) // using splice(2) }
  23. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. その他手法 -

    Effective Streaming in Golang - https://speakerdeck.com/avvmoto/effective-streaming-in-golang - package io - io.ReaderAt のキャッシュ - io.Pipe - io.TeeReader - io.ReadFrom, io.WriteTo - io.WriteString - io.ReadFull - package io/ioutil - package strings - strings.Reader - strings.Builder - package testing/iotest 23
  24. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. まとめ -

    メモリのアロケートを少なくする実装をしよう - メモリのアロケートを減らす手法のご紹介 - io.Copy - io.CopyBuffer - sync.Pool - io.ReaderFrom - これら観点でのコードレビューを通じ、パフォーマンスの高い Web サービスを開発して います。 24
  25. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. 参考資料 -

    AndAppにおけるGCP活用事例 - https://www.slideshare.net/dena_tech/andappgcp-88366004 - Go 1.11 Release Notes - https://golang.org/doc/go1.11 - Goroutines: the dark side of the runtime - Roberto Clapis - https://talks.godoc.org/github.com/empijei/gotalks/gophercon.slide#1 - Effective Streaming in Golang - https://speakerdeck.com/avvmoto/effective-streaming-in-golang - The Go scheduler - https://morsmachine.dk/go-scheduler - io - https://golang.org/pkg/io/ - Some codes in this slide is licensed under a BSD License (https://golang.org/LICENSE). 25
  26. DeNA TechCon 2019 を 2/6(水)にやります! • 今年のテーマは「SHIFT UP」 • AI

    技術の事業への応用、クラウド活用、サービス開発、ものづくりを支える技術の 4ステージで、各事業の様々な技術転換や未来に向けた動きを紹介 お願い② 濃い技術を発信し続けてる公式アカウントをぜひフォローして下さい! お願い①「DeNA TechCon 2019」で検索し、ぜひ参加登録して下さい!