diff --git a/README.md b/README.md index df925c3..5e6ef31 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ functions: * **enabled** (default `false`) * **source** (default `{ "source": "serverless-plugin-warmup" }`) * **sourceRaw** (default `false`) +* **concurrency** (default `1`) ```yml custom: @@ -146,7 +147,8 @@ custom: timeout: 20 prewarm: true // Run WarmUp immediately after a deploymentlambda source: '{ "source": "my-custom-payload" }' - sourceRaw: true // Won't JSON.stringify() the source, may be necessary for Go/AppSync deployments + sourceRaw: true // Won't JSON.stringify() the source, may be necessary for Go/AppSync deployments + concurrency: 5 // Warm up 5 concurrent instances ``` **Options should be tweaked depending on:** @@ -285,8 +287,20 @@ if(context.custom.source === 'serverless-plugin-warmup'){ ... ``` +If you're using the `concurrency` option you might consider adding a slight delay before returning when warming up to ensure your function doesn't return before all concurrent requests have been started: +```javascript +module.exports.lambdaToWarm = function(event, context, callback) { + if (event.source === 'serverless-plugin-warmup') { + /** Slightly delayed (25ms) response for WarmUP plugin to ensure concurrent invocation */ + await new Promise(r => setTimeout(r, 25)) + console.log('WarmUP - Lambda is warm!') + return + } + ... add lambda logic after +} +``` ## Deployment Once everything is configured WarmUP will run on SLS `deploy`. diff --git a/src/index.js b/src/index.js index 8a3eb26..ac3fbd2 100644 --- a/src/index.js +++ b/src/index.js @@ -135,7 +135,8 @@ class WarmUP { : defaultOpts.enabled, source: (typeof possibleConfig.source !== 'undefined') ? (possibleConfig.sourceRaw ? possibleConfig.source : JSON.stringify(possibleConfig.source)) - : (defaultOpts.sourceRaw ? defaultOpts.source : JSON.stringify(defaultOpts.source)) + : (defaultOpts.sourceRaw ? defaultOpts.source : JSON.stringify(defaultOpts.source)), + concurrency: (typeof possibleConfig.concurrency === 'number') ? possibleConfig.concurrency : defaultOpts.concurrency } } @@ -157,7 +158,8 @@ class WarmUP { const functionDefaultOpts = { enabled: false, - source: JSON.stringify({ source: 'serverless-plugin-warmup' }) + source: JSON.stringify({ source: 'serverless-plugin-warmup' }), + concurrency: 1 } const customConfig = (this.custom && typeof this.custom.warmup !== 'undefined') @@ -246,9 +248,13 @@ const aws = require("aws-sdk"); aws.config.region = "${this.options.region}"; const lambda = new aws.Lambda(); const functions = ${JSON.stringify(functions)}; + module.exports.warmUp = async (event, context, callback) => { console.log("Warm Up Start"); + const invokes = await Promise.all(functions.map(async (func) => { + console.log(\`Warming up function: \${func.name} with concurrency: \${func.config.concurrency}\`); + const params = { ClientContext: Buffer.from(\`{"custom":\${func.config.source}}\`).toString('base64'), FunctionName: func.name, @@ -257,13 +263,14 @@ module.exports.warmUp = async (event, context, callback) => { Qualifier: process.env.SERVERLESS_ALIAS || "$LATEST", Payload: func.config.source }; - + try { - const data = await lambda.invoke(params).promise(); - console.log(\`Warm Up Invoke Success: \${functionName}\`, data); + await Promise.all(Array(func.config.concurrency).fill(0) + .map(async _ => await lambda.invoke(params).promise())) + console.log(\`Warm Up Invoke Success: \${func.name}\`); return true; } catch (e) { - console.log(\`Warm Up Invoke Error: \${functionName}\`, e); + console.log(\`Warm Up Invoke Error: \${func.name}\`, e); return false; } }));