寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

GoのGraphQLライブラリのgqlgenのGetting startedをやってみた

f:id:chidakiyo:20191209014145j:plain

この記事は こちら をなぞってみた記事になります。
GraphQLをこれから始めたいという方におすすめ

gqlgenを利用してGraphQLサーバを実装する方法です。

  • todoのリストを返却する
  • 新しいtodoを作成する
  • 完了したtodoをマークする

ちなみにココからの手順はgo moduleを利用した方法になります。

プロジェクトの作成

プロジェクトのディレクトリを作成し、go moduleを初期化します

$ mkdir gqlgen-todos
$ cd gqlgen-todos
$ go mod init # 必要に応じてパスを指定

サーバを構築します

スキーマ定義します

qglgenはスキーマファーストのライブラリです。 コードを書く前に、GraphQLスキーマ定義言語を使用してAPIを記述します。 以下のようなschema.graphqlというファイルを作成し、initコマンドによってコードを生成します。

type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
}

type Query {
  todos: [Todo!]!
}

input NewTodo {
  text: String!
  userId: String!
}

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

プロジェクトスケルトンを作成する

以下のコマンドを実行します

$ go run github.com/99designs/gqlgen init

コマンドを実行すると

  • gqlgen.yml
  • generated.go
  • models_gen.go
  • resolver.go
  • server/server.go

のスケルトンが作成されます

データベースモデルの作成

Todo用に生成されたモデルは正しくありません。 ユーザが埋め込まれていますが、要求されたときのみ取得できるようにします。 そのため、代わりにtodo.goで新しいモデルを作成します。

package gqlgen_todos

type Todo struct {
    ID     string
    Text   string
    Done   bool
    UserID string
}

次に gqlgen.yml に以下を追加し、gqlgen を実行します。

models:
  Todo:
    model: github.com/[username]/gqlgen-todos.Todo

以下を実行し、再生成します

$ go run github.com/99designs/gqlgen -v

Note: -v フラグはgqlgenの動作を見るためにあります。

resolverを実装

ジェネレータが必要なresolverのインタフェースを生成しているので generated.go を確認します。

func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {}
    // ...
}

type Config struct {
    Resolvers  ResolverRoot
    // ...
}

type ResolverRoot interface {
    Mutation() MutationResolver
    Query() QueryResolver
    Todo() TodoResolver
}

type MutationResolver interface {
    CreateTodo(ctx context.Context, input NewTodo) (*Todo, error)
}
type QueryResolver interface {
    Todos(ctx context.Context) ([]Todo, error)
}
type TodoResolver interface {
    User(ctx context.Context, obj *Todo) (*User, error)
}

~~

resolverを実装する

既存のコードを修正することができないため、 resolver.go を削除した後、 gqlgen を実行することで強制的に再実行します。

$ rm resolver.go
$ go run github.com/99designs/gqlgen

resolver.go の実装されていないところを埋めていきます。

package gqlgen_todos

import (
    context "context"
    "fmt"
    "math/rand"
)

type Resolver struct {
    todos []*Todo
}

func (r *Resolver) Mutation() MutationResolver {
    return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
    return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) {
    todo := &Todo{
        Text:   input.Text,
        ID:     fmt.Sprintf("T%d", rand.Int()),
        UserID: input.UserID,
    }
    r.todos = append(r.todos, todo)
    return todo, nil
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) Todos(ctx context.Context) ([]*Todo, error) {
    return r.todos, nil
}

type todoResolver struct{ *Resolver }

func (r *todoResolver) User(ctx context.Context, obj *Todo) (*User, error) {
    return &User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}

Resolver, CreateTodo, Todos, User 内を実装しています。

動作するようになったのでサーバを起動してみましょう

$ go run server/server.go

起動が成功したら、ブラウザで http://localhost:8080 へアクセスし、
以下のクエリを試してみましょう

これがmutationなので、todoを作成するクエリです

mutation createTodo {
  createTodo(input:{text:"todo", userId:"1"}) {
    user {
      id
    }
    text
    done
  }
}

以下のクエリで作成したtodoを取得できます

query findTodos {
    todos {
      text
      done
      user {
        name
      }
    }
}

仕上げ

resolver.go のpackageとimportの間に以下の行を追加します。

//go:generate go run github.com/99designs/gqlgen

このマジックコメントは、コードを再生成したいときに実行するコマンドになります。
プロジェクト全体で再帰的に生成を実行するには、以下のコマンドを使用します。

$ go generate ./...

いい感じですね!

Goでmain関数経由でもベンチマークする

f:id:chidakiyo:20191206192054j:plain

Goはテストの機能としてベンチマークを測定する機能が標準で提供されています。
ただ、そのままだとテストコードを実行できる環境でしか実行ができなかったりするため、 main関数から実行したい場合もあるかと思います。

今回はmainからベンチマークを実行できる機能を調べてみました。
Goが標準に提供しており、Appengine上で実行するなどの目的で使えます。

通常のベンチマーク

通常のテストコードから実行するベンチマークは以下のような感じですね

package main

import (
  "testing"
    "time"
)

func Benchmark_Sample(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        // 遅い処理
        time.Sleep(10 * time.Millisecond)
    }
}
go test -bench . -benchmem

という感じに実行すると以下のような結果が表示されます。

Benchmark_Sample-12          100          11565214 ns/op               4 B/op          0 allocs/op

見方はこの記事では特に説明しません。

mainから実行するベンチマーク

mainから実行する場合には testing.Benchmark が利用できます。
コードを見たほうが速いと思うので以下のようになります

package main

import (
    "testing"
    "time"
  "fmt"
)

func main() {
    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
    for i := 0; i < b.N; i++ {
          // 遅い処理
          time.Sleep(10 * time.Millisecond)
      }
    })
    fmt.Printf("%s", result)
}

ほぼ見覚えのある形なので、特に違和感はないと思います。
テストとして実行するベンチマークと異なる点としては、 testing.Benchmark の戻り値の BenchmarkResult を利用してベンチマーク結果を取得するという点です。

BenchmarkResultString() 関数がよしなに実装されているので、適当にPrintしても以下のような感じで表示されます。

934     1275190 ns/op

その他の情報も細かく見たい人は、 BenchmarkResult の中の値を適切に表示すれば良いと思います。

まとめ

こんな感じで、テストコードとしてではなく、Goのmainから実行された処理の中でベンチマークが実行できるようになります。
おそらく嬉しい人は嬉しいのでは。

では、また。

goのプロファイラpprofを利用してパフォーマンスを計測する (GAE + ginもあり)

f:id:chidakiyo:20191031154054j:plain

pprof とは

Go言語のプロファイリングツール。
Web UIでフレームグラフや、関数の実行時間のグラフを見ることができる

通常のpprof利用方法

pprofの使い方として2つのパターンがあります。

  • runtime/pprof : ファイルにプロファイル情報を出力する
  • net/http/pprof : httpアクセスでプロファイル情報を取得する事ができる

ちょっと分かりづらいですね

runtime/pprof の場合には、go製のツールなどで利用することが想定されていると思います。
mainの実行時間が短く、mainのdeferでファイルを出力するような記述をします。

net/http/pprof の場合には、httpサーバとして動作することが想定されており、
pprofのimportを行うことでプロファイルを取得するためのハンドラが自動的に実装されます。

実装されたハンドラに対して外部からリクエストを行うことで、動作しているwebアプリケーションのプロファイル情報を見ることができます。

net/http/pprof の適用方法(net/http 利用時)

net/http 利用時には "net/http/pprof" をimportするだけでpprofを実行する準備ができます。

その場合のmain関数は以下のようになります

package main

import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof"
)

func main() {
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        writer.WriteHeader(http.StatusOK)
        fmt.Fprint(writer, "hello hogehoge.")
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}

net/http/pprof の適用方法(gin 利用時)

gin を利用している場合には net/http のときのような _ "net/http/pprof" をimportするだけでは動作しません。
ginで利用する場合には以下の2ステップが必要です。

  1. "github.com/gin-contrib/pprof" をimportする
  2. pprof.Register(route) を実行する

です。

main関数は以下のようになります。

package main

import (
    "github.com/gin-contrib/pprof"
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    route := gin.Default()
    pprof.Register(route)
    route.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "hello hogehoge.")})
    http.Handle("/", route)
    route.Run(":8080")
}

プロファイルの確認方法

プロファイルの確認方法には2つのケースがあります。

  1. pprofのパスからファイルを取得してからローカルで見る
  2. goのツールで直接パスを指定してweb UI上で見る

今回は 2 の方法をメインに確認してみます

登録されたハンドラ一覧

gin をデプロイした際に登録されたハンドラがログとして出力されたものが以下です

[GIN-debug] GET    /debug/pprof/             --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/cmdline      --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/profile      --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] POST   /debug/pprof/symbol       --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/symbol       --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/trace        --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/allocs       --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/block        --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/goroutine    --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/heap         --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/mutex        --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/threadcreate --> github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)

結構な数のハンドラが利用できるようですが、 /debug/pprof にアクセスすると以下のようなサマリが確認できます。

f:id:chidakiyo:20191031153812p:plain

ローカルからサーバのプロファイルを確認する

ローカルからサーバのプロファイルを確認する際には、閲覧するPCにgoと、graphvizがインストールされている必要があります

それぞれMacであれば

brew install go
brew install graphviz

のような形でインストールが可能です。

profileを確認する

まずはprofileを確認してみます

go tool pprof -http=":8888" http://localhost:8080/debug/pprof/profile

のようなコマンドをMac上で実行します。
コマンドの意味としては、 http://localhost:8080/debug/pprof/profile から情報を出力し、ローカルの 8888 ポートでプロファイラのWeb UIを起動するという意味合いになります。

コマンドを実行ししばらくすると、自動的に適当なブラウザが起動すると思います。

以下のようなグラフを確認することができます。

f:id:chidakiyo:20191031153836p:plain f:id:chidakiyo:20191031153846p:plain f:id:chidakiyo:20191031153855p:plain

heapを確認する

同様にheapを確認したい場合には

go tool pprof -http=":8888" http://localhost:8080/debug/pprof/heap

のように接続先のURLのパスをheapに変更します。

f:id:chidakiyo:20191031153936p:plain

appengineにデプロイする

pprof自体はappengineにデプロイする際でも特に実装上の変更はありません。
注意点としては、app.yamlでgoにマッピングをしているパスに /debug で始まるパスがgoに対してマッピングされているかを確認しましょう。

また、SourceのViewでheapを見た際、ローカルではgoのコードが表示されますが、
appengine上ではgoのコードが表示されないようです。

まとめ

プロファイラを適用した際のオーバヘッドはゼロではないのですが、かなりオーバヘッドが少なくなるように設計されているようなので、もし本番環境で問題などが起きた場合にはこのようなツールを使って一部トラフィックを流すなどして確認ができそうです。

今回は各種Viewの見方などは特に説明しませんでしたが、
パフォーマンスの問題を解決するには必須のツールだと思います。
便利に使って楽しいgoライフを!

参考

Go言語のプロファイリングツール、pprofのWeb UIがめちゃくちゃ便利なので紹介する - Eureka Engineering - Medium

Golangのpprofの使い方【基礎編】 - Carpe Diem

Bazelでbuild試してみる(Javaでgetting started)

f:id:chidakiyo:20191024145820j:plain

基本的な内容は Getting Started with Bazel をなぞってます。

Bazelとはなにか

Googleが開発したBuildツール。 もともとはGoogleが社内で使っているツール(の思想?)をベースに開発されているようですね。

makeを置き換えれるもの的な説明を見かけたが、もっと嬉しいことがありそう 実行時にディレクトリを汚染しない(サンドボックス内で実行する)や、並列ビルドが行えるなどがある

Bazelのインストール

ここ からインストール用の情報が確認できる。

今回はMac上で試すので、homebrew(tap)で入れてしまいます。

brew tap bazelbuild/tap
brew install bazelbuild/tap/bazel

一応インストール確認

bazel --version

bazel 1.0.0 と返ってくるのでリリースされたばかりの1.0.0がインストールされました(2019/10/18現在)

ちなみに こちら にあるようにIDEとの連携も可能なようです。 IntelliJを使って開発しているのでそのうち時間があるときに試してみようと思います。

また、Dockerコンテナを利用して試したい方には こちら のコンテナを利用できます。

BuildのTutorialの実行(Java

ひとまず こちらJavaチュートリアルをやってみようと思います

事前に必要なもの

  • bazelのインストール
  • JDKのインストール
    • JDK8をインストールするように書いてありますが、JDK13でやってみます

サンプルプロジェクトの取得

以下を実行し、サンプルプロジェクトを取得します

git clone https://github.com/bazelbuild/examples/

Bazelを利用してBuildする

workspaceをセットアップする

ワークスペースはプロジェクトのソースファイルとBazelの生成ファイルが置かれます。 Bazelの特別なファイルも置かれます。 WORKSPACEファイルはディレクトリ構造のrootに置く必要があります。 BUILDファイルも配置する必要がありますが、BUILDファイルを配置するディレクトリはパッケージとなります。

Bazel workspaceとして指定するディレクトリに 空の WORKSPACE というファイル を配置します。

Bazelがビルドする際の依存ファイルはすべて同一のワークスペース内に存在する必要があります (外部のワークスペースとリンクするという概念もあるようです)

java-tutorial パッケージをBuildしてみる

java-tutorial ディレクトリ配下のBUILDファイルを利用してBuildしてみます。

ディレクトリに移動してBuildコマンドを実行します。

cd java-tutorial
bazel build //:ProjectRunner

// はプロジェクトルートからの相対的なBUILDファイルの位置で、 ProjectRunner はBUILDファイル内で名付けられたターゲットです。

Buildが成功すると、ワークスペースのrootにあるbazel-binディレクトリにビルドの出力を配置します。

bazel-bin/ProjectRunner

のコマンドを実行することで動作確認が行なえます。

bazel-binのディレクトリをlsすると

$ ls bazel-bin

ProjectRunner               ProjectRunner.runfiles          libgreeter-native-header.jar
ProjectRunner.jar           ProjectRunner.runfiles_manifest     libgreeter.jar
ProjectRunner.jar-0.params      _javac                  libgreeter.jar-0.params
ProjectRunner.jar-1.params      external                libgreeter.jar-1.params
ProjectRunner.jar_manifest_proto    libgreeter-hjar.jar         libgreeter.jar_manifest_proto
ProjectRunner.jdeps         libgreeter-hjar.jdeps           libgreeter.jdeps

という感じで大量にファイルが生成されているようです。

依存グラフを参照する

BUILDファイルで定義した依存関係を表示します。

以下のコマンドを実行します

$ bazel query --noimplicit_deps "deps(//:ProjectRunner)" --output graph

チュートリアルでは --notool_deps オプションも付けていますが、それをつけるとうまく動きません。

出力結果はこんな感じ

digraph mygraph {
  node [shape=box];
  "//:ProjectRunner"
  "//:ProjectRunner" -> "//:greeter"
  "//:ProjectRunner" -> "//:src/main/java/com/example/ProjectRunner.java"
  "//:src/main/java/com/example/ProjectRunner.java"
  "//:greeter"
  "//:greeter" -> "//:src/main/java/com/example/Greeting.java"
  "//:src/main/java/com/example/Greeting.java"
}

出力結果のJSONGraphviz に食わせると以下のような画像化ができます。 これは便利。

f:id:chidakiyo:20191024145927p:plain

build targetを読む

java-tutorial/BUILD を以下のように複数に分割されています。

java_binary(
    name = "ProjectRunner",
    srcs = ["src/main/java/com/example/ProjectRunner.java"],
    main_class = "com.example.ProjectRunner",
    deps = [":greeter"],
)

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
)

このような構成にすると ProjectRunner を実行する前に greeter が実行されます。 depsで greeter が指定されています。

以下のコマンドでBuildを実行してみます

bazel build //:ProjectRunner

Buildが成功したら以下のコマンドで生成したバイナリのテストを行えます。

bazel-bin/ProjectRunner

ProjectRunner.javaを変更するとBazelはその変更されたファイルのみを再度コンパイルします。

まとめ

チュートリアルの流れ的には、単一のグラフになっているBUILDファイルを、Bazelを改善するという流れで複数に分割するという流れになってるのですが、実際にgithubから取得したexampleはすでに複数に分割されているようなので、流れが違っています。

とりあえずチュートリアル動かした、というレベルであまり良くわかってないっすw

今後やりたい

  • ScalaをBazelで
  • ElmをBazelで

discourse.elm-lang.org

databricks.com

OAuth/OpenID Connectを理解したければここを読め(メモ

f:id:chidakiyo:20191016153134j:plain

タイトルの通り。
OAuth、OpenID Connectを理解するための記事まとめ。

その他、良さそうな記事があればコメントで教えて下さい。

記事

一番分かりやすい OAuth の説明 - Qiita

一番分かりやすい OpenID Connect の説明 - Qiita

OpenID Connect 全フロー解説 - Qiita

IDトークンが分かれば OpenID Connect が分かる - Qiita

おまけ

OpenID Connect の JWT の署名を自力で検証してみると見えてきた公開鍵暗号の実装の話 - Qiita

今日 (2019/09/10) 現在のGAE/goをUPDATEする際の注意点、俺まとめ

f:id:chidakiyo:20190910182707j:plain

Appengine/goが過渡期とでもいいましょうか、
日々変化も目まぐるしく、いろいろ気をつけるべき点もありそうなので雑にまとめました。
ご参考まで。(2019/09/10現在)

GAE/go 1.9 は終わり

f:id:chidakiyo:20190910182745p:plain

画像のとおりです。

警告: Go 1.9 ランタイム バージョンは非推奨になりました。2019 年 10 月 1 日以降、新しいデプロイではこのバージョンを使用できなくなります。2019 年 10 月 1 日までに、Go 1.11 または Go 1.12 を使用するようにアプリケーションをアップグレードしてください。

とあるので、今年の10月1日以降はgo1.9でのデプロイは不可能になります。
すでにデプロイしているものは動くんじゃないか(未確認)と思いますが、速やかに移行しましょう。
go1.11 への移行は比較的容易だと思います。(appengineパッケージも利用できるので)

追記: すでにデプロイされているバージョンは引き続き利用できるようです。 [1]

f:id:chidakiyo:20190911134641p:plain

GAE/go 1.11 は気をつけろ

個人的に気になる点は3点、

  • ランタイムのパフォーマンスが 1.9 より少々悪い
  • 非appengineパッケージのライブラリのRPC部分にメモリリークの問題が有りそう
  • RPCのタイムアウトに気をつけて!

ランタイムのパフォーマンスに関しては、レスポンスの時間などは特に差を感じませんが、
CPUの利用量とメモリの使用量の増加があるように感じています。
Stackdriverのグラフで見た感じで2-3割り程度の差があるように見えます。(ざっくり

非appengineパッケージ(cloud.google.com/go/datastore など)を利用した際に今日現在でRPC部分にメモリリークする箇所がありそうです。
そのため、appengineパッケージをあえて利用することで問題を回避できます。(ですが次の問題があります)

appengineパッケージのRPCに関しては、2nd-genランタイムは多くのRPCを実行しているようで、比較的頻繁にタイムアウトするケースがあります。
service bridge HTTP failed などのログが出力される場合には internal/net.go の以下の画像の箇所のtimeoutを伸ばしてあげると良いようです。(現在修正し様子を見ようとしているところです)

f:id:chidakiyo:20190910183656p:plain

GAE/go 1.12 はまだよくわかりません

一応(?)1.12対応で実装しているものありますが、app.yamlで1.11の設定でデプロイしているので厳密にはよくわかっていません。(1.11と違いがないかもしれません。)

注意点としては、↑のgo1.11でも書いたメモリリークの問題は1.12ランタイムではappengineパッケージが利用不可になっているため、現状、回避策がないと思います。
大量にRPCを実行しているような場合にはインスタンスタイプを上げるなどして神に祈る必要がありそうです。

まとめ

1.9からの移行を皆様急いでいるかと思いますが、ライブラリ併せてのupdateは若干罠があるので(今日 2019/09/10 現在)、
一旦 GAE/go1.11 環境に appengine ライブラリを利用したままの移行が今日現在、私のオススメの予感です。

おまけ

[1] Feature Deprecations  |  App Engine Documentation  |  Google Cloud

fix · f81/appengine@15ec82a · GitHub

goのベンチマークメモ

f:id:chidakiyo:20190905164829p:plain

goのベンチマーク周りのメモです

2つのベンチマークの比較

godoc.org

pprof のツール

github.com

goのパフォーマンス周りの資料(observability)

bit.ly

GoのSSA最適化

qiita.com

おまけ

厳密なgofmt

github.com

go!