[AWS CDK] AWS Lambda の Provisioned Concurrency を平日日中帯のみ有効化する(EventBridge Scheduler 使用版)

[AWS CDK] AWS Lambda の Provisioned Concurrency を平日日中帯のみ有効化する(EventBridge Scheduler 使用版)

一部注意点あり。
2026.01.17

こんにちは、製造ビジネステクノロジー部の若槻です。

AWS Lambda 関数で Provisioned Concurrency を設定すると、指定した数の実行環境が事前にプロビジョニングされるのでコールドスタートを回避することが可能となります。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/provisioned-concurrency.html

しかし当然ながら Provisioned Concurrency を利用した場合は追加のコストが掛かります。

下記は Lambda 関数(東京リージョン、x86)のコスト比較です。Provisioned Concurrency を有効化している期間は常に 3. が課金されます。その価格はデフォルトの 2. 実行期間の 1/3 近くにも及びます。

デフォルト(最初の 60 億 GB 秒/月) Provisioned Concurrency あり
リクエスト数 リクエスト 100 万件あたり USD 0.20 リクエスト 100 万件あたり USD 0.20
実行時間 GB-秒あたり USD 0.0000166667 1 GB-秒あたり USD 0.0000125615
Provisioned Concurrency - 1 GB-秒あたり USD 0.0000053835

https://aws.amazon.com/jp/lambda/pricing/

つまり Provisioned Concurrency を有効化していると、ウォーム状態で待機しているだけで少なくないコストがかかり続けるのです。

そこで行いたいのは Provisioned Concurrency のスケーリングです。コールドスタート抑制の必要性の低い時間帯は縮退させることによりコストを最適化することが可能です。

アプローチの方法としては主に以下がありますが、今回はスケジュールベースに焦点を当てます。

よりシンプルに実装できるのは前者の Application Auto Scaling の方でしょう。実装例としてはこちらが参考になります。

一方で次のような場合に後者の EventBridge Scheduler は活用できそうです。

  • 各種スケジューラーを EventBrigde に集約したい。
  • リトライや DLQ などの失敗時の処理を柔軟に設定したい。
  • スケジュールの有効/無効を手動で簡単に操作したい。

そこで今回は、AWS Lambda の Provisioned Concurrency を EventBridge Scheduler で平日日中帯のみ有効にする設定を AWS CDK で実装してみました。

やってみた

CDK 実装

まず、Provisioned Concurrency の有効化および無効化用の EventBridge Scheduler を作成するコンストラクトです。平日8時に有効化し、毎日23時(理由は後述)に無効化することにより、平日日中帯のみコールドスタートを抑制するようにしています。

  • 有効化用スケジューラー
lib/constructs/enable-pc-scheduler/index.ts
import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as scheduler from "aws-cdk-lib/aws-scheduler";
import * as scheduler_targets from "aws-cdk-lib/aws-scheduler-targets";
import { Construct } from "constructs";

interface EnablePcSchedulerProps {
  alias: lambda.Alias;
}

/**
 * Lambda の Provisioned Concurrency を有効化するスケジューラーを作成する Construct
 */
export class EnablePcScheduler extends Construct {
  constructor(scope: Construct, id: string, props: EnablePcSchedulerProps) {
    super(scope, id);

    const { alias } = props;

    /**
     * Scheduler Target の作成
     */
    const target = new scheduler_targets.Universal({
      service: "lambda",
      action: "putProvisionedConcurrencyConfig",

      /**
       * 権限自動付与を回避するため、policyStatements で明示的に権限を指定する。
       * @see https://dev.classmethod.jp/articles/aws-cdk-eventbridge-scheduler-target-universal-iam-control/
       */
      policyStatements: [
        new iam.PolicyStatement({
          actions: ["lambda:PutProvisionedConcurrencyConfig"],
          resources: [alias.functionArn],
        }),
      ],

      input: scheduler.ScheduleTargetInput.fromObject({
        FunctionName: alias.functionName,
        Qualifier: alias.aliasName,
        ProvisionedConcurrentExecutions: 5,
      }),
    });

    /**
     * 平日8時に Provisioned Concurrency を有効化するスケジューラー
     */
    new scheduler.Schedule(this, "Default", {
      schedule: scheduler.ScheduleExpression.cron({
        minute: "0",
        hour: "8",
        weekDay: "MON-FRI",
        timeZone: cdk.TimeZone.ASIA_TOKYO,
      }),
      target,
    });
  }
}
  • 無効化用スケジューラー
lib/constructs/disable-pc-scheduler/index.ts
import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as scheduler from "aws-cdk-lib/aws-scheduler";
import * as scheduler_targets from "aws-cdk-lib/aws-scheduler-targets";
import { Construct } from "constructs";

interface DisablePcSchedulerProps {
  alias: lambda.Alias;
}

/**
 * Lambda の Provisioned Concurrency を無効化するスケジューラーを作成する Construct
 */
export class DisablePcScheduler extends Construct {
  constructor(scope: Construct, id: string, props: DisablePcSchedulerProps) {
    super(scope, id);

    const { alias } = props;

    /**
     * Scheduler Target の作成
     */
    const target = new scheduler_targets.Universal({
      service: "lambda",
      action: "deleteProvisionedConcurrencyConfig",

      /**
       * 権限自動付与を回避するため、policyStatements で明示的に権限を指定する。
       * @see https://dev.classmethod.jp/articles/aws-cdk-eventbridge-scheduler-target-universal-iam-control/
       */
      policyStatements: [
        new iam.PolicyStatement({
          actions: ["lambda:DeleteProvisionedConcurrencyConfig"],
          resources: [alias.functionArn],
        }),
      ],

      input: scheduler.ScheduleTargetInput.fromObject({
        FunctionName: alias.functionName,
        Qualifier: alias.aliasName,
      }),
    });

    /**
     * 毎日23時に Provisioned Concurrency を無効化するスケジューラー
     *
     * MEMO: 不整合により休日に Provisioned Concurrency が有効化されたままとならないように無効化は毎日実行するようにしている。
     */
    new scheduler.Schedule(this, "Default", {
      schedule: scheduler.ScheduleExpression.cron({
        minute: "0",
        hour: "23",
        weekDay: "*", // デプロイ不整合による PC 無効化漏れを防止するため毎日実行
        timeZone: cdk.TimeZone.ASIA_TOKYO,
      }),
      target,
    });
  }
}

下記は Lambda 関数およびエイリアスと、前述のコンストラクトを定義しています。

lib/sample-stack.ts
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as lambda_nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

import { DisablePcScheduler } from "./constructs/disable-pc-scheduler";
import { EnablePcScheduler } from "./constructs/enable-pc-scheduler";

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    /**
     * Lambda 関数
     */
    const sampleFunction = new lambda_nodejs.NodejsFunction(
      this,
      "SampleFunction",
      {
        entry: "src/handler.ts",
        tracing: lambda.Tracing.ACTIVE, // コールドスタート発生状況を X-Ray で確認できるようにするため有効化
      }
    );

    /**
     * Lambda エイリアス
     */
    const sampleFunctionAlias = new lambda.Alias(this, "SampleFunctionAlias", {
      aliasName: "Prod",
      version: sampleFunction.currentVersion,

      // デプロイ時の "No Provisioned Concurrency Config found for this function" エラー回避のため、
      // 既定では Provisioned Concurrency を無効にする。
    });

    /**
     * テスト呼び出し用 API Gateway Rest API Lambda プロキシ統合
     */
    new apigateway.LambdaRestApi(this, "SampleApi", {
      handler: sampleFunctionAlias, // REST API がエイリアスを呼び出すように設定
      deployOptions: {
        tracingEnabled: true, // コールドスタート発生状況を X-Ray で確認できるようにするため有効化
      },
    });

    /**
     * Lambda の Provisioned Concurrency を有効化するスケジューラー
     */
    new EnablePcScheduler(this, "EnablePcScheduler", {
      alias: sampleFunctionAlias,
    });

    /**
     * Lambda の Provisioned Concurrency を無効化するスケジューラー
     */
    new DisablePcScheduler(this, "DisablePcScheduler", {
      alias: sampleFunctionAlias,
    });
  }
}

デプロイすると次のように有効化および無効化スケジューラーが作成されます。

またデプロイ直後の時点ではエイリアスに Provisioned Concurrency は設定されていません。

$ aws lambda get-provisioned-concurrency-config \
  --function-name ${FUNCTION_NAME} \
  --qualifier Prod

An error occurred (ProvisionedConcurrencyConfigNotFoundException) when calling the GetProvisionedConcurrencyConfig operation: No Provisioned Concurrency Config found for this function

よって、明日の有効化スケジューラー実行を待たずに Provisioned Concurrency を有効化したい場合は、下記のように CLI などで手動で有効化する必要があります。

$ aws lambda put-provisioned-concurrency-config \
  --function-name ${FUNCTION_NAME} \
  --qualifier Prod \
  --provisioned-concurrent-executions 5
{
    "RequestedProvisionedConcurrentExecutions": 5,
    "AvailableProvisionedConcurrentExecutions": 0,
    "AllocatedProvisionedConcurrentExecutions": 0,
    "Status": "IN_PROGRESS",
    "LastModified": "2026-01-17T07:06:13+0000"
}

手動で有効化できました。

動作確認

スケジューラーの動作を確認してみます。

平日夜間は、無効化スケジューラが動作し、Provisioned Concurrency は設定が削除されています。

aws lambda get-provisioned-concurrency-config \
  --function-name ${FUNCTION_NAME} \
  --qualifier Prod

An error occurred (ProvisionedConcurrencyConfigNotFoundException) when calling the GetProvisionedConcurrencyConfig operation: No Provisioned Concurrency Config found for this function

CloudTrail にも23時に無効化のイベントが記録されています。

また朝8時には有効化のイベントが記録されました。

よって平日日中は、Provisioned Concurrency が指定の値 (5) で有効化されています。

$ aws lambda get-provisioned-concurrency-config \
  --function-name ${FUNCTION_NAME} \
  --qualifier Prod
{
    "RequestedProvisionedConcurrentExecutions": 5,
    "AvailableProvisionedConcurrentExecutions": 5,
    "AllocatedProvisionedConcurrentExecutions": 5,
    "Status": "READY",
    "LastModified": "2026-01-13T23:00:28+0000"
}

ここで API を呼び出して Lambda 関数エイリアスを実行してみます。

curl https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/prod/

X-Ray トレースを見るとコールドスタートが発生していないことが確認できました。

ここで Provisioned Concurrency (PC) の適用状況を確認できるメトリクスとして以下の3つがあります。

  • ProvisionedConcurrentExecutions: PC により起動された、呼び出しを処理している実行環境数
  • ProvisionedConcurrencyInvocations: PC により起動された実行環境での呼び出し数
  • ProvisionedConcurrencySpilloverInvocations: PCにより起動されていない実行環境での呼び出し数

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/monitoring-concurrency.html

関数を呼び出した後にそれぞれのメトリクスを確認すると、ProvisionedConcurrentExecutions および ProvisionedConcurrencyInvocations は 1、ProvisionedConcurrencySpilloverInvocations は 0 となり、想定通りのメトリクス状況となりました。

また、Provisioned Concurrency が設定されている場合は、関数の環境変数 AWS_LAMBDA_INITIALIZATION_TYPEprovisioned-concurrencyというフラグ値が設定されます。

該当の環境変数を返すようにハンドラーコードを修正します。

export const handler = async (): Promise<Object> => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      AWS_LAMBDA_INITIALIZATION_TYPE:
        process.env.AWS_LAMBDA_INITIALIZATION_TYPE || "N/A",
    }),
  };
};

関数を実行すると、フラグ値が設定されていることが確認できました。

$ curl https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/prod/
{"AWS_LAMBDA_INITIALIZATION_TYPE":"provisioned-concurrency"}

トラブルシュート

無効化用スケジューラーが動作した後に、Lambda 関数の何かしらの変更(ハンドラーコードの変更など)のデプロイを行うと、次のエラーが発生しました。

❌ Sample failed: ToolkitError: The stack named Sample failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [SampleFunctionAlias2CC19F6B]. ): Resource handler returned message: "No Provisioned Concurrency Config found for this function (Service: Lambda, Status Code: 404, Request ID: a56d0dc3-183a-4b2f-802e-26becb549661)

この時はエイリアスの実装で Provisioned Concurrency を既定で有効化していました。(前述の検証時のコードでは既定で無効化していた)

/**
 * Lambda エイリアス
 */
const sampleFunctionAlias = new lambda.Alias(this, "SampleFunctionAlias", {
  aliasName: "Prod",
  version: sampleFunction.currentVersion,
  provisionedConcurrentExecutions: 5,
});

Provisioned Concurrency Config が削除された状態で、Provisioned Concurrency が設定が試行されると発生するようです。この場合は Provisioned Concurrency を手動設定して、ロールバックを再開してあげる必要があります。

ちなみに無効化スケジューラーで deleteProvisionedConcurrencyConfig ではなく putProvisionedConcurrencyConfig0 を指定すれば良いのでは?と思い試してみましたが、そもそも Provisioned Concurrency に 0 は指定できませんでした。

注意するようにしましょう。

おわりに

AWS Lambda の Provisioned Concurrency を EventBridge Scheduler で平日日中帯のみ有効にする設定を AWS CDK で実装してみました。

スケジュールベースでスケールさせるだけ、と思いきや [トラブルシュート] にあるようにタイミングによっては不整合が発生するため、一筋縄ではいきませんでした。難しいですね。

また Application Auto Scaling の場合だともっと少ないコードで同様のことが実現できそうです。次回以降試してみます。

参考

  • Amazon EventBridge Scheduler について

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_Amazon-EventBridge-Scheduler_0930_v1.pdf

  • Provisioned Concurrency について

https://speakerdeck.com/_kensh/aws-lambda-provisioned-concurrency-dive-deep-and-practice

以上

この記事をシェアする

FacebookHatena blogX

関連記事