Knex: ¿Cómo utilizo Knex con AWS Lambda?

Creado en 20 ene. 2017  ·  34Comentarios  ·  Fuente: knex/knex

Tengo problemas con la agrupación de conexiones mientras pruebo algún código. Espero que se llame a mi función lambda tal vez varios miles de veces en un par de segundos, y tengo problemas para descubrir la mejor manera de conectarme a mi base de datos. Aquí hay un problema muy similar para node / postgres: esencialmente el problema es que necesito poder obtener una conexión del grupo si hay una disponible, sin embargo, no puedo depender del grupo existente debido a cómo AWS (no confiable) reutiliza contenedores lambda.

Básicamente, lo que estoy buscando es una forma de obtener o crear una conexión a mi base de datos de manera confiable. No he podido encontrar ningún ejemplo (como while(!availableConnections) { tryToGetConnection() } . ¿Necesito interactuar con node-pool ? ¿Cómo puedo hacer esto con Knex?

insightful question

Comentario más útil

Lo siento, cometí en medio de la oración, mi hijo arrojó como 3 litros de agua al piso: 1st_place_medal: Actualizaré el comentario de arriba en un momento ...

Todos 34 comentarios

La agrupación de Knex solo funciona si las conexiones se realizan desde el mismo proceso de nodo.

Si las instancias lambda de AWS no están ejecutando un proceso de nodo compartido, solo tiene que crear un nuevo grupo con conexiones mínimas / máximas 1 en cada instancia lambda y esperar que su base de datos tenga una configuración lo suficientemente buena como para permitir cientos de conexiones simultáneas (en RDS depende del tamaño de la instancia ).

Después de leer esto https://forums.aws.amazon.com/thread.jspa?threadID=216000

Parece que lambda realmente está compartiendo algunos procesos, por lo que si puede averiguar cuál es el recuento máximo de contenedores lambda que se crean, debería poder calcular el tamaño óptimo del grupo (cada contenedor tiene su grupo separado, por lo que el total de conexiones a DB es un grupo. recuento máximo * máximo de contenedores).

De todos modos, no es necesario hacer una solicitud de conexión manual desde el grupo, knex espera la conexión automáticamente y si todo termina en un par de segundos, ninguno de los tiempos de espera no se activará en ese tiempo.

Si recibe un error de la base de datos que dice que se ha alcanzado el recuento máximo de conexiones, entonces debe reducir el tamaño máximo del grupo.

Lo siento, cometí en medio de la oración, mi hijo arrojó como 3 litros de agua al piso: 1st_place_medal: Actualizaré el comentario de arriba en un momento ...

Gracias por la respuesta. Parece que estimar el tamaño máximo del grupo será mi mejor opción, aunque la cantidad de conexiones variará drásticamente con el tiempo.

Para ser claros, no hay forma de cerrar una conexión en Knex, ¿correcto? ¿Solo la capacidad de destruir un grupo de conexiones? El hecho de que Lambda a veces reutilice los contenedores desecha todo.

La destrucción del grupo de conexiones también destruye todas las conexiones (esperando graciosamente a que se completen primero) y supongo que cuando lambda destruye el contenedor, todos sus sockets TCP abiertos se cerrarán implícitamente cuando el proceso muera.

No veo por qué uno debería intentar cerrar conexiones explícitamente después de cada solicitud, ya que destruiría el beneficio de la agrupación. Obtendría el mismo efecto creando un grupo con el tamaño 1 y destruyéndolo después.

También puede configurar el tiempo de espera inactivo para el grupo que cerrará automáticamente la conexión si no se usa y solo está esperando una acción en el grupo.

¿Puedo usar Knex para enviar una consulta COPY al clúster de RedShift y no esperar los resultados?

Hacer esto con pg Pool finaliza la consulta tan pronto como se alcanza el final de la función Lambda.

@BardiaAfshin Si el contenedor lambda se destruye y todos sus sockets se liberan cuando se alcanza el final de la función lambda, en ese caso también la consulta db morirá y es posible que no finalice.

Tampoco estoy seguro de cómo reacciona postgresql al final de la conexión del lado del cliente si la consulta COPY se revertirá debido a una transacción implícita no finalizada antes de leer los valores de resultado ...

De todos modos, si la consulta se puede enviar o no de esa manera, no depende de knex, pero depende de cómo funcionen aws lambda y postgresql.

Mi observación es que la consulta se elimina en RedShift y se deshace.

De todos modos, no es necesario hacer una solicitud de conexión manual desde el grupo, knex espera la conexión automáticamente y si todo termina en un par de segundos, ninguno de los tiempos de espera no se activará en ese tiempo.

Estoy ejecutando un script de prueba de carga de base de datos y esto no parece ser cierto. Cualquier valor superior a 30 conexiones simultáneas se agota inmediatamente, en lugar de esperar una conexión abierta.

¿Hay alguna forma de liberar manualmente la conexión utilizada actualmente una vez que se realiza una consulta?

¿Alguien tiene un código de ejemplo que pueda compartir para aquellos de nosotros que recién ingresamos a AWS Lambda? Espero que alguien pueda compartir patrones y / o anti-patrones de knex / postgres / lambda.

Esto es lo que estoy usando ahora: estoy seguro de que se puede mejorar, pero espero un poco de reivindicación sobre si estoy o no en el camino correcto ...

'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();
  })
}

Estoy en el mismo barco, todavía no he descubierto cuál es la mejor manera a pesar de muchas pruebas y búsquedas en Google. Lo que estoy haciendo ahora mismo es:

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

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

De esta manera, la conexión (máximo 1 por contenedor) se mantendrá viva para que el contenedor se pueda reutilizar fácilmente. No destruyo mi conexión al final.

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

No estoy seguro de si esta es la mejor manera, pero me ha funcionado hasta ahora.

/encogimiento de hombros

@austingayler

No estoy exactamente seguro de qué sucede cuando se declara const knex : ¿es aquí donde se configura la conexión inicial? ¿Alguien puede aclarar? (Supongo que tiene información de conexión en su dbConfig)

En su código, ¿no se sobrescribe y se vuelve a crear la conexión en sí cada vez que se llama al controlador?

Simplemente hablando de alguien que está en el mismo barco, no he tenido mucha suerte tratando de averiguar los contenedores frente al tamaño de la piscina (según la sugerencia de @elhigu ). Mi solución ha sido destruir el grupo después de cada conexión (sé que no es óptimo 😒):

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: Simplemente configure context.callbackWaitsForEmptyEventLoop = false antes de devolver la llamada.

AWS Lambda espera un bucle de eventos vacío (de forma predeterminada). por lo que la función podría arrojar un error de tiempo de espera incluso ejecutada la devolución de llamada.

Consulte los enlaces a continuación para obtener más detalles:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

Para @elhigu @tgriesser : Este no es un problema de knex. Este es definitivamente un problema del entorno Lambda. Creo que etiquete este problema como pregunta y debería ser bueno para cerrar :)

@mooyoul sí, definitivamente no es un problema de knex, pero tal vez un problema de documentación ... aunque creo que no quiero ningún material específico de AWS lambda en los documentos de knex, así que cerrando.

Por favor mira este enlace
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
Debe cerrar la conexión db; de lo contrario, Lambda se ejecutará hasta que se agote el tiempo de espera.

¿Alguien ha encontrado un patrón que funcione perfectamente para usar Knex con Lambda?

¿Tiene algún otro problema cuando cierra la conexión correctamente?

Lo que intento evitar es cerrar la conexión y hacer que esté disponible en todas las llamadas de Lambda.

¿Está seguro de que Lambda permite mantener el estado entre llamadas?

Consulte la parte node.js de https://scalegrid.io/blog/how-to-use-mongodb-connection-pooling-on-aws-lambda/ sugiere una solución para colgar en un bucle de eventos no vacío.

No me gusta tocar el hilo de esta manera, pero como creo que se está perdiendo en los comentarios, la respuesta de @mooyoul anterior funcionó muy bien para nosotros.

Si no cambia ese indicador a falso, Lambda espera a que el bucle de eventos esté vacío. Si lo hace, la función finaliza la ejecución tan pronto como llame a la devolución de llamada.

Para mí, funcionó en mi máquina local, pero no después de la implementación. Me engañé un poco.

Resulta que la fuente de entrada RDS no está abierta a mi función Lambda. Solución encontrada en Stack Overflow : ya sea cambiando la fuente de entrada de RDS a 0.0.0.0/0 o use VPC.

Después de actualizar la fuente de entrada de RDS, puedo Lambda con Knex con éxito.

El tiempo de ejecución de Lambda que estoy usando es Node.js 8.10 con paquetes:

knex: 0.17.0
pg: 7.11.0

El siguiente código usando async también funciona

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
      };
    });
};

Con suerte, ayudará a las personas que puedan encontrar el mismo problema en el futuro.

Algo que me gustaría llamar la atención de la gente es el paquete serverless-mysql , que envuelve el controlador estándar mysql pero maneja muchos de los puntos débiles específicos de lambda en torno a la gestión del grupo de conexiones que se describen en este hilo.

Sin embargo, no creo que Knex funcione con serverless-mysql en este momento (de acuerdo con este problema ) ya que no hay forma de intercambiar un controlador diferente. También podría haber incompatibilidades ya que serverless-mysql usa promesas en lugar de devoluciones de llamada.

El mejor enfoque es probablemente agregar una nueva implementación de cliente en Knex. Me encantaría intentarlo, pero ¿me encantaría que alguien más familiarizado con Knex me dijera si cree que es razonable / factible?

Además, estaba pensando mientras tanto, podría usar Knex para _construir_ pero _no ejecutar_ consultas MySQL. Así que simplemente llame a toSQL() y pase la salida a serverless-mysql para ejecutar.

Sin embargo, lo que me pregunto es si Knex se puede configurar sin ninguna conexión de base de datos. No tiene sentido abrir una conexión que nunca se usa.

¿Funcionaría lo siguiente?

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

@disbelief Puede inicializar knex sin conexión. Hay un ejemplo de cómo hacerlo al final de esta sección en los documentos https://knexjs.org/#Installation -client

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

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

@elhigu ah genial, gracias. Le daré una oportunidad en el ínterin.

Actualización: lo anterior no parece funcionar. Knex arroja un error si no puede establecer una conexión de base de datos, incluso si nunca hay ninguna llamada para ejecutar una consulta.

@ incredulidad , ¿encontraste una solución para eso?

@fdecampredon no . Por el momento, simplemente estoy construyendo consultas con squel , llamando toString() en ellas y pasándolas al cliente serverless-mysql .

@disbelief Me sorprende que la construcción de las consultas fallara sin una conexión de base de datos.

¿Te importaría publicar el código y la configuración que estás usando para construir el cliente knex? También la versión knex.

Lo siguiente funciona bien para mí con
Nodo 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 ] }

Según cómo funciona el reciclaje de recursos de Lambda, la instancia Knex siempre debe mantenerse fuera de cualquier función o clase. Además, es importante tener un máximo de 1 conectado en la piscina.

Algo como:

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 esta forma, sacará el máximo partido al reciclaje de Lambda, es decir, cada contenedor activado (congelado) contendrá solo la misma conexión.

Como nota al margen, tenga en cuenta que Lambda escala horizontalmente de forma predeterminada si no limita la cantidad de contenedores simultáneos.

He tenido Knex funcionando en Lambda durante casi un año sin problemas. Declaro mi instancia de Knex fuera de mi función Lambda y utilizo context.callbackWaitsForEmptyEventLoop = false como se menciona en otras publicaciones.

Dicho esto, durante el último día, algo parece haber cambiado en el lado de Lambda ya que ahora estoy viendo un gran pico de conexión en Postgres; las conexiones no parecen estar cerradas.

¿Alguien más que utilizó el enfoque mencionado anteriormente vio alguna posibilidad durante el último día?

@jamesdixon acaba de leer esto mientras refactorizamos algunas de nuestras implementaciones de knex en lambda. ¿Alguna actualización sobre esto? ¿Ha dejado de funcionar context.callbackWaitsForEmptyEventLoop = false ?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

PaulOlteanu picture PaulOlteanu  ·  3Comentarios

tjwebb picture tjwebb  ·  3Comentarios

mattgrande picture mattgrande  ·  3Comentarios

zettam picture zettam  ·  3Comentarios

aj0strow picture aj0strow  ·  3Comentarios