Knex: Wie verwende ich Knex mit AWS Lambda?

Erstellt am 20. Jan. 2017  ·  34Kommentare  ·  Quelle: knex/knex

Beim Testen von Code treten Probleme mit dem Verbindungspooling auf. Ich erwarte, dass meine Lambda-Funktion innerhalb von ein paar Sekunden vielleicht mehrere tausend Mal aufgerufen wird, und habe Probleme, herauszufinden, wie ich mich am besten mit meiner Datenbank verbinden kann. Hier ist ein sehr ähnliches Problem für Node / Postgres: Im Wesentlichen besteht das Problem darin, dass ich in der Lage sein muss, eine Verbindung aus dem Pool zu erhalten, wenn eine verfügbar ist. Ich kann mich jedoch nicht auf den vorhandenen Pool verlassen, da AWS (unzuverlässig) wiederverwendet wird Lambda-Behälter.

Grundsätzlich suche ich nach einer Möglichkeit, eine Verbindung zu meiner Datenbank zuverlässig herzustellen oder herzustellen. Ich konnte keine Beispiele finden (wie while(!availableConnections) { tryToGetConnection() } . Muss ich mit node-pool interagieren? Wie kann ich das mit Knex machen?

insightful question

Hilfreichster Kommentar

Tut mir leid, dass ich mitten im Satz einen Fehler begangen habe. Mein Kind hat gerade 3 Liter Wasser auf den Boden geworfen: 1st_place_medal: Ich werde den obigen Kommentar gleich aktualisieren ...

Alle 34 Kommentare

Knex-Pooling funktioniert nur, wenn Verbindungen über denselben Knotenprozess hergestellt werden.

Wenn in AWS-Lambda-Instanzen kein Prozess für gemeinsam genutzte Knoten ausgeführt wird, müssen Sie lediglich einen neuen Pool mit min / max-Verbindungen 1 in jeder Lambda-Instanz erstellen und hoffen, dass Ihre Datenbank über ausreichend Einstellungen verfügt, um Hunderte von gleichzeitigen Verbindungen zuzulassen (in RDS hängt dies von der Instanzgröße ab ).

Nach dem Lesen dieser https://forums.aws.amazon.com/thread.jspa?threadID=216000

Es sieht so aus, als ob Lambda tatsächlich einige Prozesse gemeinsam nutzt. Wenn Sie also herausfinden können, wie viele Lambda-Container maximal erstellt werden, sollten Sie in der Lage sein, die optimale Poolgröße zu berechnen (jeder Container verfügt über einen separaten Pool, sodass die Gesamtverbindungen zur Datenbank Pool sind. max * max Containeranzahl).

Wie auch immer, es ist nicht erforderlich, eine manuelle Verbindung vom Pool anzufordern, knex wartet automatisch auf die Verbindung und wenn in ein paar Sekunden alles vorbei ist, wird in dieser Zeit keine der Zeitüberschreitungen ausgelöst.

Wenn Sie eine Fehlermeldung von der Datenbank erhalten, die besagt, dass die maximale Verbindungsanzahl erreicht wurde, müssen Sie die maximale Poolgröße verkleinern.

Tut mir leid, dass ich mitten im Satz einen Fehler begangen habe. Mein Kind hat gerade 3 Liter Wasser auf den Boden geworfen: 1st_place_medal: Ich werde den obigen Kommentar gleich aktualisieren ...

Danke für die Antwort. Es hört sich so an, als wäre die Schätzung der maximalen Poolgröße meine beste Wahl, obwohl die Anzahl der Verbindungen im Laufe der Zeit drastisch variieren wird.

Um klar zu sein, gibt es in Knex keine Möglichkeit, eine Verbindung zu schließen, richtig? Nur die Fähigkeit, einen Verbindungspool zu zerstören? Die Tatsache, dass Lambda manchmal Container wiederverwendet, wirft alles ab.

Durch das Zerstören des Verbindungspools werden auch alle Verbindungen zerstört (und es wird ordnungsgemäß darauf gewartet, dass sie zuerst abgeschlossen werden). Wenn Lambda den Container zerstört, werden alle offenen TCP-Sockets implizit geschlossen, wenn der Prozess abbricht.

Ich verstehe nicht, warum man versuchen sollte, Verbindungen nach jeder Anfrage explizit zu schließen, da dies den Vorteil des Pooling zerstören würde. Sie würden den gleichen Effekt erzielen, wenn Sie einen Pool mit Größe 1 erstellen und ihn anschließend zerstören.

Sie können auch ein Leerlaufzeitlimit für den Pool konfigurieren, das die Verbindung automatisch schließt, wenn es nicht verwendet wird und nur auf eine Aktion im Pool wartet.

Kann ich mit Knex eine COPY-Abfrage an den RedShift-Cluster senden und nicht auf die Ergebnisse warten?

Wenn Sie dies mit pg Pool tun, wird die Abfrage beendet, sobald das Ende der Lambda-Funktion erreicht ist.

@BardiaAfshin Wenn der Lambda-Container zerstört wird und alle seine Sockets freigegeben werden, wenn das Ende der Lambda-Funktion erreicht ist, stirbt in diesem Fall auch die Datenbankabfrage und wird möglicherweise nicht beendet.

Ich bin mir auch nicht sicher, wie postgresql auf das Beenden der clientseitigen Verbindung reagiert, wenn die COPY-Abfrage aufgrund einer nicht abgeschlossenen impliziten Transaktion vor dem Lesen der Ergebniswerte zurückgesetzt wird ...

Wie auch immer, ob die Anfrage gesendet werden kann oder nicht, liegt nicht an knex, aber es hängt davon ab, wie aws lambda und postgresql funktionieren.

Meine Beobachtung ist, dass die Abfrage in RedShift beendet und zurückgesetzt wird.

Wie auch immer, es ist nicht erforderlich, eine manuelle Verbindung vom Pool anzufordern, knex wartet automatisch auf die Verbindung und wenn in ein paar Sekunden alles vorbei ist, wird in dieser Zeit keine der Zeitüberschreitungen ausgelöst.

Ich führe ein DB-Lasttest-Skript aus und dies scheint nicht wahr zu sein. Bei mehr als 30 gleichzeitigen Verbindungen tritt sofort eine Zeitüberschreitung auf, anstatt auf eine offene Verbindung zu warten.

Gibt es eine Möglichkeit, die aktuell verwendete Verbindung manuell freizugeben, sobald eine Abfrage abgeschlossen ist?

Hat jemand einen Beispielcode, den er für diejenigen von uns teilen könnte, die gerade erst in AWS Lambda einsteigen? Ich hoffe, dass jemand Muster und / oder Anti-Muster von Knex / Postgres / Lambda teilen kann.

Folgendes verwende ich jetzt: Ich bin sicher, dass es verbessert werden kann, hoffe aber auf ein wenig Rechtfertigung, ob ich auf dem richtigen Weg bin oder nicht ...

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

Ich bin im selben Boot - habe immer noch nicht herausgefunden, was der beste Weg ist, obwohl ich viel gegoogelt und getestet habe. Was ich gerade mache ist:

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

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

Auf diese Weise bleibt die Verbindung (maximal 1 pro Container) am Leben, sodass der Container problemlos wiederverwendet werden kann. Ich zerstöre meine Verbindung am Ende nicht.

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

Ich bin mir nicht sicher, ob dies der beste Weg ist, aber es hat bisher bei mir funktioniert.

/zucken

@austingayler

Ich bin mir nicht ganz sicher, was passiert, wenn const knex deklariert wird. Wird hier die erste Verbindung hergestellt? Kann jemand klarstellen? (Ich gehe davon aus, dass Sie Verbindungsinformationen in Ihrer dbConfig haben)

Wird in Ihrem Code die Verbindung selbst nicht bei jedem Aufruf des Handlers überschrieben und neu erstellt?

Ich habe nicht viel Glück gehabt, jemanden in einem Boot zu finden, der im selben Boot sitzt , und versucht, die Container im @elhigus Vorschlag). Meine Lösung bestand darin, den Pool nach jeder Verbindung zu zerstören (ich weiß, dass es nicht optimal ist 😒):

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: Setzen Sie einfach context.callbackWaitsForEmptyEventLoop = false bevor Sie den Rückruf aufrufen.

AWS Lambda wartet auf eine leere Ereignisschleife (standardmäßig). Die Funktion kann also einen Timeout-Fehler auslösen, selbst wenn ein Rückruf ausgeführt wird.

Weitere Informationen finden Sie unter den folgenden Links:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

An @elhigu @tgriesser : Dies ist kein Knex-Problem. Dies ist definitiv ein Problem der Lambda-Umgebung. Ich denke, tagge dieses Problem in Frage und sollte gut zu schließen sein :)

@mooyoul yep, definitiv kein Knex-Problem, aber vielleicht ein Dokumentationsproblem ... obwohl ich glaube, ich möchte keine aws-Lambda-spezifischen Dinge für Knex-Dokumente, also schließen.

Bitte schauen Sie sich diesen Link an
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
Sie müssen die Datenbankverbindung schließen, sonst läuft Lambda bis zum Timeout.

Hat jemand ein Muster gefunden, das für die Verwendung von Knex mit Lambda einwandfrei funktioniert?

Haben Sie weitere Probleme, wenn Sie die Verbindung ordnungsgemäß schließen?

Ich versuche zu vermeiden, die Verbindung zu schließen und für alle Lambda-Anrufe verfügbar zu machen.

Sind Sie sicher, dass Lambda die Aufrechterhaltung des Status zwischen Anrufen zulässt?

Schauen Sie sich den Teil node.js von https://scalegrid.io/blog/how-to-use-mongodb-connection-pooling-on-aws-lambda/ an. Er schlägt eine Lösung für das Auflegen einer nicht leeren Ereignisschleife vor.

Ich mag es nicht, den Thread so zu stoßen, aber da ich denke, dass er in den Kommentaren verloren geht, hat die Antwort von @mooyoul oben für uns großartig funktioniert.

Wenn Sie dieses Flag nicht auf false setzen, wartet Lambda darauf, dass die Ereignisschleife leer ist. Wenn Sie dies tun, beendet die Funktion die Ausführung, sobald Sie den Rückruf aufrufen.

Bei mir funktionierte es auf meinem lokalen Computer, jedoch nicht nach der Bereitstellung. Ich war irgendwie irregeführt.

Es stellt sich heraus, dass die eingehende RDS-Quelle für meine Lambda-Funktion nicht geöffnet ist. Gefundene Lösung bei Stack Overflow : Ändern Sie entweder die eingehende RDS-Quelle in 0.0.0.0/0 oder verwenden Sie VPC.

Nach dem Aktualisieren der eingehenden RDS-Quelle kann ich Lambda erfolgreich mit Knex ausführen.

Die von mir verwendete Lambda-Laufzeit beträgt Node.js 8.10 mit Paketen:

knex: 0.17.0
pg: 7.11.0

Der folgende Code mit Async funktioniert ebenfalls

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

Hoffentlich hilft es Menschen, die in Zukunft das gleiche Problem haben könnten.

Ich möchte die Aufmerksamkeit der Leute auf das Serverless-MySQL- Paket lenken, das den Standardtreiber mysql umschließt, aber viele der in diesem Thread beschriebenen lambda-spezifischen Schwachstellen bei der Verwaltung von Verbindungspools behandelt.

Ich glaube jedoch nicht, dass Knex derzeit mit serverless-mysql arbeiten wird (laut diesem Problem ), da es keine Möglichkeit gibt, einen anderen Treiber einzutauschen. Es kann auch zu Inkompatibilitäten kommen, da serverless-mysql Versprechen anstelle von Rückrufen verwendet.

Der beste Ansatz ist wahrscheinlich, eine neue Client-Implementierung in Knex hinzuzufügen. Ich würde es gerne versuchen, aber würde es lieben, wenn jemand, der mit Knex besser vertraut ist, mir sagt, ob er es für vernünftig / machbar hält?

Außerdem dachte ich in der Zwischenzeit, ich könnte Knex verwenden, um MySQL-Abfragen zu erstellen, aber nicht auszuführen. Rufen Sie einfach toSQL() und übergeben Sie die Ausgabe zur Ausführung an serverless-mysql .

Ich frage mich jedoch, ob Knex ohne Datenbankverbindungen konfiguriert werden kann. Es macht keinen Sinn, eine Verbindung zu öffnen, die nie benutzt wird.

Würde folgendes funktionieren?

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

@disbelief Sie können knex ohne Verbindung initialisieren. Ein Beispiel dafür finden Sie am Ende dieses Abschnitts in den Dokumenten https://knexjs.org/#Installation -client

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

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

@elhigu ah cool, danke. Wird das in der Zwischenzeit versuchen.

Update: Das oben genannte scheint nicht zu funktionieren. Knex gibt einen Fehler aus, wenn keine Datenbankverbindung hergestellt werden kann, auch wenn nie ein Aufruf zum Ausführen einer Abfrage erfolgt.

@disbelief hast du eine lösung dafür gefunden?

@fdecampredon nein. Im Moment erstelle ich einfach Abfragen mit toString() auf und übergebe sie an den serverless-mysql Client.

@disbelief Es ist überraschend für mich, dass das Datenbankverbindung fehlgeschlagen ist.

Würde es Ihnen etwas ausmachen, den Code und die Konfiguration zu veröffentlichen, mit denen Sie den knex-Client erstellen? Auch die Knex-Version.

Das Folgende funktioniert gut für mich mit
Knoten 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 ] }

Gemäß der Funktionsweise des Ressourcenrecyclings von Lambda sollte die Instanz Knex immer außerhalb einer Funktion oder Klasse aufbewahrt werden. Außerdem ist es wichtig, maximal 1 im Pool zu haben.

Etwas wie:

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

Auf diese Weise können Sie das Recycling von Lambda optimal nutzen, dh jeder aktivierte (gefrorene) Behälter enthält nur dieselbe Verbindung.

Beachten Sie als Randnotiz, dass Lambda standardmäßig skaliert, wenn Sie die Anzahl der gleichzeitigen Container nicht begrenzen.

Ich habe Knex seit fast einem Jahr ohne Probleme in Lambda. Ich deklariere meine Knex-Instanz außerhalb meiner Lambda-Funktion und verwende context.callbackWaitsForEmptyEventLoop = false wie in anderen Beiträgen erwähnt.

Das heißt, im letzten Tag scheint sich auf der Lambda-Seite etwas geändert zu haben, da ich jetzt in Postgres eine riesige Verbindungsspitze sehe. Verbindungen scheinen nicht geschlossen zu sein.

Hat jemand anderes, der den oben genannten Ansatz verwendet, am letzten Tag oder so irgendwelche Chancen gesehen?

@jamesdixon liest dies gerade, während er einige unserer Knex-Implementierungen auf Lambda überarbeitet. Irgendwelche Updates dazu? Hat context.callbackWaitsForEmptyEventLoop = false aufgehört zu arbeiten?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen