re:Invent 2024: AWSのStep Functionsで高度なワークフロー構築
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Building advanced workflows with AWS Step Functions (API402)
この動画では、AWS Step Functionsを使った高度なワークフローの構築について解説しています。StandardとExpressという2種類のワークフローの特徴や、コスト面での違い(1,000回の実行でStandardが42セント、Expressが1セントなど)を具体的に説明しています。また、JSONataサポートやWorkflow変数など最新の機能強化や、Circuit BreakerやSagaパターンなどの失敗管理手法、Distributed Map stateを活用した大規模な並列処理の実装方法まで、実践的な内容をカバーしています。特に、Serverlesspressoのコーヒー注文システムやServerless Videosのビデオ処理など、実際のユースケースに基づいた具体例を交えながら、Step Functionsの活用方法を詳しく解説しています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
AWS Step Functionsを用いた高度なワークフロー構築:イントロダクション
おはようございます!本日はお越しいただき、ありがとうございます。多くの方が初めてre:Inventのセッションに参加されているようですね。皆様をお迎えできて大変嬉しく思います。今日は、AWS Step Functionsを使った高度なワークフローの構築についてお話しします。もし間違った部屋に来てしまった方も、きっと楽しい内容になりますので、このまま残っていただければと思います。
まず自己紹介させていただきます。私はEric Johnsonで、AWSのPrincipal Developer Advocateを務めています。私はServerlessの大のファンで、それが最初に発表された時からServerlessに携わってきました。この発表に関して、Ben Smithに感謝の意を表したいと思います。BenはStep Functionsの達人でした。私たちは皆Step Functionsの達人ですが、彼は達人の中の達人でした。現在は既にAWSを離れていますが、今でもServerlessの分野で活躍しています。今日のプレゼンテーションは、彼の仕事と私の経験に基づいています。
会場の皆様、そしてオーバーフロールームにいらっしゃる皆様、こんにちは。私の講演を初めて聞かれる方のために、いくつかプレゼンテーションのルールをお伝えします。まず、私が指を立てる時は、好きな数字を表現できます。次に、これらは引用符であって、アポストロフィではありません。そして、これらは親指です。私は指のジョークを言うのが好きなんです - 生まれつきなんです。ただし、それが不快に感じる方がいらっしゃれば、もちろん控えさせていただきます。
私は長年テクノロジー業界で働いてきました。成功も失敗もありましたが、Serverlessは私のキャリアのハイライトです。それでは、AWS Step Functionsについて詳しく見ていきましょう。多くの方が既にStep Functionsを使用されていて、これから始めようとしている方もいらっしゃるようで嬉しく思います。これは400レベルのセッションですが、特定の概念についてさらに学びたい方のために、最後にリソースをご紹介します。
本日のアジェンダは、Step Functionsのワークフローとして表示されています - これはBenのアイデアを借りています。Step Functionsの意味、どのワークフロータイプを使用すべきか(StandardとExpress)、コスト削減、データフローの改善、そして失敗の管理とStep Functionsのスケールについて説明します。たくさんの内容をカバーしますので、テンポよく進めていきたいと思います。
Serverlessアプリケーションの構築とStep Functionsの役割
Serverlessについて話す時、私たちはそれをピラミッドのように視覚化できます。一つのレベルには、ECS Fargateのようなサービスがあり、これはServerlessですが、より多くのコントロールと調整が可能です。そして、EventBridgeやLambda functionsのような、よりコントロールの少ないサービスへと進んでいきます。これらのサービスを連鎖させてアプリケーションを構築するというのが基本的な考え方です。Ajay Nairが「Serviceful」という興味深い用語を導入しましたが、これはサービスを使ってアプリケーションを構築し、コーディングを最後の手段とする考え方を強調しています。
話は少しそれますが、マイクでスラープという音を立ててしまいました。悪いコードの話をする時、すべてが悪いわけではありません。Eric Johnsonのコードだけが悪いのです。この部屋にはMattとDarrenを含め、私と一緒に仕事をしたことがある人が何人かいますが、彼らは私のコードが問題児だということを知っています。これは紛れもない事実なので、できる限り設定で済ませる方がいいんです。私のモットーは、コードは他に手段がない時だけ使うべきということです。開発者の皆さんは最高の認証システムを書けると思っているでしょうが、時には既存のサービスを使う方が賢明です。それがServicefulアプリケーションで私たちがやっていることなのです。
複数の接続されたサービスによってアプリケーションが動作している場合、これらの接続をどのように構築し、追跡し、検査し、視覚化し、オーケストレーションするのかという疑問が出てきます。多くのことが同時に進行しているので、処理の順序を指定する必要があります。その答えがAWS Step Functionsです。これは400レベルのコンテンツですが、AWS Step Functionsを知り始めた人のために説明すると、使用量に応じた課金モデルです。Visual Workflow studioを使用すると、アプリケーションのワークフローをドラッグ&ドロップで構築できます。シーケンス、並列処理、条件付きロジックを指定できます。ドラッグ&ドロップインターフェースを使用するか、ASL(Amazon States Language)を使用してワークフローを記述することができます。
組み込みのエラーハンドリングがあり、これは本当に素晴らしい機能です。また、200以上のサービスと統合されており、実際にはSDKを内部で使用しています。数年前にリリース前の段階でこれを見せてもらった時、天才的な仕事だと思いました。私たちには「Step Functions first, Step Functions always」という考え方があります。これは私たちのDAの一人であるRob Sutterが何年も前に言ったことですが、当初は同期呼び出しができなかったため、私は反対していました。今では同期呼び出しが可能になったので、完全に賛成です。Ben Smithが去年言い、私が今年言っていますが、なぜこれが理にかなっているのか、お見せしましょう。
DynamoDBと通信する簡単なアプリケーションの例を見てみましょう。DynamoDBを呼び出すLambda functionがあり、クライアントの作成、パラメータの作成、クエリを実行する非同期関数の作成、そしてLambdaハンドラーなど、複数行のコードが必要です。これは許容できますが、Step Functionsでどのように実装するか見てみましょう。Step Functionsでは、単一のワークタスクワークフローでも意味があります。Step Functionsを使用すると、組み込みの再試行ロジックを持つGetItem操作を実装できます。アイテムを取得できない場合、コーディングなしで再試行を指定できます。複数回試行しても失敗する場合は、Dead Letter Queue(DLQ)にルーティングできます。
実際の Step Functions ではこのように見えます。DynamoDB の接続、SQS、そして障害処理です。詳細にドリルダウンしてエラーを追跡し、どこで何が問題になったのか、設定のどこが失敗したのかを正確に確認できます。Step Function の入力と出力を調べることもできます。これは 400 レベルの講座としては基本的な内容ですが、皆さんにしっかり理解していただきたいと思います。エラー処理は標準搭載されており、入出力の追跡も可能で、すべてがすぐに使える状態になっています。では、最初のパターンについて説明し、Lambda-lith(モノリシックな Lambda)の分割について見ていきましょう。Lambda アプリケーションを構築する際、WordPress のような完全なアプリケーション全体を1つの関数に詰め込むという大きな振り子のような動きがありました。
私は単一の Lambda 関数の中で WordPress を実行しています。これを試みた人が何人かいますね。そういうことが起こります。その後、反対の極端に振れて、「リソースのメソッドごとに個別の Lambda 関数を用意しよう」となり、単なる To-do リストのために大量の Lambda 関数を作ることになりました。これは行き過ぎですので、行ったり来たりしながら、今は適切な分割方法を探っているところです。
API Gateway で Lambda を使って API を構築する際、「1つの Lambda ですべてを処理する」アプローチでは、プロキシとして API Gateway を使用します。入ってきたものは何でもこの Lambda 関数に渡されます。この例では、作成、更新、削除があり、Lambda 関数がそのすべてを処理します。セキュリティを重視する方々(この会場の皆さん全員がそうであるべきですが)にとって重要なのは、セキュリティ権限が全体に適用されるということです。この Lambda 関数は、実行している内容に基づいてすべての操作が可能です。パフォーマンス設定やスペース制限も全体に適用されます。多くのことを行う1つの Lambda 関数に単一の権限と設定を適用することは、必ずしもベストプラクティスではなく、影響範囲を最小限に抑えることもできません。
Step Functionsのワークフロータイプとコスト最適化
これを分割してみましょう。ドメイン固有の Lambda 関数を作成できます。作成用、更新用、削除用にそれぞれ1つずつ関数を用意します。これで3つの異なるコードページができました。これは間違いではありません - 私は Lambda 関数の大ファンですが、別のやり方もあります。次にお見せするのは「REST easy パターン」と呼ばれるものです。Amazon API Gateway は Step Functions に直接接続でき、そこで「どのメソッドか?」を判断します。GET なら GET のパスを、PUT や PATCH ならそれぞれのパスをたどります。
皆さんの中で Serverlesspresso のコーヒーを飲んだことがある方はどれくらいいますか?Serverlesspresso は、世界中で無料コーヒーを提供しているブースです。今日認定ラウンジにいらっしゃる方や、今晩 Expo ホールに来られる方は、ぜひ無料コーヒーをお試しください。数年前に最初にこれを構築したとき、キャンセル、完了、作成、作成などの機能ごとに個別の Lambda を持つ Lambda 関数駆動型のアプリケーションとして作りました。これを見直して「もっと体系的なやり方ができるはず」と考えました。実際のところ、Lambda 関数はビジネスロジックを実行していたわけではなく、ルーティングを行っていただけでした。Lambda の元ディレクターである Ajay Nair が言ったように、「Lambda はデータの転送ではなく、データの変換に使うべきです」。
このアプローチを使用することで、Version 2では各コーヒーの注文に対して1つのワークフローが実行されます。顧客からの注文なのか、キャンセルなのか、完了なのかを判断し、システム内でどのようにルーティングされるかを確認できます。これは「Step Functions優先、Step Functions常用」という考え方です。オーケストレーション、エラー処理、リトライ機能が必要な場合は、Step Functionsが最適な選択肢となります。Step Functionsには、StandardとExpressという2種類のワークフローがあります。Standardが先に登場し、最大1年間という驚くべき長期の待機時間を持っています。非同期処理が可能で、exactly-onceの配信と実行を保証します。一方、Expressは高スループット向けに設計され、コスト効率が良く、at-least-onceの配信を提供します。
Expressワークフローは同期または非同期で実行可能です。最大5分間という短時間のワークフロー向けに設計されており、興味深いことにメモリ上で実行されるためコールドスタートがありません。メモリ上で実行されるため非常に高速で、これらはStep Functionsから始まります。
実際の例で比較してみましょう。私たちはこのeコマースワークフローを構築し、Standardで1,000回、Expressで1,000回実行してみました。CloudWatchダッシュボードを作成してメトリクスを確認しました。1,000回実行した場合、Standardは42セント、Expressは1セントのコストがかかります。
Standardワークフローのコストを詳しく見てみましょう。ワークフローではトランジション数が重要です。トランジション数に実行回数を掛けるので、1,000回の実行に0.000025(1,000回あたり2.5セント)を掛けます。17回のトランジションがあるので、総コストは17×0.025で42セント/1,000回となります。Expressワークフローの場合、リクエスト数と実行時間に基づいて計算されます。1,000回の実行は同じですが、17回のトランジションはそれほど重要ではありません。リクエスト自体が重要になります。
実行コストと実行時間コストにリクエスト数を掛け、実行時間コストはミリ秒単位の実行時間を100で割ったものがメモリコストとなります。Step Functionsのメモリコストは段階的です。64メガの場合、最初の1,000回、次の4,000回というように計算されます。実際の実行は64メガなので、0.0000001042となります。これを計算すると、1セントという結果になります。比較すると、Standard 1,000回で42セント、Express 1,000回で1セントということになります。
100万回実行した場合、Standardでは420ドル、Expressでは12.77ドルになります。なぜStandardを選ぶのか疑問に思うかもしれませんが、重要な理由がいくつかあります。Standardは非常に実用的なプロダクトです。まず、ワークロードが5分以上かかる場合は、Standardを使用する必要があります。また、コールバックを使用するワークロードの場合もStandardが必要です。そして最後に、厳密に1回だけの実行が必要な場合です。べき等性が重要な場合(通常はそうですが)、これを考慮する必要があります。
Step Functionsの新機能:JSONataとワークフロー変数
では、両方のタイプをどのように活用できるか見ていきましょう。これを「Nester」パターンと呼んでいます。このパターンでは、コストを最適化するためにワークフロータイプを組み合わせたネストされたワークフローを作成できます。同じ例を使って考えてみましょう。これらのシーケンスが時々5分以上かかる場合はどうでしょうか?ワークフローにはStandardが必要ですが、特定の部分は変更できる可能性があります。DynamoDBからステータスをチェックするループが2か所あり、また、データを更新する一連のLambda関数も実行されています。StandardワークフローからExpressワークフローを呼び出して、これらのLambda関数を実行することで、それらの間の3つの遷移を削除できます。これがコストにどのような影響を与えるか見てみましょう。
Standardに戻りましょう。これはネストされたワークフロー、つまり親ワークフローのコストです。14回×1,000回×0.000025で30セントになり、そこで12セント削減できました。100万単位で考えると、これは大きな違いです。状態遷移の数が変更され、ネストされた部分のコストは0.0...となり、1セントにも満たない額です。大量に実行しない限り、コストにはほとんど影響しないので、ここで12セントを30セントまで削減できました。平均実行時間などが得られます。
コスト削減についてさらに詳しく見ていきましょう。先ほどのパターン、Nesterの効果について説明しましたが、コストを削減するための方法はまだあります。これを理解するには、Standardが状態遷移に基づいて価格設定されていることを理解する必要があります。つまり、状態遷移を減らしたいということです。Expressの場合は、実行回数と実行時間を減らし、メモリを削減することが重要です。これから説明していく中で、この2つの点に焦点を当てていきます。
価格設定をもう一度見ていきましょう。この擬似Step Functionを見ると、GetItemがあり、その操作に対して料金が発生します。アプリケーション全体について話していますが、DynamoDBのリードキャパシティユニットに対して料金を支払うことになります。その後、遷移があり、Lambda呼び出しがあります。フリーティア以外のLambda呼び出しに対して、実行時間とメモリ設定に基づいて料金が発生します。その後また遷移があり、EventBridgeのPutEventがあります。これは全体の使用量とコストに影響を与えます。
Step Functionsには、これらの問題を直接軽減できる機能がいくつかあります。その最初の一つがStep Functionsの組み込み関数です。例えば、私たちがよく行う一意のID生成について考えてみましょう。通常はUUID V4を使用するため、UUIDを生成して返すLambda関数を呼び出します。しかし、Step Functionsには、States.UUIDという組み込み関数がASL内にあり、これはトランジションのコストがかかりません。つまり、関数呼び出しとトランジションの両方を節約できるのです。これは非常に小さな例ですが、ASLでは非常に強力なトランジションが可能で、先週の発表以降さらに機能が追加されました。この方法でコストを削減し、呼び出しを減らし、管理するコードを減らすことができます。これが大きなメリットです。
次に、コールバックパターンを使用してトランジションを削減する方法を見てみましょう。現在のループ処理では、「承認は完了したか?そしてプロセスは完了したか?」というチェックを繰り返し行っています。次に紹介するのは、EmitとWaitパターンと呼ばれるものです。これはStandardワークフローでのみ使用可能で、詳しく説明していきます。このEmitでは、EventBridgeにイベントを送信し、そこにあるルールがLambda関数を呼び出します。これにはタスクトークンが適用されており、このトークンは1,2,3,4,5,6,7,8となっています。
このLambda関数、あるいは後続の別のLambda関数が、処理完了後にStep FunctionsのAPIにコールバックを行い、成功または失敗を通知します。つまり、「処理が完了したか否か、続行してよい」という通知を行うのです。ハートビートを設定した場合、「20秒以内にシステムからハートビートが届かなければタイムアウト」という指定ができます。タイムアウトが発生すると、システムは処理を終了します。これによりStep Functionが宙ぶらりんの状態になることを防ぎます。このようにしてループ処理を削減し、トランジションを減らし、代わりにタスクトークンコールを使用します。フローを見直すと、かなりシンプルになっていることがわかります。重要な処理は依然として行っているので、これがどのような影響を与えるか見てみましょう。親ワークフローのコストは、この方法で8トランジションまで削減されました。8に1,000を掛けて計算すると、1回あたり10セント削減されたことになります。
これで1,000回の実行あたり20セントまでコストが下がりました。ネストされたワークフローと比較すると、平均実行時間は同じで、単価も同じですが、1,000回あたり20セントということになります。コストを半分以下に削減できました - 正確には49.732%削減と言えますが、これは適当な数字です。しかし、まだ改善の余地があります。
ネストされたState Functionでは、4つのトランジションと4つのLambda関数の呼び出しを行っています:注文履歴の更新、データウェアハウス、顧客プロファイル、在庫の更新です。これらを1つのLambda関数にまとめることもできますが、単一責任の原則を損なう可能性があるため、適切なエラー処理のためにこれらは分離しておきたいところです。私たちが作業しているシステムでは、データウェアハウスと在庫サービスは、認証が必要なセキュアなAPIを持つサードパーティの公開エンドポイントです。
私ができることは、Step Functions内のHTTPエンドpointタスクを使用して直接呼び出すことです。これはEventBridge内の認証を使用し、EventBridge接続を使って実現します。認証設定を行えば、それを使用してサードパーティーサービスと通信できます。これにより、Lambda関数の呼び出しを2回分削減できました。ただし、トランジションは依然として残っています。これは、パブリックエンドポイントが必要なHTTP API統合のためのものです。
昨晩のResource Endpointsの発表をご覧になった方はいらっしゃいますか?これは全く新しい機能です。Resource Endpointsを使用すると、VPCからリソースを共有することができます。セットアップの詳細には触れませんが、アカウント内外、組織内外でリソースを共有することができます。EventBridgeでは、私の接続をそのリソースにポイントでき、Step Functionsから直接VPC内のリソースを呼び出すことができます。昨晩の10時まででは、VPCに接続してデータを返すためにはLambda関数が必要でした。もうその必要はありません。
失敗の管理とStep Functionsのパターン
Resource Endpointsには設定や構成の管理が必要ですが、Jeff Barrによる素晴らしいブログがあり、そこですべての手順が説明されています。これは後でリソースページに追加しておきます。これは非常に強力な機能です。なぜなら、Lambdaの呼び出しを省略でき、単なる設定で済むからです。次に私が本当にワクワクしているのは、今月22日にリリースされた2つの機能です。1つ目は、データ変換のためのJSONataサポートです。JSONataは、ワークフロー内でデータを選択および変換するための強力なクエリと式言語です。
ワークフローをセットアップする際、ワークフロー全体または特定の領域でJSONataを割り当てることができます。JSONPathも引き続き使用できますが、なぜこれが新しいデフォルトになったのかお見せしましょう。まず、タスクステートについて説明しましょう。以前は、input path、parameter、result selector、result path、output pathという5つの異なるJSONオブジェクトを扱う必要がありましたが、新しいJSONataのセットアップでは、argumentsとoutputだけです。タスクまたはサブフローワークフローの前のarguments、そしてそこから出力されるoutputです。
これは非常に強力です。構文も変更され、.
これらは予約変数です。states.inputはステートへの生の入力で、states.resultはステートが成功した場合の結果、そして失敗した場合はエラー出力となります。コンテキストについては、複数のドル記号を使う代わりに、直接contextと記述します。また、ストリーム関数、数値関数、数値集計関数を含む、完全なJSONata関数セットも利用できます。さらに、partition、range、hash、random、UUIDなどの組み込み関数の代替も提供されています。これにより、Lambda関数を使わずにJSONata内で直接UUIDを生成できるようになりました。
基本的な例をお見せしましょう。私たちはURL短縮サービスとしてs12d.comを使用しており、これはAPI Gateway、DynamoDB、VTLで構築されています。所有者を変更する必要がある場合、DynamoDBのレコードを更新します。更新プロセスでは、まずデータベースをスキャンしてすべてのレコードを取得し、states.input.idを使ってIDを取得します。そして、Lambda関数やAPIコンテキストを使う代わりに、JSONataのtimestamp関数を使って今日の日付を挿入します。また、複雑なドット記法をナビゲートする代わりに、あらかじめ設定しておいたnewOwnerという変数を使用して、より簡単に処理できるようになっています。
特に誇りに思っている機能の1つは、変更セットを出力する機能です。大量のデータを含むレコードの配列を扱う際、recordsという変数を使用して焦点を絞った変更セットを作成できます。JSONataを使って追加し、マッピングを通じてID、古い所有者、新しい所有者の情報だけを抽出します。これにより、ループ遷移を書く必要がなくなり、すべてがASL内のJSONataで処理されます。昨年は、変数の概念がなく、データをステートからステートへ渡す必要がありました。Ben Smithさんが教えてくれた並列ステートを使用するワークアラウンドはありましたが、配列のインデックスやその他の複雑な要素を知る必要がありました。
現在、Step Functionsはワークフロー変数を導入し、これはJSONataとシームレスに連携し、JSONPathもサポートしています。これは本当にクールなプロセスで、JSONataと連携して動作します。JSONPathもサポートしているので、両方を使用できます。代入は次のようになります:ASLでは新しいオブジェクトとして表示され、変数タブがあり、そこで設定します。以前使用していたold ownerには states.input.oldOwner、newOwnerには states.input.newOwner、そしてテーブルは単にハードコードされた文字列を使用します。これらを参照するには、実際にはドル記号とテーブル名だけを使用します - ドットや他の記号は不要で、単にドル記号とテーブル名だけです。
Step Functionsチームは素晴らしい仕事をしており、大きな進歩を遂げています。これらの変数の参照方法についてお話ししましたが、評価の順序について説明したいと思います。これは最初は私も戸惑いました。JSONPathを使用する場合、実際のタスクやワークフローは変数を割り当てる前に実行されるので、そこで取得するドル記号は、その変数の結果となります。エラーが発生した場合も同じ原則が適用されます。JSONataでは、引数がある場合、ワークフローの後に実行されるので、出力を行うのと同じタイミングで変数を割り当てることになります。
スコープについて理解しておくべき重要なポイントがあります。トップレベルで何かを代入した場合、そのタスク内ではその値は利用できません。同じタスク内で変数を代入して参照しようとしても機能しません。入力値を代入して使用する必要がある場合は、変数としてではなく、両方の場所で直接使用する必要があります。ただし、後続のタスクやParallelなどの子スコープでは使用可能です。つまり、外部スコープと内部スコープが存在するということです。
インラインマップや分散マップでは、Step Functionから離れて新しいStep Functionや入れ子のStep Functionを生成することになるため、これらの値は利用できません。これは手動で作成した入れ子のStep Functionでも同様です。また、兄弟スコープという概念もあります。Parallelで2つのブランチがある場合、左側のトップで代入した値は左側のブランチの後続タスクでは使用できますが、右側のブランチでは使用できません。これは2つの異なるブランチだからです。また、子スコープの外部でも使用できません。子スコープに値を渡すことはできますが、子スコープから外部に値を渡すことはできません。ただし、必要に応じて再設定することは可能です。
私は過去1ヶ月ほどWorkflow変数を使って開発を行っていますが、非常に使いやすいものです。Serverless Landにもパターンを追加していく予定です。では、失敗の管理について話しましょう。Werner Vogelsの有名な言葉に「すべてのものは常に失敗する」というものがあります。これは非常に有名な言葉であり、実際その通りです。私たちは常にこの問題に対処しています。いくつかのパターンをご紹介しましょう。最初のパターンはSagaパターンです。複数のトランザクションが必要な場合、それらをキャッチする必要があり、Step Functionsではこのように動作します。
すべてが正常に動作する幸せパスでは、ホテルを予約し、フライトを予約し、車を予約して終了します。しかし、ホテルの予約が失敗した場合はどうなるでしょうか?実行された作業をすべてキャンセルしてキャッチします。ホテルの予約は成功したものの、フライトの予約が失敗した場合は、これらが全て結合されて接続されているため、フライトとホテルの両方をキャンセルします。同様に、車の予約以外がすべて成功したものの、車の予約が失敗した場合は、車の予約、フライトの予約、ホテルの予約をキャンセルし、顧客の注文を部分的な予約のない状態でクリーンに保ちます。Step Functionsで機能するもう一つのパターンはCircuit Breakerです。
成功ケースでこれを実行すると、Circuit Statusを取得します。システムはCircuitが閉じているかどうかをチェックします。閉じている場合は良好な状態なので、Lambda関数を実行し、Catchを使用せずにすべてが正常に動作します。再度実行してCircuit Statusを取得した際、閉じている場合はLambda関数に戻ります。しかし、正常に動作していると思っていたLambda関数が失敗した場合、Circuit Statusを開いた状態(故障状態)に更新する必要があります。次回実行時には、Circuitが開いていることを確認して終了します。これは特にサードパーティや制御できないものを管理する方法です。
開発をしていると、失敗から学ぶことがあります。私自身、なぜ動くのかわからないまま、とにかく試行錯誤を繰り返して解決に至ることが何度もありました。「なぜ動いているの?何をしたの?」と聞かれて、「特殊文字の問題があったんですが、なぜ動いているのかはわかりません。とにかく動きました」としか答えられないのは歯がゆい経験です。昨年、私たちはServerlesspressoに似たServerless Videosというサービスを構築し、Serverlessテクノロジーでビデオ処理ができることを実証しようとしました。
私たちが行っていたことの1つは、Lambda関数でビデオを処理し、ストリーミングファイルをMP4に変換することでした。もしLambdaが15分以上かかるとか、より多くのメモリが必要といったタイミングの問題で失敗した場合、ECSタスクにスピンアップして切り替えることができました。このプロセスを通じて、そのブレークポイントがいつ発生したかを示すメタデータを見つけることができれば、より良い判断ができることを学びました。タスクが20分以上かかる場合、LambdaとECSの間で適切にルーティングできることがわかりました。Step Functionsからイベント駆動型でECSタスクを起動、実行、シャットダウンできるため、ECSの使用時のみ課金される点は注目に値します。
以前にリリースしたRedriveという機能は、問題が発生したときに特に便利です。例えば、ビデオ処理を行ってメタデータを永続化したい場合に、クラッシュが起きても、すべてを再実行して再度料金を支払う必要はありません。Redriveを使用すれば、その特定のステートだけを実行し、残りの部分にのみ課金されます。さらに、テストステートでは、ダミーデータや実データを使用してコンソールで個別にテストし、問題を特定して修正し、その後進めることができます。
Step Functionsのスケーリングと並列処理
Step Functionsのスケールに関して、並列実行について話しましょう。これは一部の人々が使用しているパターンですが、多くの場合、その可能性を最大限に活用していません。並列実行では、多くの独立したワークフローが同時に同じASLを実行しています。並列ステートは、固定数のブランチを同時に実行します。例えば、郵便番号の検索と電話番号の検索を並列で行うことができます。両方のプロセスが正常に完了すれば問題ありませんが、1つが失敗すると、ステート全体が失敗します。このような場合、失敗を処理するcatchを組み込んで、プロセス全体を失敗させるか、部分的な成功で続行するかを決定できます。電話番号の検索ができなくても続行させる、といった具合です。
動的並列処理は並列実行とは異なります。動的並列処理では、必要な数だけ同じ処理を実行します。つまり、データの配列がある場合、各要素に対して望むだけ処理を実行できます。実際に使用している例として、Fan-outパターンがあります。これは、ブログのCompute feedのRSSフィードをスキャンすることでブログ投稿を処理し、新しいCompute blog投稿の配列を返します。
各ブログ投稿に対して、データを取得してGitHubに保存し直します。すべての処理が完了したら、フロントエンドを再構築します。これがServerless Landの仕組みです。このようにしてServerless Landのブログの一部を管理しています。この作業の多くは自動化していますが、時にはダウンストリームのサービスが負荷に耐えられないことがあります。Step Functionsのダイナミックな並列処理を使用すると非常に高速になり、システムが対応できない可能性があります。私たちは内部で、AWSのAPIではなくスロットリングが必要なサードパーティのサービスのAPIをいくつか使用しています。
制御を維持するために、GitHubに負荷をかけすぎないようScatter-Gatherパターンを使用しています。新しいブログを取得し、メタデータを処理する際に、スクレイピングを行い、すべてを配列にまとめるという同じプロセスです。並列処理でそれを配列に分散し、その後GitHubに一度にプッシュします。しかし、最も強力な機能はDistributed Map stateです。Distributed Map stateを使用すると、何千、何万というインスタンスを作成し、ネストされたワークフローで実行できます。これにはいくつかの利点があります。実行履歴が大きくなりすぎる問題を回避できます。なぜなら、履歴は各ネストされたワークフロー内に収められるからです。
この仕組みの例として、ServerlessのGIFアニメーター機能を見てみましょう。Ben Smithは、Amazon S3のMP4を複数のGIFに変換するこのシステムを構築しました。まず、ファイル名と場所を渡します。Lambda関数がチャンクを計算し、S3バケットに保存します。その後、ダイナミックマップがそれらを処理し、JSONオブジェクトを反復処理します。各Lambda関数は指定されたセクションを処理します - 1つは0から29秒、次は30から59秒というように処理します。400分のファイルがある場合、30秒のチャンクに分割されるので、約800のLambdaが同時に処理を行い、瞬時にタスクを完了します。
Distributed Mapは、各イテレーションが個別の子ワークフローとして実行されるため、同時実行制限や履歴の制約を回避するのに役立ちます。この機能がない場合のアプリケーション例を見ると、GitHubを毎日ポーリングする際に、オブジェクトや履歴が大きくなりすぎてエラーが発生します。しかし、ペイロード分割パターンを実装すると、親を検索し、各親に対してDistributed Mapを使用し、インラインスキャンを実行することで、履歴の問題を完全に解消できます。
以上で高度なワークフローの説明を終わります。これらのパターンやその他多くのパターンは、Serverlessワークフローコレクションで利用可能です。そこでは、ビジュアル、IaCテンプレート、ASL定義を提供しています。私が説明したリソースの多くはs12d.com/api402-24のリソースページにまとめてあり、QRコードでアクセスできます。このデッキは後で共有されるので、これらのリソースすべてにアクセスできるようになります。AWSの学習を続けることをお勧めします。本日後半にはAWS Step Functionsワークショップや、Step Functionsを使用した「ゲームストーリーボードのアップグレード」など、追加のセッションがあります。ご参加ありがとうございました。素晴らしいre:Inventをお過ごしください。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion