Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Go1.8 for Google App Engine

1,905 views

Published on

GDG DevFest Tokyo 2017で発表した資料です。
https://tokyo.gdgjapan.org

Published in: Technology
  • Nice !! Download 100 % Free Ebooks, PPts, Study Notes, Novels, etc @ https://www.ThesisScientist.com
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Go1.8 for Google App Engine

  1. 1. The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. Go1.8 for Google App Engine @GDG DevFest Tokyo 2017 2017年10月09日(月) ハッシュタグ: #DevFest17 #DevFest_room1
  2. 2. 自己紹介 上田拓也 @tenntenn 2 所属 コミュニティ活動 & Go ビギナー Go Conference
  3. 3. ソウゾウ エキスパートチーム 技術をアウトプットするところに技術は集まる ■ エキスパートチームとは? ● 50%以上の時間を技術コミュニティへの貢献に充てる ■ エキスパートチームの役割 ● 社内に新しい技術を取り取り込む ● 社外のコミュニティなどを通じて社会へ還元する ■ エキスパートチームの活動 ● カンファレンス・勉強会の開催/運営 ● 対外的な講演活動 ● 執筆、雑誌への寄稿、インタビュー ● 社内外での担当技術の普及推進 3 @tenntenn 担当:Go・GCP @mhidaka 担当:Android メンバー
  4. 4. アジェンダ ● Google App Engine ● Go1.8 for Google App Engine ● Go1.8対応 ● まとめ 4
  5. 5. Google App Engine 5
  6. 6. Google App Engineとは ■ Google が提供するPaaS ● 高いスケーラビリティ ● メンテナンスコストが低い ■ スタンダード環境とフレキシブル環境 ● スタンダード環境 ○ 従来からあるGAEの環境、SEとも ○ Go、Java8、Python 2.7、PHPが使える ○ Goはインスタンスの起動が恐ろしく早い ● フレキシブル環境 ○ 旧MVMs、FEとも ○ Go、Java8、Python 2.7/3.4、Node.js、Ruby 6
  7. 7. メルカリ カウルでも採用 7 https://goo.gl/ZzHDoW
  8. 8. Hello, world! 8 package myapp import "fmt" import "net/http" func init() { http.HandleFunc("/", handler) } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, world!") } main関数ではなくinit関数 net/httpパッケージを使って HTTPリクエストを処理 HTTPハンドラ
  9. 9. app.yaml 9 runtime: go api_version: go1 handlers: - url: /.* script: _go_app ■ アプリケーションの設定ファイル すべてのリクエストをGoで処理 ルーティングやログインの有無などを設定することができる
  10. 10. Goの各バージョンの対応状況 ■ Go1.6 ● 現在のデフォルトのバージョン ● 現在のGoの最新は1.9であるためかなり離れている ■ Go1.8(ベータ) ● ベータとして対応中 ● 概ねバグはなくなってきている ● Cloud Datastoreなどのライブラリ類がまだ未対応 ● Goのひとつ前のバージョン 10 Goのバージョンは半年に1回アップデートされる
  11. 11. Go1.8 for Google App Engine 11
  12. 12. Go1.6 → 1.8で新しくなったこと 12 ■ Go1.6 → Go1.7 ● contextパッケージが標準になった ● サブテストができるようになった ■ Go1.7 → Go1.8 ● sort.Sliceが導入され、型を作る必要がなくなった ● pluginパッケージが導入
  13. 13. コンテキスト ■ Contextインタフェース ● Google Cloud Platformの各APIを使うために必要 ● Goの標準としては主にゴルーチンのキャンセル処理などに使用 13 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
  14. 14. コンテキストとキャンセル処理 ■ コンテキストの主な目的はキャンセル処理 ● ゴルーチンをまたいだ処理のキャンセルに使う 14 bc := context.Background() t := 50*time.Millisecond ctx, cancel := context.WithTimeout(bc, tc) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) }
  15. 15. コンテキストに値を持たせる ■ WithValueで値を持たせる ● 例:キャッシュを充てない 15 type withoutCacheKey struct{} func WithoutCache(c context.Context) context.Context { if IsIgnoredCache(c) { return c } return context.WithValue(c, withoutCacheKey{}, struct{}{}) } func IsIgnoredCache(c context.Context) bool { return c.Value(withoutCacheKey{}) != nil }
  16. 16. Google App Engineとコンテキスト ■ 例:Datastoreからデータの取得 16 type Person struct { ID int64 `datastore:"-"` Name string `datastore:"name"` Age int `datastore:"age"` } const k = "Person" // Kind名 key := datastore.NewKey(ctx, k,"",100, nil) var p Person if err := datastore.Get(ctx, key, &p); err != nil { /*エラー処理*/} p.ID = key.IntID() 第1引数でコンテキストを指定
  17. 17. コンテキストの歴史 ■ appengine.Context ● 初代コンテキスト ● Goの標準とは関係ない ■ golang.org/x/net/context.Context ● Go1.6までのGo準標準のコンテキスト ● Google App Engine(Go1.6の場合)で使用されている ○ google.golang.org/appengine ■ フレキシブル環境対応がされたラッパー ■ context.Context ● Go1.7からのGo標準のコンテキスト ● 現在Google App EngineはGo1.6なので使えない ● ベータ版としてであればGo1.8が使える 17 時代の流れ
  18. 18. サブテスト ■ 子テストを実行するしくみ ● Go1.7から導入された func TestAB(t *testing.T) { t.Run("A", func(t *testing.T) { t.Error("error") }) t.Run("B", func(t *testing.T) { t.Error("error") }) } go test -v sample -run TestAB/A === RUN TestAB === RUN TestAB/A --- FAIL: TestAB (0.00s) --- FAIL: TestAB/A (0.00s) sample_test.go:10: error FAIL exit status 1 FAIL sample 0.007s サブテストを指定して実行 18
  19. 19. テーブル駆動テスト ■ テスト対象のデータを羅列してテストする var flagtests = []struct { in string out string }{ {"%a", "[%a]"}, {"%-a", "[%-a]"}, {"%+a", "[%+a]"}, {"%#a", "[%#a]"}, {"% a", "[% a]"}, } func TestFlagParser(t *testing.T) { var flagprinter flagPrinter for _, tt := range flagtests { s := Sprintf(tt.in, &flagprinter) if s != tt.out { t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out) } } } 19
  20. 20. サブテストとテーブル駆動テスト func TestIsOdd(t *testing.T) { cases := []*struct {name string; input int; expected bool}{ {name: "+odd", input: 5, expected: true}, {name: "+even", input: 6, expected: false}, {name: "-odd", input: -5, expected: true}, {name: "-even", input: -6, expected: false}, {name: "zero", input: 0, expected: false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if actual := IsOdd(c.input); c.expected != actual { t.Errorf( "want IsOdd(%d) = %v, got %v", c.input, c.expected, actual)}}) } } 20
  21. 21. サブテストとテーブル駆動テスト ■ 利点 ● 落ちた箇所が分かりやすい ○ テストケースの名前が表示される ● 特定のサブテストだけ実行できる ○ テストケースが大量な場合分かりやすい 21
  22. 22. Go1.7までのソート 22 type byAge []Person func (a byAge) Less(i, j int) bool { return a[i].Age < a[j].Age } func (a byAge) Len() int { return len(a) } func (a byAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func main() { people := []Person{ {"Gopher", 7}, {"Alice", 55}, {"Vera", 24} } sort.Sort(byAge(people)) } ■ sort.Interfaceを実装する必要があった
  23. 23. sort.Sliceの導入 23 func main() { people := []Person{{"Gopher", 7}, {"Alice", 55}, {"Vera", 24}} sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age }) } ■ 型を作る必要がなくなった
  24. 24. pluginパッケージ ■ プラグイン機構の導入 ● プラグインモードとしてビルドしたものを動的に読み込める ● cgoを使用するため、Google App Engineでは使えない ○ ビルドはできるがエラーが発生し何もおこならない 24 $ go build -buildmode=plugin
  25. 25. Go1.8対応 25
  26. 26. SDKのアップデート 26 ■ gcloudコマンドの場合 ● components updateでアップデートを掛ける ■ Google App Engine SDKを落としている場合 ● ダウンロードページから再度落としてくる $ gcloud components update
  27. 27. Go1.8に対応するには ■ 設定の変更 ● app.yamlを修正 ■ コードの修正 ● golang.org/x/net/contextから標準のcontextに変更 ● ライブラリが未対応の部分を修正 ○ 型に互換のない場所 27
  28. 28. app.yamlの書き換え ■ api_versionをgo1.8に書き換える 28 runtime: go api_version: go1.8 handlers: - url: /.* script: _go_app 使用されるgoappコマンドやGOROOTがスイッチされる
  29. 29. コンテキストの互換 29 ■ インタフェースはメソッドが同じであれば互換がある ● 標準のcontext.Contextとx/net/context.Contextは 同じメソッドリスト ● インタフェース変数の代入はメソッドリストの一致のみの判定 ○ ダックタイピング ● 型が別でも代入できる type A interface { String() string } type B interface { String() string } var _ A = B(nil) 例
  30. 30. GAE/Go1.6までコンテキストの管理法 30 ■ リクエストとコンテキストをマップで管理 var ctxs = make(map[*http.Request]context) func NewContext(r *http.Request) context { ctxsMu.Lock() defer ctxsMu.Unlock() c := ctxs[r] if c == nil {/* 省略 */} return c } goroot/src/appengine_internal/api_dev.go
  31. 31. Request.WithContext問題 ■ Request.WithContextでリクエストが書き換えられる ● リクエストが書き換えられると対応するコンテキストが取れない ○ キーにヒットしなくなる ● gorilla/muxやlion v2など問題が発生する 31 func (r *Request) WithContext(ctx context.Context) *Request { if ctx == nil { panic("nil context") } r2 := new(Request) *r2 = *r r2.ctx = ctx if r.URL != nil { /* 省略 */ } return r2 } Request.WithContextの実装 フィールドをコピーしている!
  32. 32. GAE/Go1.8のコンテキストの管理法 32 ■ Contextの中にApp Engineのコンテキストを入れる ● リクエストをキーに使っていない ● Request.WithContextされてもコンテキストはコピーされる const ContextKey keyType = "App Engine context" func NewContext(req *http.Request) context { c := req.Context().Value(ContextKey) if c == nil { /* 省略 */} return c.(context) } goroot-1.8/src/appengine_internal/api_go18.go
  33. 33. コンテキストの置き換え 33 ■ go tool fixコマンドを使う ● Goのバージョン間のマイグレーションを行うコマンド ● Go1.0以前に活躍 ● x/net/context.Contextからcontext.Contextに置き換える ● goappコマンドにはない $ go tool fix -force=context main.go import文をsedしても対して変わらない
  34. 34. 型の不一致の問題 ■ 型が合わないケースが存在する ● コンテキストを含むコンポジット型 ○ マップやスライス ● コンテキストを引数や戻り値に取るクロージャ 34 err := datastore.RunInTransaction(ctx, func(ctx context.Context) error { key := datastore.NewKey(ctx, "Counter", "singleton", 0, nil) var cnt struct { Count `datastore:"count"` } err := datastore.Get(ctx, key, &cnt) if err != nil && err != datastore.ErrNoSuchEntity { return err} cnt.Count++ if _, err := datastore.Put(ctx, key, &cnt); err != nil { return err } }, nil) datastore.RunInTransactionの例
  35. 35. 型の不一致の解決 ■ ラッパーを作る ● ライブラリに合わせてクロージャの型を変換する 35 import xcontext "golang.org/x/net/context" func RunInTransaction(c context.Context, f func(c context.Context) error) error { return datastore.RunInTransaction(c, func(xc xcontext.Context) error { return f(xc) }) } datastore.RunInTransactionのラッパー
  36. 36. Go1.9にも期待 ■ 新機能 ● 型エイリアス ● t.Helper() ○ テストのヘルパー関数がより便利に ● sync.Map型 ● math/bitsパッケージ 36 Go1.8を乗り越えればきっと!
  37. 37. まとめ ■ Go1.8にはベータとして対応 ● 大きな問題は解決済み ● サブテストやsort.Sliceが使えるのは大きい ● Goはバージョンアップごとにパフォーマンスが改善している ■ 本番利用にはまだ早そう ● コンテキストの問題が残っている ● 正式版に向けてライブラリ等の改修がされる? ● メルカリ カウルではChat Opsから導入中 ● Go1.9はよ 37
  38. 38. Thank you! twitter: @tenntenn Qiita: tenntenn connpass: tenntenn 38

×