export

Power tuner, Python packaging and permissions issues

2024-05-28 | 7 min read
Armand Rego
AWS Lambda Power Tuning is a great tool to analyse how the speed and cost of your Lambda functions vary with configured memory. While running performance tuning on a Lambda function we built for a client's microservice, we came across an interesting edge-case involving Lambda aliases, Python package bundling and file permissions!

Lambda power tuning - what is it good for?

As AWS Lambda enthusiasts know, the primary lever for controlling function runtime performance is memory allocation, which ranges from 128MB to 10,240MB. Additionally, Lambda dynamically allocates virtual CPUs (vCPUs) in proportion to the configured memory, reaching a maximum of 1 vCPU at around 1,800MB.

However, finding the optimal memory configuration that balances performance (invocation time) and cost requirements can be a challenge. This is where AWS Lambda Power Tuning comes into play. It's a state machine that runs your function with a range of memory/power settings, collects runtime metrics, and presents them in a graphical format to empower data-driven decision making regarding the function configuration.

A graph generated by Lambda Power Tuning indicating ~2048MB of configured memory as the optimal balance between runtime latency and cost.

The issue

We recently developed a microservice for a client, deploying it as a single AWS Lambda function using Python CDK and fronting it with a Function URL. After verifying that the function was working as expected, we wanted to optimize its performance to strike an optimal balance between cost and latency.

Since our function was triggered via a Function URL, we initiated a request to the endpoint to capture the structure of the received event. We then configured this payload in Power Tuner, resulting in an input configuration similar to the example below:

1
{
2
  "lambdaARN": "arn:aws:lambda:eu-west-2:**********:function:DummyFunction",
3
  "powerValues": [
4
    512,
5
    1024,
6
    2048,
7
    4096
8
  ],
9
  "num": 10,
10
  "payload": {
11
    "requestId": "60e8b931-c47c-48b2-9394-631975124b93",
12
    "headers": {
13
      "some": "headers"
14
    },
15
    "requestContext": {
16
      "some": "context"
17
    },
18
    "body": "{\"key\":\"value\"}",
19
    "isBase64Encoded": false
20
  },
21
  "parallelInvocation": false,
22
  "strategy": "balanced"
23
}

Computer says no

With everything configured correctly, we ran the Power Tuner and were immediately confronted with an invocation error!

What the Power Tuner step function graph looked like. Red = bad.

A closer look at the logs showed that root cause was failure during the initialisation phase of the Lambda function due to the Lambda runtime not having sufficient permissions to read the files in the deployment package:

1
{
2
    "timestamp": "2024-05-20T12:19:10Z",
3
    "log_level": "ERROR",
4
    "errorMessage": "[Errno 13] Permission denied: '/var/task/main.py'",
5
    "errorType": "PermissionError",
6
    "requestId": "",
7
    "stackTrace": [
8
        "  File \"/var/lang/lib/python3.12/importlib/__init__.py\", line 90, in import_module\n    return _bootstrap._gcd_import(name[level:], package, level)\n",
9
        "  File \"<frozen importlib._bootstrap>\", line 1387, in _gcd_import\n",
10
        "  File \"<frozen importlib._bootstrap>\", line 1360, in _find_and_load\n",
11
        "  File \"<frozen importlib._bootstrap>\", line 1331, in _find_and_load_unlocked\n",
12
        "  File \"<frozen importlib._bootstrap>\", line 935, in _load_unlocked\n",
13
        "  File \"<frozen importlib._bootstrap_external>\", line 991, in exec_module\n",
14
        "  File \"<frozen importlib._bootstrap_external>\", line 1128, in get_code\n",
15
        "  File \"<frozen importlib._bootstrap_external>\", line 1186, in get_data\n"
16
    ]
17
}

A quick search for this error will yield many StackOverflow hits (see here, here and here) and it is explicitly mentioned in the AWS docs, but what made it strange in our case was the fact that the function ran without error when called via the function URL or invoked manually through the AWS Console!

The fix

After some RTFMing, we managed to get the Power Tuner to run by explicitly configuring the correct file permissions during the bundling process, as shown by the CDK code below:

1
example_function = _lambda.Function(
2
    self,
3
    "ExampleFunction",
4
    code=_lambda.Code.from_asset(
5
        path=os.path.join(os.path.dirname(__file__), "src"),
6
        bundling=BundlingOptions(
7
            image=_lambda.Runtime.PYTHON_3_12.bundling_image,
8
            command=[
9
                "bash",
10
                "-c",
11
                "&&".join(
12
                    [
13
                        "pip install -r requirements.txt --no-deps --platform manylinux2014_x86_64 -t /asset-output",
14
                        "cp -au . /asset-output",
15
                        "chmod -R 755 /asset-output",
16
                    ]
17
                ),
18
            ],
19
            platform="linux/amd64",
20
        ),
21
    ),
22
    handler="main.handler",
23
    runtime=_lambda.Runtime.PYTHON_3_12,
24
    memory_size=2048,
25
    logging_format=_lambda.LoggingFormat.TEXT,
26
)

Note the important line in the command for the bundling Docker image "chmod -R 755 /asset-output" which gives the runtime executable permissions to the files in the deployment package.

The Power Tuner step function diagram when running without error. Green = good.

Conclusion

Although we successfully got Power Tuner running, we still don't have a complete understanding of why it didn't work initially, particularly given that the function executed correctly when triggered via the Function URL or through the AWS Console.

The current hypothesis is that the process of creating separate Lambda function versions during the execution of the Power Tuner step function somehow reconfigured the file permissions, making them non-executable. If anyone has encountered this issue before and can shed light on the underlying reason for the behaviour, please don't hesitate to reach out!

© Weird Sheep Labs Ltd 2024
Weird Sheep Labs Ltd is a company registered in England & Wales (Company No. 15160367)
85 Great Portland St, London, W1W 7LT