1"use strict";(globalThis.webpackChunk=globalThis.webpackChunk||[]).push([[5271],{4848(e,n,o){o.r(n),o.d(n,{assets:()=>r,contentTitle:()=>c,default:()=>h,frontMatter:()=>a,metadata:()=>t,toc:()=>l});const t=JSON.parse('{"id":"other-topics/aws-lambda","title":"Using sequelize in AWS Lambda","description":"AWS Lambda is a serverless computing service that allows customers","source":"@site/docs/other-topics/aws-lambda.md","sourceDirName":"other-topics","slug":"/other-topics/aws-lambda","permalink":"/docs/v7/other-topics/aws-lambda","draft":false,"unlisted":false,"editUrl":"https://github.com/sequelize/website/tree/main/docs/other-topics/aws-lambda.md","tags":[],"version":"current","lastUpdatedBy":"renovate[bot]","lastUpdatedAt":1775795365000,"frontMatter":{"title":"Using sequelize in AWS Lambda"},"sidebar":"tutorialSidebar","previous":{"title":"Custom Data Types","permalink":"/docs/v7/other-topics/extending-data-types"},"next":{"title":"Connection Pool","permalink":"/docs/v7/other-topics/connection-pool"}}');var i=o(74848),s=o(28453);const a={title:"Using sequelize in AWS Lambda"},c=void 0,r={},l=[{value:"TL;DR",id:"tldr",level:2},{value:"Using AWS RDS Proxy",id:"using-aws-rds-proxy",level:3},{value:"The Node.js event loop",id:"the-nodejs-event-loop",level:2},{value:"AWS Lambda function handler types in Node.js",id:"aws-lambda-function-handler-types-in-nodejs",level:2},{value:"AWS Lambda execution environments (i.e. containers)",id:"aws-lambda-execution-environments-ie-containers",level:2},{value:"Sequelize connection pooling in AWS Lambda",id:"sequelize-connection-pooling-in-aws-lambda",level:2},{value:"Detailed race condition example",id:"detailed-race-condition-example",level:3}];function d(e){const n={a:"a",blockquote:"blockquote",code:"code",h2:"h2",h3:"h3",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://aws.amazon.com/lambda/",children:"AWS Lambda"})," is a serverless computing service that allows customers\nto run code without having to worry about the underlying servers. Using ",(0,i.jsx)(n.code,{children:"sequelize"})," in AWS Lambda\ncan be tricky if certain concepts are not properly understood and an appropriate configuration is\nnot used. This guide seeks to clarify some of these concepts so users of the library can properly\nconfigure ",(0,i.jsx)(n.code,{children:"sequelize"})," for AWS Lambda and troubleshoot issues."]}),"\n",(0,i.jsx)(n.h2,{id:"tldr",children:"TL;DR"}),"\n",(0,i.jsxs)(n.p,{children:["If you just want to learn how to properly configure ",(0,i.jsx)(n.code,{children:"sequelize"}),"\n",(0,i.jsx)(n.a,{href:"/docs/v7/other-topics/connection-pool",children:"connection pooling"})," for AWS Lambda, all you need to know is that\n",(0,i.jsx)(n.code,{children:"sequelize"})," connection pooling does not get along well with AWS Lambda's Node.js runtime and it ends\nup causing more problems than it solves. Therefore, the most appropriate configuration is to ",(0,i.jsx)(n.strong,{children:"use\npooling within the same invocation"})," and ",(0,i.jsx)(n.strong,{children:"avoid pooling across invocations"})," (i.e. close all\nconnections at the end):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"const { Sequelize } = require('@sequelize/core');\n\nlet sequelize = null;\n\nasync function loadSequelize() {\n const sequelize = new Sequelize({\n // (...)\n pool: {\n /*\n * Lambda functions process one request at a time but your code may issue multiple queries\n * concurrently. Be wary that `sequelize` has methods that issue 2 queries concurrently\n * (e.g. `Model.findAndCountAll()`). Using a value higher than 1 allows concurrent queries to\n * be executed in parallel rather than serialized. Careful with executing too many queries in\n * parallel per Lambda function execution since that can bring down your database with an\n * excessive number of connections.\n *\n * Ideally you want to choose a `max` number where this holds true:\n * max * EXPECTED_MAX_CONCURRENT_LAMBDA_INVOCATIONS < MAX_ALLOWED_DATABASE_CONNECTIONS * 0.8\n */\n max: 2,\n /*\n * Set this value to 0 so connection pool eviction logic eventually cleans up all connections\n * in the event of a Lambda function timeout.\n */\n min: 0,\n /*\n * Set this value to 0 so connections are eligible for cleanup immediately after they're\n * returned to the pool.\n */\n idle: 0,\n // Choose a small enough value that fails fast if a connection takes too long to be established.\n acquire: 3000,\n /*\n * Ensures the connection pool attempts to be cleaned up automatically on the next Lambda\n * function invocation, if the previous invocation timed out.\n */\n evict: CURRENT_LAMBDA_FUNCTION_TIMEOUT,\n },\n });\n\n // or `sequelize.sync()`\n await sequelize.authenticate();\n\n return sequelize;\n}\n\nmodule.exports.handler = async function (event, callback) {\n // re-use the sequelize instance across invocations to improve performance\n if (!sequelize) {\n sequelize = await loadSequelize();\n } else {\n // restart connection pool to ensure connections are not re-used across invocations\n sequelize.connectionManager.initPools();\n\n // restore `getConnection()` if it has been overwritten by `close()`\n if (sequelize.connectionManager.hasOwnProperty('getConnection')) {\n delete sequelize.connectionManager.getConnection;\n }\n }\n\n try {\n return await doSomethingWithSequelize(sequelize);\n } finally {\n // close any opened connections during the invocation\n // this will wait for any in-progress queries to finish before closing the connections\n await sequelize.connectionManager.close();\n }\n};\n"})}),"\n",(0,i.jsx)(n.h3,{id:"using-aws-rds-proxy",children:"Using AWS RDS Proxy"}),"\n",(0,i.jsxs)(n.p,{children:["If your are using ",(0,i.jsx)(n.a,{href:"https://aws.amazon.com/rds/",children:"AWS RDS"})," and you are using\n",(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html",children:"Aurora"})," or a\n",(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html",children:"supported database engine"}),",\nthen connect to your database using ",(0,i.jsx)(n.a,{href:"https://aws.amazon.com/rds/proxy/",children:"AWS RDS Proxy"}),". This will\nmake sure that opening/closing connections on each invocation is not an expensive operation for\nyour underlying database server."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.p,{children:"If you want to understand why you must use sequelize this way in AWS Lambda, continue reading the\nrest of this document:"}),"\n",(0,i.jsx)(n.h2,{id:"the-nodejs-event-loop",children:"The Node.js event loop"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/",children:"Node.js event loop"})," is:"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"what allows Node.js to perform non-blocking I/O operations \u2014 despite the fact that JavaScript is\nsingle-threaded \u2014"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["While the event loop implementation is in C++, here's a simplified JavaScript pseudo-implementation\nthat illustrates how Node.js would execute a script named ",(0,i.jsx)(n.code,{children:"index.js"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// see: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/\n// see: https://www.youtube.com/watch?v=P9csgxBgaZ8\n// see: https://www.youtube.com/watch?v=PNa9OMajw9w\nconst process = require('process');\n\n/*\n * counter of pending events\n *\n * reference counter is increased for every:\n *\n * 1. scheduled timer: `setTimeout()`, `setInterval()`, etc.\n * 2. scheduled immediate: `setImmediate()`.\n * 3. syscall of non-blocking IO: `require('net').Server.listen()`, etc.\n * 4. scheduled task to the thread pool: `require('fs').WriteStream.write()`, etc.\n *\n * reference counter is decreased for every:\n *\n * 1. elapsed timer\n * 2. executed immediate\n * 3. completed non-blocking IO\n * 4. completed thread pool task\n *\n * references can be explicitly decreased by invoking `.unref()` on some\n * objects like: `require('net').Socket.unref()`\n */\nlet refs = 0;
1\n\n/*\n * a heap of timers, sorted by next ocurrence\n *\n * whenever `setTimeout()` or `setInterval()` is invoked, a timer gets added here\n */\nconst timersHeap = /* (...) */;\n\n/*\n * a FIFO queue of immediates\n *\n * whenever `setImmediate()` is invoked, it gets added here\n */\nconst immediates = /* (...) */;\n\n/*\n * a FIFO queue of next tick callbacks\n *\n * whenever `require('process').nextTick()` is invoked, the callback gets added here\n */\nconst nextTickCallbacks = [];\n\n/*\n * a heap of Promise-related callbacks, sorted by promise constructors callbacks first,\n * and then resolved/rejected callbacks\n *\n * whenever a new Promise instance is created via `new Promise` or a promise resolves/rejects\n * the appropriate callback (if any) gets added here\n */\nconst promiseCallbacksHeap = /* ... */;\n\nfunction execTicksAndPromises() {\n while (nextTickCallbacks.length || promiseCallbacksHeap.size()) {\n // execute all callbacks scheduled with `process.nextTick()`\n while (nextTickCallbacks.length) {\n const callback = nextTickCallbacks.shift();\n callback();\n }\n\n // execute all promise-related callbacks\n while (promiseCallbacksHeap.size()) {\n const callback = promiseCallbacksHeap.pop();\n callback();\n }\n }\n}\n\ntry {\n // execute index.js\n require('./index');\n execTicksAndPromises();\n\n do {\n // timers phase: executes all elapsed timers\n getElapsedTimerCallbacks(timersHeap).forEach(callback => {\n callback();\n execTicksAndPromises();\n });\n\n // pending callbacks phase: executes some system operations (like `TCP errors`) that are not\n // executed in the poll phase\n getPendingCallbacks().forEach(callback => {\n callback();\n execTicksAndPromises();\n })\n\n // poll phase: gets completed non-blocking I/O events or thread pool tasks and invokes the\n // corresponding callbacks; if there are none and there's no pending immediates,\n // it blocks waiting for events/completed tasks for a maximum of `maxWait`\n const maxWait = computeWhenNextTimerElapses(timersHeap);\n pollForEventsFromKernelOrThreadPool(maxWait, immediates).forEach(callback => {\n callback();\n execTicksAndPromises();\n });\n\n // check phase: execute available immediates; if an immediate callback invokes `setImmediate()`\n // it will be invoked on the next event loop iteration\n getImmediateCallbacks(immediates).forEach(callback => {\n callback();\n execTicksAndPromises();\n });\n\n // close callbacks phase: execute special `.on('close')` callbacks\n getCloseCallbacks().forEach(callback => {\n callback();\n execTicksAndPromises();\n });\n\n if (refs === 0) {\n // listeners of this event may execute code that increments `refs`\n process.emit('beforeExit');\n }\n } while (refs > 0);\n} catch (err) {\n if (!process.listenerCount('uncaughtException')) {\n // default behavior: print stack and exit with status code 1\n console.error(err.stack);\n process.exit(1);\n } else {\n // there are listeners: emit the event and exit using `process.exitCode || 0`\n process.emit('uncaughtException');\n process.exit();\n }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"aws-lambda-function-handler-types-in-nodejs",children:"AWS Lambda function handler types in Node.js"}),"\n",(0,i.jsx)(n.p,{children:"AWS Lambda handlers come in two flavors in Node.js:"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#nodejs-handler-sync",children:"Non-async handlers"}),"\n(i.e. ",(0,i.jsx)(n.code,{children:"callback"}),"):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"module.exports.handler = function (event, context, callback) {\n try {\n doSomething();\n callback(null, 'Hello World!'); // Lambda returns \"Hello World!\"\n } catch (err) {\n // try/catch is not required, uncaught exceptions invoke `callback(err)` implicitly\n callback(err); // Lambda fails with `err`\n }\n};\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#no
1dejs-handler-async",children:"Async handlers"}),"\n(i.e. use ",(0,i.jsx)(n.code,{children:"async"}),"/",(0,i.jsx)(n.code,{children:"await"})," or ",(0,i.jsx)(n.code,{children:"Promise"}),"s):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// async/await\nmodule.exports.handler = async function (event, context) {\n try {\n await doSomethingAsync();\n return 'Hello World!'; // equivalent of: callback(null, \"Hello World!\");\n } catch (err) {\n // try/cath is not required, async functions always return a Promise\n throw err; // equivalent of: callback(err);\n }\n};\n\n// Promise\nmodule.exports.handler = function (event, context) {\n /*\n * must return a `Promise` to be considered an async handler\n *\n * an uncaught exception that prevents a `Promise` to be returned\n * by the handler will \"downgrade\" the handler to non-async\n */\n return Promise.resolve()\n .then(() => doSomethingAsync())\n .then(() => 'Hello World!');\n};\n"})}),"\n",(0,i.jsx)(n.p,{children:"While at first glance it seems like async VS non-async handlers are simply a code styling choice,\nthere is a fundamental difference between the two:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["In async handlers, a Lambda function execution finishes when the ",(0,i.jsx)(n.code,{children:"Promise"})," returned by the handler\nresolves or rejects, regardless of whether the event loop is empty or not."]}),"\n",(0,i.jsxs)(n.li,{children:["In non-async handlers, a Lambda function execution finishes when one of the following conditions\noccur:","\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The event loop is empty\n(",(0,i.jsxs)(n.a,{href:"https://nodejs.org/dist/latest-v12.x/docs/api/process.html#process_event_beforeexit",children:["process ",(0,i.jsx)(n.code,{children:"'beforeExit'"})," event"]}),"\nis used to detect this)."]}),"\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsx)(n.code,{children:"callback"})," argument is invoked and\n",(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html",children:(0,i.jsx)(n.code,{children:"context.callbackWaitsForEmptyEventLoop"})}),"\nis set to ",(0,i.jsx)(n.code,{children:"false"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["This fundamental difference is very important to understand in order to rationalize how ",(0,i.jsx)(n.code,{children:"sequelize"}),"\nmay be affected by it. Here are a few examples to illustrate the difference:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// no callback invoked\nmodule.exports.handler = function () {\n // Lambda finishes AFTER `doSomething()` is invoked\n setTimeout(() => doSomething(), 1000);\n};\n\n// callback invoked\nmodule.exports.handler = function (event, context, callback) {\n // Lambda finishes AFTER `doSomething()` is invoked\n setTimeout(() => doSomething(), 1000);\n callback(null, 'Hello World!');\n};\n\n// callback invoked, context.callbackWaitsForEmptyEventLoop = false\nmodule.exports.handler = function (event, context, callback) {\n // Lambda finishes BEFORE `doSomething()` is invoked\n context.callbackWaitsForEmptyEventLoop = false;\n setTimeout(() => doSomething(), 2000);\n setTimeout(() => callback(null, 'Hello World!'), 1000);\n};\n\n// async/await\nmodule.exports.handler = async function () {\n // Lambda finishes BEFORE `doSomething()` is invoked\n setTimeout(() => doSomething(), 1000);\n return 'Hello World!';\n};\n\n// Promise\nmodule.exports.handler = function () {\n // Lambda finishes BEFORE `doSomething()` is invoked\n setTimeout(() => doSomething(), 1000);\n return Promise.resolve('Hello World!');\n};\n"})}),"\n",(0,i.jsx)(n.h2,{id:"aws-lambda-execution-environments-ie-containers",children:"AWS Lambda execution environments (i.e. containers)"}),"\n",(0,i.jsxs)(n.p,{children:["AWS Lambda function handlers are invoked by built-in or custom\n",(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html",children:"runtimes"}
1)," which run in\nexecution environments (i.e. containers) that\n",(0,i.jsx)(n.a,{href:"https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/",children:"may or may not be re-used"}),"\nacross invocations. Containers can only process\n",(0,i.jsx)(n.a,{href:"https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html",children:"one request at a time"}),".\nConcurrent invocations of a Lambda function means that a container instance will be created for each\nconcurrent request."]}),"\n",(0,i.jsx)(n.p,{children:"In practice, this means that Lambda functions should be designed to be stateless but developers can\nuse state for caching purposes:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:'let sequelize = null;\n\nmodule.exports.handler = async function () {\n /*\n * sequelize will already be loaded if the container is re-used\n *\n * containers are never re-used when a Lambda function\'s code change\n *\n * while the time elapsed between Lambda invocations is used as a factor to determine whether\n * a container is re-used, no assumptions should be made of when a container is actually re-used\n *\n * AWS does not publicly document the rules of container re-use "by design" since containers\n * can be recycled in response to internal AWS Lambda events (e.g. a Lambda function container\n * may be recycled even if the function is constanly invoked)\n */\n if (!sequelize) {\n sequelize = await loadSequelize();\n }\n\n return await doSomethingWithSequelize(sequelize);\n};\n'})}),"\n",(0,i.jsx)(n.p,{children:'When a Lambda function doesn\'t wait for the event loop to be empty and a container is re-used,\nthe event loop will be "paused" until the next invocation occurs. For example:'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"let counter = 0;\n\nmodule.exports.handler = function (event, context, callback) {\n /*\n * The first invocation (i.e. container initialized) will:\n * - log:\n * - Fast timeout invoked. Request id: 00000000-0000-0000-0000-000000000000 | Elapsed ms: 5XX\n * - return: 1\n *\n * Wait 3 seconds and invoke the Lambda again. The invocation (i.e. container re-used) will:\n * - log:\n * - Slow timeout invoked. Request id: 00000000-0000-0000-0000-000000000000 | Elapsed ms: 3XXX\n * - Fast timeout invoked. Request id: 11111111-1111-1111-1111-111111111111 | Elapsed ms: 5XX\n * - return: 3\n */\n const now = Date.now();\n\n context.callbackWaitsForEmptyEventLoop = false;\n\n setTimeout(() => {\n console.log(\n 'Slow timeout invoked. Request id:',\n context.awsRequestId,\n '| Elapsed ms:',\n Date.now() - now,\n );\n counter++;\n }, 1000);\n\n setTimeout(() => {\n console.log(\n 'Fast timeout invoked. Request id:',\n context.awsRequestId,\n '| Elapsed ms:',\n Date.now() - now,\n );\n counter++;\n callback(null, counter);\n }, 500);\n};\n"})}),"\n",(0,i.jsx)(n.h2,{id:"sequelize-connection-pooling-in-aws-lambda",children:"Sequelize connection pooling in AWS Lambda"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"sequelize"})," uses connection pooling for optimizing usage of database connections. The connection\npool used by ",(0,i.jsx)(n.code,{children:"sequelize"}
1)," is implemented using ",(0,i.jsx)(n.code,{children:"setTimeout()"})," callbacks (which are processed by the\nNode.js event loop)."]}),"\n",(0,i.jsxs)(n.p,{children:["Given the fact that AWS Lambda containers process one request at a time, one would be tempted to\nconfigure ",(0,i.jsx)(n.code,{children:"sequelize"})," as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"import { Sequelize } from '@sequelize/core';\n\nconst sequelize = new Sequelize({\n // (...)\n pool: { min: 1, max: 1 },\n});\n"})}),"\n",(0,i.jsx)(n.p,{children:"This configuration prevents Lambda containers from overwhelming the database server with an\nexcessive number of connections (since each container takes at most 1 connection). It also makes\nsure that the container's connection is not garbage collected when idle so the connection does not\nneed to be re-established when the Lambda container is re-used. Unfortunately, this configuration\npresents a set of issues:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["Lambdas that wait for the event loop to be empty will always time out. ",(0,i.jsx)(n.code,{children:"sequelize"})," connection\npools schedule a ",(0,i.jsx)(n.code,{children:"setTimeout"})," every\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/interfaces/_sequelize_core.index.PoolOptions.html#evict",children:(0,i.jsx)(n.code,{children:"options.pool.evict"})}),"\nms until ",(0,i.jsx)(n.strong,{children:"all idle connections have been closed"}),". However, since ",(0,i.jsx)(n.code,{children:"min"})," is set to ",(0,i.jsx)(n.code,{children:"1"}),", there\nwill always be at least one idle connection in the pool, resulting in an infinite event loop."]}),"\n",(0,i.jsxs)(n.li,{children:["Some operations like\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/classes/_sequelize_core.index.Model.html#findAndCountAll",children:(0,i.jsx)(n.code,{children:"Model.findAndCountAll()"})}),"\nexecute multiple queries asynchronously (e.g.\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/classes/_sequelize_core.index.Model.html#count",children:(0,i.jsx)(n.code,{children:"Model.count()"})})," and\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/classes/_sequelize_core.index.Model.html#findAll",children:(0,i.jsx)(n.code,{children:"Model.findAll()"})}),"). Using a maximum of\none connection forces the queries to be executed serially (rather than in parallel using two\nconnections). While this may be an acceptable performance compromise in order to\nmaintain a manageable number of database connections, long running queries may result in\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/classes/_sequelize_core.index.ConnectionAcquireTimeoutError.html",children:(0,i.jsx)(n.code,{children:"ConnectionAcquireTimeoutError"})}),"\nif a query takes more than the default or configured\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/interfaces/_sequelize_core.index.PoolOptions.html#acquire",children:(0,i.jsx)(n.code,{children:"options.pool.acquire"})}),"\ntimeout to complete. This is because the serialized query will be stuck waiting on the pool until\nthe connection used by the other query is released."]}),"\n",(0,i.jsxs)(n.li,{children:['If the AWS Lambda function times out (i.e. the configured AWS Lambda timeout is exceeded), the\nNode.js event loop will be "paused" regardless of its state. This can cause race conditions that\nresult in connection errors. For example, you may encounter situations where a very expensive\nquery causes a Lambda function to time out, the event loop is "paused" before the expensive query\nfinishes and the connection is released back to the pool, and subsequent Lambda invocations fail\nwith a ',(0,i.jsx)(n.code,{children:"ConnectionAcquireTimeoutError"})," if the container is re-used and the connection has not\nbeen returned after ",(0,i.jsx)(n.code,{children:"options.pool.acquire"})," ms."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["You can attempt to mitigate issue ",(0,i.jsx)(n.strong,{children:"#2"})," by using ",(0,i.jsx)(n.code,{children:"{ min: 1, max: 2 }"}),". However, this will still\nsuffer from issues ",(0,i.jsx)(n.strong,{children:"#1"})," and ",(0,i.jsx)(n.strong,{children:"#3"})," whilst introducing additional ones:"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:['Race conditions may occur where the even loop "pauses" before a connection pool eviction callback\nexecutes or more than ',(0,i.jsx)(n.code,{children:"options.pool.evict"})," time elapses between Lambda invocations. This can\nresult in timeout errors, handshake errors, and other connection-related errors."]}),"\n",(0,i.jsxs)(n.li,{children:["If you use an operation like ",(0,i.jsx)(n.code,{children:"Model.findAndCountAll()"})," and either the underlying ",(0,i.jsx)(n.code,{children:"Model.count()"}
1),"\nor ",(0,i.jsx)(n.code,{children:"Model.findAll()"}),' queries fail, you won\'t be able to ensure that the other query has finished\nexecuting (and the connection is put back into the pool) before the Lambda function execution\nfinishes and the event loop is "paused". This can leave connections in a stale state which can\nresult in prematurely closed TCP connections and other connection-related errors.']}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Using ",(0,i.jsx)(n.code,{children:"{ min: 2, max: 2 }"})," mitigates additional issue ",(0,i.jsx)(n.strong,{children:"#1"}),". However, the configuration still\nsuffers from all the other issues (original ",(0,i.jsx)(n.strong,{children:"#1"}),", ",(0,i.jsx)(n.strong,{children:"#3"}),", and additional ",(0,i.jsx)(n.strong,{children:"#2"}),")."]}),"\n",(0,i.jsx)(n.h3,{id:"detailed-race-condition-example",children:"Detailed race condition example"}),"\n",(0,i.jsxs)(n.p,{children:["In order to make sense of the example, you'll need a bit more context of how certain parts of\nLambda and ",(0,i.jsx)(n.code,{children:"sequelize"})," are implemented."]}),"\n",(0,i.jsxs)(n.p,{children:["The built-in AWS Lambda runtime for ",(0,i.jsx)(n.code,{children:"nodejs.12x"})," is implemented in Node.js. You can access the\nentire source code of the runtime by reading the contents of ",(0,i.jsx)(n.code,{children:"/var/runtime/"})," inside a Node.js Lambda\nfunction. The relevant subset of the code is as follows:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"runtime/Runtime.js"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"class Runtime {\n // (...)\n\n // each iteration is executed in the event loop `check` phase\n scheduleIteration() {\n setImmediate(() => this.handleOnce().then(/* (...) */));\n }\n\n async handleOnce() {\n // get next invocation. see: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next\n let { bodyJson, headers } = await this.client.nextInvocation();\n\n // prepare `context` handler parameter\n let invokeContext = new InvokeContext(headers);\n invokeContext.updateLoggingContext();\n\n // prepare `callback` handler parameter\n let [callback, callbackContext] = CallbackContext.build(\n this.client,\n invokeContext.
1invokeId,\n this.scheduleIteration.bind(this),\n );\n\n try {\n // this listener is subscribed to process.on('beforeExit')\n // so that when when `context.callbackWaitsForEmptyEventLoop === true`\n // the Lambda execution finishes after the event loop is empty\n this._setDefaultExitListener(invokeContext.invokeId);\n\n // execute handler\n const result = this.handler(\n JSON.parse(bodyJson),\n invokeContext.attachEnvironmentData(callbackContext),\n callback,\n );\n\n // finish the execution if the handler is async\n if (_isPromise(result)) {\n result.then(callbackContext.succeed, callbackContext.fail).catch(callbackContext.fail);\n }\n } catch (err) {\n callback(err);\n }\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"The runtime schedules an iteration at the end of the initialization code:"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"runtime/index.js"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"// (...)\n\nnew Runtime(client, handler, errorCallbacks).scheduleIteration();\n"})}),"\n",(0,i.jsxs)(n.p,{children:["All SQL queries invoked by a Lambda handler using ",(0,i.jsx)(n.code,{children:"sequelize"})," are ultimately executed using\n",(0,i.jsx)(n.a,{href:"pathname:///api/v7/classes/_sequelize_core.index.Sequelize.html#query",children:"Sequelize#query()"}),".\nThis method is responsible for obtaining a connection from the pool, executing the query, and\nreleasing the connection back to the pool when the query completes. The following snippet shows\na simplification of the method's logic for queries without transactions:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"sequelize.js"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"class Sequelize {\n // (...)\n\n async query(sql, options) {\n // (...)\n\n const connection = await this.connectionManager.getConnection(options);\n const query = new this.dialect.Query(connection, this, options);\n\n try {\n return await query.run(sql, bindParameters);\n } finally {\n await this.connectionManager.releaseConnection(connection);\n }\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The field ",(0,i.jsx)(n.code,{children:"this.connectionManager"})," is an instance of a dialect-specific ",(0,i.jsx)(n.code,{children:"ConnectionManager"})," class.\nAll dialect-specific managers inherit from an abstract ",(0,i.jsx)(n.code,{children:"ConnectionManager"})," class which initializes\nthe connection pool and configures it to invoke the dialect-specific class' ",(0,i.jsx)(n.code,{children:"connect()"})," method\neverytime a new connection needs to be created. The following snippet shows a simplification of the\n",(0,i.jsx)(n.code,{children:"mysql"})," dialect ",(0,i.jsx)(n.code,{children:"connect()"})," method:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"mysql/connection-manager.js"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"class ConnectionManager {\n // (...)\n\n async connect(config) {\n // (...)\n return await new Promise((resolve, reject) => {\n // uses mysql2's `new Connection()`\n const connection = this.lib.createConnection(connectionConfig);\n\n const errorHandler = e => {\n connection.removeListener('connect', connectHandler);\n connection.removeListener('error', connectHandler);\n reject(e);\n };\n\n const connectHandler = () => {\n connection.removeListener('error', errorHandler);\n resolve(connection);\n };\n\n connection.on('error', errorHandler);\n connection.once('connect', connectHandler);\n });\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The property ",(0,i.jsx)(n.code,{children:"this.lib"})," refers to ",(0,i.jsx)(n.a,{href:"https://www.npmjs.com/package/mysql2",children:(0,i.jsx)(n.code,{children:"mysql2"})})," and the function\n",(0,i.jsx)(n.code,{children:"createConnection()"})," creates a connection by creating an instance of a ",(0,i.jsx)(n.code,{children:"Connection"})," class. The\nrelevant subset of this class is as follows:"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"mysql2/connection.js"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"class Connection extends
1EventEmitter {\n constructor(opts) {\n // (...)\n\n // create Socket\n this.stream = /* (...) */;\n\n // when data is received, clear timeout\n this.stream.on('data', data => {\n if (this.connectTimeout) {\n Timers.clearTimeout(this.connectTimeout);\n this.connectTimeout = null;\n }\n this.packetParser.execute(data);\n });\n\n // (...)\n\n // when handshake is completed, emit the 'connect' event\n handshakeCommand.on('end', () => {\n this.emit('connect', handshakeCommand.handshake);\n });\n\n // set a timeout to trigger if no data is received on the socket\n if (this.config.connectTimeout) {\n const timeoutHandler = this._handleTimeoutError.bind(this);\n this.connectTimeout = Timers.setTimeout(\n timeoutHandler,\n this.config.connectTimeout\n );\n }\n }\n\n // (...)\n\n _handleTimeoutError() {\n if (this.connectTimeout) {\n Timers.clearTimeout(this.connectTimeout);\n this.connectTimeout = null;\n }\n this.stream.destroy && this.stream.destroy();\n const err = new Error('connect ETIMEDOUT');\n err.errorno = 'ETIMEDOUT';\n err.code = 'ETIMEDOUT';\n err.syscall = 'connect';\n\n // this will emit the 'error' event\n this._handleNetworkError(err);\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Based on the previous code, the following sequence of events shows how a connection pooling\nrace condition with ",(0,i.jsx)(n.code,{children:"{ min: 1, max: 1 }"})," can result with in a ",(0,i.jsx)(n.code,{children:"ETIMEDOUT"})," error:"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["A Lambda invocation is received (new container):","\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The event loop enters the ",(0,i.jsx)(n.code,{children:"check"})," phase and ",(0,i.jsx)(n.code,{children:"runtime/Runtime.js"}),"'s ",(0,i.jsx)(n.code,{children:"handleOnce()"})," method is\ninvoked.","\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsx)(n.code,{children:"handleOnce()"})," method invokes ",(0,i.jsx)(n.code,{children:"await this.client.nextInvocation()"})," and waits."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["The event loop skips the ",(0,i.jsx)(n.code,{children:"timers"})," phase since there no pending timers."]}),"\n",(0,i.jsxs)(n.li,{children:["The event loop enters the ",(0,i.jsx)(n.code,{children:"poll"})," phase and the ",(0,i.jsx)(n.code,{children:"handleOnce()"})," method continues."]}),"\n",(0,i.jsx)(n.li,{children:"The Lambda handler is invoked."}),"\n",(0,i.jsxs)(n.li,{children:["The Lambda handler invokes ",(0,i.jsx)(n.code,{children:"Model.count()"})," which invokes ",(0,i.jsx)(n.code,{children:"sequelize.js"}),"'s ",(0,i.jsx)(n.code,{children:"query()"})," which\ninvokes ",(0,i.jsx)(n.code,{children:"connectionManager.getConnection()"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The connection pool initializes a ",(0,i.jsx)(n.code,{children:"setTimeout(..., config.pool.acquire)"})," for ",(0,i.jsx)(n.code,{children:"Model.count()"}),"\nand invokes ",(0,i.jsx)(n.code,{children:"mysql/connection-manager.js"}),"'s ",(0,i.jsx)(n.code,{children:"connect()"})," to create a new connection."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"mysql2/connection.js"})," creates the TCP socket and initializes a ",(0,i.jsx)(n.code,{children:"setTimeout()"})," for failing\nthe connection with ",(0,i.jsx)(n.code,{children:"ETIMEDOUT"}),"."]}),"\n",(0,i.jsx)(n.li,{children:'The promise returned by the handler rejects (for reasons not detailed here) so the Lambda\nfunction execution finishes and the Node.js event loop is "paused".'}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["Enough time elapses between invocations so that:","\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"config.pool.acquire"})," timer elapses."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"mysql2"})," connection timer has not elapsed yet but has almost elapsed (i.e. race condition)."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["A second Lambda invocation is received (container re-used):","\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:'The event loop is "resumed".'}),"\n",(0,i.jsxs)(n.li,{children:["The event loop enters the ",(0,i.jsx)(n.code,{children:"check"})," phase and ",(0,i.jsx)(n.code,{children:"runtime/Runtime.js"}),"'s ",(0,i.jsx)(n.code,{children:"handleOnce()"})," method is\ninvoked."]}),"\n",(0,i.jsxs)(n.li,{children:["The event loop enters the ",(0,i.jsx)(n.code,{children:"timers"})," phase and the ",(0,i.jsx)(n.code,{children:"config.pool.acquire"})," timer elapses, causing\nthe previous invocation's ",(0,i.jsx)(n.code,{children:"Model.count()"})," promise to reject with\n",(0,i.jsx)(n.code,{children:"ConnectionAcquireTimeoutError"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The event loop enters the ",(0,i.jsx)(n.code,{children:"poll"})," phase and the ",(0,i.jsx)(n.code,{children:"handleOnce()"})," method continues."]}),"\n",(0,i.jsx)(n.li,{children:"The Lambda handler is invoked."}),"\n",(0,i.jsxs)(n.li,{children:["The Lambda handler invokes ",(0,i.jsx)(n.code,{children:"Model.count()"})," which invokes ",(0,i.jsx)(n.code,{children:"sequelize.js"}),"'s ",(0,i.jsx)(n.code,{children:"query()"})," which\ninvokes ",(0,i.jsx)(n.code,{children:"connectionManager.getConnection()"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The connection pool initializes a ",(0,i.jsx)(n.code,{children:"setTimeout(..., config.pool.acquire)"})," for ",(0,i.jsx)(n.code,{children:"Model.count()"}),"\nand since ",(0,i.jsx)(n.code,{children:"{ max : 1 }"})," it waits for the pending ",(0,i.jsx)(n.code,{children:"connect()"})," promise to complete."]}),"\n",(0,i.jsxs)(n.li,{children:["The event loop skips the ",(0,i.jsx)(n.code,{children:"check"})," phase since there are no pending immediates."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Race condition:"})," The event loop enters the ",(0,i.jsx)(n.code,{children:"timers"})," phase and the ",(0,i.jsx)(n.code,{children:"mysql2"})," connection\ntimeout elapses, resulting in a ",(0,i.jsx)(n.code,{children:"ETIMEDOUT"})," error that is emitted using\n",(0,i.jsx)(n.code,{children:"connection.emit('error')"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The emitted event rejects the promise in ",(0,i.jsx)(n.code,{children:"mysql/connection-manager.js"}),"'s ",(0,i.jsx)(n.code,{children:"connect()"})," which\nin turn forwards the rejected promise to the ",(0,i.jsx)(n.code,{children:"Model.count()"})," query's promise."]}),"\n",(0,i.jsxs)(n.li,{children:["The lambda function fails with an ",(0,i.jsx)(n.code,{children:"ETIMEDOUT"})," error."]}),"\n"]}),"\n"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},28453(e,n,o){o.d(n,{R:()=>a,x:()=>c});var t=o(96540);const i={},s=t.createContext(i);function a(e){const n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.