イベントドリブン

西谷圭介氏:Lambdaには、イベントドリブンという言葉があります。イベントドリブンをちょっと簡単に説明したいと思うんですが、Lambdaとかサーバーレスアプリケーションにおける非常に重要なキーワードなんですね。先ほどのサーバーレスのスタックに置き換えたときにLambdaというものがようやく出てきたんですが、このイベントドリブンをキーワードにしたサービスと言えます。

イベントドリブンとはどういったものかをすごく簡単に言うと、状態の変化というイベントをきっかけに処理実行を行うようなアーキテクチャです。

例として、動画サービスを挙げてどんなものかを説明したいんですが、これはイベントドリブンじゃないパターンですね。これはイメージなので細かいところは気にしないでもらいたいんですが、動画をアップロードするとリクエスト受付管理をして、個別のいろんなサービスと連携して処理を行うものです。

エンコードする処理だったりサムネイルを生成して保存するみたいな、いろんなAPIと連携して動画のアップロードという処理がされます。これはイベントドリブンじゃないケースなんですが、これに例えばカタログへの追加処理みたいなものを追加しないといけない。アプリケーション的にそういった機能を追加しないといけないとしましょう。

要はインデックスのようなものを作る処理ですね。そういったことをやりたいとなった場合、そもそも新しいAPIとしてインデクシングの処理を作るというのはありますし、さらにその手前のリクエストの受付管理をするアプリケーションの部分に、そもそもの処理、インデクシングのAPIコールをするみたいな処理を追加しなければなりません。

これがイベントドリブンだとどうなるかと言うと、この真ん中の部分が変わってるのがわかるかと思います。ここの部分はいろんなやり方があるかと思います。キューだったりいろんなものがあるんですが、そういったものにリクエストの受付管理サービスはイベント発行するだけなんですね。

先ほどAPI経由で呼び出されていた各サービスは、そのキューみたいなものであったり、そういったものをサブスクライブしているだけなので、変更するときにどうなるかというと、新しいサービスを追加してそれが新たにサブスクライブするだけでよくて、リクエストの受付管理に処理の追加とか変更みたいなことをしなくて済む。というのが、すごく簡単な説明になるんですが、イベントドリブンのメリットだったりします。

要は疎結合になると思ってもらうといいかなと思います。イベントを送る側と受ける側を独立して実装できるようになるのがイベントドリブンだと思ってもらえればと思います。

サーバーレスなアプリケーションモデル

この2つを踏まえてサーバーレスなアプリケーションモデルがどういったものかというのが、ここの図です。先ほどイベントドリブンが状態変化というイベントをトリガーに処理を実行すると話しましたが、その状態変化を起こすイベントの発生元をイベントソースと呼んでいます。

そこにはいろんなAWSのサービスがあります。API GatewayだったりオブジェクトストレージのS3と呼ばれるものだったりいろいろあるんですけど、そういったイベントソースというイベントの発生元のイベントによって、Lambdaの関数、Lambdaの処理が実行されます。あと、これからちょっと説明するんですが、Lambdaの関数自体はここに書かれているような言語で、普通のコードを書くだけでよかったりします。

呼び出された関数の中からAWSのその他のサービスや外部のAPIなどを呼び出して処理できるようになっています。この呼び出す先のサービスとかは、AWSのサービスはもちろんですが、外部のサービスも使えますし、オンプレのサービスとかそういったものに関してもネットワーク的な経路があれば、接続できるようになっています。

つまりサーバーレスなアプリケーションにおいては、Lambdaが中心になってくるというのが、この図からもわかるかと思います。当たり前ですね。Lambdaが処理を実行するビジネスロジック部分を担当するので、サーバーレスアプリケーションを作っていこうと思うと、イコールでAWS Lambdaの実装をしていくと思ってもらえるといいかなと思います。

Lambdaで何ができるのか

そのLambdaなんですけど、実際のところ何ができるの? とか、どのぐらいのことができるの? みたいな話があって、細かく答えることも可能なんですが、基本的になんでもできると思ってもらってかまいません。もちろん「GPUを使いたい」とか、そういうプラットフォーム的な制限とかはあれですが、大方のことはできるかなと思います。

加えて、やらなければいけないことが減ります。サーバーレスは管理しなければいけないサーバーの数が減らすみたいな話をしましたが、Lambdaを使うことでそういったものがドンドン減っていきますし、逆に言うとやらなくてもいいことが増えると思ってもらっていいかなと思います。

これはインフラとかも含めたEC2とかオペレーションの責任範囲の話なんですが、上に行けば行くほどユーザーがやらなければいけないものが増えていって、下に行けば行くほどユーザーが自分たちでコントロールできる部分が増えると。逆に言うと、コントロールしなければいけない部分が増えると思ってもらえればいいかなと思います。

ポイントは今日の話のテーマでもある一番上のLambdaですね。ユーザー・お客様の管理範囲としている部分が、とても少ないのがわかるかと思います。

Lambdaの基本と制限

というわけで、ようやく前段階が終わって、ここからLambdaの基本の話に入っていきます。Lambdaを使うにあたって知っておくべき基本について説明すると言いつつ、残り10分で残りの45ページを説明するのは到底不可能なので、後ほど資料を公開するので、そちらを見るか、見た上でわからない部分とかあればぜひTwitterなどで質問をもらえればと思います。

まずLambdaで動かすアプリケーションはどういったかたちで実装していくかというと、Lambda関数が基本になってきます。ひとまず動かすのに必要な設定がここに書かれています。設定としては、メモリのサイズとタイムアウト、それから権限ものでどういう権限を与えるかみたいな設定だけが必要になります。

Lambdaの特徴として、そのアプリケーションの実行にあたってメモリサイズを指定します。(資料の)メモリの設定には、メモリを倍にするとCPUの能力も倍になるみたいに書いていますけど、どちらかと言うとコンピューティング処理能力全体の設定パラメータだと思ってもらったほうがいいかなと思います。

いくつか制限があるとお話ししました。例えばインバウンドネットワーク接続はブロックされます。アウトバウンドはTCP/IPとUDP/IPだけとか、いくつかの制限がありますね。普通のコードが書けると話しましたが、プラットフォーム的な制限はいくつかあります。

Lambdaファンクションとサポート言語、イベントソース

そのLambdaファンクションについて話をしていきたいんですが、AWS Lambdaで実行するアプリケーションのことだと思ってもらうといいかなと思います。サポートされている言語、もしくはカスタムランタイムで用意した言語で記述をします。ここでサポートされている言語としてPythonがあって、多くのユーザーはPythonでLambdaファンクションを書いていると思ってもらうといいかと。コンテナ内で実行されている細かい話とかは、いったん割愛したいと思います。

サポートされている言語なんですが、今日はPythonの話だけすると、2.7、3.6、3.7、3.8というバージョンがサポートされています。それ以外にもいろんな言語のサポート、メジャーなところの言語はサポートされていますし、サポートされていない言語に関してもカスタムランタイムという機能を利用することで、どんな言語でも利用できるようになっています。

それからイベントソースですね。イベントソースもいろいろありますが、これも数が多いのでまたあとで資料を見てもらえればと思います。AWSはいろんなサービスと連携できるようになっています。

制限事項と料金

Lambdaには制限事項がいくつか設定されてます。どちらかと言うとこれはセーフティガードとしてです。要は何かアプリケーションの不具合で無限ループしてしまって課金がいっぱい発生してしまったりとかみたいなことがないように制限が掛かっていると思ってください。この辺りは制限緩和が可能な制限です。

これ以外に、プラットフォーム特性として、制限緩和が不可な仕様みたいに思ってもらうといいかなと。そういった制限事項も諸々あります。

料金です。料金に関しては、リクエスト単位で課金されます。リクエストと実行時間で課金がされます。リクエストは月間100万リクエストまでは無料となっています。それを超えると100万リクエストあたり0.2ドルみたいに掛かってきます。

実行時間に関しては100ミリ秒単位の課金となっていて、100ミリ秒以下は繰り上げて計算します。要はLambdaが呼び出されてアプリケーションの処理も実行するんですが、それは実際に実行された時間を100ミリ秒単位で課金されると思ってください。

これがメモリ容量によって単価とか無料時間が異なってきまして、かなり複雑なので、これは私がQiitaに書いた記事がありますので、ぜひその辺りを参考にしてください。

Lambdaのプログラミングモデル

ようやくプログラミングモデルの話に入っていきたいと思います。Lambdaでプログラミングする場合の基本です。これはPythonに限らない基本ですね。いろんな概念を覚えておく必要があります。いずれの言語でも、Lambdaファンクションのコードを記述するには、いくつかここに書かれている主要概念が含まれるLambda関数のコードを記述する必要があります。

必ず用意しなければいけないものは、ハンドラと呼ばれているもので、要はLambdaがリクエストを実行したときに、呼び出されるエントリポイントになっていくようなメソッドを用意する必要があります。これを呼び出すことでLambdaファンクションのコードが実行開始されます。その実行環境の内容のランタイムに関する情報などが含まれるコンテキストみたいなものもあります。

それからロギング。CloudWatch Logsというサービスがありまして、Lambda関数の中にログ出力のステートメントを含めることで、それがそこに集約されて保管され、表示、出力、参照できるようになっています。この辺りは、あとでもう少し詳しくお見せしたいと思います。

あと例外に関してです。Lambdaファンクションとして使用する言語によって正常終了方法が変わってくるんですが、その例外通知の方法も変わってきます。ここもサンプルをあとでお見せしたいと思います。

ステートレスに作る

プログラミングモデルの基本なんですが、Lambdaファンクションは、ステートレスに実装していく必要があります。

(資料の)「コンピューティングインフラストラクチャとの直接的な関連性はない」って、これは何を言っているかというと、Lambdaファンクションはリクエストの度に必ずしも同じコンピューティングインスタンスで実行されるとは限らないので、後続のリクエストが同じアプリケーション、同じインスタンスで実行され続けるみたいなものを前提とした実装をしてはいけないということですね。

それからローカルファイルシステムへのアクセス、子プロセス、その他類似の生成物みたいなものも、リクエストの有効の期限、実行されている間のときだけに限定されて、リクエストがいったんその1つのリクエストが終了したら使えなくなると思ってください。

なので永続化するためには、S3というオブジェクトストレージのサービスであったり、NoSQLのDynamoDBというサービスであったり、その他のいろんなクラウドストレージだったりデータストアだったりみたいなものに保存をしておく必要があります。

Lambda関数の実装

Lambda関数の実装です。先ほどお話したように、利用可能な言語で普通に実装するだけです。言語ごとの方言はあるものの大きな違いはありません。基本概念を各言語のやり方で実装していくんですが、つまりみなさんがふだん使っているのと同じようにPythonで書くことができると思ってもらえればいいと思います。

もちろん、AWSの各サービスと連携させたい場合などは、その使い方とかSDKなどそういったものの使い方を知る必要はあるんですけど、これに関してはLambdaに限らずAWSの上で動くアプリケーションを書く場合に必要になってくると思っていただくといいかなと思います。

Lambdaファンクションはどこで書いても大丈夫なんですが、ローカルのエディタで書いたりIDEとかを使ったりすることも可能なんですが、さらっと試したい場合は、AWSのマネジメントコンソール上にコンソールエディタというものが用意されていますので、そのブラウザ上で書いてそのまま実行するみたいなこともできるので、こちらも試してもらえればいいかなと思います。

そしてデプロイです。Lambdaのファンクションとして作ったアプリケーションをどうデプロイするかと言うと、依存関係のパッケージ、外部ライブラリなどを含むzipファイルにしてアップロードします。これも詳細は割愛したいと思います。Lambda Layerも割愛したいと思います。

Lambda関数の実際のコード

最後に実際のコードを見ていきたいと思います。まずLambdaファンクションの基本形は、先ほどお話したようなハンドラです。ハンドラネームは何でもいいんですけど、そのエントリポイントとなるハンドラのメソッドを作って、それをLambdaファンクションとして指定します。

実際に動くコードが、(資料の)その下が例です。my_handlerという関数をエントリポイントとして、この関数は受け取ったイベントとコンテキストを入力値として引数に取っています。

その(引数の)中にイベントが入っているんですが、そこからデータを含めメッセージを単純に返しているのがLambdaファンクションです。これを見ておわかりのように、いわゆるLambdaファンクションと言っても普通のPythonのコードだというのがわかるかと思います。

ロギングに関しては先ほどいろいろお話しましたが、普通にファンクション内に標準出力で出したりprint文で出力したりするだけで、CloudWatch Logsに集まってきます。実際にCloudWatch Logsで記録されるのは、このようなイメージのものです。Lambda固有の出力とファンクションの中での出力が集まってくると思ってもらうといいかなと思います。

それからロガーです。ロギングライブラリみたいなものを使うことももちろんできるようになっていまして、そういったものを使った場合は、ログレベルだったりタイムスタンプ、それからリクエストIDみたいなものが埋められて出力される。これもCloudWatch Logsに集まってきます。

エラーハンドリングです。(資料の)上はエラーが発生するコードです。コードが原因でエラーが発生すると、LambdaによってエラーのJSONが生成されて返ってきます。この出力されたJSON自体はログにも残ります。下に示したのが実際に返ってくるエラーの出力です。エラーを検出したら、このようにerrorMessage、stackTraceみたいなフィールドを含んだJSONドキュメントが生成されてログ出力される。もしくは呼び出し元に返ってきます。

Lambda実装のTips

ここからは実際に実装していく上でのTipsになります。これはPythonに限らない話です。もう既にオーバーしているので、5分だけオーバーさせてもらって紹介したいと思います。

まずは効率的なファンクションのコードです。基本的に各言語のベストプラクティスとか最適化手法に則るかたちになります。

それ以外の項目として、コアロジックの分離があります。ハンドラというものを用意するという話をしたと思うんですが、その中で実際のビジネスロジックを書いていくことになるんですけど、そのロジックはハンドラの中にべた書きするのではなく、ぜひロジックとしてコアロジックは分離させることが望ましいとしています。そうすることでビジネスロジックの単体テストがしやすくなるというメリットがあります。

これは実際にどう分離するかの一例です。この場合は、Todo()というものを作ってそこに処理の実体を用意して、イベントの中身によってディスパッチして、そのTodo()を呼び出すみたいなかたちで、処理の実体を全部外に出してしまうみたいな実装をするのが望ましいとされています。

コンテナ再利用の話はちょっと複雑なので今回は置いておきたいなと思います。

なるべく呼び出すときのイベントの中に必要なもののみ読み込むようにするですが、Lambdaというのはリソースとして利用可能なリソーススタイルが限られているところがあるので、無駄に不要なデータを読み込むと、そこを圧迫してしまうとか、処理時間、実行時間自体が長くなってしまう。つまり課金に反映されてしまうので、そもそも不要なデータをなるべく読み込まないという実装をするのがいいかと思います。

あとはオーケストレーション。要は連鎖的にLambdaファンクションを呼び出して条件分岐をしたりとかそういうことを実装する方がたまにいますが、そういったステート管理とかエラーハンドリングが複雑になるオーケストレーションコードみたいなものはLambdaの中では実装しないほうがいいとしています。

こういったことをやりたい場合、Lambdaを使ったより複雑なワークロードを実現したい場合は、Step Functionsというサービスがあるので、ぜひこちらを利用してもらうといいと思います。

そしてエラーハンドリングしましょうという話とか、Lambdaでリトライするにあたっていくつか仕様がありますので、そういったリトライポリシー必ずは意識して実装する必要があります。ここもちょっと割愛しますね。こちら(依存関係と組み込まれたSDKの話)も割愛します。

実はLambdaは、同期実行、要はリクエストしてそれに対してレスポンスを待つみたいな同期実行だけではなくて、非同期実行も可能になっています。その非同期実行を活用することでよりスケーラビリティというものが発揮できますので、非同期実行を活用するのがいいかと思います。

ほとんどのAWSの各サービスがイベントソースから非同期でトリガーされているというところからもわかるかと思います。非同期でトリガーされているんですが、それをオーバーしたものに関してはスロットルされるのでお気をつください。

ちゃんと冪等性を確保しましょうねというのは、この辺りの分散処理とかそういったステートレスな処理だったりとか、そういったところではよくある話ですが、冪等性はお客様のコードで確保する必要がありますので、こちらは必ず意識をしていただくといいかなと思います。

あとはわりと基本的なことも書かれていますが、すごく大事なこととして、Lambdaおよびサーバーレスですべてやろうとしないことというのがあります。もちろんLambdaの実行モデルは基本的に何でもできるという話をしましたが、向いてないケースというのも多々あります。なのでそういったときにはLambdaで無理矢理やるよりも向いているものを使うほうがいいので、そちらをぜひ検討してもらえるといいかなと思います。

例えばそもそもステートフルなアプリケーションをもってきたい場合とかです。Lambdaだとなかなか難しかったりするので、EC2やコンテナみたいなものを使っていただいたほうがいいと思います。

それから例えばロングバッチです。Lambdaには15分というのがタイムアウトの時間があり、最長で15分です。シングルトランザクションみたいなロングバッチに関しても、1時間のロングバッチみたいなものは当然実装できなかったりするので、そういったものに関してはLambdaではなくEC2であったりコンテナあたりを使ったほうがいいと思います。

サーバーレスで目指すべきこと

サーバーレスで目指すべきことは何かというと、基本的にはいかに速く、価値のある、システムを作るというところだと思っています。つまりやらなくていいことを増やして、要はやらなきゃいけないことを減らすと、そういった意味では差別化につながらない重労働。

例えばOSのセットアップであったりセキュリティバッチの適用、そういったなかなか大変だけれども差別化そのものにはつながらないものを自分でやらないというのが大事かなと思います。お金で時間を買うみたいな感じですね。逆に言うと差別化につながることであれば積極的にやればいいと思います。

というわけで8分ほど押してしまい、かつかなり駆け足で恐縮なんですが、私からは以上となります。繰り返しになりますが、ぜひTwitterでフォローしてくれればいろいろご質問など受けられると思いますので、ぜひこちらもよろしくお願いいたします。

というわけで私からの発表を終わらせていただきます。ありがとうございます。