Search code examples
amazon-web-servicesaws-lambdaaws-billing

Does AWS Lambda charge for the time spent initializing code?


If my Lambda function written in Python takes 1.8 seconds to initialize (during a cold start) and 400 ms to execute, am I charged for the 400 ms execution time or the entire 2.2 seconds of initialization + execution time?

From X-Ray, I see:

AWS X-Ray trace

From CloudWatch logs, I see:

Duration: 404.42 ms Billed Duration: 500 ms Memory Size: 448 MB Max Memory Used: 113 MB

What I understand from this is that I was billed for 500ms of execution time, so does that mean code initialization (e.g. importing stuff) is free?


Solution

  • So I decided to try and figure it out myself with a little experiment. I created a Lambda function using Python 2.7 with 128 MB of RAM, timeout of 15 seconds and active tracing enabled. I modified the sample code to add a 10 second sleep right after the import statement:

    print "starting import"
    import json
    from time import sleep
    sleep(10)
    print "calling handler"
    
    def lambda_handler(event, context):
        return {
            'statusCode': 200,
            'body': json.dumps('Hello from Lambda!')
        }
    

    Since the Lambda started cold, I saw this in the X-ray output: AWS X-ray output - cold start

    And I saw this in CloudWatch logs:

    22:06:47 starting import
    22:06:57 calling handler
    22:06:58 START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
    22:06:58 starting import
    22:07:08 calling handler
    22:07:08 END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    22:07:08 REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Duration: 10022.57 ms   Billed Duration: 10100 ms Memory Size: 128 MB   Max Memory Used: 19 MB
    

    The function actually ran TWICE. After sleeping for 10 seconds the first time, it re-started when the handler method was called, essentially taking 20 seconds to finish execution but billing me for 10 seconds.

    I ran it again, this time a warm-start and I got this:

    X-ray output (warm start): AWS X-ray output - warm start

    CloudWatch logs (warm start):

    22:23:16 START RequestId: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy Version: $LATEST
    22:23:16 END RequestId: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
    22:23:16 REPORT RequestId: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy Duration: 6.97 ms   Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 29 MB
    

    Nothing suspicious there. I increased the function memory to 192 MB, saved it and reverted it back to 128 MB and saved it again to ensure that it'd start cold again and invoked it once again. The output of X-ray was the same as before but CloudWatch logs had something interesting:

    22:30:13 starting import
    22:30:24 START RequestId: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz Version: $LATEST
    22:30:24 starting import
    22:30:34 calling handler
    22:30:34 END RequestId: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
    22:30:34 REPORT RequestId: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz Duration: 10010.85 ms   Billed Duration: 10100 ms Memory Size: 128 MB   Max Memory Used: 19 MB
    

    It seems while my code was in the middle of sleeping for 10 seconds, Lambda cut it off and re-started it. The execution time was again 20 seconds but I was billed for 10 seconds. So I thought what if instead of 1 sleep statement, I add 15 one second sleeps?

    Updated code:

    print "starting import"
    import json
    from time import sleep
    for i in range(1, 16):
        sleep(1)
        print "completed {}th sleep".format(i)
    
    print "calling handler"
    def lambda_handler(event, context):
        return {
            'statusCode': 200,
            'body': json.dumps('Hello from Lambda!')
        }
    

    The function timed out!

    X-ray output: enter image description here

    CloudWatch logs:

    22:51:54 starting import
    22:51:55 completed 1th sleep
    22:51:56 completed 2th sleep
    22:51:57 completed 3th sleep
    22:51:58 completed 4th sleep
    22:51:59 completed 5th sleep
    22:52:00 completed 6th sleep
    22:52:01 completed 7th sleep
    22:52:02 completed 8th sleep
    22:52:03 completed 9th sleep
    22:52:04 START RequestId: 11111111-1111-1111-1111-111111111111 Version: $LATEST
    22:52:04 starting import
    22:52:05 completed 1th sleep
    22:52:06 completed 2th sleep
    22:52:07 completed 3th sleep
    22:52:08 completed 4th sleep
    22:52:09 completed 5th sleep
    22:52:10 completed 6th sleep
    22:52:11 completed 7th sleep
    22:52:12 completed 8th sleep
    22:52:13 completed 9th sleep
    22:52:14 completed 10th sleep
    22:52:15 completed 11th sleep
    22:52:16 completed 12th sleep
    22:52:17 completed 13th sleep
    22:52:18 completed 14th sleep
    22:52:19 END RequestId: 11111111-1111-1111-1111-111111111111
    22:52:19 REPORT RequestId: 11111111-1111-1111-1111-111111111111 Duration: 15015.16 ms   Billed Duration: 15000 ms Memory Size: 192 MB   Max Memory Used: 19 MB
    22:52:19
    2019-03-29T22:52:19.621Z 11111111-1111-1111-1111-111111111111 Task timed out after 15.02 seconds
    22:52:19 starting import
    22:52:20 completed 1th sleep
    22:52:21 completed 2th sleep
    22:52:22 completed 3th sleep
    22:52:23 completed 4th sleep
    22:52:24 completed 5th sleep
    22:52:25 completed 6th sleep
    22:52:26 completed 7th sleep
    22:52:27 completed 8th sleep
    22:52:28 completed 9th sleep
    22:52:29 completed 10th sleep
    

    It actually executed for 25.8 seconds but then timed out and billed me for 15 seconds. The code that executes before the handler is called ran for about 9 seconds then Lambda cut it off and re-started the function but didn't finish and ultimately timed out after 25.8 seconds. If I increase the Lambda timeout to 16 seconds, it finished executing in 25.8 seconds (as shown in X-Ray) and billed me for 15100 ms.

    So this leads me to believe that if the handler function isn't called within about 9-10 seconds after initialization, Lambda will restart the function. So what if the code initialization takes less than 10 seconds?

    Updated code:

    print "starting import"
    import json
    from time import sleep
    for i in range(1, 10):
        sleep(1)
        print "completed {}th sleep".format(i)
    
    print "calling handler"
    def lambda_handler(event, context):
        return {
            'statusCode': 200,
            'body': json.dumps('Hello from Lambda!')
        }
    

    I ran this function cold for about 10 times and my billed duration was always 100 ms. I even changed my lambda timeout to 1 second and it still finished executing successfully!

    X-Ray output: enter image description here

    CloudWatch logs:

    23:23:43 starting import
    23:23:44 completed 1th sleep
    23:23:45 completed 2th sleep
    23:23:46 completed 3th sleep
    23:23:47 completed 4th sleep
    23:23:48 completed 5th sleep
    23:23:49 completed 6th sleep
    23:23:50 completed 7th sleep
    23:23:51 completed 8th sleep
    23:23:52 completed 9th sleep
    23:23:52 calling handler
    23:23:52 START RequestId: 22222222-2222-2222-2222-222222222222 Version: $LATEST
    23:23:52 END RequestId: 22222222-2222-2222-2222-222222222222
    23:23:52 REPORT RequestId: 22222222-2222-2222-2222-222222222222 Duration: 0.73 ms   Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 44 MB
    

    As Steve HOUEL rightfully pointed out, this leads me to believe that Lambda won't charge you for the time it takes to initialize your code (e.g. importing stuff) as long as it finishes in about 9 seconds. However, if it takes longer than that, Lambda re-starts your function and assuming you set a large enough timeout, function execution effectively takes 10 seconds + regular cold start execution time but you are still billed for just the cold start execution time without the added 10 seconds.