AWS Lambda

LambdaはAWSが提供するサーバーレス実行環境です。弊社ではAlexaスキル開発等で触れる機会が多くなってきました。Lambdaを使ったことがない方向けに、導入にあたって私がつまづいた点などをまとめてみました。

実行ロール

Lambda関数で使用可能なAWSサービスの設定です。
ロール自体の設定は、「IAM」で行います。
DynamoDBやS3を使用するLambda関数では、実行ロールに当該サービスを含めましょう。

ログ

Lambda関数の実行ログは、「CloudWatch」で閲覧できます。

タイムアウト時間

Lambda関数はデフォルトで3秒のタイムアウト時間が設定されています。
javascriptに特有の同期的な処理が原因で処理が実行されていないと思っていたら、タイムアウト時間が原因で処理自体が終了していた、という場合もありました。
時間がかかる処理をさせる場合にはタイムアウト時間も気にすると良さそうです。

Node.jsでLambdaを使用する

Lambdaで使用できる言語としては、C#/Go/Java/Node.js/pythonがありますが、ここからはNode.jsでLambdaを使用する場合に特化した内容です。

デプロイパッケージの作成

httpクライアントのモジュール「http」等、デフォルトで使用できるモジュールもある程度ありますが、数は少ないです。
モジュールを使用するためには、ローカルでデプロイパッケージを作成し、zip化してアップロードする手順が必要です。

デプロイパッケージ作成手順1:ローカルでデプロイパッケージ用ディレクトリを作成し、Lambda関数index.jsを配置


deploy_package/
└── index.js


デプロイパッケージ作成手順2:パッケージ管理ツールのnpmを使用してモジュールをインストール


$npm init
$npm install node-fetch --save

deploy_package/
├── index.js
├── node_modules
│   └── node-fetch
├── package-lock.json
└── package.json

デプロイパッケージ作成手順3:デプロイパッケージをzip圧縮


$cd /path/to/deploy_package/
$zip -r ../deploy_package.zip *

デプロイパッケージ作成手順4:Lambdaコンソール画面でzip化したデプロイパッケージをアップロード

Node.jsのバージョンと非同期処理

特別な事情がなければasync awaitが使用できる8.10を推奨します。

使用するモジュールも、async awaitに対応したものを使用するようにしましょう。
例えば、lambdaでデフォルトで使用可能な「http」モジュールはasync await非対応です。httpクライアントとしては、「node-fetch」がasync awaitに対応していることを確認しています。
https://www.npmjs.com/package/node-fetch

非同期処理/async await

javascriptでは、実行速度を向上するために、db参照やhttpリクエスト等の時間がかかる処理の終了を待たずに次の処理を行います。 そのようなメリットの反面、これは上から順番にコードが実行されないという悩ましい事態も引き起こします。

では、それをLambdaで動くサンプルコードで見てみましょう。まず、悪い例です。



const http = require('http');

function jikanKakaruShori(){
  http.get("http://rooter.jp/", function(res) {
    let body = '';
    res.setEncoding('utf-8');
    res.on('data', (chunk) => {
      body += chunk;
    });
    res.on('end', (end) => {
      console.log("時間かかる処理")
      return res
    })
  });
  
}

function suguOwaruShori(){
  console.log("すぐ終わる処理")
  return "すぐ終わる処理"
}

function getResponse(){
  let html = jikanKakaruShori()
  suguOwaruShori()
  return html
}

exports.handler = (event) => {
  console.log("start")
  let html = getResponse()
    console.log("htmlは")
  console.log(html)
};

まず、Lambdaではexports.handler以下が実行されます。

サンプルコードでは、Lambdaでデフォルトで使用できますが、async-awaitに対応していないhttpモジュールを使用しています。htmlを取得し(jikanKakaruShori関数)、直後に時間のかからない処理を行い(suguOwaruShori関数)、最後に取得したhtmlをログ出力する、ベーシックなコードに見えます。直感的にはexports.handlerのconsole.log(html)でhtmlを出力しそうです。

では、適当なテストイベントを設定して実行し、ログをCloudWatchで確認してみます。

直感に反して「時間かかる処理」の出力が一番最後になっています。 さらに、htmlはundefinedとなっています。これは、時間のかかるhttpアクセスの結果を待たずに次の処理を行っているためです。

では、async awaitに対応している、node-fetchモジュールを使用して、同じ処理を行ってみます。



const fetch = require("node-fetch");

async function jikanKakaruShori(){
  const res = await fetch("https://rooter.jp/");
  console.log("時間かかる処理")
  return res.text()
}

async function suguOwaruShori(){
  console.log("すぐ終わる処理")
  return "result"
}

async function getResponse(){
  let html = await jikanKakaruShori()
  await suguOwaruShori()
  return html 
}

exports.handler = async (event) => {
  console.log("start")
  let html = await getResponse()
  console.log("htmlは")
  console.log(html)
};

CloudWatchでログを見てみます。

意図した順番の処理となっていますね。htmlも出力できました。

このように、関数をasync functionにして、呼び出し時にはawaitを付与することにより、関数の実行終了を待つことができます。

まとめ

弊社ではLambdaを使用したスマートスピーカーアプリ開発も行っております!
過去にはGoogleHome、Alexaのアプリ/スキル開発実績がございます。
スマートスピーカースキルの開発をご検討の際はぜひご相談ください。