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で書いてみるのも面白いかもしれません。
Comments
comments powered by Disqus