Knex: Como faço para usar o Knex com AWS Lambda?

Criado em 20 jan. 2017  ·  34Comentários  ·  Fonte: knex/knex

Estou tendo problemas com o pool de conexão ao testar alguns códigos. Estou esperando que minha função lambda seja chamada talvez vários milhares de vezes em alguns segundos e estou tendo problemas para descobrir a melhor maneira de se conectar ao meu banco de dados. Aqui está um problema muito semelhante para node / postgres: essencialmente, o problema é que eu preciso conseguir uma conexão do pool, se houver, no entanto, não posso depender do pool existente por causa de como o AWS (não confiável) reutiliza recipientes lambda.

Basicamente, o que estou procurando é uma maneira confiável de obter ou criar uma conexão com meu banco de dados. Não consegui encontrar nenhum exemplo (como while(!availableConnections) { tryToGetConnection() } . Preciso interagir com node-pool ? Como posso fazer isso com o Knex?

insightful question

Comentários muito úteis

Desculpe ter cometido no meio da frase, meu filho jogou uns 3 litros de água no chão: 1st_place_medal: Vou atualizar o comentário acima em um momento ...

Todos 34 comentários

O pool de Knex funciona apenas se as conexões forem feitas a partir do mesmo processo de nó.

Se as instâncias lambda da AWS não estiverem executando o processo de nó compartilhado, você só precisa criar um novo pool com conexões mín. / Máx. 1 em cada instância lambda e esperar que seu banco de dados tenha configurações boas o suficiente para permitir centenas de conexões simultâneas (em RDS, depende do tamanho da instância )

Depois de ler este https://forums.aws.amazon.com/thread.jspa?threadID=216000

Parece que lambda realmente está compartilhando alguns processos, então se você puder descobrir qual é a contagem máxima de contêineres lambda, que são criados, você deverá ser capaz de calcular o tamanho ideal do pool (cada contêiner tem seu pool separado, então o total de conexões com o banco de dados é pool. max * max container count).

De qualquer forma, não há necessidade de fazer a solicitação de conexão manual do pool, o knex espera a conexão automaticamente e se tudo terminar em alguns segundos, nenhum dos timeouts não disparará nesse tempo.

Se você estiver recebendo um erro do banco de dados que diz que a contagem máxima de conexões foi atingida, será necessário diminuir o tamanho máximo do pool.

Desculpe ter cometido no meio da frase, meu filho jogou uns 3 litros de água no chão: 1st_place_medal: Vou atualizar o comentário acima em um momento ...

Obrigado pela resposta. Parece que estimar o tamanho máximo do pool será minha melhor aposta, embora o número de conexões varie drasticamente com o tempo.

Para ser claro, não há como fechar uma conexão no Knex, correto? Apenas a capacidade de destruir um pool de conexão? O fato de que Lambda às vezes reutiliza contêineres meio que joga tudo fora.

Destruir o pool de conexões também destrói todas as conexões (normalmente esperando que elas sejam concluídas primeiro) e suponho que quando lambda destrói o contêiner, todos os seus soquetes TCP abertos serão fechados implicitamente quando o processo terminar.

Não vejo por que alguém deve tentar fechar as conexões explicitamente após cada solicitação, pois isso destruiria o benefício do pool. Você obteria o mesmo efeito criando piscina com tamanho 1 e destruindo-a depois.

Você também pode configurar o tempo limite de inatividade para o pool, que fechará automaticamente a conexão se não for usado e estiver apenas aguardando uma ação no pool.

Posso usar o Knex para enviar uma consulta COPY para o cluster RedShift e não esperar pelos resultados?

Fazer isso com pg Pool encerra a consulta assim que o fim da função Lambda é alcançado.

@BardiaAfshin Se o contêiner lambda for destruído e todos os seus soquetes forem liberados quando o fim da função lambda for atingido, nesse caso também a consulta do banco de dados morrerá e pode não ser concluída.

Também não tenho certeza de como o postgresql reagirá ao término da conexão do lado do cliente se a consulta COPY for revertida devido à transação implícita não concluída antes de ler os valores de resultado ...

De qualquer forma, se a consulta pode ser enviada ou não dessa forma não cabe ao knex, mas depende de como funcionam o aws lambda e o postgresql.

Minha observação é que a consulta é eliminada no RedShift e revertida.

De qualquer forma, não há necessidade de fazer a solicitação de conexão manual do pool, o knex espera a conexão automaticamente e se tudo terminar em alguns segundos, nenhum dos timeouts não disparará nesse tempo.

Estou executando um script de teste de carga de banco de dados e isso não parece ser verdade. Qualquer coisa acima de 30 conexões simultâneas atinge o tempo limite imediatamente, em vez de esperar por uma conexão aberta.

Existe uma maneira de liberar manualmente a conexão usada atualmente depois que uma consulta é feita?

Alguém tem um código de exemplo que poderia compartilhar para aqueles de nós que acabamos de entrar no AWS Lambda? Espero que alguém possa compartilhar padrões e / ou anti-padrões de knex / postgres / lambda.

Aqui está o que estou usando agora - tenho certeza de que pode ser melhorado, mas espero um pouco de confirmação se estou ou não no caminho correto ...

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

Estou no mesmo barco - ainda não descobri qual é a melhor maneira, apesar de muitos buscas e testes no Google. O que estou fazendo agora é:

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

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

Desta forma, a conexão (máx. 1 por contêiner) permanecerá ativa para que o contêiner possa ser reutilizado facilmente. Eu não destruo minha conexão no final.

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

Não tenho certeza se essa é a melhor maneira, mas funcionou para mim até agora.

/dar de ombros

@austingayler

Não tenho certeza do que acontece quando const knex é declarado - é aqui que a conexão inicial é configurada? Alguém pode esclarecer? (Presumo que você tenha informações de conexão em seu dbConfig)

Em seu código, a própria conexão não está sendo substituída e recriada sempre que o manipulador é chamado?

Apenas concordando com alguém que está no mesmo barco, não tive muita sorte tentando descobrir o tamanho dos contêineres em relação à piscina (conforme sugestão de @elhigu ). Minha solução tem sido destruir o pool após cada conexão (eu sei que não é o ideal 😒):

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: Basta definir context.callbackWaitsForEmptyEventLoop = false antes de chamar o retorno de chamada.

O AWS Lambda aguarda o loop de evento vazio (por padrão). portanto, a função pode gerar um erro de tempo limite até mesmo o retorno de chamada executado.

Consulte os links abaixo para obter detalhes:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

Para @elhigu @tgriesser : Este não é um problema do Knex. Definitivamente, esta é uma questão do ambiente Lambda. Acho que marcar esse problema para questionar e deve ser bom para fechar :)

@mooyoul sim, definitivamente não é o problema do knex, mas talvez o problema da documentação ... embora eu não queira nenhum material específico do aws lambda para os documentos do knex, então fechando.

Por favor, olhe este link
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
Você precisa fechar a conexão db, caso contrário, o Lambda será executado até atingir o tempo limite.

Alguém encontrou um padrão que funciona perfeitamente para usar Knex com Lambda?

Você tem algum outro problema ao fechar a conexão corretamente?

O que estou tentando evitar é fechar a conexão e torná-la disponível nas chamadas Lambda.

Tem certeza de que o Lambda permite manter o estado entre as chamadas?

Confira a parte node.js de https://scalegrid.io/blog/how-to-use-mongodb-connection-pooling-on-aws-lambda/ que sugere uma solução para desligar em loop de evento não vazio.

Não gosto de interromper o tópico dessa forma, mas como acho que está se perdendo nos comentários, a resposta de @mooyoul acima funcionou muito bem para nós.

Se você não transformar esse sinalizador em falso, o Lambda espera que o loop de eventos fique vazio. Se você fizer isso, a execução da função será concluída assim que você chamar o retorno de chamada.

Para mim, funcionou na minha máquina local, mas não após a implantação. Eu estava meio que enganado.

Acontece que a fonte de entrada RDS não está aberta para minha função Lambda. Solução encontrada no Stack Overflow : alterando a fonte de entrada RDS para 0.0.0.0/0 ou use VPC.

Depois de atualizar a fonte de entrada RDS, posso aplicar Lambda com Knex com sucesso.

O tempo de execução do Lambda que estou usando é Node.js 8.10 com pacotes:

knex: 0.17.0
pg: 7.11.0

O código abaixo usando async também 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
      };
    });
};

Esperançosamente, ajudará pessoas que possam enfrentar o mesmo problema no futuro.

Algo para o qual eu gostaria de chamar a atenção das pessoas é o pacote serverless-mysql , que envolve o driver mysql padrão, mas lida com muitos dos pontos problemáticos específicos de lambda em torno do gerenciamento do pool de conexão que são descritos neste tópico.

No entanto, não acho que o Knex funcionará com serverless-mysql neste momento (de acordo com este problema ), pois não há como trocar por um driver diferente. Também pode haver incompatibilidades, já que serverless-mysql usa promessas em vez de retornos de chamada.

A melhor abordagem é provavelmente adicionar uma nova implementação de cliente no Knex. Ficaria feliz em tentar, mas adoraria que alguém mais familiarizado com o Knex me dissesse se acha que é razoável / factível?

Além disso, estava pensando enquanto isso, eu poderia usar o Knex para _construir_, mas _não executar_ consultas MySQL. Então, basta chamar toSQL() e passar a saída para serverless-mysql para executar.

O que estou me perguntando é se o Knex pode ser configurado sem nenhuma conexão db? Não faz sentido abrir uma conexão que nunca foi usada.

O seguinte funcionaria?

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

@disbelief Você pode inicializar o knex sem conexão. Há um exemplo de como fazer isso no final desta seção nos documentos https://knexjs.org/#Installation -client

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

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

@elhigu ah legal, obrigado. Vou dar uma chance nesse ínterim.

Atualizar: o acima não parece funcionar. O Knex gera um erro se não conseguir estabelecer uma conexão db, mesmo que nunca haja nenhuma chamada para executar uma consulta.

@disbelief você encontrou uma solução para isso?

@fdecampredon não. No momento, estou simplesmente construindo consultas com squel , chamando toString() nelas e passando-as para o cliente serverless-mysql .

@disbelief É surpreendente para mim que a construção das consultas falhou sem uma conexão db.

Você se importaria de postar o código e a configuração que está usando para construir o cliente knex? Também a versão Knex.

O seguinte funciona bem para mim com
Node 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 ] }

De acordo com o funcionamento da reciclagem de recursos do Lambda, Knex instance deve sempre ser mantida fora de qualquer função ou classe. Além disso, é importante ter no máximo 1 conexão no pool.

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

Dessa forma, você aproveitará ao máximo a reciclagem da Lambda, ou seja, cada recipiente ativado (congelado) manterá apenas a mesma conexão.

Como uma observação lateral, lembre-se de que o Lambda dimensiona por padrão se você não limitar o número de contêineres simultâneos.

Eu tive Knex rodando em Lambda por quase um ano sem problemas. Estou declarando minha instância Knex fora da minha função Lambda e utilizando context.callbackWaitsForEmptyEventLoop = false como mencionado em outros posts.

Dito isso, no dia anterior, algo parece ter mudado no lado do Lambda, pois agora estou vendo um grande pico de conexão no Postgres; conexões não parecem estar fechadas.

Alguém mais usando a abordagem mencionada viu alguma chance no último dia ou depois?

@jamesdixon acabou de ler isso agora, enquanto refatorava algumas de nossas implementações de knex em lambda. Alguma atualização sobre isso? context.callbackWaitsForEmptyEventLoop = false parou de funcionar?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

marianomerlo picture marianomerlo  ·  3Comentários

npow picture npow  ·  3Comentários

zettam picture zettam  ·  3Comentários

nklhrstv picture nklhrstv  ·  3Comentários

mtom55 picture mtom55  ·  3Comentários