うたもく

i am a student.

AdTech Challengeで初めてGinに触れて感動した話。

CyberAgentさんのAdTech Challengeに参加してきました。
その時にGinを用いて少し感動した話です。
そもそも筆者はこのハッカソンでGoを初めてちょっと本格的に触れました。
www.cyberagent.co.jp

ハッカソンの内容

  • 2000QPSで位置情報が投げられる(タイムアウト: 100ms)のでどうにかする
  • 学生3人 + 社員1人のチーム開発
  • うちのチームはGin + MySQL + Kubernetsで構築

何が気になったか

うちのチームは「まぁまずは2000QPSを体感するか」ということで
まず、手始めに受け取ったデータをいい感じにしてDBに打ち込みだけのものを作ってみました。

僕の予想では

  1. DBに触れ過ぎで死ぬ
  2. タイムアウトしてリクエスト中身を見れない

かなと思いながら見守っていました。

実際にやってみると何と2の方は問題がありませんでした。
「え???」
ってなりました。
実際にリクエストの処理をし始めるまでに100ms経過していました。
その場では開発時間が短いのもあって
「Ginがいい感じにしてるのだろう。以上!」
って感じで1の方のボトルネックの解決をしていきました。
2の方が気になった点だったので終わった後に少しだけ調べてみました

Ginが何をしていたのか

結論はGinがいい感じにリクエスト等をPoolをしていてくれたおかげでゆっくり処理をしてもタイムアウトした後でもリクエストの中身を処理していました。
今回の場合だと大量のリクエストをDBに格納するのみでした。
そのため、レスポンスはなくても良かったので問題ありませんでした。
Ginは軽いフレームワークだと認識していたのでそこまで手厚くしてくれると思っていなかったので少しびっくりしました。
間違いがあったらごめんなさい。

結論はそういうことだったんですが、とりあえず原因探しでGinのコードを追ってみました。

Engineをみてみるとpool...
あっそれっぽいのがありました。

https://github.com/gin-gonic/gin/blob/v1.2/gin.go#L46

type Engine struct {
    RouterGroup
    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    pool             sync.Pool
    trees            methodTrees
               ・
               ・
               ・

sync.Poolとはなんぞい。

sync - The Go Programming Language

いい感じにPoolしてくれそうだ。 それでそれで何をPoolしているのだ?

https://github.com/gin-gonic/gin/blob/v1.2/gin.go#L108

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{"{{", "}}"},
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

あっpool.Newしてる。。。

ContextをPoolしてるみたいだ。hmhm。
https://github.com/gin-gonic/gin/blob/v1.2/gin.go#L141

func (engine *Engine) allocateContext() *Context {
    return &Context{engine: engine}
}

あっpool.Get()してる。あっServeHTTPだ。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

調べた感じこんな感じでした。
いや、さすが並列処理に強そうなGo言語なだけあってサクッとできてますね。
しゅ、シュゴイ。
Goの力の片鱗を体感しました。

もともとchannelでやってたのを変えてたみたいでした。 github.com

ハッカソンの結果

審査員特別賞をいただきました。
ボトルネックを的確に判断して、最小限の構成でいったのが評価されたみたいです。
景品にギンピカAbemaくんをいただきました。
f:id:utam0k:20171024000429j:plain