【アップデート】CodeGuru ProfilerがPythonアプリ向けのレコメンデーションをサポートするようになりました

Pythonアプリのチューニングが捗るアップデートです
2021.08.13

CX事業本部@大阪の岩田です。CodeGuru Profilerのレコメンデーションは元々Javaのみサポートされていましたが、本日のアップデートでCodeGuru ProfilerがPythonアプリ向けのレコメンデーションをサポートするようになりました。

すでにCodeGuru Profilerを利用している場合は、特に設定変更やコード修正無しでそのままRecomendationが利用できます。実際にマネコンから確認してみたところ、本リリース以前である7月末に収集したプロファイル情報に対してレコメンデーションレポートが作成されていました。

やってみる

実際にレコメンデーションを試してみましょう。今回はLambdaの実装でやってしまいがちなboto3のresource/clientを再利用せずに都度生成する実装をチェックしてみます。

ソースコードの準備とデプロイ

試しにServerless FrameworkのExampleからAPI GW × Lambda × DynamoDBを利用した簡単なREST APIを作成するアプリaws-python-rest-api-with-dynamodbを微修正したアプリをデプロイして確認してみます。

まずソースコードを一部改変し、Lambdaのhandler内でboto3にresourceクラスを生成するように修正します

$ git diff aws-python-rest-api-with-dynamodb/todos/list.py
diff --git a/aws-python-rest-api-with-dynamodb/todos/list.py b/aws-python-rest-api-with-dynamodb/todos/list.py
index b133210..11ef307 100644
--- a/aws-python-rest-api-with-dynamodb/todos/list.py
+++ b/aws-python-rest-api-with-dynamodb/todos/list.py
@@ -3,10 +3,11 @@ import os

 from todos import decimalencoder
 import boto3
-dynamodb = boto3.resource('dynamodb')
+# dynamodb = boto3.resource('dynamodb')


 def list(event, context):
+    dynamodb = boto3.resource('dynamodb')
     table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

     # fetch all todos from the database

ソースコード全体は以下のようになります

import json
import os

from todos import decimalencoder
import boto3
# dynamodb = boto3.resource('dynamodb')


def list(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

    # fetch all todos from the database
    result = table.scan()

    # create a response
    response = {
        "statusCode": 200,
        "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder)
    }

    return response

続いてserverless.ymlを修正し、CodeGuru Profilerを利用するための設定を追加します。デプロイを簡略化するために、AWSが提供しているLambda Layersを利用する方法を採用しています

$ git diff aws-python-rest-api-with-dynamodb/serverless.yml
diff --git a/aws-python-rest-api-with-dynamodb/serverless.yml b/aws-python-rest-api-with-dynamodb/serverless.yml
index fac75da..27e6b86 100644
--- a/aws-python-rest-api-with-dynamodb/serverless.yml
+++ b/aws-python-rest-api-with-dynamodb/serverless.yml
@@ -7,6 +7,11 @@ provider:
   runtime: python3.8
   environment:
     DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
+    AWS_CODEGURU_PROFILER_GROUP_NAME: !Ref ProfilingGroup
+    AWS_LAMBDA_EXEC_WRAPPER: /opt/codeguru_profiler_lambda_exec
+  layers:
+    - arn:aws:lambda:ap-northeast-1:157417159150:layer:AWSCodeGuruProfilerPythonAgentLambdaLayer:10
+  region: ap-northeast-1
   iamRoleStatements:
     - Effect: Allow
       Action:
@@ -17,7 +22,11 @@ provider:
         - dynamodb:UpdateItem
         - dynamodb:DeleteItem
       Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
-
+    - Effect: Allow
+      Action:
+        - codeguru-profiler:ConfigureAgent
+        - codeguru-profiler:PostAgentProfile
+      Resource: "*"
 functions:
   create:
     handler: todos/create.create
@@ -77,3 +86,11 @@ resources:
           ReadCapacityUnits: 1
           WriteCapacityUnits: 1
         TableName: ${self:provider.environment.DYNAMODB_TABLE}
+    ProfilingGroup:
+      Type: AWS::CodeGuruProfiler::ProfilingGroup
+      Properties:
+        ProfilingGroupName: MyProfilingGroup
+        ComputePlatform: AWSLambda
+        AgentPermissions:
+          Principals:
+            - !GetAtt IamRoleLambdaExecution.Arn
\ No newline at end of file

serverless.ymlの全体です

service: serverless-rest-api-with-dynamodb

frameworkVersion: ">=1.1.0 <=2.1.1"

provider:
  name: aws
  runtime: python3.8
  environment:
    DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
    AWS_CODEGURU_PROFILER_GROUP_NAME: !Ref ProfilingGroup
    AWS_LAMBDA_EXEC_WRAPPER: /opt/codeguru_profiler_lambda_exec
  layers:
    - arn:aws:lambda:ap-northeast-1:157417159150:layer:AWSCodeGuruProfilerPythonAgentLambdaLayer:10
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
    - Effect: Allow
      Action:
        - codeguru-profiler:ConfigureAgent
        - codeguru-profiler:PostAgentProfile  
      Resource: "*"
functions:
  create:
    handler: todos/create.create
    events:
      - http:
          path: todos
          method: post
          cors: true

  list:
    handler: todos/list.list
    events:
      - http:
          path: todos
          method: get
          cors: true

  get:
    handler: todos/get.get
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true

  update:
    handler: todos/update.update
    events:
      - http:
          path: todos/{id}
          method: put
          cors: true

  delete:
    handler: todos/delete.delete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true

resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
    ProfilingGroup:
      Type: AWS::CodeGuruProfiler::ProfilingGroup
      Properties: 
        ProfilingGroupName: MyProfilingGroup
        ComputePlatform: AWSLambda
        AgentPermissions:
          Principals:
            - !GetAtt IamRoleLambdaExecution.Arn

準備できたらデプロイしましょう

$sls deploy

プロファイル情報の収集

デプロイできたらプロファイル情報を収集するために、しばらくTODO一覧取得のAPIを実行します。

while true; do curl https://<API GWのエンドポイント>/todos; sleep 10; done

プロファイル結果とどの程度関連するかは分かりませんが、初期状態だとTODO一覧取得のレスポンスが空になるので、事前にTODO作成のAPIを何度か実行して適当にデータを作成してから実行しました。

レコメンデーションレポートの確認

しばらく待ったらレコメンデーションレポートの確認へ...進みたかったのですが、レポートが表示されるまでに時間がかかるので、7月末に収集していたプロファイル情報に対するレポートを確認してみました。7月末時点ではPythonアプリに対するレコメンデーションレポートがサポートされていませんでしたが、後付でレポートが生成されているところが素晴らしいですね!

期待通りboto3のresource/clientを再利用していない点がレコメンデーションとして指摘してくれました!これでLambdaのパフォーマンスを改善しつつ、コスト削減も図れそうです。

まとめ

以前Lambdaのプロファイルにかんする記事を書いたのですが、プロファイル情報の取得/分析はそれなりに面倒な作業でした。CodeGuru Profilerを利用すると、諸々の面倒な作業を全て自動でやってくれる上に推奨事項まで提示してくれて非常に便利です。うまく利用してパフォーマンス問題の事前防止やLambdaの実行時間削減によるコスト最適化に活用していきたいですね

参考