Edited at

【AWS re:Invent2018】AWS Lambda で Rubyを使う(Hello worldからDynamoDBとの連携、速度計測まで)


Ruby support for Lambda

LambdaでついにRuby Runtimeがサポートされました。

UNADJUSTEDNONRAW_thumb_34b8.jpg

現在対応しているバージョンは2.5です。

とにかく触ってみることが大事ということで、早速動かしてみました。


  • Hello world

  • DynamoDB連携

  • pythonとnodeとの速度比較


Lambda関数を作成

もうGAなので選択肢にruby2.5が表示されています。(嬉しい...!)

screenshot1.png

作成。

スクリーンショット 2018-11-30 4.37.47.png

最初のコードはこんな感じですね。


lambda_function


require 'json'

def lambda_handler(event:, context:)
# TODO implement
{ statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end


何はともあれとりあえずこのまま実行します。 Hello from Lambda!

screenshot2.png


色々遊んで見る

標準ライブラリ以外を利用したい場合には、NodeやPythonなどと同じように、ローカルでrubyアプリを作成してそれをまとめてsamでアップロードする形になります。 AWSブログのチュートリアルを使って解説していきます。


DynamoDBとの連携


1. 下準備

まずはローカルにアプリディレクトリを作ります。


bash

$ mkdir lambda_ruby

$ cd lambda_ruby


2. Gemfileを作成する

中身にaws用のライブラリを記述します。他のライブラリを利用したい場合は合わせて書くと良いでしょう。


Gemfile

source 'https://rubygems.org'

gem 'aws-record', '~> 2'

bundle installも忘れずに実行してください。ローカルのrubyにインストールしたい場合はbundle installだけでもいいのですが、今回はライブラリも含めてLambda側にdeployするので、--deploymentオプションを付けて実行しておきましょう


bash

$ bundle install --deployment



3. Lambdaの実行スクリプトを作成する

hello_ruby_record.rbを作成します。 put_item を今回のlambdaのhandlerメソッドとしています。(後述のtemplate.yamlの設定でhandlerの関数は自由に指定できます)


hello_ruby_record.rb

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 を利用します。


template.yaml

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"}


成功しました。

screenshot3.png

DynamoDBにも同じ値が入っていますね。

スクリーンショット 2018-11-30 8.49.46.png

こんな感じです。途中から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回ループで起動時間を計測します。


lambda_handler.rb

require 'json'

def lambda_handler(event:, context:)
# TODO implement
{ statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end



lambda_handler.js

exports.handler = async (event) => {

// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};


lambda_handler.py

import json

def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}


では100回回します。下記のようなスクリプトを言語ごとに回しました。


loop.rb

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"



result

"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がサポートされたのは非常に嬉しかったですね。

これからどんどん使っていきたいと思います。