AWS CDK で Lambda 関数に Provisioned Concurenncy を Application Auto Scaling と一緒に設定してみた

2021.11.01

はじめに

プロフィールビューアーサービスProflly(プロフリー)の開発にて、一部の Lambda の処理のコールドスタートが影響し、パフォーマンスの問題を抱えていました。 この問題を解決するために、Provisioned Concurenncy を導入してみることにしたのですが、なるべくコストも抑えたかったので、合わせて Application Auto Scaling を設定し、利用者が多い時間帯(08:00 - 20:00)だけ有効になるように設定してみましたので、その実装方法を紹介します。

作成するアーキテクチャ

今回作成するのはシンプルに決まった時間帯だけ、Provisioned Concurenncy が有効になるように、Application Auto Scaling を設定します。08:00 にスケールアウトするようにminCapacity: 1, maxCapacity: 1を設定し、20:00 にスケールインするように minCapacity: 0, maxCapacity: 0を設定します。

環境

  • AWS CDK 1.130.0
  • TypeScript 3.9.10

実装内容

利用するパッケージをインストール

今回作成するアーキテクチャに必要なパッケージをインストールします。

npm install --save-dev @aws-cdk/aws-lambda-nodejs @aws-cdk/aws-applicationautoscaling

スタックの実装

スタックの中で各 Construt を定義して、リソースを作成します。 Provisioned Concurenncy は特定のバージョンまたはエイリアスに対して設定することができる($LATEST は不可)ので、今回は最新バージョンのエイリアスを作成して設定します。

lib/provisioned-concurrency-autoscaling-cdk-sample-stack.ts

import * as cdk from "@aws-cdk/core";
import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";
import { Schedule } from "@aws-cdk/aws-applicationautoscaling";

export class ProvisionedConcurrencyAutoscalingCdkSampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda 関数を作成
    const sampleLambda = new NodejsFunction(this, "sampleLambda", {
      entry: "src/index.ts",
      handler: "handler",
    });

    // currentVersion でエイリアスを作成
    const alias = sampleLambda.currentVersion.addAlias("currentVersion");

    // スケーラブルターゲットを作成
    const scalableAttribute = alias.addAutoScaling({
      minCapacity: 1,
      maxCapacity: 1,
    });

    // JST の 20:00 にスケールインするアクションを作成
    scalableAttribute.scaleOnSchedule("sampleLambdaScaleIn", {
      minCapacity: 0,
      maxCapacity: 0,
      schedule: Schedule.cron({ minute: "0", hour: "11" }),
    });

    // JST の 08:00 にスケールアウトするアクションを作成
    scalableAttribute.scaleOnSchedule("sampleLambdaScaleOut", {
      minCapacity: 1,
      maxCapacity: 1,
      schedule: Schedule.cron({ minute: "0", hour: "23" }),
    });
  }
}

デプロイ後のリソースを確認

以下のコマンドでデプロイを実行し、実行結果を確認してみます。

cdk deploy

作成した Lambda 関数の currentVersion エイリアスに Provisioned Concurenncy が設定されていることを確認できました。

スケーラブルターゲットおよび各スケーリングアクションが設定されていることを確認(AWS CLIにて)できました。

$ aws application-autoscaling describe-scalable-targets \
--service-namespace lambda
...
{
    "ServiceNamespace": "lambda",
    "ResourceId": "function:ProvisionedConcurrencyAutosca-sampleLambdaBA9DE42C-1vtOI2HB2pLx:currentVersion",
    "ScalableDimension": "lambda:function:ProvisionedConcurrency",
    "MinCapacity": 1,
    "MaxCapacity": 1,
    "RoleARN": "arn:aws:iam::XXXXXXXX:role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency",
    "CreationTime": "2021-10-30T09:07:57.052000+09:00",
    "SuspendedState": {
        "DynamicScalingInSuspended": false,
        "DynamicScalingOutSuspended": false,
        "ScheduledScalingSuspended": false
    }
}
...


$ aws application-autoscaling describe-scheduled-actions \
--service-namespace lambda
...
{
    "ScheduledActionName": "sampleLambdaScaleOut",
    "ScheduledActionARN": "arn:aws:autoscaling:ap-northeast-1:XXXXXXXX:scheduledAction:77f0bd55-cb28-45d5-a6bf-19bbb33807ad:resource/lambda/function:ProvisionedConcurrencyAutosca-sampleLambdaBA9DE42C-1vtOI2HB2pLx:currentVersion:scheduledActionName/sampleLambdaScaleOut",
    "ServiceNamespace": "lambda",
    "Schedule": "cron(0 23 * * ? *)",
    "ResourceId": "function:ProvisionedConcurrencyAutosca-sampleLambdaBA9DE42C-1vtOI2HB2pLx:currentVersion",
    "ScalableDimension": "lambda:function:ProvisionedConcurrency",
    "ScalableTargetAction": {
        "MinCapacity": 1,
        "MaxCapacity": 1
    },
    "CreationTime": "2021-10-30T15:12:05.932000+09:00"
},
{
    "ScheduledActionName": "sampleLambdaScaleIn",
    "ScheduledActionARN": "arn:aws:autoscaling:ap-northeast-1:XXXXXXXX:scheduledAction:77f0bd55-cb28-45d5-a6bf-19bbb33807ad:resource/lambda/function:ProvisionedConcurrencyAutosca-sampleLambdaBA9DE42C-1vtOI2HB2pLx:currentVersion:scheduledActionName/sampleLambdaScaleIn",
    "ServiceNamespace": "lambda",
    "Schedule": "cron(0 11 * * ? *)",
    "ResourceId": "function:ProvisionedConcurrencyAutosca-sampleLambdaBA9DE42C-1vtOI2HB2pLx:currentVersion",
    "ScalableDimension": "lambda:function:ProvisionedConcurrency",
    "ScalableTargetAction": {
        "MinCapacity": 0,
        "MaxCapacity": 0
    },
    "CreationTime": "2021-10-30T15:12:05.694000+09:00"
}
...

さいごに

Lambda のコールドスタート対策として、Provisioned Concurenncy を導入したいけど、コストを抑えたい、効果の高い時間帯だけスケールしたいなどを実現したい際に、Application Auto Scaling も合わせて設定しておくことで、より高い効果を得られると感じました。
今回は一部の処理にのみ導入してみましたが、ここの処理だけはなるべくコールドスタートしてほしくないといった処理に設定するのは、有効な対応かなと思いました。
部分的に導入してみて、すごくよい効果を得ることができたから、全部の Lambda に設定しよう!というのは、コスト面やパフォーマンス面的にもメリットが薄くなるかなと思います。その際は、この処理は Lambda でいいんだっけ?というのを今一度考えてみてもよいかなと思います。
どなたかの参考になれば幸いです。

参考