まずは高速化が行われていないシステムをデプロイします。
このシステムはECショップのAPIを提供しており、購入機能、購入履歴の表示機能があります。
今回権限不足によるワークショップの失敗を防ぐ為に、AdministratorAccessをもつIAMを作成します。
本来は最小権限の原則に基づき必要な権限のみ設定するようにしてください。
マネコン上部の検索バーで「IAM」を検索してクリックし、AWS IAMサービスのコンソールに移動します。
IAMコンソール左側の「ユーザー」をクリックし、画面右側の「ユーザーの作成」をクリックします。
ユーザー名:お好きなあなたのユーザー名(handsonなど)
「AWS マネジメントコンソールへのユーザーアクセスを提供する」にチェックを入れる
コンソールパスワード:「カスタムパスワード」を選択しお好きなパスワードを入力
「ユーザーは次回のサインイン時に新しいパスワードを作成する必要があります」のチェックを外す
「次へ」をクリックし、「ポリシーを直接アタッチする」を選択して、許可ポリシーから「AdministratorAccess」を検索してチェックを入れます。
「次へ」をクリックすると確認画面になるので、「ユーザーの作成」をクリックします。
その後、マネコン右上のAWSアカウント名をクリックして「サインアウト」をクリックします。
ログイン後、Cloud9を作成します。
マネコン画面上部の検索バーから「Cloud9」を検索してクリックし、Cloud9サービスのコンソールへ移動します。
「環境を作成」をクリックして、新たな開発環境用のEC2インスタンスをセットアップします。
インスタンスタイプを「t3.small (2 GiB RAM + 2 vCPU)」
名前はお好きなものを使ってください。
その他はデフォルトのままで作成をクリックしてください。
もしデフォルトVPCを削除してしまっている場合は次のコマンドを CloudShellを利用して実行してください。
デフォルトVPCを作成する事が出来ます。
aws ec2 create-default-vpc
このシステムをデプロイするために、Git Cloneを行ってCloud9にプログラムを持ってきます。
Cloud9のコンソールを開いて次のコマンドを実行してください。
git clone https://github.com/seike460/serverless-performance-handson
X-rayのトレーシングが出来るように以下コマンドを実行します。
cd serverless-performance-handson/lambda
npm install
その後AWS SAM( https://aws.amazon.com/jp/serverless/sam )を利用してビルドします。
ビルド -> デプロイの流れを取らないと、変更が反映されないので注意してください。
cd ../
sam build
Build Succeeded
と表示されていればOKです。
続いてデプロイします。
sam deploy --guided
このコマンドを実行すると、対話形式でAWSアカウント上に複数のリソースを生成・デプロイします。 質問内容と回答の例を次に用意しましたので、参考にしながら操作を完了させてください。
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [serverless-performance-handson]: serverless-performance-handson
AWS Region [ap-northeast-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N
PurchaseHandler has no authentication. Is this okay? [y/N]: y
HistoryHandler has no authentication. Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
...
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
基本的には、スタック名(Stack Name)以外はほぼ全てにY
と回答してもらえればOKです。
デプロイに成功すると、次のようなリソースがAWSアカウント内に生成されます。
CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HistoryHandlerHistoryEventPermissionProd AWS::Lambda::Permission N/A
+ Add HistoryHandlerRole AWS::IAM::Role N/A
+ Add HistoryHandler AWS::Lambda::Function N/A
+ Add PurchaseHandlerPurchaseEventPermissionProd AWS::Lambda::Permission N/A
+ Add PurchaseHandlerRole AWS::IAM::Role N/A
+ Add PurchaseHandler AWS::Lambda::Function N/A
+ Add PurchaseHistoryTable AWS::DynamoDB::Table N/A
+ Add ServerlessRestApiDeployment2593192fda AWS::ApiGateway::Deployment N/A
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A
+ Add ServerlessRestApi AWS::ApiGateway::RestApi N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
デプロイに成功すると、最後に次のようなメッセージが表示されます。
CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key ApiUrl
Description API Gateway endpoint URL for Prod stage
Value https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
このURLをワークショップでは利用しますので、自分のURLをコピーしておいてください。 ... ※①
またインストールに時間がかかる 負荷試験ツールであるArtilleryをインストールします。
npm install -g artillery
ここまで行ったら説明を挟みます。
実際に負荷をかける事で、どこにボトルネックが存在するかを炙り出しましょう。
load-first-test.ymlを編集して※①でコピーしておいたURLをtargetに設定しましょう。
config:
- target: 'https://XxxxxxxxxX.execute-api.ap-northeast-1.amazonaws.com/Prod/'
+ target: 'https://【あなた独自のURL】.execute-api.ap-northeast-1.amazonaws.com/Prod/'
phases:
その後、以下のコマンドを利用して負荷をかけます。
Cloud9からリクエストが飛んで、先程デプロイしたAPIに負荷がかかります。
artillery run load-first-test.yml
結果を見てみると、http.codes.502(Bad Gateway)が出てる方もいらっしゃるのではないでしょうか。
--------------------------------
Summary report @ 23:50:10(+0000)
--------------------------------
http.codes.200: ................................................................ 2499
http.codes.502: ................................................................ 1101
負荷が高すぎて、APIが耐えれなかったという事になります。
後ほど結果比較出来るようにhttp.response_timeを記録しておきましょう。
http.response_time:
min: ......................................................................... 30
max: ......................................................................... 2489
mean: ........................................................................ 761.4
median: ...................................................................... 1022.7
p95: ......................................................................... 1249.1
p99: ......................................................................... 2143.5
応答時間: min、max、mean、median、p95、p99は応答時間の指標です。特にmean(平均応答時間)とp95(95パーセンタイル応答時間)が重要です。 これらの値が低いほど、システムの応答性が良いことを示します。
その後X-rayの画面に移動して、ボトルネックを特定してみます。
マネコン上部の検索バーで「X-Ray」を検索してクリックし、X-Rayサービスのコンソールに移動します。
その後[X-Ray traces]内にあるTrace Mapをクリックしましょう。
東京リージョンにデプロイしている場合は、こちらのURLから直接移動することもできます。
Trace Mapが表示されているはずです。
ここで購入履歴の表示mp「serverless-performance-handson-HistoryHandler-XXXXXXX」が大量にエラーになっている事がわかります。
なおかつDynamoDBに対しての接続でエラーとなっているようです。
ソースコードを見てみると、じつはこのプログラムはDynamoDBを全Scanしている事がわかりました。
const params = {
TableName: tableName,
};
const command = new ScanCommand(params);
const data = await dynamoDBClient.send(command);
すべてのデータを取得していると考えると、改善の余地がありますね
続いて購入処理の「serverless-performance-handson-PurchaseHandler-XXXXXXXX」を見ていると購入処理は成功しているのですが、1秒ほどかかっている事がわかります。
どこで時間がかかっているかを特定するために、トレースの分析を押します。
「トレースのリスト」が下部にあるのでそちらの一番時間がかかっているものをクリックしてみます
セグメントのタイムラインを見てみると、DynamoDBではなくて処理に時間がかかっているようです。
ソースコードを見てみると、「ここで別システムに対して購入処理を行っている」というコメントを見つけました。
これは私達ではどうしようもありません。 せめて購入時の体験を良くするために、SQSを利用した購入体験を向上させましょう。
SQSについては、builders.flashに入門記事がありますので、ぜひこちらもお試しください。
それでは改善版のプログラムをデプロイします。
まずCloud9のターミナルで、現在いるディレクトリを確認しましょう。
pwd
/home/ec2-user/environment/serverless-performance-handson
もし他のディレクトリ名が表示された場合は、一旦serverless-performance-handson
内まで移動してください。
その後、以下のコマンドを実行して、すべてのプログラムを入れ替えます。
mv lambda first_lambda
mv Performance/* .
これでAWS Lambdaのソースコードを、新しいものに入れ替えることができました。
ls -la
コマンドを実行すると、first_lambda
とlambda
ディレクトリの両方があることが確認できます。
ls -la
total 56
drwxr-xr-x. 7 ec2-user ec2-user 16384 Mar 2 00:14 .
drwxr-xr-x. 4 ec2-user ec2-user 72 Mar 1 23:39 ..
drwxr-xr-x. 5 ec2-user ec2-user 62 Mar 1 23:40 .aws-sam
drwxr-xr-x. 8 ec2-user ec2-user 163 Mar 1 23:39 .git
-rw-r--r--. 1 ec2-user ec2-user 3484 Mar 1 23:39 .gitignore
drwxr-xr-x. 2 ec2-user ec2-user 6 Mar 2 00:14 Performance
-rw-r--r--. 1 ec2-user ec2-user 11806 Mar 1 23:39 README.md
drwxr-xr-x. 3 ec2-user ec2-user 140 Mar 1 23:39 first_lambda
-rw-r--r--. 1 ec2-user ec2-user 356 Mar 1 23:39 generate-payload.js
drwxr-xr-x. 2 ec2-user ec2-user 92 Mar 1 23:39 lambda
Lambdaの中身が変わりましたので、再びビルドを行います。
sam build
Build Succeeded
とメッセージが表示されれば、ビルド成功です。続いてデプロイも実行しましょう。
sam deploy
デプロイに成功すると、最後に次のようなメッセージが表示されます。
CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key ApiUrl
Description API Gateway endpoint URL for Prod stage
Value https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
URLが再度出力されるので、今度はload-test.yml
のtargetにコピペします。
config:
- target: 'https://XxxxxxxxxX.execute-api.ap-northeast-1.amazonaws.com/Prod/'
+ target: 'https://【あなた独自のURL】.execute-api.ap-northeast-1.amazonaws.com/Prod/'
phases:
その後、負荷試験を行います。
artillery run load-test.yml
すると明らかに改善が見られる事がわかると思います。
http.response_time:
min: ......................................................................... 0
max: ......................................................................... 1588
mean: ........................................................................ 50.8
median: ...................................................................... 27.9
p95: ......................................................................... 67.4
p99: ......................................................................... 1380.5
http.codes.502
もほとんど出なくなったのではないかと思います。
--------------------------------
Summary report @ 00:30:45(+0000)
--------------------------------
http.codes.200: ................................................................ 1754
http.codes.202: ................................................................ 1846
http.downloaded_bytes: ......................................................... 317151
http.request_rate: ............................................................. 61/sec
購入履歴の表示はDynamoDBはScanからQueryに変更されています。
const params = {
TableName: tableName,
KeyConditionExpression: "customerId = :customerId",
ExpressionAttributeValues: {
":customerId": { S: customerId }
}
};
try {
const command = new QueryCommand(params);
今回はパーティションキーのみの指定ですが、更にソートキーやセカンダリインデックスを活用する事でパフォーマンスの向上が見込めます。
また購入処理はSQSにデータを送信しておいて、バックエンド処理でDynamoDBに保存されるようになっています。
const message = { body: event.body, timestamp: new Date().toISOString() };
const command = new SendMessageCommand({
QueueUrl: queueUrl,
MessageBody: JSON.stringify(message)
});
await client.send(command);
await new Promise(resolve => setTimeout(resolve, 1000)); // 1秒遅延
const message = JSON.parse(record.body)
const purchaseData = JSON.parse(message.body);
const params = {
TableName: tableName,
Item: {
customerId: { S: purchaseData.customerId },
orderId: { S: purchaseData.orderId },
orderDate: { S: message.timestamp },
items: { S: JSON.stringify(purchaseData.items) },
totalAmount: { N: purchaseData.totalAmount.toString() }
}
};
try {
await client.send(new PutItemCommand(params));
console.log(`Purchase record saved: ${purchaseData.orderId}`);
} catch (error) {
console.error(`Error saving purchase record:`, error);
}
});
購入処理は自分たちではどうしようもないのですが、バックエンドに処理を送ることでユーザー体験は明らかに改善が見込めました。
ここから先は時間がある方が行ってみてください。
購入履歴の表示を更に改善してみましょう。
今回購入履歴は、1分間キャシュを聞かせても良いという事になったと仮定します。
そこで、API Gatewayのキャッシュをつかって更に負荷を軽減しましょう
template.yamlの CacheClusterEnabled
のあたりのコメントアウトを解除してデプロイします。
その後デプロイを行い、負荷試験を行います。
sam build
sam deploy
artillery run load-test.yml
するとDynamoDBの読込処理が異常なまでに少なくなっている事がわかると思います。
sam delete
Are you sure you want to delete the stack serverless-ec-shop in the region ap-northeast-1 ? [y/N]: y
Are you sure you want to delete the folder serverless-ec-shop in S3 which contains the artifacts? [y/N]: y
最後にCloud9を削除しておしまいです。 お疲れ様でした!