Beheben von Konfigurationsproblemen in Lambda
Ihre Funktionskonfigurationseinstellungen können sich auf die Gesamtleistung und das Verhalten Ihrer Lambda-Funktion auswirken. Diese verursachen möglicherweise keine tatsächlichen Funktionsfehler, können jedoch zu unerwarteten Timeouts und Ergebnissen führen.
Die folgenden Themen enthalten Tipps zur Fehlerbehebung für häufig auftretende Probleme im Zusammenhang mit den Konfigurationseinstellungen von Lambda-Funktionen.
Themen
Arbeitsspeicherkonfigurationen
Sie können eine Lambda-Funktion so konfigurieren, dass sie zwischen 128 MB und 10 240 MB Speicher verwendet. Standardmäßig wird jeder in der Konsole erstellten Funktion die kleinste Speichermenge zugewiesen. Viele Lambda-Funktionen sind bei dieser niedrigsten Einstellung leistungsfähig. Wenn Sie jedoch große Codebibliotheken importieren oder speicherintensive Aufgaben ausführen, reichen 128 MB nicht aus.
Wenn Ihre Funktionen viel langsamer als erwartet ausgeführt werden, besteht der erste Schritt darin, die Speichereinstellung zu erhöhen. So können Sie die Funktion Ihres Handlers nutzen, um die Leistung Ihrer Funktion zu verbessern.
CPU-gebundene Konfigurationen
Bei rechenintensiven Vorgängen kann es vorkommen, dass Ihre Funktion eine geringere Leistung als erwartet aufweist. Dies kann daran liegen, dass Ihre Funktion CPU-gebunden ist. In diesem Fall kann die Berechnungskapazität der Funktion nicht mit der Arbeit Schritt halten.
Mit Lambda können Sie die CPU-Konfiguration zwar nicht direkt ändern, die CPU wird jedoch indirekt über die Speichereinstellungen gesteuert. Der Lambda-Dienst weist proportional mehr virtuelle CPU zu, wenn Sie mehr Arbeitsspeicher zuweisen. Bei 1,8 GB Arbeitsspeicher wird einer Lambda-Funktion eine ganze vCPU zugewiesen und oberhalb dieser Grenze hat sie Zugriff auf mehr als einen vCPU-Kern. Mit 10 240 MB hat sie 6 vCPUs zur Verfügung. Mit anderen Worten: Sie können die Leistung verbessern, indem Sie die Speicherzuweisung erhöhen, auch wenn die Funktion nicht den gesamten Speicher nutzt.
Zeitüberschreitungen
Timeouts für Lambda-Funktionen können zwischen 1 und 900 Sekunden (15 Minuten) eingestellt werden. Standardmäßig ist dieser Wert in der Lambda-Konsole auf 3 Sekunden eingestellt. Der Timeout-Wert ist eine Sicherheitsvorrichtung, die sicherstellt, dass Funktionen nicht unbegrenzt ausgeführt werden. Nachdem der Timeout-Wert erreicht ist, stoppt Lambda den Funktionsaufruf.
Wenn ein Timeout-Wert nahe der durchschnittlichen Dauer einer Funktion festgelegt wird, erhöht sich das Risiko, dass die Funktion unerwartet abbricht. Die Dauer einer Funktion kann je nach Umfang der Datenübertragung und -verarbeitung sowie der Latenz aller Dienste, mit denen die Funktion interagiert, variieren. Häufige Ursachen für Timeouts:
-
Beim Herunterladen von Daten aus S3-Buckets oder anderen Datenspeichern ist der Download größer oder dauert länger als im Durchschnitt.
-
Eine Funktion sendet eine Anfrage an einen anderen Service, dessen Beantwortung länger dauert.
-
Die einer Funktion zur Verfügung gestellten Parameter erfordern eine höhere Rechenkomplexität in der Funktion, wodurch der Aufruf länger dauert.
Stellen Sie beim Testen Ihrer Anwendung sicher, dass Ihre Tests die Größe und Menge der Daten sowie realistische Parameterwerte genau wiedergeben. Wichtig ist, dass Sie Datensätze verwenden, die an der Obergrenze dessen liegen, was für Ihren Workload vernünftigerweise zu erwarten ist.
Implementieren Sie zusätzlich Obergrenzen für Ihren Workload, wo immer dies möglich ist. In diesem Beispiel könnte die Anwendung für jeden Dateityp eine maximale Größenbeschränkung verwenden. Anschließend können Sie die Leistung Ihrer Anwendung für eine Reihe erwarteter Dateigrößen bis einschließlich der Höchstgrenzen testen.
Speicherlecks zwischen Aufrufen
Globale Variablen und Objekte, die in der INIT-Phase eines Lambda-Aufrufs gespeichert sind, behalten ihren Status zwischen warmen Aufrufen bei. Sie werden erst vollständig zurückgesetzt, wenn die Ausführungsumgebung zum ersten Mal ausgeführt wird (auch als „Kaltstart“ bezeichnet). Alle im Handler gespeicherten Variablen werden zerstört, wenn der Handler beendet wird. Die INIT-Phase wird am besten zum Einrichten von Datenbankverbindungen, Laden von Bibliotheken, Erstellen von Caches und Laden unveränderlicher Assets verwendet.
Wenn Sie Bibliotheken von Drittanbietern für mehrere Aufrufe in derselben Ausführungsumgebung verwenden, sollten Sie deren Dokumentation zur Verwendung in einer Serverless-Computerumgebung überprüfen. Einige Bibliotheken für Datenbankverbindungen und Protokollierung speichern möglicherweise Zwischenaufrufergebnisse und andere Daten. Dies führt dazu, dass der Speicherverbrauch dieser Bibliotheken mit nachfolgenden Warmaufrufen zunimmt. In diesem Fall kann es vorkommen, dass die Lambda-Funktion nicht über genügend Speicher verfügt, selbst wenn Ihr benutzerdefinierter Code Variablen korrekt entsorgt.
Dieses Problem betrifft Aufrufe, die in warmen Ausführungsumgebungen auftreten. Der folgende Code erzeugt beispielsweise ein Speicherleck zwischen Aufrufen. Die Lambda-Funktion verbraucht bei jedem Aufruf zusätzlichen Speicher, indem sie die Größe eines globalen Arrays erhöht:
let a = []
exports.handler = async (event) => {
a.push(Array(100000).fill(1))
}
Bei einer Konfiguration mit 128 MB Speicher zeigt die Registerkarte Überwachung der Lambda-Funktion nach 1 000 Aufrufen dieser Funktion die typischen Änderungen bei Aufrufen, Dauer und Fehleranzahl, wenn ein Speicherleck auftritt:
-
Aufrufe: Eine gleichmäßige Transaktionsrate wird periodisch unterbrochen, da die Aufrufe länger dauern, bis sie abgeschlossen sind. Im eingeschwungenen Zustand verbraucht das Speicherleck nicht den gesamten der Funktion zugewiesenen Speicher. Wenn die Leistung abnimmt, paginiert das Betriebssystem den lokalen Speicher, um den wachsenden Speicherbedarf der Funktion zu decken, was dazu führt, dass weniger Transaktionen abgeschlossen werden.
-
Dauer: Bevor der Funktion der Speicherplatz ausgeht, beendet sie Aufrufe mit einer konstanten zweistelligen Millisekundenrate. Wenn ein Paging stattfindet, verlängert sich die Dauer um eine Größenordnung.
-
Anzahl der Fehler: Wenn der Speicherverlust den zugewiesenen Speicher übersteigt, kommt es schließlich zu einem Funktionsfehler, weil die Berechnung den Timeout überschreitet, oder die Ausführungsumgebung stoppt die Funktion.
Nach dem Fehler startet Lambda die Ausführungsumgebung neu, was erklärt, warum alle drei Diagramme eine Rückkehr zum ursprünglichen Zustand zeigen. Wenn Sie die CloudWatch-Metriken für die Dauer erweitern, erhalten Sie mehr Details zu den Statistiken für die minimale, maximale und durchschnittliche Dauer:
Um die Fehler zu finden, die bei den 1 000 Aufrufen erzeugt wurden, können Sie die Abfragesprache CloudWatch Insights verwenden. Die folgende Abfrage schließt Informationsprotokolle aus, um nur die Fehler zu melden:
fields @timestamp, @message | sort @timestamp desc | filter @message not like 'EXTENSION' | filter @message not like 'Lambda Insights' | filter @message not like 'INFO' | filter @message not like 'REPORT' | filter @message not like 'END' | filter @message not like 'START'
Ein Abgleich mit der Protokollgruppe für diese Funktion zeigt, dass Zeitüberschreitungen für die periodischen Fehler verantwortlich waren:
Asynchrone Ergebnisse wurden bei einem späteren Aufruf zurückgegeben
Bei Funktionscode, der asynchrone Muster verwendet, ist es möglich, dass die Callback-Ergebnisse eines Aufrufs in einem zukünftigen Aufruf zurückgegeben werden. In diesem Beispiel wird Node.js verwendet, aber dieselbe Logik kann auch auf andere Laufzeiten angewendet werden, die asynchrone Muster verwenden. Die Funktion verwendet die traditionelle Callback-Syntax in JavaScript. Sie ruft eine asynchrone Funktion mit einem inkrementellen Zähler auf, der die Anzahl der Aufrufe verfolgt:
let seqId = 0 exports.handler = async (event, context) => { console.log(`Starting: sequence Id=${++seqId}`) doWork(seqId, function(id) { console.log(`Work done: sequence Id=${id}`) }) } function doWork(id, callback) { setTimeout(() => callback(id), 3000) }
Wenn sie mehrmals hintereinander aufgerufen werden, treten die Ergebnisse der Callbacks bei nachfolgenden Aufrufen auf:
-
Der Code ruft die
doWork-Funktion auf und stellt als letzten Parameter eine Callback-Funktion bereit. -
Es dauert einige Zeit, bis die
doWork-Funktion abgeschlossen ist, bevor der Callback aufgerufen wird. -
Die Protokollierung der Funktion zeigt an, dass der Aufruf beendet wird, bevor die Ausführung der
doWork-Funktion abgeschlossen ist. Darüber hinaus werden nach dem Start einer Iteration Callbacks aus früheren Iterationen verarbeitet, wie in den Protokollen gezeigt.
In JavaScript werden asynchrone Callbacks mit einer Event-Schleife
Dadurch besteht die Möglichkeit, dass private Daten aus einem früheren Aufruf in einem späteren Aufruf erscheinen. Es gibt zwei Möglichkeiten, die Funktion Ihres Handlers aufzuweisen. Erstens bietet JavaScript die Schlüsselwörter async und await
let seqId = 0 exports.handler = async (event) => { console.log(`Starting: sequence Id=${++seqId}`) const result = await doWork(seqId) console.log(`Work done: sequence Id=${result}`) } function doWork(id) { return new Promise(resolve => { setTimeout(() => resolve(id), 4000) }) }
Die Verwendung dieser Syntax verhindert, dass der Handler beendet wird, bevor die asynchrone Funktion beendet ist. Wenn in diesem Fall der Callback länger dauert als das Timeout der Lambda-Funktion, gibt die Funktion einen Fehler aus, anstatt das Callback-Ergebnis bei einem späteren Aufruf zurückzugeben:
-
Der Code ruft die asynchrone
doWork-Funktion unter Verwendung des Schlüsselworts „await“ im Handler auf. -
Die Funktion
doWorkbenötigt eine gewisse Zeit, bevor sie das Versprechen auflöst. -
Bei der Funktion kommt es zu einem Timeout, da
doWorklänger dauert, als der Timeout erlaubt, und das Ergebnis des Rückrufs bei einem späteren Aufruf nicht zurückgegeben wird.
Im Allgemeinen sollten Sie sicherstellen, dass alle Hintergrundprozesse oder Rückrufe im Code abgeschlossen sind, bevor der Code beendet wird. Wenn dies in Ihrem Anwendungsfall nicht möglich ist, können Sie einen Bezeichner verwenden, um sicherzustellen, dass der Rückruf zum aktuellen Aufruf gehört. Dazu können Sie die vom Kontextobjekt bereitgestellte awsRequestId verwenden. Indem Sie diesen Wert an den asynchronen Callback übergeben, können Sie den übergebenen Wert mit dem aktuellen Wert vergleichen, um festzustellen, ob der Callback von einem anderen Aufruf stammt:
let currentContext exports.handler = async (event, context) => { console.log(`Starting: request id=$\{context.awsRequestId}`) currentContext = context doWork(context.awsRequestId, function(id) { if (id != currentContext.awsRequestId) { console.info(`This callback is from another invocation.`) } }) } function doWork(id, callback) { setTimeout(() => callback(id), 3000) }
-
Der Lambda-Funktionshandler verwendet den Kontextparameter, der Zugriff auf eine eindeutige Aufrufanforderungs-ID ermöglicht.
-
Die
awsRequestIdwird an die doWork-Funktion übergeben. Im Callback wird die ID mit derawsRequestIddes aktuellen Aufrufs verglichen. Wenn diese Werte unterschiedlich sind, kann der Code entsprechende Maßnahmen ergreifen.