Skip to content

seike460/serverless-performance-handson

Repository files navigation

serverless-ec-shop

手順1

まずは高速化が行われていないシステムをデプロイします。
このシステムはECショップのAPIを提供しており、購入機能、購入履歴の表示機能があります。

JAWS drawio

今回権限不足によるワークショップの失敗を防ぐ為に、AdministratorAccessをもつIAMを作成します。
本来は最小権限の原則に基づき必要な権限のみ設定するようにしてください。

マネコン上部の検索バーで「IAM」を検索してクリックし、AWS IAMサービスのコンソールに移動します。
IAMコンソール左側の「ユーザー」をクリックし、画面右側の「ユーザーの作成」をクリックします。

1

ユーザー名:お好きなあなたのユーザー名(handsonなど)
「AWS マネジメントコンソールへのユーザーアクセスを提供する」にチェックを入れる
コンソールパスワード:「カスタムパスワード」を選択しお好きなパスワードを入力
「ユーザーは次回のサインイン時に新しいパスワードを作成する必要があります」のチェックを外す

2

「次へ」をクリックし、「ポリシーを直接アタッチする」を選択して、許可ポリシーから「AdministratorAccess」を検索してチェックを入れます。

3

「次へ」をクリックすると確認画面になるので、「ユーザーの作成」をクリックします。
その後、マネコン右上のAWSアカウント名をクリックして「サインアウト」をクリックします。

ログイン後、Cloud9を作成します。

マネコン画面上部の検索バーから「Cloud9」を検索してクリックし、Cloud9サービスのコンソールへ移動します。
「環境を作成」をクリックして、新たな開発環境用のEC2インスタンスをセットアップします。

インスタンスタイプを「t3.small (2 GiB RAM + 2 vCPU)」

名前はお好きなものを使ってください。
その他はデフォルトのままで作成をクリックしてください。

もしデフォルトVPCを削除してしまっている場合は次のコマンドを CloudShellを利用して実行してください。
デフォルトVPCを作成する事が出来ます。

aws ec2 create-default-vpc
4

このシステムをデプロイするために、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

ここまで行ったら説明を挟みます。

手順2

実際に負荷をかける事で、どこにボトルネックが存在するかを炙り出しましょう。
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をクリックしましょう。

スクリーンショット 2024-03-02 8 51 29

東京リージョンにデプロイしている場合は、こちらのURLから直接移動することもできます。

https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#xray:service-map/map

5

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);

すべてのデータを取得していると考えると、改善の余地がありますね

6

続いて購入処理の「serverless-performance-handson-PurchaseHandler-XXXXXXXX」を見ていると購入処理は成功しているのですが、1秒ほどかかっている事がわかります。

スクリーンショット 2024-03-02 8 53 27

どこで時間がかかっているかを特定するために、トレースの分析を押します。 スクリーンショット 2024-03-02 8 58 04

「トレースのリスト」が下部にあるのでそちらの一番時間がかかっているものをクリックしてみます

スクリーンショット 2024-03-02 8 58 21

セグメントのタイムラインを見てみると、DynamoDBではなくて処理に時間がかかっているようです。

スクリーンショット 2024-03-02 8 58 49

ソースコードを見てみると、「ここで別システムに対して購入処理を行っている」というコメントを見つけました。

これは私達ではどうしようもありません。 せめて購入時の体験を良くするために、SQSを利用した購入体験を向上させましょう。

SQSについては、builders.flashに入門記事がありますので、ぜひこちらもお試しください。

手順3

それでは改善版のプログラムをデプロイします。
まずCloud9のターミナルで、現在いるディレクトリを確認しましょう。

pwd
/home/ec2-user/environment/serverless-performance-handson

もし他のディレクトリ名が表示された場合は、一旦serverless-performance-handson内まで移動してください。

その後、以下のコマンドを実行して、すべてのプログラムを入れ替えます。

mv lambda first_lambda
mv Performance/* .

これでAWS Lambdaのソースコードを、新しいものに入れ替えることができました。 ls -laコマンドを実行すると、first_lambdalambdaディレクトリの両方があることが確認できます。

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);
    }
  });

購入処理は自分たちではどうしようもないのですが、バックエンドに処理を送ることでユーザー体験は明らかに改善が見込めました。

手順4

ここから先は時間がある方が行ってみてください。

購入履歴の表示を更に改善してみましょう。
今回購入履歴は、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を削除しておしまいです。 お疲れ様でした!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published