この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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の実行時間削減によるコスト最適化に活用していきたいですね