追記
追記 (3) aws-sdk 以外の gem だけをロードする場合ってどうなのか
検証コード
以下のように furikake で利用する aws-sdk を除いて, 依存している gem を require してみます.
require 'benchmark' def run(event:, context:) Benchmark.bm(10) do |r| r.report 'require gems' do require 'thor' require 'markdown-tables' require 'backlog_kit' end end end run(event: {}, context: {})
結果
# On macOS (Ruby 2.5.1) $ bundle exec ruby lambda.rb user system total real require gems 0.091279 0.049211 0.140490 ( 0.387624) $ bundle exec ruby lambda.rb user system total real require gems 0.091667 0.041068 0.132735 ( 0.133426) $ bundle exec ruby lambda.rb user system total real require gems 0.087537 0.043253 0.130790 ( 0.132725) # On Docker Container (lambci/lambda:build-ruby2.5) bash-4.2# ruby lambda.rb user system total real require gems 0.060000 0.310000 0.370000 ( 1.752973) bash-4.2# ruby lambda.rb user system total real require gems 0.060000 0.190000 0.250000 ( 1.729625) bash-4.2# ruby lambda.rb user system total real require gems 0.140000 0.120000 0.260000 ( 1.668639) # On Lambda user system total real require gems 0.000016 0.000006 0.000022 ( 0.000020) user system total real require gems 0.000021 0.000008 0.000029 ( 0.000026) user system total real require gems 0.000030 0.000011 0.000041 ( 0.000037)
Lambda での処理時間が圧倒的に高速で優秀だと思います. 全く aws-sdk に依存しない gem を利用する場合にはロードに要する時間はとても短いことを確認しました.
見解
これらの結果から, 以下のようなことが言えそうです.
- サードパーティ製の gem において, aws-sdk に依存している gem を Lambda で利用する場合, 関数の初回起動時だけ gem のロードに時間が掛かる可能性がる (furikake はまさにそうで, furikake 自身が aws-sdk に依存している)
追記 (2) Ruby ランタイムに組み込まれている gem の一覧
以下, Ruby ランタイムに事前に組み込まれている gem の一覧です. aws-sdk は予め組み込まれていました (そりゃそうだ...).
codestar (1.7.0) aws-sdk-cognitoidentity (1.5.0) aws-sdk-cognitoidentityprovider (1.10.0) aws-sdk-cognitosync (1.5.0) aws-sdk-comprehend (1.8.0) aws-sdk-configservice (1.19.0) aws-sdk-connect (1.8.0) aws-sdk-core (3.37.0) aws-sdk-costandusagereportservice (1.5.0) aws-sdk-costexplorer (1.12.0) aws-sdk-databasemigrationservice (1.13.0) aws-sdk-datapipeline (1.5.0) aws-sdk-dax (1.7.0) aws-sdk-devicefarm (1.12.0) aws-sdk-directconnect (1.8.0) aws-sdk-directoryservice (1.10.0) aws-sdk-dlm (1.6.0) aws-sdk-dynamodb (1.16.0) aws-sdk-dynamodbstreams (1.5.0) aws-sdk-ec2 (1.56.0) aws-sdk-ecr (1.8.0) aws-sdk-ecs (1.22.0) aws-sdk-efs (1.6.0) aws-sdk-eks (1.7.0) aws-sdk-elasticache (1.9.0) aws-sdk-elasticbeanstalk (1.13.0) aws-sdk-elasticloadbalancing (1.7.0) aws-sdk-elasticloadbalancingv2 (1.16.0) aws-sdk-elasticsearchservice (1.14.0) aws-sdk-elastictranscoder (1.6.0) aws-sdk-emr (1.7.0) aws-sdk-firehose (1.8.0) aws-sdk-fms (1.6.0) aws-sdk-gamelift (1.9.0) aws-sdk-glacier (1.13.0) aws-sdk-glue (1.20.0) aws-sdk-greengrass (1.10.0) aws-sdk-guardduty (1.10.0) aws-sdk-health (1.7.0) aws-sdk-iam (1.10.0) aws-sdk-importexport (1.5.0) aws-sdk-inspector (1.11.0) aws-sdk-iot (1.18.0) aws-sdk-iot1clickdevicesservice (1.5.0) aws-sdk-iot1clickprojects (1.5.0) aws-sdk-iotanalytics (1.9.0) aws-sdk-iotdataplane (1.5.0) aws-sdk-iotjobsdataplane (1.6.0) aws-sdk-kinesis (1.8.0) aws-sdk-kinesisanalytics (1.7.0) aws-sdk-kinesisvideo (1.6.0) aws-sdk-kinesisvideoarchivedmedia (1.6.0) aws-sdk-kinesisvideomedia (1.5.0) aws-sdk-kms (1.11.0) aws-sdk-lambda (1.13.0) aws-sdk-lambdapreview (1.5.0) aws-sdk-lex (1.8.0) aws-sdk-lexmodelbuildingservice (1.11.0) aws-sdk-lightsail (1.10.0) aws-sdk-machinelearning (1.5.0) aws-sdk-macie (1.5.0) aws-sdk-marketplacecommerceanalytics (1.5.0) aws-sdk-marketplaceentitlementservice (1.5.0) aws-sdk-marketplacemetering (1.5.0) aws-sdk-mediaconvert (1.16.0) aws-sdk-medialive (1.15.0) aws-sdk-mediapackage (1.9.0) aws-sdk-mediastore (1.6.0) aws-sdk-mediastoredata (1.7.0) aws-sdk-mediatailor (1.6.0) aws-sdk-migrationhub (1.7.0) aws-sdk-mobile (1.5.0) aws-sdk-mq (1.7.0) aws-sdk-mturk (1.8.0) aws-sdk-neptune (1.6.0) aws-sdk-opsworks (1.8.0) aws-sdk-opsworkscm (1.9.0) aws-sdk-organizations (1.15.0) aws-sdk-pi (1.5.0) aws-sdk-pinpoint (1.12.0) aws-sdk-pinpointemail (1.0.0) aws-sdk-polly (1.13.0) aws-sdk-pricing (1.5.0) aws-sdk-rds (1.36.0) aws-sdk-redshift (1.13.0) aws-sdk-rekognition (1.14.0) aws-sdk-resourcegroups (1.7.0) aws-sdk-resourcegroupstaggingapi (1.5.0) aws-sdk-resources (3.27.0) aws-sdk-route53 (1.15.0) aws-sdk-route53domains (1.7.0) aws-sdk-s3 (1.23.1) aws-sdk-sagemaker (1.22.0) aws-sdk-sagemakerruntime (1.6.0) aws-sdk-secretsmanager (1.19.0) aws-sdk-serverlessapplicationrepository (1.9.0) aws-sdk-servicecatalog (1.12.0) aws-sdk-servicediscovery (1.7.0) aws-sdk-ses (1.13.0) aws-sdk-shield (1.8.0) aws-sdk-signer (1.4.0) aws-sdk-simpledb (1.5.0) aws-sdk-sms (1.5.0) aws-sdk-snowball (1.9.0) aws-sdk-sns (1.7.0) aws-sdk-sqs (1.9.0) aws-sdk-ssm (1.32.0) aws-sdk-states (1.7.0) aws-sdk-storagegateway (1.12.0) aws-sdk-support (1.5.0) aws-sdk-swf (1.5.0) aws-sdk-transcribeservice (1.10.0) aws-sdk-translate (1.6.0) aws-sdk-waf (1.10.0) aws-sdk-wafregional (1.11.0) aws-sdk-workdocs (1.6.0) aws-sdk-workmail (1.6.0) aws-sdk-workspaces (1.8.0) aws-sdk-xray (1.8.0) aws-sigv2 (1.0.1) aws-sigv4 (1.0.3) bigdecimal (default: 1.3.4) bundler (1.17.1) cmath (default: 1.0.0) csv (default: 1.0.0) date (default: 1.0.0) dbm (default: 1.0.0) etc (default: 1.0.0) fcntl (default: 1.0.0) fiddle (default: 1.0.0) fileutils (default: 1.0.2) gdbm (default: 2.0.0) io-console (default: 0.4.6) ipaddr (default: 1.2.0) jmespath (1.4.0) json (default: 2.1.0) openssl (default: 2.1.2) psych (default: 3.0.2) rdoc (default: 6.0.1) scanf (default: 1.0.0) sdbm (default: 1.0.0) stringio (default: 0.0.1) strscan (default: 1.0.0) webrick (default: 1.4.2) zlib (default: 1.0.0)
追記 (1) Ruby ランタイムの gem environment
以下のようになっていました.
RubyGems Environment: - RUBYGEMS VERSION: 2.7.6 - RUBY VERSION: 2.5.3 (2018-10-18 patchlevel 105) [x86_64-linux] - INSTALLATION DIRECTORY: /var/runtime - USER INSTALLATION DIRECTORY: /.gem/ruby/2.5.0 - RUBY EXECUTABLE: /var/lang/bin/ruby - EXECUTABLE DIRECTORY: /var/runtime/bin - SPEC CACHE DIRECTORY: /.gem/specs - SYSTEM CONFIGURATION DIRECTORY: /var/lang/etc - RUBYGEMS PLATFORMS: - ruby - x86_64-linux - GEM PATHS: - /var/runtime - /var/task/vendor/bundle/ruby/2.5.0 - /opt/ruby/gems/2.5.0 - GEM CONFIGURATION: - :update_sources => true - :verbose => true - :backtrace => false - :bulk_threshold => 1000 - REMOTE SOURCES: - https://rubygems.org/ - SHELL PATH: - /var/lang/bin - /var/lang/bin - /usr/local/bin - /usr/bin/ - /bin - /opt/bin
これは
俺でもわかるシリーズ Advent Calendar 2018 第 8 日目の記事になる予定です.
初老丸 Advent Calendar 2018 第 12 日目の記事になる予定です.
tl;dr
昨日, 以下のような記事を書きました.
Ruby が AWS Lambda 上で動くなんてすごいなあと興奮していたのもつかの間, 自分で作ったライブラリが動くかなあと試そうとして躓いた (今も躓いている) ので, メモっておきます.
あれ, 関数の起動が遅いよ...これ
furikake を Lambda で
以下のような issue を立てて, furikake を Lambda 上で動かしてみようとしました.
以下のようなエントリーポイントになるコードを書いて sam でローカルテストを実行するところまで漕ぎ着けました.
require 'furikake' def run(event:, context:) report = Furikake::Report.new report.show end
以下, 実際の実行例です. 関数のタイムアウト自体は 60 秒に設定しています.
$ echo '{"test":"dayo"}' | sam local invoke ServerlessFurikake 2018-12-01 07:26:47 Reading invoke payload from stdin (you can also pass it from file with --event) 2018-12-01 07:26:47 Found credentials in shared credentials file: ~/.aws/credentials 2018-12-01 07:26:47 Invoking lambda.run (ruby2.5) Fetching lambci/lambda:ruby2.5 Docker container image...... 2018-12-01 07:26:49 Mounting /path/to/serverless-furikake as /var/task:ro inside runtime container 2018-12-01 07:27:50 Function 'ServerlessFurikake' timed out after 60 seconds
あれ...なんでや...タイムアウトしてしまいました.
何が起きているのか
gem のロードが遅くない??
furikake は aws-sdk 等の幾つかの gem に依存しているのですが, これらの gem のロードが遅いんじゃないかな...という仮説を立ててみました. ほんとにそれくらいしか思いつかなかったので. ということで, ローカル環境と sam invoke local で実行する Docker コンテナ環境, Lambda 環境の三環境で gem の読み込み速度の比較をしてみたいと思います.
検証環境
sam は local invoke する際に Docker コンテナを起動して, そのコンテナ上で関数を実行しているので, そのコンテナ上でデバッグしてみたいと思います.
尚, 利用する Docker イメージは以下のイメージです.
また, Docker を動かしている環境は以下の通りです.
$ sw_vers ProductName: Mac OS X ProductVersion: 10.13.6 BuildVersion: 17G65 $ ruby --version ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
ハードウェア的には以下の通りです.
- MacBook Pro (Retina, 13-inch, Early 2015)
- 2.7 GHz Intel Core i5
- 16 GB 1867 MHz DDR3
2018 年でも快適に利用出来る環境だと思います.
で, 一旦, furikake から離れて, シンプルな検証用のコードを書いて検証してみます.
require 'benchmark' def run(event:, context:) Benchmark.bm(10) do |r| r.report 'require aws-sdk' do require 'aws-sdk' end end end run(event: {}, context: {})
gem は bundle init
で Gemfile を生成して以下のように記載して bundle install --path vendor/bundle
と実行してインストールしました.
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'aws-sdk'
以下のようなディレクトリ, ファイル構成となります.
$ tree . -L 2 . ├── Gemfile ├── Gemfile.lock ├── lambda.rb ├── template.yaml └── vendor └── bundle 2 directories, 4 files
ローカル環境
以下のように, real で 1.5 秒程度掛かっていますが, 2 回目以降は 0.5 秒程度となり, 個人的に違和感はありません.
$ bundle exec ruby lambda.rb user system total real require aws-sdk 0.327319 0.268804 0.596123 ( 1.496711) $ bundle exec ruby lambda.rb user system total real require aws-sdk 0.296917 0.187150 0.484067 ( 0.489871) $ bundle exec ruby lambda.rb user system total real require aws-sdk 0.299350 0.188319 0.487669 ( 0.494612)
sam の local invoke で利用する Docker 環境
以下のようにコンテナを起動します.
$ docker run -t -i --rm -v "$PWD":/var/task lambci/lambda:build-ruby2.5 /bin/bash
以下のように 100 秒以上掛かるようになりました.
bash-4.2# ruby lambda.rb && ruby lambda.rb && ruby lambda.rb user system total real require aws-sdk 27.580000 8.450000 36.030000 (119.208587) user system total real require aws-sdk 25.780000 9.320000 35.100000 (105.250906) user system total real require aws-sdk 24.730000 9.860000 34.590000 (102.976592)
あれ.
Lambda 環境 (sam でデプロイ)
sam を使ってデプロイしてマネジメントコンソールで確認します. タイムアウトは最大の 15 分に設定しています.
user system total real require aws-sdk 17.486155 1.504303 18.990458 (232.400251) user system total real require aws-sdk 0.000095 0.000008 0.000103 ( 0.000101) user system total real require aws-sdk 0.000026 0.000002 0.000028 ( 0.000025) user system total real require aws-sdk 0.000026 0.000002 0.000028 ( 0.000025)
初回が鬼のように時間が掛かっています. 2 回目以降は 1 回目が嘘のように速くなっていて, むしろローカル環境で実行するよりも速くなっています. どのような仕組みになっているかは分かりかねますが, gem がキャッシュに載っているんだと思います... きっと.
エントリーポイントになるコードを修正して, デプロイし直したところ, キャッシュを含む環境が破棄されて新しい環境に展開される為, 初回起動と同じくらいの起動時間を要してしまいました.
user system total real require aws-sdk 18.216496 1.705607 19.922103 (243.839962) Hello. user system total real require aws-sdk 0.000100 0.000010 0.000110 ( 0.000107) Hello.
なぜか, 初回起動時だけ 2 回 require 'aws-sdk'
が実行されているのは...すいません. コードの問題だとおもいます.
Lambda 環境 (手動で Lambda 関数を作成)
マネジメントコンソールにて手動で Lambda 関数を作って, コードを貼り付けてテストしてみます.
user system total real require aws-sdk 0.264964 0.043559 0.308523 ( 3.765794) user system total real require aws-sdk 0.000027 0.000004 0.000031 ( 0.000027) user system total real require aws-sdk 0.000024 0.000004 0.000028 ( 0.000026)
初回起動時は 3 秒ほど掛かっていますが, 2 回目以降は一瞬です. Ruby ランタイムには既に aws-sdk が利用可能な状態で組み込まれているようですので, 単純に aws-sdk だけを利用する Lambda 関数を実行する場合には, この程度の起動時間を要すると考えておくと良さそうです.
結果のまとめ
- Lambda 環境では初めて関数を起動する際, サードパーティ製 gem のロードに時間を要する場合があるので気長に待つ (タイムアウトは長めに設定しておく必要がある)
- 2 回目以降の実行は爆速, コードを変更してデプロイし直すと環境は破棄されて gem を再ロードしてしまうので...起動は遅くなる
- sam を使ってローカル実行する場合には, 毎回 gem をロードしてしまうので注意 (そんなもんだと割り切るしかないのかな...)
以上
初回起動時間にさえ注意すれば, 多くの Ruby ライブラリを Lambda 上で動かすことができそうです!
ということで, furikake を動かせそうな気がしてきました.