View a markdown version of this page

Risolvi i problemi di configurazione in Lambda - AWS Lambda

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Risolvi i problemi di configurazione in Lambda

Le impostazioni di configurazione delle funzioni possono avere un impatto sulle prestazioni e sul comportamento complessivi della funzione Lambda. Queste potrebbero non causare errori di funzione effettivi, ma possono causare timeout e risultati imprevisti.

I seguenti argomenti forniscono consigli per la risoluzione dei problemi più comuni che potresti riscontrare relativi alle impostazioni di configurazione delle funzioni Lambda.

Configurazioni della memoria

È possibile configurare una funzione Lambda per utilizzare tra 128 MB e 10.240 MB di memoria. Per impostazione predefinita, a qualsiasi funzione creata nella console viene assegnata la quantità di memoria minima. Molte funzioni Lambda sono performanti con questa impostazione minima. Tuttavia, se stai importando librerie di codice di grandi dimensioni o completando attività che richiedono molta memoria, 128 MB non sono sufficienti.

Se le funzioni vengono eseguite molto più lentamente del previsto, il primo passo è aumentare l'impostazione della memoria. Per le funzioni legate alla memoria, questo risolve il problema e potrebbe migliorare le prestazioni della funzione.

CPU-bound configurazioni

Per le operazioni ad alta intensità di calcolo, se le prestazioni della funzione sono inferiori al previsto, ciò potrebbe essere dovuto al fatto che la funzione è in uso. CPU-bound In questo caso, la capacità di calcolo della funzione non può tenere il passo con il lavoro.

Sebbene Lambda non consenta di modificare direttamente la configurazione della CPU, la CPU viene controllata indirettamente tramite le impostazioni della memoria. Il servizio Lambda alloca proporzionalmente più CPU virtuale man mano che si alloca più memoria. Con 1,8 GB di memoria, una funzione Lambda ha un'intera vCPU allocata e, al di sopra di questo livello, ha accesso a più di un core vCPU. Con 10.240 MB, sono disponibili 6 vCPU. In altre parole, è possibile migliorare le prestazioni aumentando l'allocazione di memoria, anche se la funzione non utilizza tutta la memoria.

Timeout

I timeout per le funzioni Lambda possono essere impostati tra 1 e 900 secondi (15 minuti). Per impostazione predefinita, la console Lambda lo imposta su 3 secondi. Il valore di timeout è una valvola di sicurezza che garantisce che le funzioni non vengano eseguite all'infinito. Una volta raggiunto il valore di timeout, Lambda interrompe l'invocazione della funzione.

Se un valore di timeout è impostato vicino alla durata media di una funzione, aumenta il rischio che la funzione scada inaspettatamente. La durata di una funzione può variare in base alla quantità di trasferimento ed elaborazione dei dati e alla latenza dei servizi con cui interagisce la funzione. Le cause più comuni di timeout includono:

  • Quando si scaricano dati da bucket S3 o altri archivi di dati, il download è più grande o richiede più tempo della media.

  • Una funzione invia una richiesta a un altro servizio, che impiega più tempo a rispondere.

  • I parametri forniti a una funzione richiedono una maggiore complessità computazionale della funzione, il che fa sì che l'invocazione richieda più tempo.

Quando testate l'applicazione, assicuratevi che i test riflettano accuratamente le dimensioni e la quantità di dati e i valori dei parametri realistici. È importante sottolineare che utilizzate i set di dati al limite superiore di ciò che è ragionevolmente previsto per il carico di lavoro.

Inoltre, implementa i limiti massimi del carico di lavoro laddove possibile. In questo esempio, l'applicazione potrebbe utilizzare un limite di dimensione massima per ogni tipo di file. È quindi possibile testare le prestazioni dell'applicazione per una gamma di dimensioni di file previste, fino ai limiti massimi inclusi.

Perdita di memoria tra invocazioni

Le variabili e gli oggetti globali memorizzati nella fase INIT di una invocazione Lambda mantengono il loro stato tra le invocazioni a caldo. Vengono ripristinati completamente solo quando l'ambiente di esecuzione viene eseguito per la prima volta (operazione nota anche come «avvio a freddo»). Tutte le variabili memorizzate nell'handler vengono distrutte quando l'handler viene terminato. È consigliabile utilizzare la fase INIT per configurare connessioni al database, caricare librerie, creare cache e caricare risorse immutabili.

Quando utilizzi librerie di terze parti per più chiamate nello stesso ambiente di esecuzione, controlla la loro documentazione per verificarne l'utilizzo in un ambiente di elaborazione senza server. Alcune librerie di connessione e registrazione al database potrebbero salvare i risultati delle chiamate intermedie e altri dati. Ciò fa sì che l'utilizzo della memoria di queste librerie aumenti con le successive invocazioni a caldo. In tal caso, potresti scoprire che la funzione Lambda esaurisce la memoria, anche se il tuo codice personalizzato sta eliminando le variabili correttamente.

Questo problema riguarda le invocazioni che si verificano in ambienti di esecuzione a caldo. Ad esempio, il seguente codice crea una perdita di memoria tra le invocazioni. La funzione Lambda consuma memoria aggiuntiva ad ogni invocazione aumentando le dimensioni di un array globale:

let a = []

exports.handler = async (event) => {
    a.push(Array(100000).fill(1))
}

Configurata con 128 MB di memoria, dopo aver richiamato questa funzione 1.000 volte, la scheda Monitoraggio della funzione Lambda mostra le tipiche modifiche nelle invocazioni, nella durata e nel conteggio degli errori quando si verifica una perdita di memoria:

La scheda di monitoraggio della console Lambda mostra l'interruzione delle chiamate, l'aumento della durata e l'aumento del numero di errori durante una perdita di memoria.
  1. Richiami: una velocità di transazione costante viene interrotta periodicamente poiché le chiamate richiedono più tempo per essere completate. Durante lo stato stazionario, la perdita di memoria non sta consumando tutta la memoria allocata alla funzione. Man mano che le prestazioni peggiorano, il sistema operativo utilizza la memoria locale per adattarla alla crescente quantità di memoria richiesta dalla funzione, il che comporta un minor numero di transazioni completate.

  2. Durata: prima che la funzione esaurisca la memoria, termina le chiamate a una velocità costante di millisecondi a due cifre. Man mano che si verifica la paginazione, la durata aumenta di un ordine di grandezza.

  3. Numero di errori: quando la perdita di memoria supera la memoria allocata, alla fine la funzione si blocca a causa del superamento del timeout del calcolo o dell'ambiente di esecuzione che interrompe la funzione.

Dopo l'errore, Lambda riavvia l'ambiente di esecuzione, il che spiega perché tutti e tre i grafici mostrano un ritorno allo stato originale. L'espansione delle CloudWatch metriche relative alla durata fornisce maggiori dettagli per le statistiche sulla durata minima, massima e media:

CloudWatch metriche sulla durata che mostrano statistiche minime, massime e medie con un picco durante il periodo di perdita di memoria.

Per trovare gli errori generati nelle 1000 chiamate, puoi utilizzare il linguaggio di interrogazione Insights. CloudWatch La seguente query esclude i log informativi per riportare solo gli errori:

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'

Se eseguita sul gruppo di log per questa funzione, ciò dimostra che i timeout erano responsabili degli errori periodici:

CloudWatch Risultati della query Logs Insights che mostrano gli errori di timeout della funzione Lambda.

Risultati asincroni restituiti a una invocazione successiva

Per il codice di funzione che utilizza pattern asincroni, è possibile che i risultati di callback di una chiamata vengano restituiti in una chiamata futura. Questo esempio utilizza Node.js, ma la stessa logica può essere applicata ad altri runtime che utilizzano pattern asincroni. La funzione utilizza la tradizionale sintassi di callback in. JavaScript Richiama una funzione asincrona con un contatore incrementale che tiene traccia del numero di invocazioni:

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

Quando vengono richiamati più volte in successione, i risultati dei callback si verificano nelle invocazioni successive:

CloudWatch registri che mostrano i risultati di callback di una chiamata che appare nelle invocazioni successive.
  1. Il codice richiama la funzione doWork, fornendo una funzione di callback come ultimo parametro.

  2. Il completamento della funzione doWork richiede un certo periodo di tempo prima di richiamare il callback.

  3. La registrazione della funzione indica che l'invocazione termina prima che la funzione termini l'esecuzione. doWork Inoltre, dopo l'avvio di un'iterazione, vengono elaborati i callback delle iterazioni precedenti, come mostrato nei log.

In JavaScript, i callback asincroni vengono gestiti con un ciclo di eventi. https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop Altri runtime utilizzano meccanismi diversi per gestire la simultaneità. Quando l'ambiente di esecuzione della funzione termina, Lambda blocca l'ambiente fino alla chiamata successiva.

Dopo la ripresa, JavaScript continua l'elaborazione del ciclo di eventi, che in questo caso include un callback asincrono da una chiamata precedente. Senza questo contesto, può sembrare che la funzione esegua codice senza motivo e restituisca dati arbitrari. In effetti, è davvero un artefatto del modo in cui la simultaneità di runtime e gli ambienti di esecuzione interagiscono.

Ciò crea la possibilità che i dati privati di una invocazione precedente vengano visualizzati in una invocazione successiva. Esistono due modi per prevenire o rilevare questo comportamento. Innanzitutto, JavaScript fornisce le parole chiave async e await per semplificare lo sviluppo asincrono e inoltre forzare l'esecuzione del codice in attesa del completamento di una chiamata asincrona. La funzione precedente può essere riscritta utilizzando questo approccio nel modo seguente:

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

L'utilizzo di questa sintassi impedisce all'handler di uscire prima che la funzione asincrona sia terminata. In questo caso, se il callback richiede più tempo del timeout della funzione Lambda, la funzione genera un errore, invece di restituire il risultato del callback in una chiamata successiva:

CloudWatch registri che mostrano il timeout della funzione quando si utilizza await, impedendo che il callback si trasmetta a richiami successivi.
  1. Il codice richiama la funzione doWork asincrona utilizzando la parola chiave await nell'handler.

  2. Il completamento della funzione doWork richiede un certo periodo di tempo prima di risolvere la promessa.

  3. La funzione scade perché doWork impiega più tempo del limite di timeout consentito e il risultato del callback non viene restituito in una invocazione successiva.

Di solito, è necessario accertarsi che tutti i processi in background e le callback nel codice vengano completate prima che il codice sia terminato. Se ciò non è possibile nel tuo caso d'uso, puoi utilizzare un identificatore per assicurarti che il callback appartenga all'invocazione corrente. A tale scopo, è possibile utilizzare gli aws RequestId forniti dall'oggetto context. Passando questo valore al callback asincrono, è possibile confrontare il valore passato con il valore corrente per rilevare se il callback ha avuto origine da un'altra invocazione:

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) }
CloudWatch log che mostrano la funzione che rileva e registra quando un callback ha avuto origine da una chiamata diversa.
  1. L'handler della funzione Lambda accetta il parametro context, che fornisce l'accesso a un ID di richiesta di invocazione univoco.

  2. awsRequestId viene passato alla funzione doWork. Nel callback, l'ID viene confrontato con awsRequestId dell'invocazione corrente. Se questi valori sono diversi, il codice può agire di conseguenza.