This will be a short, but hopefully useful post for anyone trying to keep Lambda functions warm when using them in AWS Step Functions. It was not as straightforward as I initially thought – simply adjusting the provisionedConcurrency value in the function configurations will not suffice. In short, you must specify the Provisioned Concurrency alias in the step function configuration in order for it to be used by the state machine.

Let’s start with a minimal step function example:

YAML

service: step-function-test
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.10
  timeout: 60
  memorySize: 512
  versionFunctions: false
  stage: ${opt:stage, "sbx"}
  region: us-east-1

functions:
  hello:
    handler: handler.hello
  hi:
    handler: handler.hi

stepFunctions:
  stateMachines:
    helloStepFunc:
      name: 'hello'
      definition:
        Comment: "A Hello World example"
        StartAt: HelloWorld
        States:
          HelloWorld:
            Type: Task
            Resource: !GetAtt hello.Arn
            Next: HiWorld
          HiWorld:
            Type: Task
            Resource: !GetAtt hi.Arn
            End: true

plugins:
  - serverless-step-functions

These Lambda functions are of course not configured with provisionedConcurrency and may suffer from cold starts. If we simply enable it in the serverless.yml, deploy, and invoke the step function again, we won’t see an improvement. Reviewing the logs, we can identify the invoked version:

The step function is currently configured to invoke the $LATEST version of the Lambda. The docs on Configuring provisioned concurrency describe the problem:

Provisioned Concurrency is not supported on the unpublished version of the function ($LATEST). Ensure your client application is not pointing to $LATEST before configuring provisioned concurrency.

In this case, the client application is the state machine configuration. Using serverless framework’s built-in support for provisionedConcurrency, we can enable it and infer the aliases. Serverless generates the provisioned concurrency alias like this:

JavaScript

getLambdaProvisionedConcurrencyAliasLogicalId(functionName) {
    return `${this.getNormalizedFunctionName(functionName)}ProvConcLambdaAlias`;
  }

Here’s the updated serverless.yml:

YAML

service: step-function-test
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.10
  timeout: 60
  memorySize: 512
  versionFunctions: false
  stage: ${opt:stage, "sbx"}
  region: us-east-1

functions:
  hello:
    handler: handler.hello
    provisionedConcurrency: 3
  hi:
    handler: handler.hi
    provisionedConcurrency: 3

stepFunctions:
  stateMachines:
    helloStepFunc:
      name: 'hello'
      definition:
        Comment: "A Hello World example"
        StartAt: HelloWorld
        States:
          HelloWorld:
            Type: Task
            Resource: !Ref HelloProvConcLambdaAlias
            Next: HiWorld
          HiWorld:
            Type: Task
            Resource: !Ref HiProvConcLambdaAlias
            End: true

plugins:
  - serverless-step-functions

Note the AWS::Lambda::Alias resource returns the ARN directly when used with Ref.

Now we can invoke the step function with sls invoke stepf -n helloStepFunc and see the version updated in the CloudWatch logs:

Share this: