Ruby support for Lambda
LambdaでついにRuby Runtimeがサポートされました。
現在対応しているバージョンは2.5です。
とにかく触ってみることが大事ということで、早速動かしてみました。
- Hello world
- DynamoDB連携
- pythonとnodeとの速度比較
Lambda関数を作成
もうGAなので選択肢にruby2.5が表示されています。(嬉しい...!)
最初のコードはこんな感じですね。
require 'json'
def lambda_handler(event:, context:)
# TODO implement
{ statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end
何はともあれとりあえずこのまま実行します。 Hello from Lambda!
色々遊んで見る
標準ライブラリ以外を利用したい場合には、NodeやPythonなどと同じように、ローカルでrubyアプリを作成してそれをまとめてsamでアップロードする形になります。 AWSブログのチュートリアルを使って解説していきます。
DynamoDBとの連携
1. 下準備
まずはローカルにアプリディレクトリを作ります。
$ mkdir lambda_ruby
$ cd lambda_ruby
2. Gemfileを作成する
中身にaws用のライブラリを記述します。他のライブラリを利用したい場合は合わせて書くと良いでしょう。
source 'https://rubygems.org'
gem 'aws-record', '~> 2'
bundle installも忘れずに実行してください。ローカルのrubyにインストールしたい場合はbundle install
だけでもいいのですが、今回はライブラリも含めてLambda側にdeployするので、--deployment
オプションを付けて実行しておきましょう
$ bundle install --deployment
3. Lambdaの実行スクリプトを作成する
hello_ruby_record.rb
を作成します。 put_item
を今回のlambdaのhandlerメソッドとしています。(後述のtemplate.yamlの設定でhandlerの関数は自由に指定できます)
require 'aws-record'
class DemoTable
include Aws::Record
set_table_name ENV[‘DDB_TABLE’]
string_attr :id, hash_key: true
string_attr :body
end
def put_item(event:,context:)
body = event["body"]
item = DemoTable.new(id: SecureRandom.uuid, body: body)
item.save! # raise an exception if save fails
item.to_h
end
4. template.yamlの作成
lambda側にアップロードするための設定ファイルを作成します。Lambda側へのアップロードは、AWS SAM を利用します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'sample ruby application'
Resources:
HelloRubyRecordFunction:
Type: AWS::Serverless::Function
Properties:
Handler: hello_ruby_record.put_item
Runtime: ruby2.5
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref RubyExampleDDBTable
Environment:
Variables:
DDB_TABLE: !Ref RubyExampleDDBTable
RubyExampleDDBTable:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey:
Name: id
Type: String
Outputs:
HelloRubyRecordFunction:
Description: Hello Ruby Record Lambda Function ARN
Value:
Fn::GetAtt:
- HelloRubyRecordFunction
- Arn
今回はLambda関数とDynamoDBテーブルを利用するので、Serverless::FunctionとServerless::SimpleTableをリソースとして定義します。
このテンプレートファイルの詳しい解説はチュートリアルを見てください。
5. 一旦フォルダ構成を確認
今までの作業でフォルダ内はこの様になっているはずです。
$ tree -L 2 -a
.
├── .bundle
│ └── config
├── Gemfile
├── Gemfile.lock
├── hello_ruby_record.rb
└── vendor
└── bundle
あとはSAMを使ってdeployを実施するだけです。
6. SAMパッケージの作成
SAMが入っていない場合はインストールしておきます。
$ pip install aws-sam-cli
先程作成したテンプレートファイルを使ってsamパッケージを作成します。
※S3バケットは予め作成しておいてください。
sam package --template-file template.yaml \
--output-template-file packaged-template.yaml \
--s3-bucket <bucketname>
フォルダにpackaged-template.yaml
が作成されたら成功です。
7. パッケージのアップロード
SAMのdeployコマンドを利用してパッケージをアップロードします。
stack-name
はlambdaの関数名になりますので、好きな名前を入力してください。
$ sam deploy --template-file packaged-template.yaml \
--stack-name helloRubyDynamo \
--capabilities CAPABILITY_IAM
Waiting for changeset to be created...
Waiting for stack create/update to complete
Successfully created/updated stack - helloRubyRecord
注意点
CLIに紐付いているIAMにCloudFormationの権限を付けておかないとエラーになります。
An error occurred (AccessDenied) when calling the CreateChangeSet operation:
User: arn:aws:iam::*** is not authorized to perform:
cloudformation:CreateChangeSet on resource:
arn:aws:cloudformation:ap-northeast-1:***:stack/helloRubyDynamo/*
8. テスト
CloudFormationでデプロイしたので、DynamoDBのセットアップも含めてデプロイが完了しています。下記のようなテストイベントを送ってテストしてみましょう
{"body": "hello lambda"}
こんな感じです。途中からSAMの解説になってしまいましたが、Rubyのライブラリを入れたいならSAMを導入しておくとその後の手間が大いに削減されるので、設定しておいて損はないと思います。
処理時間はどうなのか計測
nodeやpythonと違い、新しく発表されたCustom Runtimeをバックエンドに利用しているとのことなので、ruby Runtimeの読み込み時間なども含めて処理時間ってどうなんだろう、と気になったので、計測しました。
初回起動
言語 | バージョン | 実行時間 |
---|---|---|
ruby | 2.5 | 23.31 ms |
node.js | 8.10 | 12.28 ms |
python | 3.7 | 4.62 ms |
なるほど。。。Custom Runtimeの読み込み分が乗っかってる感じでしょうかね。。。
2回目以降
それぞれ100回叩いて平均値を計測しました。公平を期すためにHello worldのみのコードで100回ループで起動時間を計測します。
require 'json'
def lambda_handler(event:, context:)
# TODO implement
{ statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
では100回回します。下記のようなスクリプトを言語ごとに回しました。
require 'json'
require 'base64'
duration_total_ruby = 0
100.times do |i|
result = `aws lambda invoke --invocation-type RequestResponse --function-name HelloworldRuby --log-type Tail --payload \'{}\' outputfile.txt`
obj = JSON.parse(result)
duration = Base64.decode64(obj["LogResult"])[/Duration: (.*) ms\tB/,1]
p "#{i}, #{duration} ms."
duration_total_ruby += duration.to_f
end
p "Invoke ruby duration total: #{duration_total_ruby} ms"
p "Invoke ruby duration agverage: #{duration_total_ruby / 100} ms"
"Invoke ruby duration total: 704.3100000000002 ms"
"Invoke ruby duration agverage: 7.043100000000002 ms"
"Invoke node duration total: 137.77000000000004 ms"
"Invoke node duration agverage: 1.3777000000000004 ms"
"Invoke python duration total: 638.43 ms"
"Invoke python duration agverage: 6.3843 ms"
言語 | バージョン | 総実行時間 | 平均実行時間 |
---|---|---|---|
ruby | 2.5 | 704.31 ms | 7.0431 ms |
node.js | 8.10 | 137.77 ms | 1.3777 ms |
python | 3.7 | 638.43 ms | 6.3843 ms |
なんとnode.jsが一番 早いという結果に!(当然rubyは一番遅い)
終わりに
個人的にも馴染みのあるRubyがサポートされたのは非常に嬉しかったですね。
これからどんどん使っていきたいと思います。