Knex: Comment utiliser Knex avec AWS Lambda?

Créé le 20 janv. 2017  ·  34Commentaires  ·  Source: knex/knex

Je rencontre des problèmes de mise en commun de connexions lors du test de code. Je m'attends à ce que ma fonction lambda soit appelée peut-être plusieurs milliers de fois en quelques secondes, et j'ai du mal à trouver le meilleur moyen de se connecter à ma base de données. Voici un problème très similaire pour node / postgres: essentiellement, le problème est que je dois pouvoir obtenir une connexion à partir du pool s'il en existe une, mais je ne peux pas dépendre du pool existant à cause de la manière dont AWS réutilise (de manière non fiable) conteneurs lambda.

Fondamentalement, ce que je recherche, c'est un moyen d'obtenir ou de créer de manière fiable une connexion à ma base de données. Je n'ai trouvé aucun exemple (comme while(!availableConnections) { tryToGetConnection() } . Dois-je interagir avec node-pool ? Comment puis-je faire cela avec Knex?

insightful question

Commentaire le plus utile

Désolé j'ai commis en milieu de phrase, mon enfant vient de jeter comme 3 litres d'eau au sol: 1st_place_medal: Je mettrai à jour le commentaire ci-dessus dans un instant ...

Tous les 34 commentaires

Le regroupement Knex ne fonctionne que si les connexions sont établies à partir du même processus de nœud.

Si les instances AWS lambda n'exécutent pas de processus de nœud partagé, il vous suffit de créer un nouveau pool avec les connexions min / max 1 dans chaque instance lambda et espérer que votre base de données dispose de paramètres suffisamment bons pour autoriser des centaines de connexions simultanées (dans RDS, cela dépend de la taille de l'instance ).

Après avoir lu ce https://forums.aws.amazon.com/thread.jspa?threadID=216000

On dirait que lambda partage vraiment certains processus, donc si vous pouvez déterminer le nombre maximal de conteneurs lambda créés, vous devriez être en mesure de calculer la taille optimale du pool (chaque conteneur a son pool séparé, donc le total des connexions à DB est pool. nombre max * max de conteneurs).

Quoi qu'il en soit, il n'est pas nécessaire de faire une demande de connexion manuelle depuis le pool, knex attend automatiquement la connexion et si tout est terminé dans quelques secondes, aucun des délais d'attente ne se déclenchera pendant ce temps.

Si vous obtenez une erreur de la base de données indiquant que le nombre maximal de connexions a été atteint, vous devez réduire la taille maximale du pool.

Désolé j'ai commis en milieu de phrase, mon enfant vient de jeter comme 3 litres d'eau au sol: 1st_place_medal: Je mettrai à jour le commentaire ci-dessus dans un instant ...

Merci pour la réponse. Il semble que l'estimation de la taille maximale du pool sera mon meilleur pari, même si le nombre de connexions variera considérablement au fil du temps.

Pour être clair, il n'y a aucun moyen de fermer une connexion à Knex, n'est-ce pas? Seule la possibilité de détruire un pool de connexions? Le fait que Lambda réutilise parfois des conteneurs en quelque sorte gâche tout.

La destruction du pool de connexions détruit également toutes les connexions (en attendant gracieusement qu'elles se terminent en premier) et je suppose que lorsque lambda détruit le conteneur, toutes ses sockets TCP ouverts se fermeront implicitement lorsque le processus meurt.

Je ne vois pas pourquoi on devrait essayer de fermer les connexions explicitement après chaque demande car cela détruirait l'avantage de la mise en commun. Vous obtiendrez le même effet en créant un pool de taille 1 et en le détruisant par la suite.

Vous pouvez également configurer le délai d'inactivité pour le pool qui fermera automatiquement la connexion s'il n'est pas utilisé et attend juste une action dans le pool.

Puis-je utiliser Knex pour envoyer une requête COPY au cluster RedShift et ne pas attendre les résultats?

Faire cela avec pg Pool met fin à la requête dès que la fin de la fonction Lambda est atteinte.

@BardiaAfshin Si le conteneur lambda est détruit et que toutes ses sockets sont libérées lorsque la fin de la fonction lambda est atteinte, dans ce cas également la requête db mourra et pourrait ne pas être terminée.

Je ne sais pas non plus comment postgresql réagit à la fin de la connexion côté client si la requête COPY sera annulée en raison d'une transaction implicite non terminée avant de lire les valeurs de résultat ...

Quoi qu'il en soit, si la requête peut être envoyée ou non de cette manière, ce n'est pas à knex, mais cela dépend du fonctionnement de aws lambda et postgresql.

Mon observation est que la requête est tuée sur RedShift et elle est annulée.

Quoi qu'il en soit, il n'est pas nécessaire de faire une demande de connexion manuelle depuis le pool, knex attend automatiquement la connexion et si tout est terminé dans quelques secondes, aucun des délais d'attente ne se déclenchera pendant ce temps.

J'exécute un script de test de charge de base de données et cela ne semble pas être vrai. Tout ce qui dépasse 30 connexions simultanées expire immédiatement, plutôt que d'attendre une connexion ouverte.

Existe-t-il un moyen de libérer manuellement la connexion actuellement utilisée une fois qu'une requête est effectuée?

Quelqu'un a-t-il un exemple de code à partager pour ceux d'entre nous qui viennent juste d'entrer dans AWS Lambda? J'espère que quelqu'un pourrait partager des modèles et / ou des anti-modèles de knex / postgres / lambda.

Voici ce que j'utilise maintenant - je suis certain que cela peut être amélioré, mais en espérant un peu de confirmation pour savoir si je suis sur la bonne voie ou non ...

'use strict';
var pg = require('pg');

function initKnex(){
  return require('knex')({
      client: 'pg',
      connection: { ...details... }
  });
}

module.exports.hello = (event, context) =>
{
  var knex = initKnex();

  // Should I be returning knex here or in the final catch?
  knex
  .select('*')
  .from('my_table')
  .then(function (rows) {
    context.succeed('Succeeded: ' + JSON.stringify(rows || []));
  })
  .catch(function (error) {
    context.fail(error);
  })
  .then(function(){
    // is destroy overkill? - is there an option for knex.client.release, etc?
    knex.destroy();
  })
}

Je suis dans le même bateau - je n'ai toujours pas compris quel est le meilleur moyen malgré beaucoup de recherches sur Google et de tests. Ce que je fais en ce moment, c'est:

const dbConfig = require('./db');
const knex = require('knex')(dbConfig);

exports.handler = function (event, context, callback) {
...
connection = {..., pool: { min: 1, max: 1 },

De cette façon, la connexion (max 1 par conteneur) restera active afin que le conteneur puisse être réutilisé facilement. Je ne détruis pas ma connexion à la fin.

http://blog.rowanudell.com/database-connections-in-lambda/

Je ne sais pas si c'est le meilleur moyen, mais cela a fonctionné pour moi jusqu'à présent.

/hausser les épaules

@austingayler

Je ne sais pas exactement ce qui se passe lorsque const knex est déclaré - est-ce là que la connexion initiale est établie? Quelqu'un peut-il clarifier? (Je suppose que vous avez des informations de connexion dans votre dbConfig)

Dans votre code, la connexion elle-même n'est-elle pas écrasée et recréée à chaque fois que le gestionnaire est appelé?

Juste en sonnant quelqu'un qui est dans le même bateau, je n'ai pas eu beaucoup de chance à essayer de comprendre les conteneurs par rapport à la taille de la piscine (selon la suggestion de @elhigu ). Ma solution a été de détruire le pool après chaque connexion (je sais que ce n'est pas optimal 😒):

const knex = require('knex');

const client = knex(dbConfig);

client(tableName).select('*')
  .then((result) => { 

    return Promise.all([
      result,
      client.destroy(),
    ])  
  })
  .then(([ result ]) => {

    return result;
  });

TL; DR: définissez simplement context.callbackWaitsForEmptyEventLoop = false avant d'appeler le callback.

AWS Lambda attend une boucle d'événements vide (par défaut). Ainsi, la fonction peut générer une erreur Timeout même un rappel exécuté.

Veuillez consulter les liens ci-dessous pour plus de détails:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

À @elhigu @tgriesser : Ce n'est pas un problème de knex. C'est certainement un problème d'environnement Lambda. Je pense que ce problème doit être posé et qu'il devrait être bon de le fermer :)

@mooyoul ouais , certainement pas un problème de knex, mais peut-être un problème de documentation ... même si je pense que je ne veux pas de trucs spécifiques à aws lambda dans les documents de knex, donc fermeture.

Veuillez regarder ce lien
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
Vous devez fermer la connexion db, sinon Lambda s'exécute jusqu'à expiration du délai.

Quelqu'un a-t-il trouvé un modèle qui fonctionne parfaitement pour utiliser Knex avec Lambda?

Avez-vous d'autres problèmes lorsque vous fermez correctement la connexion?

La chose que j'essaie d'éviter est de fermer la connexion et de la rendre disponible pour les appels Lambda.

Êtes-vous sûr que Lambda autorise le maintien de l'état entre les appels?

Consultez la partie node.js de https://scalegrid.io/blog/how-to-use-mongodb-connection- Covoiturage-on-aws-lambda/, elle suggère une solution pour raccrocher sur une boucle d'événement non vide.

Je n'aime pas cogner le fil comme ça, mais comme je pense que ça se perd dans les commentaires, la réponse de @mooyoul ci-dessus a très bien fonctionné pour nous.

Si vous ne définissez pas cet indicateur sur false, Lambda attend que la boucle d'événements soit vide. Si vous le faites, la fonction termine son exécution dès que vous appelez le rappel.

Pour moi, cela a fonctionné sur ma machine locale mais pas après le déploiement. J'étais un peu induit en erreur.

Il s'avère que la source entrante RDS n'est pas ouverte à ma fonction Lambda. Solution trouvée chez Stack Overflow : soit en changeant la source entrante RDS en 0.0.0.0/0 soit en utilisant VPC.

Après avoir mis à jour la source entrante RDS, je peux Lambda avec Knex avec succès.

Le runtime Lambda que j'utilise est Node.js 8.10 avec les packages:

knex: 0.17.0
pg: 7.11.0

Le code ci-dessous utilisant async fonctionne également

const Knex = require('knex');

const pg = Knex({ ... });

module.exports. submitForm = async (event) => {
  const {
    fields,
  } = event['body-json'] || {};

  return pg('surveys')
    .insert(fields)
    .then(() => {
      return {
        status: 200
      };
    })
    .catch(err => {
      return {
        status: 500
      };
    });
};

Espérons que cela aidera les personnes qui pourraient rencontrer le même problème à l'avenir.

Quelque chose sur lequel j'aimerais attirer l'attention des gens est le package serverless-mysql , qui encapsule le pilote standard mysql mais gère de nombreux problèmes spécifiques à lambda autour de la gestion du pool de connexions qui sont décrits dans ce fil.

Cependant, je ne pense pas que Knex fonctionnera avec serverless-mysql pour le moment (selon ce problème ) car il n'y a aucun moyen d'échanger un pilote différent. Il pourrait aussi y avoir des incompatibilités puisque serverless-mysql utilise des promesses au lieu de rappels.

La meilleure approche consiste probablement à ajouter une nouvelle implémentation client dans Knex. Je serais heureux de tenter le coup, mais aimerais que quelqu'un de plus familier avec Knex me dise s'il pense que c'est raisonnable / faisable?

Aussi, je pensais en attendant, je pourrais utiliser Knex pour _build_ mais _pas exécuter_ des requêtes MySQL. Appelez simplement toSQL() et passez la sortie à serverless-mysql pour l'exécuter.

Ce que je me demande cependant, c'est si Knex peut être configuré sans aucune connexion db? Cela n'a aucun sens d'ouvrir une connexion qui n'est jamais utilisée.

Est-ce que ce qui suit fonctionnerait?

connection = {..., pool: { min: 0, max: 0 ) },

@disbelief Vous pouvez initialiser knex sans connexion. Il y a un exemple comment le faire à la fin de cette section dans la documentation https://knexjs.org/#Installation -client

const knex = require('knex')({client: 'mysql'});

const generatedQuery = knex('table').where('id',1).toSQL().toNative();

@elhigu ah cool, merci. Je vais donner une chance dans l'intervalle.

Mise à jour: ce qui précède ne semble pas fonctionner. Knex lève une erreur s'il ne peut pas établir de connexion à la base de données, même s'il n'y a jamais d'appel pour exécuter une requête.

@disbelief avez-vous trouvé une solution pour cela?

@fdecampredon non . Pour le moment, je construis simplement des requêtes avec squel , en appelant toString() dessus et en les passant au client serverless-mysql .

@disbelief Je suis surpris que la création des requêtes ait échoué sans connexion à la base de données.

Pourriez-vous publier le code et la configuration que vous utilisez pour créer le client knex? Aussi la version knex.

Ce qui suit fonctionne bien pour moi avec
Nœud v8.11.1
mysql : 2.13.0
knex : 0,18,3

const k = require('knex')

const client = k({ client: 'mysql' })

console.log('Knex version:', require('knex/package.json').version)
// => 0.18.3
console.log('sql:', client('table').where('id',1).toSQL().toNative())
// => { sql: 'select * from `table` where `id` = ?', bindings: [ 1 ] }

Selon le fonctionnement du recyclage des ressources de Lambda, l'instance Knex doit toujours être conservée en dehors de toute fonction ou classe. En outre, il est important d'avoir un maximum de 1 en connexion dans la piscine.

Quelque chose comme:

const Knex = require('knex');
let instance = null;

module.exports = class DatabaseManager {
  constructor({ host, user, password, database, port = 3306, client = 'mysql', pool = { min: 1, max: 1 }}) {
    this._client = client;
    this._poolOptions = pool;
    this._connectionOptions = {
      host: DB_HOST || host,
      port: DB_PORT || port,
      user: DB_USER || user,
      password: DB_PASSWORD || password,
      database: DB_NAME || database,
    };
  }

  init() {
    if (instance !== null) {
      return;
    }

    instance = Knex({
      client: this._client,
      pool: this._poolOptions,
      connection: this._connectionOptions,
      debug: process.env.DEBUG_DB == true,
      asyncStackTraces: process.env.DEBUG_DB == true,
    });
  }

  get instance() {
    return instance;
  }
}

De cette façon, vous tirerez le meilleur parti du recyclage de Lambda, c'est-à-dire que chaque conteneur activé (gelé) ne contiendra que la même connexion.

En remarque, gardez à l'esprit que Lambda évolue par défaut si vous ne limitez pas le nombre de conteneurs simultanés.

Je fais tourner Knex en Lambda depuis presque un an maintenant sans aucun problème. Je déclare mon instance Knex en dehors de ma fonction Lambda et j'utilise context.callbackWaitsForEmptyEventLoop = false comme mentionné dans d'autres articles.

Cela dit, au cours de la dernière journée, quelque chose semble avoir changé du côté de Lambda car je vois maintenant un énorme pic de connexion dans Postgres; les connexions ne semblent pas fermées.

Est-ce que quelqu'un d'autre utilisant l'approche susmentionnée a vu des chances au cours des derniers jours environ?

@jamesdixon vient de lire ceci maintenant tout en refactorisant certaines de nos implémentations knex sur lambda. Des mises à jour à ce sujet? context.callbackWaitsForEmptyEventLoop = false cessé de fonctionner?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

zettam picture zettam  ·  3Commentaires

nklhrstv picture nklhrstv  ·  3Commentaires

arconus picture arconus  ·  3Commentaires

sandrocsimas picture sandrocsimas  ·  3Commentaires

legomind picture legomind  ·  3Commentaires