Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Create aws-lambda.md #12642

Merged
merged 3 commits into from
Nov 22, 2021
Merged

Create aws-lambda.md #12642

merged 3 commits into from
Nov 22, 2021

Conversation

marcogrcr
Copy link
Contributor

@marcogrcr marcogrcr commented Aug 25, 2020

This document aims to guide developers on how to use sequelize in AWS Lambda. Properly configuring sequelize for AWS Lambda is a common source of pain for developers.

@codecov
Copy link

codecov bot commented Aug 25, 2020

Codecov Report

Merging #12642 into master will decrease coverage by 0.46%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #12642       /-   ##
==========================================
- Coverage   96.32%   95.85%   -0.47%     
==========================================
  Files          95       92       -3     
  Lines        9261     9057     -204     
  Branches       90       90              
==========================================
- Hits         8921     8682     -239     
- Misses        323      358       35     
  Partials       17       17              
Impacted Files Coverage Δ
src/dialects/mariadb/query-generator.js 18.75% <0.00%> (-81.25%) ⬇️
src/dialects/mariadb/data-types.js 47.36% <0.00%> (-52.64%) ⬇️
src/dialects/abstract/query-interface.js 92.28% <0.00%> (-1.86%) ⬇️
src/sequelize.js 94.90% <0.00%> (-0.64%) ⬇️
src/dialects/mariadb/connection-manager.js
src/dialects/mariadb/index.js
src/dialects/mariadb/query.js

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e144683...ee3f724. Read the comment docs.

@marcogrcr
Copy link
Contributor Author

Here are some of the issues that I found related to AWS Lambda and connection pools: #3895, #4938, #5441, #6026, #8087, #8468, #9242, #10439, #11021, #11394, #12245, #12363, #12364, #12633

@marcogrcr
Copy link
Contributor Author

marcogrcr commented Aug 26, 2020

I have updated the guide with a simpler solution and more in-depth details for people that may be interested in diving deeper (diff).

@marcogrcr
Copy link
Contributor Author

I have updated the guide fixing some typos and other minor clarifications (diff).

@catamphetamine
Copy link
Contributor

Man, you really dug deep into this...

Respect

@amitaymolko
Copy link
Contributor

Thanks for this document it's very useful.
I didn't understand what connection pool settings you recommend in the end?
Also if you are using RDS proxy are any of the pool settings relevant?

@marcogrcr
Copy link
Contributor Author

Hi @amitaymolko:

I didn't understand what connection pool settings you recommend in the end?

Also if you are using RDS proxy are any of the pool settings relevant?

Since connection pooling is not used across invocations, the settings are not as important as before. For most cases the default settings should be OK:

{
  "max": 5,
  "min": 0,
  "idle": 10000,
  "acquire": 60000,
  "evict": 1000,
}

However you can optimize on a few things:

  • max: as far as I know, all sequelize operations issue at most 2 concurrent queries. So you should probably not want to use a value lower than 2 if you don't want your queries to be serialized. If you explicitly issue queries concurrently, same principle applies. That being said be careful about not exceeding your underlying server's capacity.
  • idle: this value is still important because your Lambda may timeout before you close the connections on the pool. So you want those connections to be closed when the function is invoked again. Use 1ms when you configure evict as recommended.
  • acquire: since Lambda containers handle a single request at a time, you can use a more aggressive value here. If you have a max value for all the Lambda's current queries, then you can put a value that represents the maximum time you're willing to wait for a connection to be established (e.g. 3000).
  • evict: this basically is the argument for a setTimeout() used by the pool to clean idle connections. Since you close them manually at the end of the request but your Lambda function may timeout, the ideal setting is to use the same value as your Lambda function's timeout.

I'll probably update the pull request with these recommendations.

@johnwquarles
Copy link

johnwquarles commented Sep 4, 2020

@marcogrcr Really appreciate the time & thought put into this writeup!

I ran into the issue today when setting up a serverless framework API, and noted that the TL;DR workaround seemed to produce errors on warm lambda invocations (cold starts were fine):

Error: ConnectionManager.getConnection was called after the connection manager was closed!

I believe it's because calling connectionManager.close() rewrites connectionManager.getConnection to throw the error I was seeing on those warm invocations:

throw new Error('ConnectionManager.getConnection was called after the connection manager was closed!');

I got around this by writing a function to do the same things, but without rewriting getConnection,

  const closeConnection = async () => {
    await sequelize.connectionManager.pool.drain()
    return sequelize.connectionManager.pool.destroyAllNow()
  }

and calling it at the end of each handler in lieu of sequelize.connectionManager.close()

My understanding is that this setup closes & then reinstantiates the pool on each subsequent lambda invocation, which is all that we need-- and that reusing the connectionManager itself poses no problem given that setup.

If anyone reading this sees a flaw with that approach, or something I may have misunderstood with the original writeup, please let me know!

edit: this is using sequelize 6.3.5

@amitaymolko
Copy link
Contributor

@johnwquarles I can confirm your code works for me, thanks!
Sequelize: 5.21.2

@marcogrcr
Copy link
Contributor Author

marcogrcr commented Sep 7, 2020

@johnwquarles you're right about that. As a disclaimer, I have not had the opportunity run the TL;DR code on my project yet. When I analyzed that code, I got confused for a moment and thought the sequelize.connectionManager.pool.acquire() method was the one being overwritten instead of sequelize.connectionManager.getConnection(). My initial thought was that by using sequelize.connectionManager.initPools() the method overwrite would be reverted since a new instance of the pool would be created and assigned to sequelize.connectionManager.pool.

The code that you're proposing should do the trick since it's the equivalent of sequelize.connectionManager.close() except for the method override:

v6.3.5

async close() {
// Mark close of pool
this.getConnection = async function getConnection() {
throw new Error('ConnectionManager.getConnection was called after the connection manager was closed!');
};
return await this._onProcessExit();
}

async _onProcessExit() {
if (!this.pool) {
return;
}
await this.pool.drain();
debug('connection drain due to process exit');
return await this.pool.destroyAllNow();
}

v5.22.3

close() {
// Mark close of pool
this.getConnection = function getConnection() {
return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!'));
};
return this._onProcessExit();
}

_onProcessExit() {
if (!this.pool) {
return Promise.resolve();
}
return this.pool.drain().then(() => {
debug('connection drain due to process exit');
return this.pool.destroyAllNow();
});
}


The only downside I see with this code is that sequelize.connectionManager.pool is not part of the public API:

v6.3.5

export interface ConnectionManager {
refreshTypeParser(dataTypes: object): void;
/**
* Drain the pool and close it permanently
*/
close(): Promise<void>;
/**
* Initialize connection pool. By default pool autostart is set to false, so no connection will be
* be created unless `pool.acquire` is called.
*/
initPools(): void;
/**
* Get connection from pool. It sets database version if it's not already set.
* Call pool.acquire to get a connection.
*/
getConnection(opts: GetConnectionOptions): Promise<Connection>;
/**
* Release a pooled connection so it can be utilized by other connection requests
*/
releaseConnection(conn: Connection): Promise<void>;
}

v5.22.3

export interface ConnectionManager {
refreshTypeParser(dataTypes: object): void;
/**
* Drain the pool and close it permanently
*/
close(): Promise<void>;
/**
* Initialize connection pool. By default pool autostart is set to false, so no connection will be
* be created unless `pool.acquire` is called.
*/
initPools(): void;
/**
* Get connection from pool. It sets database version if it's not already set.
* Call pool.acquire to get a connection.
*/
getConnection(opts: GetConnectionOptions): Promise<Connection>;
/**
* Release a pooled connection so it can be utilized by other connection requests
*/
releaseConnection(conn: Connection): Promise<void>;
}

This means that:

  • TypeScript users will have to break type-safety.
  • The code may break on newer versions of sequelize.

As an alternative to accessing internals, you can:

const { Sequelize } = require("sequelize");

let sequelize = null;

module.exports.handler = async function (event, callback) {
  // re-use the sequelize instance across invocations to improve performance
  if (!sequelize) {
    sequelize = await loadSequelize();
  } else {
    // restart connection pool to ensure connections are not re-used across invocations
    sequelize.connectionManager.initPools();
    // restore `getConnection` if it has been overwritten
    if (sequelize.connectionManager.hasOwnProperty("getConnection")) {
      delete sequelize.connectionManager.getConnection;
    }
  }

  try {
    return await doSomethingWithSequelize(sequelize);
  } finally {
    // close any opened connections during the invocation
    // this will wait for any in-progress queries to finish before closing the connections
    await sequelize.connectionManager.close();
  }
};

I guess the ideal solution would be to have sequelize.connectionManager.initPools() restores the overwritten method so there's no need to access class internals and avoid creating a new instance of Sequelize on every invocation. I guess it's a matter of proposing it to the sequelize team in a pull request to see if they accept it.

@marcogrcr
Copy link
Contributor Author

@amitaymolko / @johnwquarles : Thanks to your feedback, I have created a new revision (see diff here).

@papb
Copy link
Member

papb commented Sep 8, 2020

Hello everyone!! Thank you so much @marcogrcr for the detailed explanation! I will not merge yet because I want to take a closer look first, and I haven't had much time, please wait a little more and thank you very much!! 🚀

RobertFischer
RobertFischer previously approved these changes Dec 23, 2020
@djake djake added the type: docs For issues and PRs. Things related to documentation, such as changes in the manuals / API reference. label Jan 12, 2021
@marcogrcr
Copy link
Contributor Author

Thank you @cathykc! I've rebased the commit so that the CI build passes.

I don't think there's more than I can do other than wait for @papb to merge it.

@marcogrcr
Copy link
Contributor Author

Hello @papb

Just checking if you've had a chance to take a look at this pull request? It seems like various people are taking notice of it and many more would benefit from this being published in https://sequelize.org/.

Would you be able to share an ETA of when do you think someone from the sequelize project may be able to review?

@marcogrcr
Copy link
Contributor Author

Hey @papb just pinging here again to see if can find a chance to look at this PR. I understand that you're probably busy with other priorities, but it's been more than a year since your update 😬

@sdepold
Copy link
Member

sdepold commented Oct 24, 2021

Hey there :)

First of all: Thanks a bunch for your contribution to Sequelize! Much appreciated!
Second: Unfortunately we haven't had the chance to look into this ever since you created it. Sorry for that!

A couple of months ago, we have switched from master to main branch as our primary development branch and hence this PR is now outdated :(

If you still think this change is making sense, please consider recreating the PR against main. Thanks in advance and sorry for the additional work.

✌️

@marcogrcr marcogrcr changed the base branch from master to main October 24, 2021 22:28
@marcogrcr marcogrcr dismissed RobertFischer’s stale review October 24, 2021 22:28

The base branch was changed.

This document aims to guide developers into how to use sequelize in AWS Lambda. Properly configuring sequelize in AWS Lambda is a common source of pain for developers.
@marcogrcr
Copy link
Contributor Author

Hey @sdepold!

I have updated the pull request branch and rebased the commit as requested.

@github-actions github-actions bot added the stale label Nov 1, 2021
@github-actions github-actions bot closed this Nov 7, 2021
@marcogrcr
Copy link
Contributor Author

Hey @sdepold:

Can we re-open this pull-request? I rebased the branch on top of main as requested.

@sdepold sdepold reopened this Nov 8, 2021
@sdepold sdepold self-assigned this Nov 8, 2021
@github-actions github-actions bot removed the stale label Nov 9, 2021
@sdepold sdepold merged commit d5bfaf4 into sequelize:main Nov 22, 2021
@sdepold
Copy link
Member

sdepold commented Nov 22, 2021

Haven't tried it but you have my trust :)

sdepold added a commit that referenced this pull request Dec 3, 2021
* feat(typescript): create alpha release with ts

* ci: add other ts versions to ci (#13686)

* fix: wrong interface used within mixin (#13685)

* fix(increment): fix key value broken query (#12985)

Co-authored-by: Sascha Depold <[email protected]>

* fix(upsert): fall back to DO NOTHING if no update key values provided (#13863)

* fix(types): add instance member declaration (#13684)

* fix(types): add instance member declaration

* test(types): add static/instance members test cases

Co-authored-by: Sascha Depold <[email protected]>

* Create aws-lambda.md (#12642)

* fix(docs): add aws-lamda route (#13693)

* fix(types): allow override json function with custom return type (#13694)

* fix(types): allow override to json function with custom return type

* fix(types): remove automatic linter changes

Co-authored-by: sander-mol <[email protected]>

* ci(docs): add doc generation to checks (#13704)

* docs: add logo (#13700)

* docs: add logo

* fix(docs): logo not show up (#13699)

* fix(build): markdownlint

* docs(readme): use internal link

* docs(index.md): use internal link

* docs(index): update logo rendering in docs

* Center logo and headline

Co-authored-by: Sascha Depold <[email protected]>
Co-authored-by: Sascha Depold <[email protected]>

* test: fix mocha (#13707)

Co-authored-by: Rik Smale <[email protected]>
Co-authored-by: Sascha Depold <[email protected]>

* test: fix failing stack trace test (#13708)

* test: fix failing tests  (#13709)

* test: fix failing tests due to minification

Removes esbuild minification which was causing tests to fail and some changed behavior.

* Revert "fix(upsert): fall back to DO NOTHING if no update key values provided (#13863)"

This reverts commit 4071378.

* fix(upsert): fall back to DO NOTHING if no update key values provided (#13711)

* fix(upsert): fall back to DO NOTHING if no update key values provided (#13863)

* fix: remove erroneous .length in _.isEmpty

* refactor: use includes instead of or operators (#13706)

* docs(contributing): add section on adding/updating deps (#13715)

* docs(contributing): add Node versions

Fixes #13714

* docs(contributing): add section on adding/updating deps

* fix(types): add Col to where Ops (#13717)

* fix(types): add Col to where Ops

* fix(types): tests

* fix(data-types): unnecessary warning when getting data with DATE dataTypes (#13712)

* fix(data-types): unnecessary error when getting data with DATE dataTypes

* fix(data-types): date stringify mariadb

* fix(types): add missing schema field to sequelize options

Fixes #12606

Co-authored-by: Sascha Depold <[email protected]>

* fix(example): fix coordinates format as per GeoJson (#13718)

* fix(example): fix coordinates format as per GeoJson

* Update data-types.js

Co-authored-by: Sascha Depold <[email protected]>

* ci(node): use Node 16 instead of 12 (#13703)

* ci(node): add Node 14 and 16 to DB tests

* ci(node): move linting, test typing and release to Node 16

* ci(docs): use Node 16 for docs

* ci(node): run tests on Node 10 and 16

Co-authored-by: Rik Smale <[email protected]>

* refactor(postgres): move `clientMinMessages` from general to pg options (#13720)

* refactor(postgres): move `clientMinMessages` from general to pg options

* refactor(postgres): address review comments

* refactor(postgres): address review comments

* refactor(postgres): fix pipeline

* fix(dialect): try to fix flaky test

* fix(dialect): try to fix flaky test

Co-authored-by: Jesse Peng <[email protected]>

* refactor(build): use rm instead of rmdir for Node 14 and up (#13702)

* fix(build): refactor rmdir to rm

* refactor(build): only use rm in Node 14 and up

* refactor(build): move consts inside function

* refactor(build): fix definition

Co-authored-by: Rik Smale <[email protected]>
Co-authored-by: Sascha Depold <[email protected]>

* docs(postgres): warn about deprecated clientMinMessage option (#13727)

* docs(postgres): warn about deprecated clientMinMessage option

* docs(deprecation-warning): fix typo in deprecation warning

* docs(deprecation-warning): fix typo in deprecation warning

* meta(deps): upgrade (dev)deps (#13729)

Co-authored-by: Rik Smale <[email protected]>

* test(integration): mark and fix flaky test (#13735)

* feat(dialect): snowflake dialect support (#13406)

Co-authored-by: Mohamed El Mahallawy <[email protected]>
Co-authored-by: bparan <[email protected]>
Co-authored-by: Matthew Blasius <[email protected]>
Co-authored-by: WeRDyin <[email protected]>
Co-authored-by: Marco Gonzalez <[email protected]>
Co-authored-by: Constantin Metz <[email protected]>
Co-authored-by: Sander Mol <[email protected]>
Co-authored-by: sander-mol <[email protected]>
Co-authored-by: Rik Smale <13023439 [email protected]>
Co-authored-by: Fauzan <[email protected]>
Co-authored-by: Rik Smale <[email protected]>
Co-authored-by: AllAwesome497 <47748690 [email protected]>
Co-authored-by: manish kakoti <[email protected]>
Co-authored-by: Marco Kerwitz <[email protected]>
Co-authored-by: Jesse Peng <[email protected]>
Co-authored-by: Jesse Peng <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2021

🎉 This PR is included in version 6.12.0-beta.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@github-actions
Copy link
Contributor

🎉 This PR is included in version 6.12.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

aliatsis pushed a commit to creditiq/sequelize that referenced this pull request Jun 2, 2022
@agustinp93
Copy link

Hello @marcogrcr

"Since connection pooling is not used across invocations, the settings are not as important as before. For most cases the default settings should be OK"

Here (#4938 (comment)) you also wrote: "Keep in mind that AWS has released a product for AWS Lambda called Amazon RDS Proxy where you don't have to worry about connection pooling and you can create a new connection for every request."

How do you suggest we build our "lambda.handler"?

Followed the guide here (https://sequelize.org/docs/v6/other-topics/aws-lambda/) but we continue to experience "SequelizeConnectionAcquireTimeoutError" from time to time. I even removed the close() and initPool() because seems unnecessary by your comments.

And RDS connections to the DB seems to be stacking to the limit of out current allowance.

Thank you for the time you put doing this.

@patricktyndall
Copy link

Hi all, thanks for all the work here.

I'm still having an issue with sequelize (^6.19.0) and lambda (nodejs12.x) w/ RDS Aurora Postgres.

We've implemented the code as described in the lambda doc, and are now seeing new error when a lambda function is invoked after "re-connecting" using the same sequelize instance (e.g. after calling the sequelize.connectionManager.initPools()).

The symptoms of the error:

  • We're getting an error SequelizeDatabaseError with message Error: Connection terminated.

    • Here's a copy/paste of stack trace:
    • { name: 'SequelizeDatabaseError', parent: Error: Connection terminated at Connection.<anonymous> (/var/task/node_modules/pg/lib/client.js:131:36) at Object.onceWrapper (events.js:420:28) at Connection.emit (events.js:326:22) at Connection.EventEmitter.emit (domain.js:483:12) at Socket.<anonymous> (/var/task/node_modules/pg/lib/connection.js:62:12) at Socket.emit (events.js:314:20) at Socket.EventEmitter.emit (domain.js:483:12) at TCP.<anonymous> (net.js:675:12)
  • This often happens in the middle of a request, after other successful DB calls. It seems that some connections in the pool work just fine, but others do not.

  • We don't see this error in requests that have to initialize a brand new sequelize instance

  • We also didn't see this error when we were closing and reconnecting the pool on each request (but clearly don't want to do this)

For more context, our entire API is implemented using a single lambda function, so we're reusing containers at a pretty high frequency.

Also, here's our sequelize config. I have played with many configurations (including using the config in the doc), and have not found any solutions, so I currently have it stripped down. Anything here that could be the problem?

	sequelize = new Sequelize(
		db_name,
		db_user,
		db_pass,
		{
			host: db_host,
			dialect: 'postgres',
			port: db_port,
			benchmark: true,
			minifyAliases: true,
		},
	);

TIA!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
released on @v6-beta released type: docs For issues and PRs. Things related to documentation, such as changes in the manuals / API reference.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet