Knex: Как использовать Knex с AWS Lambda?

Созданный на 20 янв. 2017  ·  34Комментарии  ·  Источник: knex/knex

У меня возникают проблемы с пулом соединений при тестировании некоторого кода. Я ожидаю, что моя лямбда-функция будет вызвана, возможно, несколько тысяч раз за пару секунд, и у меня возникнут проблемы с определением наилучшего способа подключения к моей базе данных. Вот очень похожая проблема для node / postgres: по сути, проблема в том, что мне нужно иметь возможность получить соединение из пула, если он доступен, однако я не могу зависеть от существующего пула из-за того, как AWS (ненадежно) повторно использует лямбда-контейнеры.

В основном то, что я ищу, - это способ надежно получить или создать соединение с моей БД. Мне не удалось найти никаких примеров (например, while(!availableConnections) { tryToGetConnection() } . Нужно ли мне взаимодействовать с node-pool ? Как это сделать с Knex?

insightful question

Самый полезный комментарий

Извините, что совершил ошибку в середине предложения, мой ребенок только что вылил 3 литра воды на пол: 1st_place_medal: Я обновлю комментарий выше ...

Все 34 Комментарий

Пул Knex работает только в том случае, если соединения выполняются из одного процесса узла.

Если на инстансах лямбда AWS не запущен процесс общего узла, вам просто нужно создать новый пул с минимальным / максимальным количеством подключений 1 в каждом экземпляре лямбда-выражения и надеяться, что ваша база данных имеет достаточно хорошие настройки, чтобы разрешить сотни одновременных подключений (в RDS это зависит от размера экземпляра. ).

Прочитав это https://forums.aws.amazon.com/thread.jspa?threadID=216000

Похоже, лямбда-функция действительно разделяет некоторые процессы, поэтому, если вы можете выяснить, какое максимальное количество созданных лямбда-контейнеров, вы сможете рассчитать оптимальный размер пула (каждый контейнер имеет свой отдельный пул, поэтому общее количество подключений к БД составляет пул. max * максимальное количество контейнеров).

В любом случае нет необходимости вручную запрашивать соединение из пула, knex ожидает подключения автоматически, и если все закончится через пару секунд, ни один из тайм-аутов не сработает в это время.

Если вы получаете сообщение об ошибке из БД, в котором говорится, что достигнуто максимальное количество подключений, вам необходимо уменьшить максимальный размер пула.

Извините, что совершил ошибку в середине предложения, мой ребенок только что вылил 3 литра воды на пол: 1st_place_medal: Я обновлю комментарий выше ...

Спасибо за ответ. Похоже, что оценка максимального размера пула будет моим лучшим выбором, даже если количество подключений будет сильно меняться со временем.

Чтобы было понятно, нет возможности закрыть соединение в Knex, верно? Только возможность уничтожить пул соединений? Тот факт, что Lambda иногда повторно использует контейнеры, отбрасывает все.

Уничтожение пула соединений также уничтожает все соединения (изящно ожидая, что они завершатся первыми), и я полагаю, что когда лямбда уничтожает контейнер, все его открытые сокеты TCP закрываются неявно, когда процесс умирает.

Я не понимаю, почему нужно пытаться закрывать соединения явным образом после каждого запроса, поскольку это разрушит преимущества объединения. Вы получите тот же эффект, если создадите пул размером 1 и затем уничтожите его.

Вы также можете настроить тайм-аут простоя для пула, который автоматически закроет соединение, если оно не используется и просто ожидает действия в пуле.

Могу ли я использовать Knex для отправки запроса COPY в кластер RedShift и не ждать результатов?

Выполнение этого с помощью pg Pool завершает запрос, как только достигается конец функции Lambda.

@BardiaAfshin Если лямбда-контейнер уничтожен и все его сокеты освобождаются при достижении конца лямбда-функции, в этом случае также db-запрос умрет и может не быть завершен.

Я не уверен, как postgresql отреагирует на завершение клиентского соединения, если запрос COPY будет отменен из-за незавершенной неявной транзакции перед чтением значений результатов ...

В любом случае, можно ли отправить запрос или нет, это не до knex, но это зависит от того, как работают aws lambda и postgresql.

По моим наблюдениям, в RedShift запрос прерывается и откатывается.

В любом случае нет необходимости вручную запрашивать соединение из пула, knex ожидает подключения автоматически, и если все закончится через пару секунд, ни один из тайм-аутов не сработает в это время.

Я запускаю сценарий нагрузочного тестирования базы данных, и это не похоже на правду. Все, что превышает 30 одновременных подключений, немедленно истекает, а не ожидает открытия подключения.

Есть ли способ вручную освободить текущее используемое соединение после выполнения запроса?

Есть ли у кого-нибудь пример кода, которым они могли бы поделиться для тех из нас, кто только начинает знакомиться с AWS Lambda? Я надеюсь, что кто-нибудь сможет поделиться шаблонами и / или анти-шаблонами knex / postgres / lambda.

Вот то, что я использую сейчас - я уверен, что это можно улучшить, но надеюсь на некоторое подтверждение того, нахожусь ли я на правильном пути ...

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

Я нахожусь в одной лодке - до сих пор не понял, что лучше всего, несмотря на множество поисков в Google и тестирования. Что я делаю прямо сейчас:

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

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

Таким образом, соединение (максимум 1 на контейнер) останется активным, и контейнер можно будет легко использовать повторно. Я не разрушаю свою связь в конце.

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

Не уверен, что это лучший способ, но до сих пор у меня он работал.

/ пожимать плечами

@austingayler

Я не совсем уверен, что происходит, когда объявляется const knex - здесь устанавливается первоначальное соединение? Может кто уточнить? (Я предполагаю, что у вас есть информация о подключении в вашем dbConfig)

В вашем коде не перезаписывается и не создается заново само соединение каждый раз при вызове обработчика?

Просто подключившись к кому-то, кто находится в той же лодке, мне не повезло, пытаясь выяснить, контейнеры и размер пула (согласно предложению @elhigu ). Мое решение - уничтожать пул после каждого подключения (я знаю, что это не оптимально 😒):

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: просто установите context.callbackWaitsForEmptyEventLoop = false перед вызовом обратного вызова.

AWS Lambda ожидает пустой цикл событий (по умолчанию). поэтому функция могла вызывать ошибку тайм-аута даже при выполнении обратного вызова.

Пожалуйста, смотрите ссылки ниже для получения подробной информации:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

К @elhigu @tgriesser : Это не проблема knex. Это определенно проблема среды Lambda. Я думаю, пометьте эту проблему как вопрос, и ее следует закрыть :)

@mooyoul да, определенно не проблема с knex, но, возможно, проблема с документацией ... хотя я думаю, что не хочу, чтобы какие-либо специфические лямбда-данные aws в knex docs, так что закрытие.

Пожалуйста, посмотрите эту ссылку
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
Вам необходимо закрыть соединение с базой данных, иначе Lambda будет работать до истечения времени ожидания.

Кто-нибудь нашел шаблон, который безупречно работает для использования Knex с Lambda?

Есть ли у вас другие проблемы при правильном закрытии соединения?

Я стараюсь не закрывать соединение и делать его доступным для вызовов Lambda.

Вы уверены, что Lambda позволяет сохранять состояние между вызовами?

Ознакомьтесь с частью node.js https://scalegrid.io/blog/how-to-use-mongodb-connection-pooling-on-aws-lambda/, в которой предлагается решение для зависания непустого цикла событий.

Мне не нравится наталкиваться на эту тему, но поскольку я думаю, что она теряется в комментариях, ответ @mooyoul выше отлично сработал для нас.

Если вы не установите для этого флага значение false, Lambda будет ждать, пока цикл событий будет пустым. Если вы это сделаете, функция завершит выполнение, как только вы вызовете обратный вызов.

Для меня это сработало на моем локальном компьютере, но не после развертывания. Я был введен в заблуждение.

Оказывается, входящий источник RDS не открыт для моей функции Lambda. Решение найдено в Stack Overflow : либо изменить источник входящего RDS на 0.0.0.0/0 либо использовать VPC.

После обновления входящего источника RDS я могу успешно использовать Lambda с Knex.

Я использую среду выполнения Lambda Node.js 8.10 с пакетами:

knex: 0.17.0
pg: 7.11.0

Код ниже с использованием async также просто работает

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

Надеюсь, это поможет людям, которые могут столкнуться с той же проблемой в будущем.

Я хотел бы обратить внимание людей на пакет serverless-mysql , который включает в себя стандартный драйвер mysql но обрабатывает множество проблемных моментов, связанных с лямбда-выражением, связанных с управлением пулом соединений, которые описаны в этом потоке.

Однако я не думаю, что Knex будет работать с serverless-mysql в настоящее время (в соответствии с этой проблемой ), поскольку нет возможности заменить другой драйвер. Также может быть несовместимость, поскольку serverless-mysql использует обещания вместо обратных вызовов.

Лучшим подходом, вероятно, является добавление новой клиентской реализации в Knex. Я был бы счастлив попробовать это, но хотел бы, чтобы кто-нибудь, более знакомый с Knex, сказал мне, считает ли он это разумным / выполнимым?

Кроме того, тем временем я думал, что могу использовать Knex для _build_, но не _ выполнять_ запросы MySQL. Так что просто вызовите toSQL() и передайте результат serverless-mysql для выполнения.

Что мне интересно, так это то, можно ли настроить Knex без каких-либо подключений к базе данных? Нет смысла открывать соединение, которое никогда не использовалось.

Будет ли работать следующее?

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

@disbelief Вы можете инициализировать knex без подключения. В конце этого раздела в документации https://knexjs.org/#Installation -client есть пример, как это сделать.

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

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

@elhigu ах круто, спасибо. Дам это шанс тем временем.

Обновление: похоже, что вышеуказанное не работает. Knex выдает ошибку, если не может установить соединение с базой данных, даже если никогда не было вызова для выполнения запроса.

@disbelief вы нашли для этого решение?

@fdecampredon нет. На данный момент я просто создаю запросы с помощью squel , toString() и передаю их клиенту serverless-mysql .

@disbelief Меня удивляет, что создание запросов не удалось без подключения к базе данных.

Не могли бы вы опубликовать код и конфигурацию, которые вы используете для создания клиента knex? Также версия knex.

Следующее отлично работает для меня с
Узел 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 ] }

В соответствии с тем, как работает утилизация ресурсов Lambda, экземпляр Knex всегда должен храниться вне любой функции или класса. Также важно иметь максимум 1 соединение в пуле.

Что-то вроде:

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

Таким образом, вы получите максимальную отдачу от утилизации Lambda, т.е. каждый активированный (замороженный) контейнер будет содержать только одно и то же соединение.

В качестве дополнительного примечания имейте в виду, что Lambda масштабируется по умолчанию, если вы не ограничиваете количество одновременных контейнеров.

У меня Knex работает в Lambda почти год без проблем. Я объявляю свой экземпляр Knex за пределами моей функции Lambda и использую context.callbackWaitsForEmptyEventLoop = false как упоминалось в других сообщениях.

Тем не менее, за последний день, похоже, что-то изменилось на стороне Lambda, поскольку теперь я вижу огромный всплеск соединения в Postgres; связи не кажутся закрытыми.

Видел ли кто-нибудь еще, использующий вышеупомянутый подход, какие-нибудь шансы за последний день или около того?

@jamesdixon просто читал это сейчас, пока реорганизовал некоторые из наших реализаций knex на лямбда. Есть обновления по этому поводу? context.callbackWaitsForEmptyEventLoop = false перестал работать?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги