Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(aws-lambda): inline dynamic chunks to reduce first invokation time #650

Closed

Conversation

WinterYukky
Copy link
Contributor

@WinterYukky WinterYukky commented Nov 13, 2022

πŸ”— Linked issue

None

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

This PR will reduce AWS Lambda runtime and improve cost savings and performance for customers.

By default, inlineDynamicImports is false in nitro. Therefore, if we define an API that uses any module under the server/api directory and run it, the module will be loaded at runtime and the first time execution will take a long time. In fact, I created and run an API that just scans DynamoDB using aws-sdk v3, it will take more than 3 seconds the first time. This exceeds the default timeout time of AWS Lambda, which causes the API Gateway response to be an Internal Server Error. To avoid this, set inlineDynamicImports to true in the aws-lambda preset. This change speeds up the API from taking 3 seconds to 300 milliseconds.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Copy link
Member

Would you provide an example of some code that takes that additional time? Inlining dynamic imports will make the entrypoint larger, so this is paradoxical behaviour.

@pi0
Copy link
Member

pi0 commented Nov 14, 2022

Thanks for the PR. Dynamic chunks loading in general (both browser and Node) has a natural overhead. But it is essential to make timings stable when bundle size does grow while if we inline dependencies, it increases size even when not used. So whenever possible with Nitro presets, we tend to use chunks.

As @danielroe mentioned, would be nice if you can provide a public reproduction of your project to test timing differences.

@WinterYukky
Copy link
Contributor Author

WinterYukky commented Nov 14, 2022

Thank you for reviewing @danielroe @pi0. I understand what you mean, but fact is not so in AWS Lambda.
Please se the document that write about performance optimization Lambda.

Here's what the documentation says.

Libraries should be defined in the initialization code outside of the handler, so they are loaded once when the execution environment is created. This implementation causes the library to be loaded on every invocation, slowing performance.

So dynamic chunk loading may not be a good match for AWS Lambda.


Also I created public reproduction here. Please try it.
https://github.com/WinterYukky/nitro-api-gateway-has-cold-start

requires

  • Node.js 16
  • AWS CLI v2

Deploy command

To deploy, just run the following command:

git clone https://github.com/WinterYukky/nitro-api-gateway-has-cold-start.git
cd nitro-api-gateway-has-cold-start
yarn install
yarn cdk deploy

# If you want to destroy, run this command
# yarn cdk destroy

...and, my executed logs is this. Please focus the Duration.

inlineDynamicImports is false

aws lambda invoke --function-name chunked-function result.json
aws logs tail /aws/lambda/chunked-function
# REPORT RequestId: e9fb43e8-853d-4797-b9d9-3881a3be8007       Duration: 7451.13 ms    Billed Duration: 7452 ms        Memory Size: 128 MB     Max Memory Used: 95 MB     Init Duration: 224.17 ms

inlineDynamicImports is true

aws lambda invoke --function-name inline-dynamic-imports-function result.json
aws logs tail /aws/lambda/inline-dynamic-imports-function
# REPORT RequestId: 0e99c650-9930-4530-8a3a-1c581d38ed43       Duration: 537.15 ms     Billed Duration: 538 ms Memory Size: 128 MB     Max Memory Used: 93 MB     Init Duration: 601.72 ms

@pi0
Copy link
Member

pi0 commented Dec 8, 2022

Thanks for sharing links and stats @WinterYukky ❀️ Also linking this.

When assuming most of the chunks of the server bundle will be used during every invocation, inlining them makes perfect sense and reduces latency (actually faster invokation). However, when the number of server handlers (and individual libraries and other assets they use) grows, I believe this adds to the cold-start time. Also nitro function is special in the way that it is not a single function but a function dispatcher itself that invokes different handlers.

I'm positive this would be a nice idea to enable inlining by default but please let me run some more tests and check alternatives if we can keep using chunks but warmup opon code-initialization for entry critical chunks.

@pi0 pi0 changed the title perf(aws-lambda): reduction cold start perf(aws-lambda): inline dynamic chunks to reduce first invokation time Dec 8, 2022
@Hebilicious Hebilicious self-assigned this Jun 30, 2023
@Hebilicious Hebilicious self-requested a review June 30, 2023 19:30
@Hebilicious
Copy link
Member

@pi0 We could add this asnitro.options.aws so user can decide for themselves.

pi0 added a commit that referenced this pull request Jul 14, 2023
Co-Authored-By: WinterYukky <49480575+WinterYukky@users.noreply.github.com>
@pi0
Copy link
Member

pi0 commented Jul 14, 2023

I have added docs for aws deployment for now to note about inlineDynamicImports option linking to this PR (ff38dc0).

@pi0 pi0 closed this Jul 14, 2023
@pi0 pi0 mentioned this pull request Aug 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants