このエントリーをはてなブックマークに追加

eawsyのaws-lambda-goを使ってみる

AWS lambdaは昨今人気ですね。しかし、実行環境がpython、java、nodeだけです。ぼくはpythonも結構やってきましたが、近頃ずっとgoを使っており、goで実行できると良いなと思っていました。

ということで、昨日eawsy/aws-lambda-goというgoでlambdaを記述できるライブラリを知ったので、試してみました。 (以下eawsyと呼びます)

apexとの比較

lambdaをgoで書きたいなと言う気持ちは過去にはAWS Lambdaで効率的にgoバイナリを実行するという記事を書いてました。このときは、lambda_procというライブラリを使っていたのですが、その後いろいろでてきて、最近だとapexが有名です。

では、eawsyとapexとの違いはなんでしょうか。eawsyはPythonのC拡張を用いて実行します。

  • apex
    • lambdaはruntimeとしてnodeを呼び出し、さらにnodeがgoのバイナリをspawnで呼び出す。
    • 実行するのは普通のバイナリ。Linuxでそのまま実行できる
  • eawsy
    • goを-buildmode=c-sharedでshared libraryとしてbuild
    • lambdaはpythonを実行。pythonはgoをC拡張として読み込んで実行

つまり、apexでは二回のプロセス生成が入るのに比べて、eawsyでは一回だけとなります。その代わり、buildにcgo環境が必要になります。しかし、eawsyはDockerコンテナを提供していますので、cgo環境を用意する必要はありません。

使ってみる

maing.goはこんな感じで書きます。

package main

import (
      "encoding/json"
      "log"
      "time"

      "github.com/eawsy/aws-lambda-go/service/lambda/runtime"
)

func handle(evt json.RawMessage, ctx *runtime.Context) (interface{}, error) {
      ret := make(map[string]string)
      ret["now"] = time.Now().UTC().Format(time.RFC3339)

      return ret, nil
}

func init() {
      runtime.HandleFunc(handle)
}

func main() {}

mainは空で、initでhandleを登録します。json.RawMessageにはパラメータが入って渡ってきます。

retはinterface{}なので任意の型を返せます。これがJSONになって返答されます。

ベンチマーク

apexでほぼおなじコードを書いてみました。

package main

import (
     "encoding/json"
     "time"
     apex "github.com/apex/go-apex"
)

type message struct {
   Time string `json:"time"`
}

func main() {
     apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) {
             var m message
             if err := json.Unmarshal(event, &m); err != nil {
                     return nil, err
             }
             m.Time = time.Now().Format(time.RFC3339)
             return m, nil
     })
}

以下のように直接実行して、CloudWatchのログで実行時間を見てみます。

eawsy
$ aws lambda invoke --function-name preview-go output.txt
apex
$ apex invoke hello
ベンチマーク
回数 eawsy apex
一回目 16.38 ms 41.11 ms
二回目 0.48 ms 1.21 ms
三回目 0.50 ms 0.64 ms

lambdaは一回目はコンテナ(かどうか不明ですが)を起動するので時間がかかります。二回目以降は起動済みなので早いです。ということで大事な一回目なのですが、apexが40msecに比べてeawsyが16msecと、約半分になっています。めんどいので一回だけの結果しか載せませんが、数回やって基本的な傾向は同じでした。

一度起動が完了したら、1msec以下になり、これは両者とも同じです。



しかし、ぶっちゃけ、40msecが16msecになったところで一回だけです。これが重要なワークロードもあるでしょうが、あんまり意味があるとは思えません。そもそもlambdaの実行時間が不安定なので、数msec速くなったところで、それがどうしたよ、という感じです。

eawsyの利点はベンチマークではなく、ログ出力やruntimeの関数を呼べることです。(ということを、redditのFinally vanilla Go on AWS Lambda (no serverless!) startup <5msで作者が述べています。)

eawsyの利点

ログ出力

apexはnodeのruntimeを通している関係上、ログ出力はstdoutに出したものだけとなります。そのため、一手間必要になってしまっていました。それに対して、eawsyは標準のlogパッケージが使えます。

log.Printf("Log stream name: %s", ctx.LogStreamName)
log.Printf("Log group name: %s", ctx.LogGroupName)
log.Printf("Request ID: %s", ctx.AWSRequestID)
log.Printf("Mem. limits(MB): %d", ctx.MemoryLimitInMB)
log.Printf("RemainingTime: %d", ctx.RemainingTimeInMillis)

このように普通のlog出力と同じようにしておくと、CloudWatchのログに以下のように出ます。

13:19:55 START RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683 Version: $LATEST
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Log stream name: 2016/11/02/[$LATEST]1e58f3ef77894283988110ea452dc931
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Log group name: /aws/lambda/preview-go
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Request ID: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
13:19:55 2016-11-02T04:19:55.919Z     9bf7d852-a0b3-11e6-b64b-7dec169bb683    Mem. limits(MB): 128
13:19:55 END RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
13:19:55 REPORT RequestId: 9bf7d852-a0b3-11e6-b64b-7dec169bb683
Duration: 16.38 ms
Billed Duration: 100 ms Memory Size: 128 MB   Max Memory Used: 8 MB

Fatalfだとエラーに、Panicだとstacktraceも表示されます。

エラー処理

handleの返り値にerrorを入れると

return ret, fmt.Errorf("Oops")

以下のようなログがCloudWatchに書き出されます。

Oops: error
Traceback (most recent call last):
File "/var/runtime/awslambda/bootstrap.py", line 204, in handle_event_request
result = request_handler(json_input, context)
error: Oops

runtimeの関数を呼べる

apexのアプローチでは、あくまで実行はgoのため、nodeに提供されている情報を得ることは出来ませんでした。しかし、 eawsyではruntimeに提供されている情報をgoから得ることが出来ます。

上のログ出力の例にctx.RemainingTimeInMillisという残り時間を得る箇所があります。これがpython runtimeに提供されている情報を利用できている証拠です。

まとめ

PythonからC拡張経由でgoを呼び出すというアプローチが面白かったので使ってみました。

といっても、ベンチマーク的には(元から速いぶん)決定的な違いではなく、runtimeの関数を呼べたり標準logパッケージが使えるのはちょっとうれしいかもしれませんが、プログラミングモデルとしてもそこまで大きな差はありません。

apexはgolangに限らないことと、apex deployというように管理ツールとしても優れていることから、現時点ではapexの方に軍配が上がると思います。

おまけ

proxy.cがruntimeの実体ですね。handleでgoを呼んでます。

普通にC拡張なので、rustやC++などでも同じアプローチが出来ます。rustで書いてみるのも面白いかもしれません。