├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── THIRD-PARTY-LICENSES.txt ├── diagrams ├── lambda-optimizations.drawio ├── no-proxy.png ├── optimized-aws.png ├── optimized.png ├── proxy.png ├── unoptimized-aws.png └── unoptimized.png ├── optimized ├── .vscode │ └── settings.json ├── README.md ├── bin │ └── rds-proxy.ts ├── cdk.json ├── lambda │ ├── database │ │ ├── Cache.ts │ │ ├── DBValueObject.ts │ │ ├── DBparameters.ts │ │ ├── PostgresDB.ts │ │ └── StadiumValueObject.ts │ ├── getData.ts │ ├── populate.ts │ ├── powertools │ │ └── utilities.ts │ └── stadium-data.json ├── lib │ ├── lambda-powertuning.ts │ ├── parameters-stack.ts │ └── stadiums-stack.ts ├── package-lock.json ├── package.json ├── scripts │ ├── execute.sh │ ├── loadTestStrategy.yml │ └── params.json └── tsconfig.json └── unoptimized ├── README.md ├── bin └── rds-proxy.ts ├── cdk.json ├── lambda ├── database │ ├── DBValueObject.ts │ ├── DBparametersSDK.ts │ └── sequelize.ts ├── getData.ts ├── populate.ts ├── powertools │ └── utilities.ts └── stadium-data.json ├── lib ├── parameters-stack.ts └── stadiums-stack.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !.vscode/settings.json 3 | !.vscode/tasks.json 4 | !.vscode/launch.json 5 | !.vscode/extensions.json 6 | !.vscode/*.code-snippets 7 | 8 | # Local History for Visual Studio Code 9 | .history/ 10 | 11 | # Built Visual Studio Code Extensions 12 | *.vsix 13 | 14 | # Logs 15 | logs 16 | *.log 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | lerna-debug.log* 21 | .pnpm-debug.log* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | *.lcov 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | web_modules/ 60 | 61 | # TypeScript cache 62 | *.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Optional stylelint cache 71 | .stylelintcache 72 | 73 | # Microbundle cache 74 | .rpt2_cache/ 75 | .rts2_cache_cjs/ 76 | .rts2_cache_es/ 77 | .rts2_cache_umd/ 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # dotenv environment variable files 89 | .env 90 | .env.development.local 91 | .env.test.local 92 | .env.production.local 93 | .env.local 94 | 95 | # parcel-bundler cache (https://parceljs.org/) 96 | .cache 97 | .parcel-cache 98 | 99 | # Next.js build output 100 | .next 101 | out 102 | 103 | # Nuxt.js build / generate output 104 | .nuxt 105 | dist 106 | 107 | # Gatsby files 108 | .cache/ 109 | # Comment in the public line in if your project uses Gatsby and not Next.js 110 | # https://nextjs.org/blog/next-9-1#public-directory-support 111 | # public 112 | 113 | # vuepress build output 114 | .vuepress/dist 115 | 116 | # vuepress v2.x temp and cache directory 117 | .temp 118 | .cache 119 | 120 | # Docusaurus cache and generated files 121 | .docusaurus 122 | 123 | # Serverless directories 124 | .serverless/ 125 | 126 | # FuseBox cache 127 | .fusebox/ 128 | 129 | # DynamoDB Local files 130 | .dynamodb/ 131 | 132 | # TernJS port file 133 | .tern-port 134 | 135 | # Stores VSCode versions used for testing VSCode extensions 136 | .vscode-test 137 | 138 | # yarn v2 139 | .yarn/cache 140 | .yarn/unplugged 141 | .yarn/build-state.yml 142 | .yarn/install-state.gz 143 | .pnp.* 144 | 145 | # CDK asset staging directory 146 | .cdk.staging 147 | cdk.out 148 | *.context.json 149 | 150 | # General 151 | old/ 152 | /optimized/old 153 | **/.DS_Store 154 | .DS_Store 155 | 156 | #typescript 157 | *.js 158 | !jest.config.js 159 | !generator.js 160 | *.d.ts -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | 6 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 7 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 8 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda performance tuning 2 | 3 | ## Introduction 4 | 5 | The genesis for this solution was based on a Chalk Talk for re:invent 2023 - 6 | SVS319 AWS Lambda performance tuning: Best practices and guidance. Our goal is 7 | to take an already good API and make it great. 8 | 9 | Our focus is to create an optimized solution for a maximum number of transactions per second (TPS) using Lambda. 10 | 11 | The solution idea/starting point was from [Serverless 12 | Land](https://serverlessland.com/patterns/apigw-http-api-lambda-rds-proxy-cdk) 13 | which was a very basic solution for reading/writing records from an Aurora RDS. 14 | Using the very popular ORM (Object Relational Mapping) 15 | [sequelize](https://sequelize.org/) library, allowed Lambda to 16 | connect to Aurora RDS via RDS Proxy, write a list of NFL stadiums from a JSON file or read 17 | from the datbase. This was all invoked by an Amazon API Gatweay (HTTP), and 18 | deployed via CDK as Infrastrucure as Code (IaaC). We refer to this as the 19 | unoptimized soltuion. This solution on average took ~250ms to execute. 20 | After making a few key changes to update the solution, optmized 21 | solution, where the execution time is now ~9ms. 22 | 23 | ## More Details 24 | 25 | if you are interested to learn more about the thought process behind the decisions listed below, we highly encourage to read the related [blog post](https://community.aws/posts/improved-lambda-warm-start-speed-95) to this example 26 | 27 | ## Pre-requisites 28 | 29 | - [Node.js](https://nodejs.org/en/download/) 30 | - [AWS CLI](https://aws.amazon.com/cli/) 31 | - [AWS CDK](https://aws.amazon.com/cdk/) 32 | 33 | ## Deploying the solutions 34 | Wnat to deploy this for yourself? With CDK, this is very easy. You will find 35 | two diretories, optimized and unoptimized. You'll need to deploy each solution 36 | seprately. 37 | 38 | ### Deployment commands 39 | * `cdk bootstrap` bootstrap your environment with CDK 40 | * `cdk deploy --all` deploy this stack to your default AWS account/region 41 | 42 | ## Unoptimized Solution 43 | 44 | ![](./diagrams/unoptimized-aws.png) 45 | 46 | The unoptimized solution, contains the following: 47 | 48 | * sequlize ORM library for conecting to PostgresSQL (Amazon Aurora Postgres) 49 | * AWS SDK 50 | * RDS Proxy 51 | * Runtime: NodeJS 18 with TypeScript 52 | * Parameter Store for storing parameters 53 | * Calling AWS SDK during runtime 54 | * x86 architecture 55 | * 256MB memory size 56 | 57 | ## Optimized Solution 58 | 59 | ![](./diagrams/optimized-aws.png) 60 | 61 | The optimized solution, contains the following: 62 | 63 | * Postgres.js library for conecting to PostgresSQL (Amazon Aurora Postgres) 64 | * RDS Proxy 65 | * Runtime: NodeJS 18 with TypeScript 66 | * Parameter Store for storing parameters 67 | * Retrieving parameters through a Lambda extension 68 | * Amazon ElastiCache 69 | * Graviton 2 architecture 70 | * 1024MB memory size 71 | 72 | 73 | ## Deleting the Solution 74 | To delete the stacks via AWS Console follow these instructions: 75 | 76 | 1. Open the CloudFormation Console page and choose the relevant stack, then choose "Delete" 77 | 2. Once the confirmation modal appears, choose "Delete stack". 78 | 3. Wait for the CloudFormation stack to finish updating. Completion is indicated when the "Stack status" is "DELETE_COMPLETE" 79 | 80 | ## License 81 | This library is licensed under the MIT-0 License. See the LICENSE file. -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES.txt: -------------------------------------------------------------------------------- 1 | ** aws-cdk; version 2.33.0 -- https://github.com/aws/aws-cdk 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | * For aws-cdk see also this required NOTICE: 206 | AWS Cloud Development Kit (AWS CDK) 207 | Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 208 | 209 | 210 | ------ 211 | 212 | ** cdk-nag; version 2.22.2 -- https://github.com/cdklabs/cdk-nag 213 | 214 | Apache License 215 | Version 2.0, January 2004 216 | http://www.apache.org/licenses/ 217 | 218 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 219 | 220 | 1. Definitions. 221 | 222 | "License" shall mean the terms and conditions for use, reproduction, 223 | and distribution as defined by Sections 1 through 9 of this document. 224 | 225 | "Licensor" shall mean the copyright owner or entity authorized by 226 | the copyright owner that is granting the License. 227 | 228 | "Legal Entity" shall mean the union of the acting entity and all 229 | other entities that control, are controlled by, or are under common 230 | control with that entity. For the purposes of this definition, 231 | "control" means (i) the power, direct or indirect, to cause the 232 | direction or management of such entity, whether by contract or 233 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 234 | outstanding shares, or (iii) beneficial ownership of such entity. 235 | 236 | "You" (or "Your") shall mean an individual or Legal Entity 237 | exercising permissions granted by this License. 238 | 239 | "Source" form shall mean the preferred form for making modifications, 240 | including but not limited to software source code, documentation 241 | source, and configuration files. 242 | 243 | "Object" form shall mean any form resulting from mechanical 244 | transformation or translation of a Source form, including but 245 | not limited to compiled object code, generated documentation, 246 | and conversions to other media types. 247 | 248 | "Work" shall mean the work of authorship, whether in Source or 249 | Object form, made available under the License, as indicated by a 250 | copyright notice that is included in or attached to the work 251 | (an example is provided in the Appendix below). 252 | 253 | "Derivative Works" shall mean any work, whether in Source or Object 254 | form, that is based on (or derived from) the Work and for which the 255 | editorial revisions, annotations, elaborations, or other modifications 256 | represent, as a whole, an original work of authorship. For the purposes 257 | of this License, Derivative Works shall not include works that remain 258 | separable from, or merely link (or bind by name) to the interfaces of, 259 | the Work and Derivative Works thereof. 260 | 261 | "Contribution" shall mean any work of authorship, including 262 | the original version of the Work and any modifications or additions 263 | to that Work or Derivative Works thereof, that is intentionally 264 | submitted to Licensor for inclusion in the Work by the copyright owner 265 | or by an individual or Legal Entity authorized to submit on behalf of 266 | the copyright owner. For the purposes of this definition, "submitted" 267 | means any form of electronic, verbal, or written communication sent 268 | to the Licensor or its representatives, including but not limited to 269 | communication on electronic mailing lists, source code control systems, 270 | and issue tracking systems that are managed by, or on behalf of, the 271 | Licensor for the purpose of discussing and improving the Work, but 272 | excluding communication that is conspicuously marked or otherwise 273 | designated in writing by the copyright owner as "Not a Contribution." 274 | 275 | "Contributor" shall mean Licensor and any individual or Legal Entity 276 | on behalf of whom a Contribution has been received by Licensor and 277 | subsequently incorporated within the Work. 278 | 279 | 2. Grant of Copyright License. Subject to the terms and conditions of 280 | this License, each Contributor hereby grants to You a perpetual, 281 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 282 | copyright license to reproduce, prepare Derivative Works of, 283 | publicly display, publicly perform, sublicense, and distribute the 284 | Work and such Derivative Works in Source or Object form. 285 | 286 | 3. Grant of Patent License. Subject to the terms and conditions of 287 | this License, each Contributor hereby grants to You a perpetual, 288 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 289 | (except as stated in this section) patent license to make, have made, 290 | use, offer to sell, sell, import, and otherwise transfer the Work, 291 | where such license applies only to those patent claims licensable 292 | by such Contributor that are necessarily infringed by their 293 | Contribution(s) alone or by combination of their Contribution(s) 294 | with the Work to which such Contribution(s) was submitted. If You 295 | institute patent litigation against any entity (including a 296 | cross-claim or counterclaim in a lawsuit) alleging that the Work 297 | or a Contribution incorporated within the Work constitutes direct 298 | or contributory patent infringement, then any patent licenses 299 | granted to You under this License for that Work shall terminate 300 | as of the date such litigation is filed. 301 | 302 | 4. Redistribution. You may reproduce and distribute copies of the 303 | Work or Derivative Works thereof in any medium, with or without 304 | modifications, and in Source or Object form, provided that You 305 | meet the following conditions: 306 | 307 | (a) You must give any other recipients of the Work or 308 | Derivative Works a copy of this License; and 309 | 310 | (b) You must cause any modified files to carry prominent notices 311 | stating that You changed the files; and 312 | 313 | (c) You must retain, in the Source form of any Derivative Works 314 | that You distribute, all copyright, patent, trademark, and 315 | attribution notices from the Source form of the Work, 316 | excluding those notices that do not pertain to any part of 317 | the Derivative Works; and 318 | 319 | (d) If the Work includes a "NOTICE" text file as part of its 320 | distribution, then any Derivative Works that You distribute must 321 | include a readable copy of the attribution notices contained 322 | within such NOTICE file, excluding those notices that do not 323 | pertain to any part of the Derivative Works, in at least one 324 | of the following places: within a NOTICE text file distributed 325 | as part of the Derivative Works; within the Source form or 326 | documentation, if provided along with the Derivative Works; or, 327 | within a display generated by the Derivative Works, if and 328 | wherever such third-party notices normally appear. The contents 329 | of the NOTICE file are for informational purposes only and 330 | do not modify the License. You may add Your own attribution 331 | notices within Derivative Works that You distribute, alongside 332 | or as an addendum to the NOTICE text from the Work, provided 333 | that such additional attribution notices cannot be construed 334 | as modifying the License. 335 | 336 | You may add Your own copyright statement to Your modifications and 337 | may provide additional or different license terms and conditions 338 | for use, reproduction, or distribution of Your modifications, or 339 | for any such Derivative Works as a whole, provided Your use, 340 | reproduction, and distribution of the Work otherwise complies with 341 | the conditions stated in this License. 342 | 343 | 5. Submission of Contributions. Unless You explicitly state otherwise, 344 | any Contribution intentionally submitted for inclusion in the Work 345 | by You to the Licensor shall be under the terms and conditions of 346 | this License, without any additional terms or conditions. 347 | Notwithstanding the above, nothing herein shall supersede or modify 348 | the terms of any separate license agreement you may have executed 349 | with Licensor regarding such Contributions. 350 | 351 | 6. Trademarks. This License does not grant permission to use the trade 352 | names, trademarks, service marks, or product names of the Licensor, 353 | except as required for reasonable and customary use in describing the 354 | origin of the Work and reproducing the content of the NOTICE file. 355 | 356 | 7. Disclaimer of Warranty. Unless required by applicable law or 357 | agreed to in writing, Licensor provides the Work (and each 358 | Contributor provides its Contributions) on an "AS IS" BASIS, 359 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 360 | implied, including, without limitation, any warranties or conditions 361 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 362 | PARTICULAR PURPOSE. You are solely responsible for determining the 363 | appropriateness of using or redistributing the Work and assume any 364 | risks associated with Your exercise of permissions under this License. 365 | 366 | 8. Limitation of Liability. In no event and under no legal theory, 367 | whether in tort (including negligence), contract, or otherwise, 368 | unless required by applicable law (such as deliberate and grossly 369 | negligent acts) or agreed to in writing, shall any Contributor be 370 | liable to You for damages, including any direct, indirect, special, 371 | incidental, or consequential damages of any character arising as a 372 | result of this License or out of the use or inability to use the 373 | Work (including but not limited to damages for loss of goodwill, 374 | work stoppage, computer failure or malfunction, or any and all 375 | other commercial damages or losses), even if such Contributor 376 | has been advised of the possibility of such damages. 377 | 378 | 9. Accepting Warranty or Additional Liability. While redistributing 379 | the Work or Derivative Works thereof, You may choose to offer, 380 | and charge a fee for, acceptance of support, warranty, indemnity, 381 | or other liability obligations and/or rights consistent with this 382 | License. However, in accepting such obligations, You may act only 383 | on Your own behalf and on Your sole responsibility, not on behalf 384 | of any other Contributor, and only if You agree to indemnify, 385 | defend, and hold each Contributor harmless for any liability 386 | incurred by, or claims asserted against, such Contributor by reason 387 | of your accepting any such warranty or additional liability. 388 | 389 | END OF TERMS AND CONDITIONS 390 | 391 | APPENDIX: How to apply the Apache License to your work. 392 | 393 | To apply the Apache License to your work, attach the following 394 | boilerplate notice, with the fields enclosed by brackets "[]" 395 | replaced with your own identifying information. (Don't include 396 | the brackets!) The text should be enclosed in the appropriate 397 | comment syntax for the file format. We also recommend that a 398 | file or class name and description of purpose be included on the 399 | same "printed page" as the copyright notice for easier 400 | identification within third-party archives. 401 | 402 | Copyright [yyyy] [name of copyright owner] 403 | 404 | Licensed under the Apache License, Version 2.0 (the "License"); 405 | you may not use this file except in compliance with the License. 406 | You may obtain a copy of the License at 407 | 408 | http://www.apache.org/licenses/LICENSE-2.0 409 | 410 | Unless required by applicable law or agreed to in writing, software 411 | distributed under the License is distributed on an "AS IS" BASIS, 412 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 413 | See the License for the specific language governing permissions and 414 | limitations under the License. 415 | * For cdk-nag see also this required NOTICE: 416 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 417 | SPDX-License-Identifier: Apache-2.0 418 | 419 | ------ 420 | 421 | ** NodeJS-preact; version 10.12.1 -- https://preactjs.com/ 422 | The MIT License (MIT) 423 | 424 | Copyright (c) 2015-present Jason Miller 425 | 426 | Permission is hereby granted, free of charge, to any person obtaining a copy 427 | of this software and associated documentation files (the "Software"), to deal 428 | in the Software without restriction, including without limitation the rights 429 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 430 | copies of the Software, and to permit persons to whom the Software is 431 | furnished to do so, subject to the following conditions: 432 | 433 | The above copyright notice and this permission notice shall be included in all 434 | copies or substantial portions of the Software. 435 | 436 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 437 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 438 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 439 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 440 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 441 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 442 | SOFTWARE. 443 | ** node-html-parser; version 6.1.4 -- https://github.com/taoqf/node-html-parser 444 | Copyright 2019 Tao Qiufeng 445 | 446 | Permission is hereby granted, free of charge, to any person obtaining a copy of 447 | this software and associated documentation files (the "Software"), to deal in 448 | the Software without restriction, including without limitation the rights to 449 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 450 | the Software, and to permit persons to whom the Software is furnished to do so, 451 | subject to the following conditions: 452 | 453 | The above copyright notice and this permission notice shall be included in all 454 | copies or substantial portions of the Software. 455 | 456 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 457 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 458 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 459 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 460 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 461 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 462 | ** htm; version 2.1.1 -- https://github.com/developit/htm 463 | 464 | Apache License 465 | Version 2.0, January 2004 466 | http://www.apache.org/licenses/ 467 | 468 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 469 | 470 | 1. Definitions. 471 | 472 | "License" shall mean the terms and conditions for use, reproduction, 473 | and distribution as defined by Sections 1 through 9 of this document. 474 | 475 | "Licensor" shall mean the copyright owner or entity authorized by 476 | the copyright owner that is granting the License. 477 | 478 | "Legal Entity" shall mean the union of the acting entity and all 479 | other entities that control, are controlled by, or are under common 480 | control with that entity. For the purposes of this definition, 481 | "control" means (i) the power, direct or indirect, to cause the 482 | direction or management of such entity, whether by contract or 483 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 484 | outstanding shares, or (iii) beneficial ownership of such entity. 485 | 486 | "You" (or "Your") shall mean an individual or Legal Entity 487 | exercising permissions granted by this License. 488 | 489 | "Source" form shall mean the preferred form for making modifications, 490 | including but not limited to software source code, documentation 491 | source, and configuration files. 492 | 493 | "Object" form shall mean any form resulting from mechanical 494 | transformation or translation of a Source form, including but 495 | not limited to compiled object code, generated documentation, 496 | and conversions to other media types. 497 | 498 | "Work" shall mean the work of authorship, whether in Source or 499 | Object form, made available under the License, as indicated by a 500 | copyright notice that is included in or attached to the work 501 | (an example is provided in the Appendix below). 502 | 503 | "Derivative Works" shall mean any work, whether in Source or Object 504 | form, that is based on (or derived from) the Work and for which the 505 | editorial revisions, annotations, elaborations, or other modifications 506 | represent, as a whole, an original work of authorship. For the purposes 507 | of this License, Derivative Works shall not include works that remain 508 | separable from, or merely link (or bind by name) to the interfaces of, 509 | the Work and Derivative Works thereof. 510 | 511 | "Contribution" shall mean any work of authorship, including 512 | the original version of the Work and any modifications or additions 513 | to that Work or Derivative Works thereof, that is intentionally 514 | submitted to Licensor for inclusion in the Work by the copyright owner 515 | or by an individual or Legal Entity authorized to submit on behalf of 516 | the copyright owner. For the purposes of this definition, "submitted" 517 | means any form of electronic, verbal, or written communication sent 518 | to the Licensor or its representatives, including but not limited to 519 | communication on electronic mailing lists, source code control systems, 520 | and issue tracking systems that are managed by, or on behalf of, the 521 | Licensor for the purpose of discussing and improving the Work, but 522 | excluding communication that is conspicuously marked or otherwise 523 | designated in writing by the copyright owner as "Not a Contribution." 524 | 525 | "Contributor" shall mean Licensor and any individual or Legal Entity 526 | on behalf of whom a Contribution has been received by Licensor and 527 | subsequently incorporated within the Work. 528 | 529 | 2. Grant of Copyright License. Subject to the terms and conditions of 530 | this License, each Contributor hereby grants to You a perpetual, 531 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 532 | copyright license to reproduce, prepare Derivative Works of, 533 | publicly display, publicly perform, sublicense, and distribute the 534 | Work and such Derivative Works in Source or Object form. 535 | 536 | 3. Grant of Patent License. Subject to the terms and conditions of 537 | this License, each Contributor hereby grants to You a perpetual, 538 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 539 | (except as stated in this section) patent license to make, have made, 540 | use, offer to sell, sell, import, and otherwise transfer the Work, 541 | where such license applies only to those patent claims licensable 542 | by such Contributor that are necessarily infringed by their 543 | Contribution(s) alone or by combination of their Contribution(s) 544 | with the Work to which such Contribution(s) was submitted. If You 545 | institute patent litigation against any entity (including a 546 | cross-claim or counterclaim in a lawsuit) alleging that the Work 547 | or a Contribution incorporated within the Work constitutes direct 548 | or contributory patent infringement, then any patent licenses 549 | granted to You under this License for that Work shall terminate 550 | as of the date such litigation is filed. 551 | 552 | 4. Redistribution. You may reproduce and distribute copies of the 553 | Work or Derivative Works thereof in any medium, with or without 554 | modifications, and in Source or Object form, provided that You 555 | meet the following conditions: 556 | 557 | (a) You must give any other recipients of the Work or 558 | Derivative Works a copy of this License; and 559 | 560 | (b) You must cause any modified files to carry prominent notices 561 | stating that You changed the files; and 562 | 563 | (c) You must retain, in the Source form of any Derivative Works 564 | that You distribute, all copyright, patent, trademark, and 565 | attribution notices from the Source form of the Work, 566 | excluding those notices that do not pertain to any part of 567 | the Derivative Works; and 568 | 569 | (d) If the Work includes a "NOTICE" text file as part of its 570 | distribution, then any Derivative Works that You distribute must 571 | include a readable copy of the attribution notices contained 572 | within such NOTICE file, excluding those notices that do not 573 | pertain to any part of the Derivative Works, in at least one 574 | of the following places: within a NOTICE text file distributed 575 | as part of the Derivative Works; within the Source form or 576 | documentation, if provided along with the Derivative Works; or, 577 | within a display generated by the Derivative Works, if and 578 | wherever such third-party notices normally appear. The contents 579 | of the NOTICE file are for informational purposes only and 580 | do not modify the License. You may add Your own attribution 581 | notices within Derivative Works that You distribute, alongside 582 | or as an addendum to the NOTICE text from the Work, provided 583 | that such additional attribution notices cannot be construed 584 | as modifying the License. 585 | 586 | You may add Your own copyright statement to Your modifications and 587 | may provide additional or different license terms and conditions 588 | for use, reproduction, or distribution of Your modifications, or 589 | for any such Derivative Works as a whole, provided Your use, 590 | reproduction, and distribution of the Work otherwise complies with 591 | the conditions stated in this License. 592 | 593 | 5. Submission of Contributions. Unless You explicitly state otherwise, 594 | any Contribution intentionally submitted for inclusion in the Work 595 | by You to the Licensor shall be under the terms and conditions of 596 | this License, without any additional terms or conditions. 597 | Notwithstanding the above, nothing herein shall supersede or modify 598 | the terms of any separate license agreement you may have executed 599 | with Licensor regarding such Contributions. 600 | 601 | 6. Trademarks. This License does not grant permission to use the trade 602 | names, trademarks, service marks, or product names of the Licensor, 603 | except as required for reasonable and customary use in describing the 604 | origin of the Work and reproducing the content of the NOTICE file. 605 | 606 | 7. Disclaimer of Warranty. Unless required by applicable law or 607 | agreed to in writing, Licensor provides the Work (and each 608 | Contributor provides its Contributions) on an "AS IS" BASIS, 609 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 610 | implied, including, without limitation, any warranties or conditions 611 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 612 | PARTICULAR PURPOSE. You are solely responsible for determining the 613 | appropriateness of using or redistributing the Work and assume any 614 | risks associated with Your exercise of permissions under this License. 615 | 616 | 8. Limitation of Liability. In no event and under no legal theory, 617 | whether in tort (including negligence), contract, or otherwise, 618 | unless required by applicable law (such as deliberate and grossly 619 | negligent acts) or agreed to in writing, shall any Contributor be 620 | liable to You for damages, including any direct, indirect, special, 621 | incidental, or consequential damages of any character arising as a 622 | result of this License or out of the use or inability to use the 623 | Work (including but not limited to damages for loss of goodwill, 624 | work stoppage, computer failure or malfunction, or any and all 625 | other commercial damages or losses), even if such Contributor 626 | has been advised of the possibility of such damages. 627 | 628 | 9. Accepting Warranty or Additional Liability. While redistributing 629 | the Work or Derivative Works thereof, You may choose to offer, 630 | and charge a fee for, acceptance of support, warranty, indemnity, 631 | or other liability obligations and/or rights consistent with this 632 | License. However, in accepting such obligations, You may act only 633 | on Your own behalf and on Your sole responsibility, not on behalf 634 | of any other Contributor, and only if You agree to indemnify, 635 | defend, and hold each Contributor harmless for any liability 636 | incurred by, or claims asserted against, such Contributor by reason 637 | of your accepting any such warranty or additional liability. 638 | 639 | END OF TERMS AND CONDITIONS 640 | 641 | APPENDIX: How to apply the Apache License to your work. 642 | 643 | To apply the Apache License to your work, attach the following 644 | boilerplate notice, with the fields enclosed by brackets "[]" 645 | replaced with your own identifying information. (Don't include 646 | the brackets!) The text should be enclosed in the appropriate 647 | comment syntax for the file format. We also recommend that a 648 | file or class name and description of purpose be included on the 649 | same "printed page" as the copyright notice for easier 650 | identification within third-party archives. 651 | 652 | Copyright 2018 Google Inc. 653 | 654 | Licensed under the Apache License, Version 2.0 (the "License"); 655 | you may not use this file except in compliance with the License. 656 | You may obtain a copy of the License at 657 | 658 | http://www.apache.org/licenses/LICENSE-2.0 659 | 660 | Unless required by applicable law or agreed to in writing, software 661 | distributed under the License is distributed on an "AS IS" BASIS, 662 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 663 | See the License for the specific language governing permissions and 664 | limitations under the License. 665 | ** Fastify; version 4.13.0 -- https://www.fastify.io/ 666 | MIT License 667 | 668 | Copyright (c) 2016-2023 The Fastify Team 669 | 670 | The Fastify team members are listed at https://github.com/fastify/fastify#team 671 | and in the README file. 672 | 673 | Permission is hereby granted, free of charge, to any person obtaining a copy 674 | of this software and associated documentation files (the "Software"), to deal 675 | in the Software without restriction, including without limitation the rights 676 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 677 | copies of the Software, and to permit persons to whom the Software is 678 | furnished to do so, subject to the following conditions: 679 | 680 | The above copyright notice and this permission notice shall be included in all 681 | copies or substantial portions of the Software. 682 | 683 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 684 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 685 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 686 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 687 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 688 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 689 | SOFTWARE. 690 | ** @babel/plugin-transform-react-jsx; version 7.18.6 -- https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx 691 | MIT License 692 | 693 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 694 | 695 | Permission is hereby granted, free of charge, to any person obtaining 696 | a copy of this software and associated documentation files (the 697 | "Software"), to deal in the Software without restriction, including 698 | without limitation the rights to use, copy, modify, merge, publish, 699 | distribute, sublicense, and/or sell copies of the Software, and to 700 | permit persons to whom the Software is furnished to do so, subject to 701 | the following conditions: 702 | 703 | The above copyright notice and this permission notice shall be 704 | included in all copies or substantial portions of the Software. 705 | 706 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 707 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 708 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 709 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 710 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 711 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 712 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 713 | 714 | MIT License 715 | 716 | Copyright (c) 717 | 718 | Permission is hereby granted, free of charge, to any person obtaining a copy of 719 | this software and associated documentation files (the "Software"), to deal in 720 | the Software without restriction, including without limitation the rights to 721 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 722 | the Software, and to permit persons to whom the Software is furnished to do so, 723 | subject to the following conditions: 724 | 725 | The above copyright notice and this permission notice shall be included in all 726 | copies or substantial portions of the Software. 727 | 728 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 729 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 730 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 731 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 732 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 733 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 734 | 735 | ------ 736 | 737 | ** babel-loader; version 8.2.2 -- https://github.com/babel/babel-loader 738 | Copyright (c) 2014-2019 Luís Couto 739 | 740 | Copyright (c) 2014-2019 Luís Couto 741 | 742 | MIT License 743 | 744 | Permission is hereby granted, free of charge, to any person obtaining 745 | a copy of this software and associated documentation files (the 746 | "Software"), to deal in the Software without restriction, including 747 | without limitation the rights to use, copy, modify, merge, publish, 748 | distribute, sublicense, and/or sell copies of the Software, and to 749 | permit persons to whom the Software is furnished to do so, subject to 750 | the following conditions: 751 | 752 | The above copyright notice and this permission notice shall be 753 | included in all copies or substantial portions of the Software. 754 | 755 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 756 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 757 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 758 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 759 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 760 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 761 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 762 | 763 | ------ 764 | 765 | ** axios; version 0.27.0 -- https://github.com/axios/axios 766 | Copyright (c) 2014-present Matt Zabriskie 767 | 768 | Copyright (c) 2014-present Matt Zabriskie 769 | 770 | Permission is hereby granted, free of charge, to any person obtaining a copy 771 | of this software and associated documentation files (the "Software"), to deal 772 | in the Software without restriction, including without limitation the rights 773 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 774 | copies of the Software, and to permit persons to whom the Software is 775 | furnished to do so, subject to the following conditions: 776 | 777 | The above copyright notice and this permission notice shall be included in 778 | all copies or substantial portions of the Software. 779 | 780 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 781 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 782 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 783 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 784 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 785 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 786 | THE SOFTWARE. 787 | 788 | ------ 789 | 790 | ** @babel/core; version 7.18.13 -- https://github.com/babel/babel/tree/master/packages/babel-core 791 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 792 | ** @babel/preset-env; version 7.18.10 -- https://github.com/babel/babel/tree/master/packages/babel-preset-env 793 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 794 | 795 | MIT License 796 | 797 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 798 | 799 | Permission is hereby granted, free of charge, to any person obtaining 800 | a copy of this software and associated documentation files (the 801 | "Software"), to deal in the Software without restriction, including 802 | without limitation the rights to use, copy, modify, merge, publish, 803 | distribute, sublicense, and/or sell copies of the Software, and to 804 | permit persons to whom the Software is furnished to do so, subject to 805 | the following conditions: 806 | 807 | The above copyright notice and this permission notice shall be 808 | included in all copies or substantial portions of the Software. 809 | 810 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 811 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 812 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 813 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 814 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 815 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 816 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 817 | 818 | ------ 819 | 820 | ** webpack-cli; version 4.10.0 -- https://github.com/webpack/webpack-cli 821 | Copyright JS Foundation and other contributors 822 | 823 | Copyright JS Foundation and other contributors 824 | 825 | Permission is hereby granted, free of charge, to any person obtaining 826 | a copy of this software and associated documentation files (the 827 | 'Software'), to deal in the Software without restriction, including 828 | without limitation the rights to use, copy, modify, merge, publish, 829 | distribute, sublicense, and/or sell copies of the Software, and to 830 | permit persons to whom the Software is furnished to do so, subject to 831 | the following conditions: 832 | 833 | The above copyright notice and this permission notice shall be 834 | included in all copies or substantial portions of the Software. 835 | 836 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 837 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 838 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 839 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 840 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 841 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 842 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 843 | 844 | ------ 845 | 846 | ** webpack; version 5.74.0 -- https://github.com/webpack/webpack 847 | Copyright JS Foundation and other contributors 848 | 849 | Permission is hereby granted, free of charge, to any person obtaining 850 | a copy of this software and associated documentation files (the 851 | 'Software'), to deal in the Software without restriction, including 852 | without limitation the rights to use, copy, modify, merge, publish, 853 | distribute, sublicense, and/or sell copies of the Software, and to 854 | permit persons to whom the Software is furnished to do so, subject to 855 | the following conditions: 856 | 857 | The above copyright notice and this permission notice shall be 858 | included in all copies or substantial portions of the Software. 859 | 860 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 861 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 862 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 863 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 864 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 865 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 866 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 867 | -------------------------------------------------------------------------------- /diagrams/lambda-optimizations.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /diagrams/no-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/no-proxy.png -------------------------------------------------------------------------------- /diagrams/optimized-aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/optimized-aws.png -------------------------------------------------------------------------------- /diagrams/optimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/optimized.png -------------------------------------------------------------------------------- /diagrams/proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/proxy.png -------------------------------------------------------------------------------- /diagrams/unoptimized-aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/unoptimized-aws.png -------------------------------------------------------------------------------- /diagrams/unoptimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/optimizations-for-lambda-functions/8aa38e9dfa4979469575a6f2897e1ce86240e431/diagrams/unoptimized.png -------------------------------------------------------------------------------- /optimized/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws.telemetry": false 3 | } -------------------------------------------------------------------------------- /optimized/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project 2 | 3 | This is a blank project for CDK development with TypeScript. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | 16 | ## interesting reading on pooling and Sequelize 17 | https://sequelize.org/docs/v6/other-topics/aws-lambda/ 18 | 19 | 20 | -------------------------------------------------------------------------------- /optimized/bin/rds-proxy.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // import 'source-map-support/register'; 3 | import { App } from 'aws-cdk-lib'; 4 | 5 | import { StadiumsStack } from "../lib/stadiums-stack.ts"; 6 | import { ParametersStack } from "../lib/parameters-stack.ts"; 7 | import { PowerTuningStack } from '../lib/lambda-powertuning.ts'; 8 | 9 | const stackProps = { 10 | env: { 11 | account: process.env.CDK_DEFAULT_ACCOUNT as string, 12 | region: process.env.CDK_DEFAULT_REGION as string, 13 | }, 14 | }; 15 | const app = new App(); 16 | 17 | const powertuningStack = new PowerTuningStack(app, "PowerTuningStack", { 18 | ...stackProps, 19 | description: "This stack creates PowerTuning stack for Lambda testing" 20 | }) 21 | 22 | const parametersStack = new ParametersStack(app, "ParametersStack", { 23 | ...stackProps, 24 | description: "This stack creates the parameters needed to configure the infrastrcture of the other stacks" 25 | }) 26 | 27 | const rdsProxyStack = new StadiumsStack (app, "StadiumsStack", { 28 | ...stackProps, 29 | description: "This stack creates a new Postgres database in a private (isolated) subnet", 30 | }); 31 | 32 | app.synth(); -------------------------------------------------------------------------------- /optimized/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx tsx bin/rds-proxy.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 21 | "@aws-cdk/core:checkSecretUsage": true, 22 | "@aws-cdk/core:target-partitions": [ 23 | "aws", 24 | "aws-cn" 25 | ], 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 41 | "@aws-cdk/aws-route53-patters:useCertificate": true, 42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false, 43 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, 44 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, 45 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, 46 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, 47 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, 48 | "@aws-cdk/aws-redshift:columnId": true, 49 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, 50 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, 51 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, 52 | "@aws-cdk/aws-kms:aliasNameRef": true, 53 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, 54 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, 55 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /optimized/lambda/database/Cache.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@redis/client'; 2 | import { logger, tracer } from '../powertools/utilities.ts'; 3 | 4 | class Cache{ 5 | private client:any 6 | 7 | @tracer.captureMethod({ 8 | subSegmentName: "### Redis init" 9 | }) 10 | async init(endpoint:String, port:Number){ 11 | try{ 12 | if(!this.client){ 13 | this.client = await createClient({ 14 | url: `redis://${endpoint}:${port}` 15 | }) 16 | .on('error', err => logger.error(`Redis client error redis://${endpoint}:${port}`, err)) 17 | .connect(); 18 | } 19 | } catch (err) { 20 | const rootTraceId = tracer.getRootXrayTraceId(); 21 | logger.error(`Cannot connect to Redis. redis://${endpoint}:${port}. TraceId ${rootTraceId}`, err as Error) 22 | } 23 | 24 | } 25 | 26 | @tracer.captureMethod({ 27 | subSegmentName: "### Redis get data" 28 | }) 29 | async getData(key:string){ 30 | 31 | try{ 32 | const res = await this.client.get(key); 33 | 34 | if(res) 35 | return JSON.parse(res); 36 | else 37 | return undefined; 38 | 39 | } catch(err) { 40 | const rootTraceId = tracer.getRootXrayTraceId(); 41 | logger.error(`Cannot get data from Redis. TraceId ${rootTraceId}`, err as Error) 42 | } 43 | } 44 | 45 | @tracer.captureMethod({ 46 | subSegmentName: "### Redis set data" 47 | }) 48 | async setData(key:string, value:any){ 49 | try{ 50 | if(!this.client.isOpen) 51 | await this.client.connect(); 52 | 53 | await this.client.set( 54 | key, 55 | JSON.stringify(value), 56 | { 57 | EX: 10, 58 | NX: true 59 | }); 60 | } catch (err) { 61 | const rootTraceId = tracer.getRootXrayTraceId(); 62 | logger.error(`Cannot get data from Redis. TraceId ${rootTraceId}`, err as Error) 63 | } 64 | } 65 | 66 | } 67 | 68 | export { Cache } -------------------------------------------------------------------------------- /optimized/lambda/database/DBValueObject.ts: -------------------------------------------------------------------------------- 1 | interface DBParams { 2 | database:string, 3 | endpoint:string, 4 | user:string, 5 | password:string, 6 | port:number, 7 | isCacheEnabled:boolean, 8 | cacheEndpoint:string, 9 | cachePort:number, 10 | } 11 | 12 | export {DBParams} -------------------------------------------------------------------------------- /optimized/lambda/database/DBparameters.ts: -------------------------------------------------------------------------------- 1 | import { logger, tracer } from "../powertools/utilities.ts"; 2 | import { DBParams } from './DBValueObject.ts'; 3 | 4 | class DBparameters { 5 | private SESSION_TOKEN = process.env.AWS_SESSION_TOKEN!; 6 | private SSM_PORT = process.env.PARAMETERS_SECRETS_EXTENSION_HTTP_PORT!; 7 | private PARAMS_ID_LIST = [ 8 | "/lambda-optimized/dbUsername", 9 | "/lambda-optimized/dbName", 10 | "/lambda-optimized/dbPort", 11 | "/lambda-optimized/dbEndpoint", 12 | "/lambda-optimized/isCacheEnabled", 13 | "/lambda-optimized/cacheEndpoint", 14 | "/lambda-optimized/cachePort"]; 15 | 16 | public async getParameters(){ 17 | const list:Array = this.PARAMS_ID_LIST; 18 | const params: { [key: string]: any } = {}; 19 | let parameterID:String; 20 | 21 | for(let i=0; i < list.length; i++){ 22 | parameterID = list[i].toString(); 23 | 24 | const param = await fetch(`http://localhost:${this.SSM_PORT}/systemsmanager/parameters/get?name=${parameterID}`, 25 | { 26 | method: "GET", 27 | headers: { 28 | 'X-Aws-Parameters-Secrets-Token': this.SESSION_TOKEN 29 | } 30 | }); 31 | 32 | if (param.ok) { 33 | const key = parameterID.split("/").pop() as string; 34 | const result = await param.json(); 35 | params[key] = result.Parameter.Value; 36 | } else { 37 | logger.error(`Parameter ${list[i]} not retrieved | ${param.statusText}`); 38 | } 39 | } 40 | 41 | const dbParams:DBParams = { 42 | database: params.dbName, 43 | user: params.dbUsername, 44 | endpoint: params.dbEndpoint, 45 | password: "", 46 | port: Number(params.dbPort), 47 | isCacheEnabled: params.isCacheEnabled.toLowerCase() === "true", 48 | cacheEndpoint: params.cacheEndpoint, 49 | cachePort: params.cachePort, 50 | } 51 | 52 | logger.info(`DB params: ${dbParams}`); 53 | return dbParams; 54 | } 55 | 56 | } 57 | 58 | export { DBparameters } -------------------------------------------------------------------------------- /optimized/lambda/database/PostgresDB.ts: -------------------------------------------------------------------------------- 1 | import postgres from 'postgres'; 2 | import { Signer } from 'aws-sdk-js-v3-rds-signer'; 3 | import { DBParams } from './DBValueObject.ts'; 4 | import { logger, tracer } from "../powertools/utilities.ts"; 5 | import { Stadium } from "./StadiumValueObject.ts"; 6 | 7 | class PostgresDB { 8 | private sql: postgres.Sql>; 9 | 10 | @tracer.captureMethod({ 11 | subSegmentName: "### authenticate to DB" 12 | }) 13 | public async init(params:DBParams){ 14 | if(!this.sql){ 15 | const signer = new Signer({ 16 | hostname: params.endpoint, 17 | port: params.port, 18 | region: process.env.AWS_REGION, 19 | username: params.user 20 | }); 21 | 22 | const psw = await signer.getAuthToken() 23 | 24 | this.sql = postgres({ 25 | host:params.endpoint, 26 | port:params.port, 27 | database:params.database, 28 | username:params.user, 29 | password: psw, 30 | ssl: true, 31 | idle_timeout: 40, 32 | max_lifetime: 60 * 3, 33 | transform: postgres.camel 34 | }) 35 | } 36 | } 37 | 38 | @tracer.captureMethod({ 39 | subSegmentName: "### Get ALL stadiums" 40 | }) 41 | public async getAllStadiums(){ 42 | return await this.sql`SELECT * FROM stadiums` 43 | } 44 | 45 | @tracer.captureMethod({ 46 | subSegmentName: "### close DB connection" 47 | }) 48 | public async close(){ 49 | return await this.sql.end(); 50 | } 51 | 52 | @tracer.captureMethod({ 53 | subSegmentName: "### insert all stadiums" 54 | }) 55 | public async insertStadiums(stadiums:Array){ 56 | 57 | await this.sql` 58 | CREATE TABLE IF NOT EXISTS stadiums ( 59 | name TEXT, 60 | capacity INTEGER, 61 | location TEXT, 62 | surface TEXT, 63 | roof TEXT, 64 | team TEXT, 65 | year_opened TEXT, 66 | created_at TIMESTAMP, 67 | update_at TIMESTAMP 68 | )` 69 | 70 | await this.sql`TRUNCATE stadiums` 71 | 72 | const result = await this.sql.begin(async sql => { 73 | 74 | stadiums.forEach(async stadium => { 75 | const data = await sql` 76 | INSERT into stadiums ${ sql({ 77 | ...stadium, 78 | "createdAt": Date.now(), 79 | "updateAt": Date.now() 80 | }) }` 81 | }) 82 | 83 | }) 84 | 85 | return result; 86 | } 87 | } 88 | 89 | export { PostgresDB } -------------------------------------------------------------------------------- /optimized/lambda/database/StadiumValueObject.ts: -------------------------------------------------------------------------------- 1 | interface Stadium { 2 | name:string, 3 | capacity:number, 4 | location:string, 5 | surface:string, 6 | roof:string, 7 | team:string, 8 | yearOpened:string 9 | } 10 | 11 | export {Stadium} -------------------------------------------------------------------------------- /optimized/lambda/getData.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyResultV2 } from 'aws-lambda'; 2 | import { PostgresDB } from './database/PostgresDB.ts'; 3 | import { DBParams } from './database/DBValueObject.ts'; 4 | import { DBparameters } from './database/DBparameters.ts'; 5 | import { Cache } from './database/Cache.ts'; 6 | import { logger, tracer, metrics } from "./powertools/utilities.ts"; 7 | import { captureLambdaHandler } from '@aws-lambda-powertools/tracer'; 8 | import { injectLambdaContext } from '@aws-lambda-powertools/logger'; 9 | import { logMetrics, MetricUnits } from '@aws-lambda-powertools/metrics'; 10 | import middy from "@middy/core"; 11 | 12 | let parametersList:DBParams; 13 | const cache = new Cache(); 14 | const db = new PostgresDB(); 15 | 16 | const lambdaHandler = async function (): Promise { 17 | 18 | let responseStruct; 19 | let stadiums; 20 | 21 | const segment = tracer.getSegment()!; 22 | tracer.annotateColdStart(); 23 | 24 | try { 25 | if(!parametersList){ 26 | const parameters = new DBparameters() 27 | parametersList = await parameters.getParameters(); 28 | } 29 | 30 | const isCacheEnabled = parametersList.isCacheEnabled as Boolean; 31 | 32 | if(isCacheEnabled){ 33 | 34 | logger.info('cache enable', { details: { value: isCacheEnabled } }) 35 | await cache.init(parametersList.cacheEndpoint, parametersList.cachePort); 36 | 37 | stadiums = await cache.getData("stadiums"); 38 | } 39 | 40 | if (!stadiums) { 41 | 42 | await db.init(parametersList); 43 | 44 | stadiums = await db.getAllStadiums(); 45 | 46 | if(isCacheEnabled){ 47 | cache?.setData("stadiums", stadiums); 48 | logger.info('cache populated with Stadiums', { details: { value: stadiums.length } }); 49 | } 50 | 51 | metrics.addMetric('Redis cache not hit', MetricUnits.Count, 1); 52 | } 53 | 54 | logger.info('total stadiums retrieved', { details: { total_stadiums: stadiums.length } }); 55 | metrics.addMetric('all stadiums requests', MetricUnits.Count, 1); 56 | 57 | responseStruct = { 58 | statusCode: 200, 59 | headers: { "Content-Type": "application/json" }, 60 | body: JSON.stringify(stadiums) 61 | }; 62 | 63 | } catch (error) { 64 | 65 | logger.error('Unexpected error occurred while trying to retrieve stadiums', error as Error); 66 | const rootTraceId = tracer.getRootXrayTraceId(); 67 | responseStruct = { 68 | statusCode: 500, 69 | headers: { _X_AMZN_TRACE_ID: rootTraceId || "" }, 70 | body: JSON.stringify(error) 71 | }; 72 | 73 | } 74 | 75 | tracer.setSegment(segment); 76 | return responseStruct 77 | }; 78 | 79 | export const handler = middy(lambdaHandler) 80 | .use(captureLambdaHandler(tracer)) 81 | .use(logMetrics(metrics, { captureColdStartMetric: true, throwOnEmptyMetrics: true })) 82 | .use(injectLambdaContext(logger, { clearState: true })); 83 | -------------------------------------------------------------------------------- /optimized/lambda/populate.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda'; 2 | import { PostgresDB } from './database/PostgresDB.ts'; 3 | import { DBParams } from './database/DBValueObject.ts'; 4 | import stadiumData from './stadium-data.json'; 5 | import { DBparameters } from './database/DBparameters.ts'; 6 | import { Stadium } from "./database/StadiumValueObject.ts"; 7 | import { logger, tracer } from "./powertools/utilities.ts"; 8 | import { captureLambdaHandler } from '@aws-lambda-powertools/tracer'; 9 | import { injectLambdaContext } from '@aws-lambda-powertools/logger'; 10 | 11 | import middy from "@middy/core"; 12 | 13 | const lambdaHandler = async function (event: APIGatewayProxyEventV2): Promise { 14 | try { 15 | const parameters = new DBparameters() 16 | const parametersList:DBParams = await parameters.getParameters(); 17 | logger.info('DB params retrieved', { details: { dbParams: parametersList } }); 18 | const db = new PostgresDB(); 19 | 20 | await db.init(parametersList); 21 | 22 | const stadiums:Array = []; 23 | for(const stadium of stadiumData.stadiums) { 24 | stadiums.push(stadium as Stadium); 25 | } 26 | 27 | const stadiumsInserted = await db.insertStadiums(stadiums); 28 | logger.info('stadiums inserted', { details: { result: stadiumsInserted } }); 29 | 30 | return { 31 | statusCode: 200, 32 | headers: { "Content-Type": "application/json" }, 33 | body: JSON.stringify({ "message": "Stadium table and data successfully created." }) 34 | }; 35 | 36 | } catch (error) { 37 | console.error('Unable to connect to the database:', error); 38 | 39 | return { 40 | statusCode: 500, 41 | headers: { "Content-Type": "application/json" }, 42 | body: JSON.stringify(error) 43 | } 44 | } 45 | }; 46 | 47 | export const handler = middy(lambdaHandler) 48 | .use(captureLambdaHandler(tracer)) 49 | .use(injectLambdaContext(logger, { clearState: true })); -------------------------------------------------------------------------------- /optimized/lambda/powertools/utilities.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@aws-lambda-powertools/logger'; 2 | import { Metrics } from '@aws-lambda-powertools/metrics'; 3 | import { Tracer } from '@aws-lambda-powertools/tracer'; 4 | 5 | const logger = new Logger({ 6 | persistentLogAttributes: { 7 | aws_account_id: process.env.AWS_ACCOUNT_ID || 'N/A', 8 | aws_region: process.env.AWS_REGION || 'N/A', 9 | } 10 | }); 11 | 12 | const metrics = new Metrics({ 13 | defaultDimensions: { 14 | aws_account_id: process.env.AWS_ACCOUNT_ID || 'N/A', 15 | aws_region: process.env.AWS_REGION || 'N/A', 16 | } 17 | }); 18 | 19 | const tracer = new Tracer(); 20 | 21 | export { 22 | logger, 23 | metrics, 24 | tracer 25 | }; -------------------------------------------------------------------------------- /optimized/lambda/stadium-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "stadiums": [ 3 | { 4 | "name": "Los Angeles Memorial Coliseum", 5 | "capacity": 93607, 6 | "location": "Los Angeles, California", 7 | "surface": "Natural grass", 8 | "roof": "Open", 9 | "team": "Los Angeles Rams", 10 | "yearOpened": "1923" 11 | }, 12 | { 13 | "name": "MetLife Stadium", 14 | "capacity": 82500, 15 | "location": "East Rutherford, New Jersey", 16 | "surface": "UBU Speed Series S5-M Synthetic Turf", 17 | "roof": "Open", 18 | "team": "New York Giants", 19 | "yearOpened": "2010" 20 | }, 21 | { 22 | "name": "MetLife Stadium", 23 | "capacity": 82500, 24 | "location": "East Rutherford, New Jersey", 25 | "surface": "UBU Speed Series S5-M Synthetic Turf", 26 | "roof": "Open", 27 | "team": "New York Jets", 28 | "yearOpened": "2010" 29 | }, 30 | { 31 | "name": "FedEx Field", 32 | "capacity": 82000, 33 | "location": "Landover, Maryland", 34 | "surface": "Latitude 36 Bermuda Grass", 35 | "roof": "Open", 36 | "team": "Washington Football Team", 37 | "yearOpened": "1997" 38 | }, 39 | { 40 | "name": "Lambeau Field", 41 | "capacity": 81435, 42 | "location": "Green Bay, Wisconsin", 43 | "surface": "Hybrid Grass-Synthetic", 44 | "roof": "Open", 45 | "team": "Green Bay Packers", 46 | "yearOpened": "1957" 47 | }, 48 | { 49 | "name": "AT&T Stadium", 50 | "capacity": 80000, 51 | "location": "Arlington, Texas", 52 | "surface": "Matrix RealGrass artificial turf", 53 | "roof": "Retractable", 54 | "team": "Dallas Cowboys", 55 | "yearOpened": "2009" 56 | }, 57 | { 58 | "name": "Arrowhead Stadium", 59 | "capacity": 76416, 60 | "location": "Kansas City, Missouri", 61 | "surface": "Latitude 36 Bermuda Grass", 62 | "roof": "Open", 63 | "team": "Kansas City Chiefs", 64 | "yearOpened": "1972" 65 | }, 66 | { 67 | "name": "Empower Field at Mile High", 68 | "capacity": 76125, 69 | "location": "Denver, Colorado", 70 | "surface": "Kentucky Bluegrass", 71 | "roof": "Open", 72 | "team": "Denver Broncos", 73 | "yearOpened": "2001" 74 | }, 75 | { 76 | "name": "Bank of America Stadium", 77 | "capacity": 75419, 78 | "location": "Charlotte, North Carolina", 79 | "surface": "Voyager Bermuda Grass", 80 | "roof": "Open", 81 | "team": "Carolina Panthers", 82 | "yearOpened": "1996" 83 | }, 84 | { 85 | "name": "Mercedes-Benz Superdome", 86 | "capacity": 73000, 87 | "location": "New Orleans, Louisiana", 88 | "surface": "UBU Turf (artificial)", 89 | "roof": "Fixed", 90 | "team": "New Orleans Saints", 91 | "yearOpened": "1975" 92 | }, 93 | { 94 | "name": "NRG Stadium", 95 | "capacity": 72220, 96 | "location": "Houston, Texas", 97 | "surface": "AstroTurf GameDay Grass 3D", 98 | "roof": "Retractable", 99 | "team": "Houston Texans", 100 | "yearOpened": "2002" 101 | }, 102 | { 103 | "name": "New Era Field", 104 | "capacity": 71870, 105 | "location": "Orchard Park, New York", 106 | "surface": "A-Turf Titan 50 (artificial)", 107 | "roof": "Open", 108 | "team": "Buffalo Bills", 109 | "yearOpened": "1973" 110 | }, 111 | { 112 | "name": "Georgia Dome", 113 | "capacity": 71250, 114 | "location": "Atlanta, Georgia", 115 | "surface": "FieldTurf Classic HD", 116 | "roof": "Fixed", 117 | "team": "Atlanta Falcons", 118 | "yearOpened": "1992" 119 | }, 120 | { 121 | "name": "M&T Bank Stadium", 122 | "capacity": 71008, 123 | "location": "Baltimore, Maryland", 124 | "surface": "Latitude 36 Bermuda Grass", 125 | "roof": "Open", 126 | "team": "Baltimore Ravens", 127 | "yearOpened": "1998" 128 | }, 129 | { 130 | "name": "Qualcomm Stadium", 131 | "capacity": 70561, 132 | "location": "San Diego, California", 133 | "surface": "Bandera Bermuda Grass", 134 | "roof": "Open", 135 | "team": "San Diego Chargers", 136 | "yearOpened": "1967" 137 | }, 138 | { 139 | "name": "Lincoln Financial Field", 140 | "capacity": 69596, 141 | "location": "Philadelphia, Pennsylvania", 142 | "surface": "Desso GrassMaster", 143 | "roof": "Open", 144 | "team": "Philadelphia Eagles", 145 | "yearOpened": "2003" 146 | }, 147 | { 148 | "name": "Nissan Stadium", 149 | "capacity": 69143, 150 | "location": "Nashville, Tennessee", 151 | "surface": "TifSport Bermuda Grass", 152 | "roof": "Open", 153 | "team": "Tennessee Titans", 154 | "yearOpened": "1999" 155 | }, 156 | { 157 | "name": "Levi's Stadium", 158 | "capacity": 68500, 159 | "location": "Santa Clara, California", 160 | "surface": "Tifway II Bermuda Grass / Perennial Ryegrass mixture", 161 | "roof": "Open", 162 | "team": "San Francisco 49ers", 163 | "yearOpened": "2014" 164 | }, 165 | { 166 | "name": "Heinz Field", 167 | "capacity": 68400, 168 | "location": "Pittsburgh, Pennsylvania", 169 | "surface": "Kentucky Bluegrass", 170 | "roof": "Open", 171 | "team": "Pittsburgh Steelers", 172 | "yearOpened": "2001" 173 | }, 174 | { 175 | "name": "CenturyLink Field", 176 | "capacity": 68000, 177 | "location": "Seattle, Washington", 178 | "surface": "FieldTurf Revolution", 179 | "roof": "Open", 180 | "team": "Seattle Seahawks", 181 | "yearOpened": "2002" 182 | }, 183 | { 184 | "name": "FirstEnergy Stadium", 185 | "capacity": 67431, 186 | "location": "Cleveland, Ohio", 187 | "surface": "Kentucky Bluegrass", 188 | "roof": "Open", 189 | "team": "Cleveland Browns", 190 | "yearOpened": "1999" 191 | }, 192 | { 193 | "name": "EverBank Field", 194 | "capacity": 67246, 195 | "location": "Jacksonville, Florida", 196 | "surface": "Tifway 419 Bermuda Grass", 197 | "roof": "Open", 198 | "team": "Jacksonville Jaguars", 199 | "yearOpened": "1999" 200 | }, 201 | { 202 | "name": "Lucas Oil Stadium", 203 | "capacity": 67000, 204 | "location": "Indianapolis, Indiana", 205 | "surface": "FieldTurf Classic HD", 206 | "roof": "Retractable", 207 | "team": "Indianapolis Colts", 208 | "yearOpened": "2008" 209 | }, 210 | { 211 | "name": "Gillette Stadium", 212 | "capacity": 66829, 213 | "location": "Foxborough, Massachusetts", 214 | "surface": "FieldTurf Revolution", 215 | "roof": "Open", 216 | "team": "New England Patriots", 217 | "yearOpened": "2002" 218 | }, 219 | { 220 | "name": "U.S. Bank Stadium", 221 | "capacity": 66200, 222 | "location": "Minneapolis, Minnesota", 223 | "surface": "UBU Speed Series S5-M Synthetic Turf", 224 | "roof": "Fixed", 225 | "team": "Minnesota Vikings", 226 | "yearOpened": "2016" 227 | }, 228 | { 229 | "name": "Raymond James Stadium", 230 | "capacity": 65890, 231 | "location": "Tampa, Florida", 232 | "surface": "Tifway 419 Bermuda Grass", 233 | "roof": "Open", 234 | "team": "Tampa Bay Buccaneers", 235 | "yearOpened": "1998" 236 | }, 237 | { 238 | "name": "Paul Brown Stadium", 239 | "capacity": 65515, 240 | "location": "Cincinnati, Ohio", 241 | "surface": "UBU Speed Series S5-M Synthetic Turf", 242 | "roof": "Open", 243 | "team": "Cincinnati Bengals", 244 | "yearOpened": "2000" 245 | }, 246 | { 247 | "name": "Hard Rock Stadium", 248 | "capacity": 65326, 249 | "location": "Miami Gardens, Florida", 250 | "surface": "Platinum TE Paspalum", 251 | "roof": "Open", 252 | "team": "Miami Dolphins", 253 | "yearOpened": "1987" 254 | }, 255 | { 256 | "name": "Ford Field", 257 | "capacity": 65000, 258 | "location": "Detroit, Michigan", 259 | "surface": "FieldTurf Classic HD", 260 | "roof": "Fixed", 261 | "team": "Detroit Lions", 262 | "yearOpened": "2002" 263 | }, 264 | { 265 | "name": "University of Phoenix Stadium", 266 | "capacity": 63400, 267 | "location": "Glendale, Arizona", 268 | "surface": "Tifway 419 Bermuda Grass", 269 | "roof": "Retractable", 270 | "team": "Arizona Cardinals", 271 | "yearOpened": "2006" 272 | }, 273 | { 274 | "name": "Soldier Field", 275 | "capacity": 61500, 276 | "location": "Chicago, Illinois", 277 | "surface": "Kentucky Bluegrass", 278 | "roof": "Open", 279 | "team": "Chicago Bears", 280 | "yearOpened": "1924" 281 | }, 282 | { 283 | "name": "Allegiant Stadium", 284 | "capacity": 65000, 285 | "location": "Paradise, Nevada", 286 | "surface": "Bermuda grass", 287 | "roof": "Fixed", 288 | "team": "Las Vegas Raiders", 289 | "yearOpened": "2020" 290 | } 291 | ] 292 | } -------------------------------------------------------------------------------- /optimized/lib/lambda-powertuning.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib'; 3 | import { CfnApplication } from 'aws-cdk-lib/aws-sam'; 4 | import { Construct } from 'constructs'; 5 | 6 | 7 | export class PowerTuningStack extends Stack { 8 | constructor(scope: Construct, id: string, props?: StackProps) { 9 | super(scope, id, props); 10 | let powerValues = '128,256,512,1024,1536,3008'; 11 | let lambdaResource = "*"; 12 | 13 | new CfnApplication(this, 'powerTuner', { 14 | location: { 15 | applicationId: 'arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning', 16 | semanticVersion: '4.2.0' 17 | }, 18 | parameters: { 19 | "lambdaResource": lambdaResource, 20 | "PowerValues": powerValues 21 | } 22 | }) 23 | 24 | }; 25 | } -------------------------------------------------------------------------------- /optimized/lib/parameters-stack.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib'; 3 | import { Construct } from 'constructs'; 4 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 5 | 6 | export class ParametersStack extends Stack { 7 | constructor(scope: Construct, id: string, props?: StackProps) { 8 | super(scope, id, props); 9 | 10 | const dbUsernameValue = new StringParameter(this, "DBUsername", { 11 | parameterName: "/lambda-optimized/dbUsername", 12 | description: "DBUsername", 13 | stringValue: 'syscdk' 14 | }); 15 | 16 | const dbNameValue = new StringParameter(this, "DBName", { 17 | parameterName: "/lambda-optimized/dbName", 18 | description: "dbName", 19 | stringValue: 'Stadiums' 20 | }); 21 | 22 | const dbPortValue = new StringParameter(this, "DBPort", { 23 | parameterName: "/lambda-optimized/dbPort", 24 | description: "dbPort", 25 | stringValue: "5432" 26 | }); 27 | 28 | const cacheEnabled = new StringParameter(this, "IsCacheEnabled", { 29 | parameterName: "/lambda-optimized/isCacheEnabled", 30 | description: "cache enabled", 31 | stringValue: "true" 32 | }); 33 | 34 | const EUParameterStoreExtensionValue = new StringParameter(this, "EUParameterStoreExtensionValue", { 35 | parameterName: "/lambda-optimized/parameters-store/eu-west-1/arm", 36 | description: "ARN for ParameterStore extension in eu-west-1 for ARM architecture", 37 | stringValue: "arn:aws:lambda:eu-west-1:015030872274:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11" 38 | }); 39 | 40 | const USParameterStoreExtensionValue = new StringParameter(this, "USParameterStoreExtensionValue", { 41 | parameterName: "/lambda-optimized/parameters-store/us-east-1/arm", 42 | description: "ARN for ParameterStore extension in us-east-1 for ARM architecture", 43 | stringValue: "arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11" 44 | }); 45 | }; 46 | } -------------------------------------------------------------------------------- /optimized/lib/stadiums-stack.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { Stack, StackProps, CfnOutput, Duration } from 'aws-cdk-lib'; 4 | import { aws_ec2 as ec2 } from 'aws-cdk-lib'; 5 | import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; 6 | import { aws_rds as rds } from 'aws-cdk-lib'; 7 | import { Runtime, Tracing, Architecture, LayerVersion } from 'aws-cdk-lib/aws-lambda'; 8 | import { NodejsFunction,NodejsFunctionProps, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs'; 9 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 10 | import { Construct } from 'constructs'; 11 | import { RetentionDays } from 'aws-cdk-lib/aws-logs'; 12 | import { HttpApi, HttpMethod} from '@aws-cdk/aws-apigatewayv2-alpha'; 13 | import { HttpLambdaIntegration} from '@aws-cdk/aws-apigatewayv2-integrations-alpha'; 14 | import { AnyPrincipal, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; 15 | import { CfnCacheCluster, CfnSubnetGroup } from 'aws-cdk-lib/aws-elasticache'; 16 | 17 | const __filename = fileURLToPath(import.meta.url); 18 | const __dirname = dirname(__filename); 19 | 20 | const REDIS_NODE_TYPE = "cache.t3.micro"; 21 | 22 | const commonEnvVariables = { 23 | POWERTOOLS_SERVICE_NAME: 'NFL-stadiums', 24 | POWERTOOLS_METRICS_NAMESPACE: 'NFLStadiums', 25 | LOG_LEVEL: 'DEBUG', 26 | PARAMETERS_SECRETS_EXTENSION_HTTP_PORT: "2773", 27 | SSM_PARAMETER_STORE_TTL: "100", 28 | PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL: "DEBUG", 29 | } 30 | 31 | const commonProps: Partial = { 32 | runtime: Runtime.NODEJS_18_X, 33 | memorySize: 1024, 34 | tracing: Tracing.ACTIVE, 35 | architecture: Architecture.ARM_64, 36 | timeout: Duration.seconds(10), 37 | logRetention: RetentionDays.ONE_DAY, 38 | environment: { 39 | ...commonEnvVariables 40 | }, 41 | bundling: { 42 | banner: "import { createRequire } from 'module';const require = createRequire(import.meta.url);", 43 | minify: true, 44 | format: OutputFormat.ESM, 45 | tsconfig: join(__dirname, '../tsconfig.json'), 46 | esbuildArgs:{ 47 | "--tree-shaking": "true" 48 | }, 49 | nodeModules: [ '@aws-lambda-powertools/logger', '@aws-lambda-powertools/tracer', '@aws-lambda-powertools/metrics' ], 50 | externalModules: [] 51 | } 52 | }; 53 | 54 | export class StadiumsStack extends Stack { 55 | constructor(scope: Construct, id: string, props?: StackProps) { 56 | super(scope, id, props); 57 | 58 | const dbUsername = StringParameter.valueForStringParameter(this, "/lambda-optimized/dbUsername"); 59 | const dbName = StringParameter.valueForStringParameter(this, "/lambda-optimized/dbName"); 60 | const parameterStoreExtensionARN = StringParameter.valueForStringParameter(this, `/lambda-optimized/parameters-store/${process.env.CDK_DEFAULT_REGION}/arm`) 61 | 62 | 63 | const vpc = new ec2.Vpc(this, "StadiumsVpc", { 64 | subnetConfiguration: [ 65 | { 66 | name: 'Isolated', 67 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 68 | }, 69 | { 70 | name: 'Private', 71 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 72 | }, 73 | { 74 | name: 'Public', 75 | subnetType: ec2.SubnetType.PUBLIC, 76 | } 77 | ] 78 | }); 79 | 80 | const rdsSecret = new Secret(this, 'RdsProxyExampleSecret', { 81 | secretName: `${id}-rds-credentials`, 82 | generateSecretString: { 83 | secretStringTemplate: JSON.stringify({ username: dbUsername}), 84 | generateStringKey: 'password', 85 | excludePunctuation: true, 86 | includeSpace: false, 87 | } 88 | }); 89 | 90 | // Create a security group to be used on the lambda functions 91 | const lambdaSecurityGroup = new ec2.SecurityGroup(this, 'Lambda Security Group', { 92 | vpc 93 | }); 94 | 95 | // Create a security group to be used on the RDS proxy 96 | const rdsProxySecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From Lambda', { 97 | vpc 98 | }); 99 | rdsProxySecurityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(5432), 'allow lambda connection to rds proxy'); 100 | 101 | // Create a security group to be used on the RDS instances 102 | const rdsSecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From RDS Proxy', { 103 | vpc 104 | }); 105 | rdsSecurityGroup.addIngressRule(rdsProxySecurityGroup, ec2.Port.tcp(5432), 'allow db connections from the rds proxy'); 106 | 107 | const ecSecurityGroup = new ec2.SecurityGroup(this, 'elasticache-sg', { 108 | vpc: vpc 109 | }); 110 | ecSecurityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(6379), 'allow access to ElastiCache Redis from lambda'); 111 | 112 | const vpcEndpointSSM = new ec2.InterfaceVpcEndpoint(this, `SSMVpcEndpoint`, { 113 | service: ec2.InterfaceVpcEndpointAwsService.SSM, 114 | vpc: vpc, 115 | subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 116 | }) 117 | 118 | vpcEndpointSSM.addToPolicy(new PolicyStatement({ 119 | actions: [ 120 | 'ssm:Get*', 121 | ], 122 | resources: ['*'], 123 | principals: [new AnyPrincipal()] 124 | })) 125 | 126 | const rdsCredentials = rds.Credentials.fromSecret(rdsSecret); 127 | 128 | const postgreSql = new rds.DatabaseCluster(this, 'RDSProxyCluster', { 129 | defaultDatabaseName: dbName, 130 | clusterIdentifier: 'RDSProxyCluster', 131 | engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_3 }), 132 | credentials: rdsCredentials, 133 | writer: rds.ClusterInstance.provisioned('writer', { 134 | publiclyAccessible: false, 135 | }), 136 | readers: [ 137 | rds.ClusterInstance.provisioned('reader1', { promotionTier: 1 }), 138 | ], 139 | securityGroups: [rdsSecurityGroup], 140 | vpcSubnets: { 141 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 142 | }, 143 | vpc, 144 | }); 145 | 146 | const rdsProxy = postgreSql.addProxy('rdsProxy', { 147 | secrets: [ rdsSecret ], 148 | securityGroups: [ rdsProxySecurityGroup ], 149 | debugLogging: true, 150 | iamAuth: true, 151 | vpc 152 | }); 153 | 154 | const PGHost = new StringParameter(this, "PGHost", { 155 | parameterName: "/lambda-optimized/dbEndpoint", 156 | description: "DB endpoint", 157 | stringValue: rdsProxy.endpoint 158 | }); 159 | 160 | const RedisSG = new CfnSubnetGroup( 161 | this, 162 | "RedisClusterPrivateSubnetGroup", 163 | { 164 | cacheSubnetGroupName: "private", 165 | subnetIds: vpc.selectSubnets({subnetType: ec2.SubnetType.PRIVATE_ISOLATED}).subnetIds, 166 | description: "redis private subnet group" 167 | } 168 | ); 169 | 170 | const RedisCluster = new CfnCacheCluster(this, `RedisCluster`, { 171 | engine: "redis", 172 | cacheNodeType: REDIS_NODE_TYPE, 173 | numCacheNodes: 1, 174 | clusterName: "redis-cache", 175 | vpcSecurityGroupIds: [ecSecurityGroup.securityGroupId], 176 | cacheSubnetGroupName: RedisSG.ref, 177 | engineVersion: "7.1" 178 | }); 179 | 180 | RedisCluster.addDependency(RedisSG); 181 | 182 | const cacheEndpointValue = new StringParameter(this, "CacheEndpoint", { 183 | parameterName: "/lambda-optimized/cacheEndpoint", 184 | description: "cacheEndpoint", 185 | stringValue: RedisCluster.attrRedisEndpointAddress 186 | }); 187 | 188 | const cachePortValue = new StringParameter(this, "CachePort", { 189 | parameterName: "/lambda-optimized/cachePort", 190 | description: "cachePort", 191 | stringValue: RedisCluster.attrRedisEndpointPort 192 | }); 193 | 194 | const lambdaIAMRole = new Role(this, 'extensionforLambdaIAMRole', { 195 | assumedBy: new ServicePrincipal('lambda.amazonaws.com'), 196 | description: 'Role for getting access to SSM', 197 | inlinePolicies: { 198 | "extensionsPolicy": new PolicyDocument({ 199 | statements: [new PolicyStatement({ 200 | actions: [ 201 | 'ssm:Get*', 202 | 'logs:CreateLogGroup', 203 | 'logs:CreateLogStream', 204 | 'logs:PutLogEvents', 205 | 'cloudwatch:PutMetricStream', 206 | 'cloudwatch:PutMetricData', 207 | "ec2:DescribeNetworkInterfaces", 208 | "ec2:CreateNetworkInterface", 209 | "ec2:DeleteNetworkInterface", 210 | "ec2:DescribeInstances", 211 | "ec2:AttachNetworkInterface", 212 | "xray:PutTraceSegments", 213 | "xray:PutTelemetryRecords" 214 | ], 215 | resources: ['*'] 216 | })], 217 | }) 218 | } 219 | }); 220 | 221 | const parameterStoreExtension = LayerVersion.fromLayerVersionArn(this, "parameterStoreExtension", parameterStoreExtensionARN); 222 | 223 | const populateDBLambda: NodejsFunction = new NodejsFunction(this, `${id}-populateLambda`, { 224 | ...commonProps, 225 | handler: 'handler', 226 | entry: join(__dirname, '../lambda/populate.ts'), 227 | vpc: vpc, 228 | vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 229 | securityGroups: [ lambdaSecurityGroup ], 230 | layers: [ 231 | parameterStoreExtension 232 | ], 233 | role: lambdaIAMRole, 234 | }); 235 | 236 | const getStadiumsDataLambda: NodejsFunction = new NodejsFunction(this, `${id}-getDataLambda`, { 237 | ...commonProps, 238 | handler: 'handler', 239 | entry: join(__dirname, '../lambda/getData.ts'), 240 | vpc: vpc, 241 | vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 242 | securityGroups: [ lambdaSecurityGroup ], 243 | layers: [ 244 | parameterStoreExtension 245 | ], 246 | role: lambdaIAMRole, 247 | }); 248 | 249 | rdsProxy.grantConnect(populateDBLambda, dbUsername); 250 | rdsProxy.grantConnect(getStadiumsDataLambda, dbUsername); 251 | 252 | const httpApi: HttpApi = new HttpApi(this, 'StadiumsHttpApi'); 253 | 254 | const getDataLambdaIntegration = new HttpLambdaIntegration('getStadiumsDataLambda', getStadiumsDataLambda); 255 | 256 | httpApi.addRoutes({ 257 | path: '/', 258 | methods: [HttpMethod.GET], 259 | integration: getDataLambdaIntegration, 260 | }); 261 | 262 | new CfnOutput(this, 'stadiumsEndpointUrl', { 263 | value: `${httpApi.url}` 264 | }); 265 | 266 | }; 267 | 268 | } -------------------------------------------------------------------------------- /optimized/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rds-proxy", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "bin": { 6 | "rds-proxy": "bin/rds-proxy.js" 7 | }, 8 | "scripts": { 9 | "build": "tsc", 10 | "watch": "tsc -w", 11 | "test": "jest", 12 | "cdk": "cdk", 13 | "analyse": "esbuild --bundle ./lambda/getData.ts --external:@aws-sdk --outfile=out.js --target=es2020 --tree-shaking=true --platform=node --minify --analyze", 14 | "powertuning": "./scripts/execute.sh", 15 | "loadtest": "artillery run ./scripts/loadTestStrategy.yml" 16 | }, 17 | "devDependencies": { 18 | "@aws-cdk/aws-apigatewayv2-alpha": "^2.108.0-alpha.0", 19 | "@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.108.0-alpha.0", 20 | "@aws-cdk/aws-ec2": "^1.204.0", 21 | "@k-foss/ts-esnode": "^2.0.3", 22 | "@types/aws-lambda": "^8.10.83", 23 | "@types/jest": "^29.5.3", 24 | "@types/node": "^20.6.0", 25 | "artillery": "^2.0.0-37", 26 | "aws-cdk": "^2.108.0", 27 | "aws-cdk-lib": "^2.108.0", 28 | "esbuild": "^0.19.3", 29 | "esbuild-plugin-polyfill-node": "^0.3.0", 30 | "jest": "^29.6.2", 31 | "ts-jest": "^29.1.1", 32 | "ts-node": "^10.9.1", 33 | "typescript": "~5.1.6" 34 | }, 35 | "dependencies": { 36 | "@aws-lambda-powertools/logger": "^1.12.1", 37 | "@aws-lambda-powertools/metrics": "^1.12.1", 38 | "@aws-lambda-powertools/tracer": "^1.12.1", 39 | "@middy/core": "^4.6.2", 40 | "@redis/client": "^1.5.10", 41 | "aws-sdk-js-v3-rds-signer": "^1.0.1", 42 | "constructs": "^10.0.0", 43 | "postgres": "^3.3.5", 44 | "source-map-support": "^0.5.21" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /optimized/scripts/execute.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # config 3 | PROFILE=playground 4 | STACK_NAME=PowerTuningStack 5 | INPUT=$(cat scripts/params.json) # or use a static string 6 | STATE_MACHINE_ARN=arn:aws:states:region:account:stateMachine:powerTuningStateMachine-xxxxxx 7 | 8 | # start execution 9 | EXECUTION_ARN=$(aws stepfunctions start-execution --state-machine-arn $STATE_MACHINE_ARN --input "$INPUT" --query 'executionArn' --profile $PROFILE --output text) 10 | 11 | echo -n "Execution started..." 12 | 13 | # poll execution status until completed 14 | while true; 15 | do 16 | # retrieve execution status 17 | STATUS=$(aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'status' --profile $PROFILE --output text) 18 | 19 | if test "$STATUS" == "RUNNING"; then 20 | # keep looping and wait if still running 21 | echo -n "." 22 | sleep 1 23 | elif test "$STATUS" == "FAILED"; then 24 | # exit if failed 25 | echo -e "\nThe execution failed, you can check the execution logs with the following script:\naws stepfunctions get-execution-history --execution-arn $EXECUTION_ARN" 26 | break 27 | else 28 | # print execution output if succeeded 29 | echo $STATUS 30 | echo "Execution output: " 31 | # retrieve output 32 | aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'output' --profile $PROFILE --output text 33 | break 34 | fi 35 | done -------------------------------------------------------------------------------- /optimized/scripts/loadTestStrategy.yml: -------------------------------------------------------------------------------- 1 | config: 2 | 3 | target: URL to test HERE 4 | phases: 5 | - duration: 20 6 | arrivalRate: 1 7 | rampTo: 20 8 | name: 1 to 20 RPS 9 | - duration: 50 10 | arrivalRate: 20 11 | rampTo: 100 12 | name: to 100 RPS 13 | plugins: 14 | apdex: {} 15 | apdex: 16 | threshold: 100 17 | scenarios: 18 | - flow: 19 | - get: 20 | url: "/" 21 | expect: 22 | - statusCode: 200 23 | - contentType: json -------------------------------------------------------------------------------- /optimized/scripts/params.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambdaARN": "arn:aws:lambda:region:account:function:LambdaFunctionName", 3 | "powerValues": [128, 256, 512, 1024, 2048, 3072, 4096], 4 | "num": 5, 5 | "payload": {}, 6 | "parallelInvocation": true, 7 | "strategy": "balanced" 8 | } -------------------------------------------------------------------------------- /optimized/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "Node16", 5 | "lib": [ 6 | "es2020", 7 | "dom" 8 | ], 9 | "allowImportingTsExtensions": true, 10 | "noEmit": true, 11 | "moduleResolution": "Node16", 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "strict": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": false, 20 | "noUnusedParameters": false, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": false, 23 | "inlineSourceMap": true, 24 | "inlineSources": true, 25 | "experimentalDecorators": true, 26 | "strictPropertyInitialization": false, 27 | "resolveJsonModule": true, 28 | "typeRoots": [ 29 | "./node_modules/@types" 30 | ], 31 | "types": ["node"] 32 | }, 33 | "exclude": [ 34 | "node_modules", 35 | "cdk.out" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /unoptimized/README.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda performance tuning: Best practices and guidance 2 | This repo is broken into two parts. The first example (located in the 3 | unoptimized folder) is related to an simple application, a Lambda 4 | writing/reading data from a Amazon Aurora RDS Postgres Database. 5 | The second example (located in in the optimized folder) has a few more bells and 6 | whistles. This app is the same Lambda, but now it interacts with the Amazon 7 | Aurora RDS Postgres Database by way of a RDS Proxy to manage the connections. 8 | 9 | ## Useful commands 10 | * `npm run build` install the dependencies 11 | * `cdk deploy` deploy this stack to your default AWS account/region 12 | 13 | 14 | ## Optimized Application 15 | ![Optimized Application](../diagrams/optimized.png) 16 | 17 | ## Unoptimized Application 18 | 19 | ![Unoptimized Application](../diagrams/unoptimized.png) 20 | 21 | 22 | 23 | ## Interesting reading on pooling and Sequelize 24 | There are a few observations that our team idenfied while running the code 25 | https://sequelize.org/docs/v6/other-topics/aws-lambda/ 26 | 27 | 28 | -------------------------------------------------------------------------------- /unoptimized/bin/rds-proxy.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { App } from 'aws-cdk-lib'; 3 | 4 | import { StadiumsStack } from "../lib/stadiums-stack.ts"; 5 | import { ParametersStack } from "../lib/parameters-stack.ts"; 6 | 7 | const stackProps = { 8 | env: { 9 | account: process.env.CDK_DEFAULT_ACCOUNT as string, 10 | region: "eu-west-2", 11 | }, 12 | }; 13 | const app = new App(); 14 | 15 | const parametersStack = new ParametersStack(app, "ParametersStackUnoptimized", { 16 | ...stackProps, 17 | description: "This stack creates the parameters needed to configure the infrastrcture of the other stacks" 18 | }) 19 | 20 | const rdsProxyStack = new StadiumsStack (app, "StadiumsStackUnoptimized", { 21 | ...stackProps, 22 | description: "This stack creates a new Postgres database in a private (isolated) subnet", 23 | }); 24 | -------------------------------------------------------------------------------- /unoptimized/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx tsx bin/rds-proxy.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 21 | "@aws-cdk/core:checkSecretUsage": true, 22 | "@aws-cdk/core:target-partitions": [ 23 | "aws", 24 | "aws-cn" 25 | ], 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 41 | "@aws-cdk/aws-route53-patters:useCertificate": true, 42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false, 43 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, 44 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, 45 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, 46 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, 47 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, 48 | "@aws-cdk/aws-redshift:columnId": true, 49 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, 50 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, 51 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, 52 | "@aws-cdk/aws-kms:aliasNameRef": true, 53 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, 54 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, 55 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /unoptimized/lambda/database/DBValueObject.ts: -------------------------------------------------------------------------------- 1 | interface DBParams { 2 | database:string, 3 | endpoint:string, 4 | user:string, 5 | password:string, 6 | port:number, 7 | } 8 | 9 | export {DBParams} -------------------------------------------------------------------------------- /unoptimized/lambda/database/DBparametersSDK.ts: -------------------------------------------------------------------------------- 1 | import { logger, tracer } from "../powertools/utilities.ts"; 2 | import { DBParams } from './DBValueObject.ts'; 3 | import { SSMClient, GetParametersCommand } from "@aws-sdk/client-ssm"; 4 | 5 | class DBparameters { 6 | private PARAMS_ID_LIST = [ 7 | "/lambda-optimized/dbUsername", 8 | "/lambda-optimized/dbName", 9 | "/lambda-optimized/dbPort", 10 | "/lambda-optimized/dbEndpoint"]; 11 | private client: SSMClient; 12 | 13 | constructor(){ 14 | this.client = tracer.captureAWSv3Client(new SSMClient({region: process.env.AWS_REGION})); 15 | } 16 | 17 | @tracer.captureMethod({ 18 | subSegmentName: "### get DB parameters" 19 | }) 20 | public async getParameters(){ 21 | const params: { [key: string]: any } = {}; 22 | 23 | try{ 24 | const input = { 25 | Names: this.PARAMS_ID_LIST, 26 | }; 27 | const command = new GetParametersCommand(input); 28 | const response = await this.client.send(command); 29 | 30 | response.Parameters!.forEach(parameter => { 31 | params[parameter.Name!.split("/").pop() as string] = parameter.Value!; 32 | }); 33 | 34 | const dbParams:DBParams = { 35 | database: params.dbName, 36 | user: params.dbUsername, 37 | endpoint: params.dbEndpoint, 38 | password: "", 39 | port: Number(params.dbPort), 40 | } 41 | 42 | logger.info(`DB params: ${dbParams}`); 43 | return dbParams; 44 | } catch(error){ 45 | const rootTraceId = tracer.getRootXrayTraceId(); 46 | logger.error(`Unexpected error occurred while trying to retrieve stadiums. TraceID: ${rootTraceId}`, error as Error); 47 | throw error; 48 | } 49 | } 50 | 51 | } 52 | 53 | export { DBparameters } -------------------------------------------------------------------------------- /unoptimized/lambda/database/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, Options as SequelizeOptions, DataTypes } from 'sequelize'; 2 | import { Signer } from 'aws-sdk-js-v3-rds-signer'; 3 | import { DBParams } from './DBValueObject.js'; 4 | import { tracer } from "../powertools/utilities.js"; 5 | 6 | //check out the implementation for lambda: https://sequelize.org/docs/v6/other-topics/aws-lambda/ 7 | 8 | class Database { 9 | 10 | private _Stadiums:any 11 | private _sequelize:Sequelize 12 | 13 | @tracer.captureMethod({ 14 | subSegmentName: "### init DB connection" 15 | }) 16 | public init(params:DBParams){ 17 | 18 | const sequelizeConfig: SequelizeOptions = { 19 | host: params.endpoint, 20 | dialect: "postgres", 21 | pool:{ 22 | max: 1, 23 | min: 1, 24 | idle: 1000 25 | } 26 | } 27 | 28 | if (process.env.STAGE !== 'local') { 29 | sequelizeConfig.dialectOptions = { 30 | ssl: { 31 | rejectUnauthorized: true 32 | } 33 | }; 34 | } 35 | 36 | const signer = new Signer({ 37 | hostname: params.endpoint, 38 | port: params.port, 39 | region: process.env.AWS_REGION, 40 | username: params.user 41 | }); 42 | 43 | if(this._sequelize){ 44 | // @ts-ignore 45 | this._sequelize.connectionManager.initPools(); 46 | // @ts-ignore 47 | if (this._sequelize.connectionManager.hasOwnProperty("getConnection")) { 48 | // @ts-ignore 49 | delete this._sequelize.connectionManager.getConnection; 50 | } 51 | } else { 52 | this._sequelize = new Sequelize(params.database || '', params.user || '', params.password || '', sequelizeConfig); 53 | } 54 | 55 | 56 | if (process.env.STAGE !== 'local' && !this._sequelize.hasHook('beforeConnect')) { 57 | this._sequelize.addHook('beforeConnect', async (config) => { 58 | // @ts-ignore 59 | config.password = await signer.getAuthToken(); 60 | }); 61 | } 62 | 63 | this._Stadiums = this._sequelize.define('Stadium', { 64 | name: { 65 | type: DataTypes.STRING, 66 | allowNull: false 67 | }, 68 | capacity: { 69 | type: DataTypes.INTEGER, 70 | allowNull: false 71 | }, 72 | location: { 73 | type: DataTypes.STRING, 74 | allowNull: false 75 | }, 76 | surface: { 77 | type: DataTypes.STRING, 78 | allowNull: false 79 | }, 80 | roof: { 81 | type: DataTypes.STRING, 82 | allowNull: false 83 | }, 84 | team: { 85 | type: DataTypes.STRING, 86 | allowNull: false 87 | }, 88 | yearOpened: { 89 | type: DataTypes.STRING, 90 | allowNull: false 91 | } 92 | }, { 93 | tableName: params.database.toLowerCase() 94 | }); 95 | 96 | } 97 | 98 | public get stadiums() { 99 | return this._Stadiums; 100 | } 101 | 102 | @tracer.captureMethod({ 103 | subSegmentName: "### authenticate to DB" 104 | }) 105 | public async authenticate(){ 106 | return await this._sequelize.authenticate() 107 | } 108 | @tracer.captureMethod({ 109 | subSegmentName: "### close DB connection" 110 | }) 111 | public async closeConnection(){ 112 | return await this._sequelize.close(); 113 | } 114 | 115 | } 116 | export {Database}; -------------------------------------------------------------------------------- /unoptimized/lambda/getData.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyResultV2 } from 'aws-lambda'; 2 | import { DBParams } from './database/DBValueObject.ts'; 3 | import { DBparameters } from './database/DBparametersSDK.ts'; 4 | import { Database } from './database/sequelize.ts'; 5 | import { logger, tracer, metrics } from "./powertools/utilities.js"; 6 | import { captureLambdaHandler } from '@aws-lambda-powertools/tracer'; 7 | import { injectLambdaContext } from '@aws-lambda-powertools/logger'; 8 | import { logMetrics, MetricUnits } from '@aws-lambda-powertools/metrics'; 9 | import middy from "@middy/core"; 10 | 11 | 12 | const lambdaHandler = async function (): Promise { 13 | 14 | let responseStruct, db; 15 | 16 | const segment = tracer.getSegment()!; 17 | tracer.annotateColdStart(); 18 | 19 | try { 20 | const SSMSubSegment = segment.addNewSubsegment(`### get params from SSM`); 21 | tracer.setSegment(SSMSubSegment); 22 | const parameters = new DBparameters() 23 | const parametersList:DBParams = await parameters.getParameters(); 24 | SSMSubSegment.close(); 25 | 26 | db = new Database(); 27 | await db.init(parametersList); 28 | 29 | const querySubSegment = segment.addNewSubsegment(`### get all stadiums`); 30 | tracer.setSegment(querySubSegment); 31 | const stadiums = await db.stadiums.findAll(); 32 | querySubSegment.close(); 33 | logger.info('total stadiums retrieved', { details: { total_stadiums: stadiums.length } }); 34 | metrics.addMetric('all stadiums requests', MetricUnits.Count, 1); 35 | 36 | responseStruct = { 37 | statusCode: 200, 38 | headers: { "Content-Type": "application/json" }, 39 | body: JSON.stringify(stadiums) 40 | }; 41 | 42 | } catch (error) { 43 | 44 | logger.error('Unexpected error occurred while trying to retrieve stadiums', error as Error); 45 | const rootTraceId = tracer.getRootXrayTraceId(); 46 | responseStruct = { 47 | statusCode: 500, 48 | headers: { _X_AMZN_TRACE_ID: rootTraceId || "" }, 49 | body: JSON.stringify(error) 50 | }; 51 | 52 | } 53 | 54 | await db?.closeConnection(); 55 | 56 | tracer.setSegment(segment); 57 | return responseStruct 58 | }; 59 | 60 | export const handler = middy(lambdaHandler) 61 | .use(captureLambdaHandler(tracer)) 62 | .use(logMetrics(metrics, { captureColdStartMetric: true, throwOnEmptyMetrics: true })) 63 | .use(injectLambdaContext(logger, { clearState: true })); -------------------------------------------------------------------------------- /unoptimized/lambda/populate.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda'; 2 | import { Database } from './database/sequelize.js'; 3 | import { DBParams } from './database/DBValueObject.js'; 4 | import stadiumData from './stadium-data.json'; 5 | import { DBparameters } from './database/DBparametersSDK.ts'; 6 | import { logger, tracer } from "./powertools/utilities.js"; 7 | import { captureLambdaHandler } from '@aws-lambda-powertools/tracer'; 8 | import { injectLambdaContext } from '@aws-lambda-powertools/logger'; 9 | import middy from "@middy/core"; 10 | 11 | const parameters = new DBparameters() 12 | const parametersList:DBParams = await parameters.getParameters(); 13 | 14 | const lambdaHandler = async function (event: APIGatewayProxyEventV2): Promise { 15 | try { 16 | logger.info('DB params retrieved', { details: { dbParams: parametersList } }); 17 | 18 | const db = new Database(); 19 | await db.init(parametersList); 20 | await db.authenticate(); 21 | await db.stadiums.sync({ force: true }); 22 | for(const stadium of stadiumData.stadiums) { 23 | await db.stadiums.create(stadium); 24 | } 25 | logger.info('total stadiums created', { details: { total_stadiums: stadiumData.stadiums.length } }); 26 | 27 | return { 28 | statusCode: 201, 29 | headers: { "Content-Type": "application/json" }, 30 | body: JSON.stringify({ "message": "Stadium table and data successfully created." }) 31 | }; 32 | 33 | } catch (error) { 34 | console.error('Unable to connect to the database:', error); 35 | 36 | return { 37 | statusCode: 500, 38 | headers: { "Content-Type": "application/json" }, 39 | body: JSON.stringify(error) 40 | } 41 | } 42 | }; 43 | 44 | export const handler = middy(lambdaHandler) 45 | .use(captureLambdaHandler(tracer)) 46 | .use(injectLambdaContext(logger, { clearState: true })); -------------------------------------------------------------------------------- /unoptimized/lambda/powertools/utilities.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@aws-lambda-powertools/logger'; 2 | import { Metrics } from '@aws-lambda-powertools/metrics'; 3 | import { Tracer } from '@aws-lambda-powertools/tracer'; 4 | 5 | const logger = new Logger({ 6 | persistentLogAttributes: { 7 | aws_account_id: process.env.AWS_ACCOUNT_ID || 'N/A', 8 | aws_region: process.env.AWS_REGION || 'N/A', 9 | } 10 | }); 11 | 12 | const metrics = new Metrics({ 13 | defaultDimensions: { 14 | aws_account_id: process.env.AWS_ACCOUNT_ID || 'N/A', 15 | aws_region: process.env.AWS_REGION || 'N/A', 16 | } 17 | }); 18 | 19 | const tracer = new Tracer(); 20 | 21 | export { 22 | logger, 23 | metrics, 24 | tracer 25 | }; -------------------------------------------------------------------------------- /unoptimized/lambda/stadium-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "stadiums": [ 3 | { 4 | "name": "Los Angeles Memorial Coliseum", 5 | "capacity": 93607, 6 | "location": "Los Angeles, California", 7 | "surface": "Natural grass", 8 | "roof": "Open", 9 | "team": "Los Angeles Rams", 10 | "yearOpened": "1923" 11 | }, 12 | { 13 | "name": "MetLife Stadium", 14 | "capacity": 82500, 15 | "location": "East Rutherford, New Jersey", 16 | "surface": "UBU Speed Series S5-M Synthetic Turf", 17 | "roof": "Open", 18 | "team": "New York Giants", 19 | "yearOpened": "2010" 20 | }, 21 | { 22 | "name": "MetLife Stadium", 23 | "capacity": 82500, 24 | "location": "East Rutherford, New Jersey", 25 | "surface": "UBU Speed Series S5-M Synthetic Turf", 26 | "roof": "Open", 27 | "team": "New York Jets", 28 | "yearOpened": "2010" 29 | }, 30 | { 31 | "name": "FedEx Field", 32 | "capacity": 82000, 33 | "location": "Landover, Maryland", 34 | "surface": "Latitude 36 Bermuda Grass", 35 | "roof": "Open", 36 | "team": "Washington Football Team", 37 | "yearOpened": "1997" 38 | }, 39 | { 40 | "name": "Lambeau Field", 41 | "capacity": 81435, 42 | "location": "Green Bay, Wisconsin", 43 | "surface": "Hybrid Grass-Synthetic", 44 | "roof": "Open", 45 | "team": "Green Bay Packers", 46 | "yearOpened": "1957" 47 | }, 48 | { 49 | "name": "AT&T Stadium", 50 | "capacity": 80000, 51 | "location": "Arlington, Texas", 52 | "surface": "Matrix RealGrass artificial turf", 53 | "roof": "Retractable", 54 | "team": "Dallas Cowboys", 55 | "yearOpened": "2009" 56 | }, 57 | { 58 | "name": "Arrowhead Stadium", 59 | "capacity": 76416, 60 | "location": "Kansas City, Missouri", 61 | "surface": "Latitude 36 Bermuda Grass", 62 | "roof": "Open", 63 | "team": "Kansas City Chiefs", 64 | "yearOpened": "1972" 65 | }, 66 | { 67 | "name": "Empower Field at Mile High", 68 | "capacity": 76125, 69 | "location": "Denver, Colorado", 70 | "surface": "Kentucky Bluegrass", 71 | "roof": "Open", 72 | "team": "Denver Broncos", 73 | "yearOpened": "2001" 74 | }, 75 | { 76 | "name": "Bank of America Stadium", 77 | "capacity": 75419, 78 | "location": "Charlotte, North Carolina", 79 | "surface": "Voyager Bermuda Grass", 80 | "roof": "Open", 81 | "team": "Carolina Panthers", 82 | "yearOpened": "1996" 83 | }, 84 | { 85 | "name": "Mercedes-Benz Superdome", 86 | "capacity": 73000, 87 | "location": "New Orleans, Louisiana", 88 | "surface": "UBU Turf (artificial)", 89 | "roof": "Fixed", 90 | "team": "New Orleans Saints", 91 | "yearOpened": "1975" 92 | }, 93 | { 94 | "name": "NRG Stadium", 95 | "capacity": 72220, 96 | "location": "Houston, Texas", 97 | "surface": "AstroTurf GameDay Grass 3D", 98 | "roof": "Retractable", 99 | "team": "Houston Texans", 100 | "yearOpened": "2002" 101 | }, 102 | { 103 | "name": "New Era Field", 104 | "capacity": 71870, 105 | "location": "Orchard Park, New York", 106 | "surface": "A-Turf Titan 50 (artificial)", 107 | "roof": "Open", 108 | "team": "Buffalo Bills", 109 | "yearOpened": "1973" 110 | }, 111 | { 112 | "name": "Georgia Dome", 113 | "capacity": 71250, 114 | "location": "Atlanta, Georgia", 115 | "surface": "FieldTurf Classic HD", 116 | "roof": "Fixed", 117 | "team": "Atlanta Falcons", 118 | "yearOpened": "1992" 119 | }, 120 | { 121 | "name": "M&T Bank Stadium", 122 | "capacity": 71008, 123 | "location": "Baltimore, Maryland", 124 | "surface": "Latitude 36 Bermuda Grass", 125 | "roof": "Open", 126 | "team": "Baltimore Ravens", 127 | "yearOpened": "1998" 128 | }, 129 | { 130 | "name": "Qualcomm Stadium", 131 | "capacity": 70561, 132 | "location": "San Diego, California", 133 | "surface": "Bandera Bermuda Grass", 134 | "roof": "Open", 135 | "team": "San Diego Chargers", 136 | "yearOpened": "1967" 137 | }, 138 | { 139 | "name": "Lincoln Financial Field", 140 | "capacity": 69596, 141 | "location": "Philadelphia, Pennsylvania", 142 | "surface": "Desso GrassMaster", 143 | "roof": "Open", 144 | "team": "Philadelphia Eagles", 145 | "yearOpened": "2003" 146 | }, 147 | { 148 | "name": "Nissan Stadium", 149 | "capacity": 69143, 150 | "location": "Nashville, Tennessee", 151 | "surface": "TifSport Bermuda Grass", 152 | "roof": "Open", 153 | "team": "Tennessee Titans", 154 | "yearOpened": "1999" 155 | }, 156 | { 157 | "name": "Levi's Stadium", 158 | "capacity": 68500, 159 | "location": "Santa Clara, California", 160 | "surface": "Tifway II Bermuda Grass / Perennial Ryegrass mixture", 161 | "roof": "Open", 162 | "team": "San Francisco 49ers", 163 | "yearOpened": "2014" 164 | }, 165 | { 166 | "name": "Heinz Field", 167 | "capacity": 68400, 168 | "location": "Pittsburgh, Pennsylvania", 169 | "surface": "Kentucky Bluegrass", 170 | "roof": "Open", 171 | "team": "Pittsburgh Steelers", 172 | "yearOpened": "2001" 173 | }, 174 | { 175 | "name": "CenturyLink Field", 176 | "capacity": 68000, 177 | "location": "Seattle, Washington", 178 | "surface": "FieldTurf Revolution", 179 | "roof": "Open", 180 | "team": "Seattle Seahawks", 181 | "yearOpened": "2002" 182 | }, 183 | { 184 | "name": "FirstEnergy Stadium", 185 | "capacity": 67431, 186 | "location": "Cleveland, Ohio", 187 | "surface": "Kentucky Bluegrass", 188 | "roof": "Open", 189 | "team": "Cleveland Browns", 190 | "yearOpened": "1999" 191 | }, 192 | { 193 | "name": "EverBank Field", 194 | "capacity": 67246, 195 | "location": "Jacksonville, Florida", 196 | "surface": "Tifway 419 Bermuda Grass", 197 | "roof": "Open", 198 | "team": "Jacksonville Jaguars", 199 | "yearOpened": "1999" 200 | }, 201 | { 202 | "name": "Lucas Oil Stadium", 203 | "capacity": 67000, 204 | "location": "Indianapolis, Indiana", 205 | "surface": "FieldTurf Classic HD", 206 | "roof": "Retractable", 207 | "team": "Indianapolis Colts", 208 | "yearOpened": "2008" 209 | }, 210 | { 211 | "name": "Gillette Stadium", 212 | "capacity": 66829, 213 | "location": "Foxborough, Massachusetts", 214 | "surface": "FieldTurf Revolution", 215 | "roof": "Open", 216 | "team": "New England Patriots", 217 | "yearOpened": "2002" 218 | }, 219 | { 220 | "name": "U.S. Bank Stadium", 221 | "capacity": 66200, 222 | "location": "Minneapolis, Minnesota", 223 | "surface": "UBU Speed Series S5-M Synthetic Turf", 224 | "roof": "Fixed", 225 | "team": "Minnesota Vikings", 226 | "yearOpened": "2016" 227 | }, 228 | { 229 | "name": "Raymond James Stadium", 230 | "capacity": 65890, 231 | "location": "Tampa, Florida", 232 | "surface": "Tifway 419 Bermuda Grass", 233 | "roof": "Open", 234 | "team": "Tampa Bay Buccaneers", 235 | "yearOpened": "1998" 236 | }, 237 | { 238 | "name": "Paul Brown Stadium", 239 | "capacity": 65515, 240 | "location": "Cincinnati, Ohio", 241 | "surface": "UBU Speed Series S5-M Synthetic Turf", 242 | "roof": "Open", 243 | "team": "Cincinnati Bengals", 244 | "yearOpened": "2000" 245 | }, 246 | { 247 | "name": "Hard Rock Stadium", 248 | "capacity": 65326, 249 | "location": "Miami Gardens, Florida", 250 | "surface": "Platinum TE Paspalum", 251 | "roof": "Open", 252 | "team": "Miami Dolphins", 253 | "yearOpened": "1987" 254 | }, 255 | { 256 | "name": "Ford Field", 257 | "capacity": 65000, 258 | "location": "Detroit, Michigan", 259 | "surface": "FieldTurf Classic HD", 260 | "roof": "Fixed", 261 | "team": "Detroit Lions", 262 | "yearOpened": "2002" 263 | }, 264 | { 265 | "name": "University of Phoenix Stadium", 266 | "capacity": 63400, 267 | "location": "Glendale, Arizona", 268 | "surface": "Tifway 419 Bermuda Grass", 269 | "roof": "Retractable", 270 | "team": "Arizona Cardinals", 271 | "yearOpened": "2006" 272 | }, 273 | { 274 | "name": "Soldier Field", 275 | "capacity": 61500, 276 | "location": "Chicago, Illinois", 277 | "surface": "Kentucky Bluegrass", 278 | "roof": "Open", 279 | "team": "Chicago Bears", 280 | "yearOpened": "1924" 281 | }, 282 | { 283 | "name": "Allegiant Stadium", 284 | "capacity": 65000, 285 | "location": "Paradise, Nevada", 286 | "surface": "Bermuda grass", 287 | "roof": "Fixed", 288 | "team": "Las Vegas Raiders", 289 | "yearOpened": "2020" 290 | } 291 | ] 292 | } -------------------------------------------------------------------------------- /unoptimized/lib/parameters-stack.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib'; 3 | import { Construct } from 'constructs'; 4 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 5 | 6 | export class ParametersStack extends Stack { 7 | constructor(scope: Construct, id: string, props?: StackProps) { 8 | super(scope, id, props); 9 | 10 | const dbUsernameValue = new StringParameter(this, "DBUsername", { 11 | parameterName: "/lambda-optimized/dbUsername", 12 | description: "DBUsername", 13 | stringValue: 'syscdk' 14 | }); 15 | 16 | const dbNameValue = new StringParameter(this, "DBName", { 17 | parameterName: "/lambda-optimized/dbName", 18 | description: "dbName", 19 | stringValue: 'Stadiums' 20 | }); 21 | 22 | const dbPortValue = new StringParameter(this, "DBPort", { 23 | parameterName: "/lambda-optimized/dbPort", 24 | description: "dbPort", 25 | stringValue: "5432" 26 | }); 27 | 28 | }; 29 | } -------------------------------------------------------------------------------- /unoptimized/lib/stadiums-stack.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { Stack, StackProps, CfnOutput, Duration } from 'aws-cdk-lib'; 4 | import { aws_ec2 as ec2 } from 'aws-cdk-lib'; 5 | import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; 6 | import { aws_rds as rds } from 'aws-cdk-lib'; 7 | import { Runtime, Tracing, Architecture, RuntimeFamily } from 'aws-cdk-lib/aws-lambda'; 8 | import { NodejsFunction,NodejsFunctionProps, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs'; 9 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 10 | import { Construct } from 'constructs'; 11 | import { RetentionDays } from 'aws-cdk-lib/aws-logs'; 12 | import { HttpApi, HttpMethod} from '@aws-cdk/aws-apigatewayv2-alpha'; 13 | import { HttpLambdaIntegration} from '@aws-cdk/aws-apigatewayv2-integrations-alpha'; 14 | import { AnyPrincipal, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; 15 | 16 | const __filename = fileURLToPath(import.meta.url); 17 | const __dirname = dirname(__filename); 18 | 19 | const commonEnvVariables = { 20 | POWERTOOLS_SERVICE_NAME: 'NFL-stadiums-unoptimized', 21 | POWERTOOLS_METRICS_NAMESPACE: 'NFLStadiums-unoptimized', 22 | LOG_LEVEL: 'DEBUG', 23 | } 24 | 25 | const commonProps: Partial = { 26 | runtime: Runtime.NODEJS_18_X, 27 | memorySize: 256, 28 | tracing: Tracing.ACTIVE, 29 | architecture: Architecture.X86_64, 30 | timeout: Duration.seconds(10), 31 | logRetention: RetentionDays.ONE_DAY, 32 | environment: { 33 | ...commonEnvVariables 34 | }, 35 | bundling: { 36 | banner: "import { createRequire } from 'module';const require = createRequire(import.meta.url);", 37 | minify: true, 38 | format: OutputFormat.ESM, 39 | tsconfig: join(__dirname, '../tsconfig.json'), 40 | nodeModules: [ 'pg', 'pg-hstore', '@aws-lambda-powertools/logger', '@aws-lambda-powertools/tracer', '@aws-lambda-powertools/metrics' ], 41 | externalModules: [] 42 | } 43 | }; 44 | 45 | export class StadiumsStack extends Stack { 46 | constructor(scope: Construct, id: string, props?: StackProps) { 47 | super(scope, id, props); 48 | 49 | const dbUsername = StringParameter.valueForStringParameter(this, "/lambda-optimized/dbUsername"); 50 | const dbName = StringParameter.valueForStringParameter(this, "/lambda-optimized/dbName"); 51 | 52 | const vpc = new ec2.Vpc(this, "StadiumsVpc", { 53 | subnetConfiguration: [ 54 | { 55 | name: 'Isolated', 56 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 57 | }, 58 | { 59 | name: 'Private', 60 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 61 | }, 62 | { 63 | name: 'Public', 64 | subnetType: ec2.SubnetType.PUBLIC, 65 | } 66 | ] 67 | }); 68 | 69 | const rdsSecret = new Secret(this, 'RdsProxyExampleSecret', { 70 | secretName: `${id}-rds-credentials`, 71 | generateSecretString: { 72 | secretStringTemplate: JSON.stringify({ username: dbUsername}), 73 | generateStringKey: 'password', 74 | excludePunctuation: true, 75 | includeSpace: false, 76 | } 77 | }); 78 | 79 | // Create a security group to be used on the lambda functions 80 | const lambdaSecurityGroup = new ec2.SecurityGroup(this, 'Lambda Security Group', { 81 | vpc 82 | }); 83 | 84 | // Create a security group to be used on the RDS proxy 85 | const rdsProxySecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From Lambda', { 86 | vpc 87 | }); 88 | rdsProxySecurityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(5432), 'allow lambda connection to rds proxy'); 89 | 90 | // Create a security group to be used on the RDS instances 91 | const rdsSecurityGroup = new ec2.SecurityGroup(this, 'Only Allow Access From RDS Proxy', { 92 | vpc 93 | }); 94 | rdsSecurityGroup.addIngressRule(rdsProxySecurityGroup, ec2.Port.tcp(5432), 'allow db connections from the rds proxy'); 95 | 96 | const vpcEndpointSSM = new ec2.InterfaceVpcEndpoint(this, `SSMVpcEndpoint`, { 97 | service: ec2.InterfaceVpcEndpointAwsService.SSM, 98 | vpc: vpc, 99 | subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 100 | }) 101 | 102 | vpcEndpointSSM.addToPolicy(new PolicyStatement({ 103 | actions: [ 104 | 'ssm:Get*', 105 | ], 106 | resources: ['*'], 107 | principals: [new AnyPrincipal()] 108 | })) 109 | 110 | const rdsCredentials = rds.Credentials.fromSecret(rdsSecret); 111 | 112 | const postgreSql = new rds.DatabaseCluster(this, 'RDSProxyCluster', { 113 | defaultDatabaseName: dbName, 114 | clusterIdentifier: 'RDSProxyCluster', 115 | engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_3 }), 116 | credentials: rdsCredentials, 117 | writer: rds.ClusterInstance.provisioned('writer', { 118 | publiclyAccessible: false, 119 | instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM), 120 | }), 121 | serverlessV2MinCapacity: 5, 122 | serverlessV2MaxCapacity: 15, 123 | readers: [ 124 | rds.ClusterInstance.serverlessV2('reader1'), 125 | ], 126 | securityGroups: [rdsSecurityGroup], 127 | vpcSubnets: { 128 | subnetType: ec2.SubnetType.PRIVATE_ISOLATED, 129 | }, 130 | vpc, 131 | }); 132 | 133 | const rdsProxy = postgreSql.addProxy('rdsProxy', { 134 | secrets: [ rdsSecret ], 135 | securityGroups: [ rdsProxySecurityGroup ], 136 | debugLogging: true, 137 | iamAuth: true, 138 | vpc 139 | }); 140 | 141 | const PGHost = new StringParameter(this, "PGHost", { 142 | parameterName: "/lambda-optimized/dbEndpoint", 143 | description: "DB endpoint", 144 | stringValue: rdsProxy.endpoint 145 | }); 146 | 147 | const lambdaIAMRole = new Role(this, 'extensionforLambdaIAMRole', { 148 | assumedBy: new ServicePrincipal('lambda.amazonaws.com'), 149 | description: 'Role for getting access to SSM', 150 | inlinePolicies: { 151 | "extensionsPolicy": new PolicyDocument({ 152 | statements: [new PolicyStatement({ 153 | actions: [ 154 | 'ssm:Get*', 155 | 'logs:CreateLogGroup', 156 | 'logs:CreateLogStream', 157 | 'logs:PutLogEvents', 158 | 'cloudwatch:PutMetricStream', 159 | 'cloudwatch:PutMetricData', 160 | "ec2:DescribeNetworkInterfaces", 161 | "ec2:CreateNetworkInterface", 162 | "ec2:DeleteNetworkInterface", 163 | "ec2:DescribeInstances", 164 | "ec2:AttachNetworkInterface", 165 | "xray:PutTraceSegments", 166 | "xray:PutTelemetryRecords" 167 | ], 168 | resources: ['*'] 169 | })], 170 | }) 171 | } 172 | }); 173 | 174 | const populateDBLambda: NodejsFunction = new NodejsFunction(this, `${id}-populateLambda`, { 175 | ...commonProps, 176 | handler: 'handler', 177 | retryAttempts: 0, 178 | entry: join(__dirname, '../lambda/populate.ts'), 179 | vpc: vpc, 180 | vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 181 | securityGroups: [ lambdaSecurityGroup ], 182 | role: lambdaIAMRole, 183 | }); 184 | 185 | const getStadiumsDataLambda: NodejsFunction = new NodejsFunction(this, `${id}-getDataLambda`, { 186 | ...commonProps, 187 | retryAttempts: 0, 188 | handler: 'handler', 189 | entry: join(__dirname, '../lambda/getData.ts'), 190 | vpc: vpc, 191 | vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, 192 | securityGroups: [ lambdaSecurityGroup ], 193 | role: lambdaIAMRole, 194 | }); 195 | 196 | rdsProxy.grantConnect(populateDBLambda, dbUsername); 197 | rdsProxy.grantConnect(getStadiumsDataLambda, dbUsername); 198 | 199 | const httpApi: HttpApi = new HttpApi(this, 'StadiumsHttpApi'); 200 | 201 | const getDataLambdaIntegration = new HttpLambdaIntegration('getStadiumsDataLambda', getStadiumsDataLambda); 202 | 203 | httpApi.addRoutes({ 204 | path: '/', 205 | methods: [HttpMethod.GET], 206 | integration: getDataLambdaIntegration, 207 | }); 208 | 209 | new CfnOutput(this, 'stadiumsEndpointUrl', { 210 | value: `${httpApi.url}` 211 | }); 212 | }; 213 | } -------------------------------------------------------------------------------- /unoptimized/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rds-proxy", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "bin": { 6 | "rds-proxy": "bin/rds-proxy.js" 7 | }, 8 | "scripts": { 9 | "build": "tsc", 10 | "watch": "tsc -w", 11 | "test": "jest", 12 | "cdk": "cdk", 13 | "analyse": "esbuild --bundle ./lambda/getData.ts --external:@aws-sdk --outfile=out.js --target=es2020 --tree-shaking=true --platform=node --minify --analyze" 14 | }, 15 | "devDependencies": { 16 | "@aws-cdk/aws-apigatewayv2-alpha": "^2.92.0-alpha.0", 17 | "@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.92.0-alpha.0", 18 | "@aws-cdk/aws-ec2": "^1.19.0", 19 | "@k-foss/ts-esnode": "^2.0.3", 20 | "@types/aws-lambda": "^8.10.83", 21 | "@types/jest": "^29.5.3", 22 | "@types/node": "^20.6.0", 23 | "aws-cdk": "2.92.0", 24 | "aws-cdk-lib": "2.92.0", 25 | "esbuild": "^0.19.3", 26 | "esbuild-plugin-polyfill-node": "^0.3.0", 27 | "jest": "^29.6.2", 28 | "ts-jest": "^29.1.1", 29 | "ts-node": "^10.9.1", 30 | "typescript": "~5.1.6" 31 | }, 32 | "dependencies": { 33 | "@aws-lambda-powertools/logger": "^1.12.1", 34 | "@aws-lambda-powertools/metrics": "^1.12.1", 35 | "@aws-lambda-powertools/tracer": "^1.12.1", 36 | "@aws-sdk/client-ssm": "^3.418.0", 37 | "@middy/core": "^4.6.2", 38 | "aws-sdk-js-v3-rds-signer": "^1.0.1", 39 | "constructs": "^10.0.0", 40 | "pg": "^8.11.3", 41 | "pg-hstore": "^2.3.4", 42 | "sequelize": "^6.32.1", 43 | "source-map-support": "^0.5.21" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /unoptimized/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "Node16", 5 | "lib": [ 6 | "es2020", 7 | "dom" 8 | ], 9 | "allowImportingTsExtensions": true, 10 | "noEmit": true, 11 | "moduleResolution": "Node16", 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "strict": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": false, 20 | "noUnusedParameters": false, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": false, 23 | "inlineSourceMap": true, 24 | "inlineSources": true, 25 | "experimentalDecorators": true, 26 | "strictPropertyInitialization": false, 27 | "resolveJsonModule": true, 28 | "typeRoots": [ 29 | "./node_modules/@types" 30 | ], 31 | "types": ["node"] 32 | }, 33 | "exclude": [ 34 | "node_modules", 35 | "cdk.out" 36 | ] 37 | } --------------------------------------------------------------------------------