{"id":1283,"date":"2020-03-20T12:21:18","date_gmt":"2020-03-20T11:21:18","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1283"},"modified":"2021-03-17T15:12:41","modified_gmt":"2021-03-17T14:12:41","slug":"analisi-completa-di-aws-lambda-come-ottimizzare-i-picchi-e-prevenire-i-cold-start","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/analisi-completa-di-aws-lambda-come-ottimizzare-i-picchi-e-prevenire-i-cold-start\/","title":{"rendered":"Analisi completa di AWS Lambda: come ottimizzare i picchi e prevenire i cold start"},"content":{"rendered":"
Quando si parla di paradigma Serverless, molti sono gli aspetti che dobbiamo tenere in considerazione per evitare problemi di latenza e produrre cos\u00ec applicazioni pi\u00f9 belle, pi\u00f9 robuste e sicure. In questo articolo discuteremo di molti aspetti da tenere in considerazione quando si decide di sviluppare in AWS Lambda, come evitare alcuni dei problemi pi\u00f9 comuni ma anche di come sfruttare le innovazioni introdotte recentemente da Amazon per creare app Serverless pi\u00f9 efficienti, performanti e meno costose.\u00a0<\/span><\/p>\n Per anni l\u2019argomento cold start \u00e8 stato uno dei pi\u00f9 accesi e pi\u00f9 frequentemente dibattuti della community di Serverless.<\/span><\/p>\n Supponiamo che abbiate provvisionato una nuova funzione Lambda. Indipendentemente dal modo in cui la funzione \u00e8 invocata, una nuova Micro VM deve essere istanziata; questo perch\u00e8 non ci sono ancora istanze disponibili per rispondere a questo preciso evento. L\u2019unione dei tempi necessari a fare il setup del Runtime di Lambda, insieme con il vostro codice e le sue dipendenze, viene comunemente chiamato Cold Start.<\/span><\/p>\n Indipendentemente dal tipo di Runtime scelto, un processo di setup pu\u00f2 impiegare almeno dai 50 ai 200 ms prima che l\u2019esecuzione abbia realmente luogo. Lambda scritte in Java e .Net possono anche dar vita a cold start di diversi secondi!<\/span><\/p>\n Facendo riferimento al vostro caso d\u2019uso, i cold start possono diventare sicuramente un collo di bottiglia, prevenendo cos\u00ec la possibile adozione di un paradigma Serverless per la vostra applicazione. Per fortuna, per molti sviluppatori, questo problema \u00e8 facilmente aggirabile perch\u00e9 il carico di lavoro della loro soluzione \u00e8 prevedibile e stabile o, in alcuni casi, basato su calcoli ad uso esclusivamente interno, ad es. data-processing.<\/span><\/p>\n La documentazione di AWS ci mostra un ottimo esempio per comprendere meglio i cold start e i problemi correlati con le nostre necessit\u00e0 di scalare. Immaginiamo di avere a che fare con societ\u00e0 come JustEat e Deliveroo solite a ricevere picchi di traffico durante gli orari di pranzo e cena.<\/span><\/p>\n Questi picchi causano il raggiungimento potenziale dei limiti di una applicazione come ad esempio quanto velocemente AWS Lambda sia in grado di scalare oltre il suo limite iniziale di <\/span>burst-capacity<\/b>. Dopo il <\/span>burst<\/b> iniziale, una lambda pu\u00f2 scalare linearmente fino a 500 istanze per minuto per servire richieste concorrenti. Prima di poter servire tali richieste, per\u00f2, ogni nuova istanza si trova ad affrontare un cold start. Limiti di concurrency e una alta latenza di risposta dovuta ai cold start possono far s\u00ec che le vostre politiche di scaling non siano in grado di far fronte al traffico, causando l\u2019eventuale scarto di nuove richieste.<\/span><\/p>\n <\/p>\n <\/p>\n La Concurrency di AWS Lambda ha un limite per regione, condiviso da tutte le funzioni in quella specifica regione, altro punto da tenere in considerazione quando diverse Lambda sono soggette a invocazioni molto frequenti. Facendo riferimento alla tabella sottostante abbiamo:<\/span><\/p>\n Per garantire che una specifica funzione possa sempre raggiungere uno specifico livello di concurrency e per restringere il numero di istanze di una Lambda function aventi accesso a risorse di basso livello (ad es. Un database), \u00e8 possibile configurare la <\/span>reserved concurrency<\/b>.<\/span><\/p>\n Quando una funzione ha abilitato la <\/span>reserved concurrency<\/b>, quest\u2019ultima ha sempre la possibilit\u00e0 di aumentare il numero di istanze al valore specificato nella configurazione della reserved concurrency, indipendentemente dall\u2019utilizzo delle altre Lambda. La Reserved Concurrency si applica ad una funzione Lambda nella sua interezza, alias e versioni comprese.<\/span><\/p>\n Per riservare la concurrency per una funzione, basta seguire questi semplici passi:<\/span><\/p>\n L\u2019esempio successivo mostra come la reserved concurrency possa aiutare a mitigare il throttling delle richieste.<\/span><\/p>\n La Reserved Concurrency pu\u00f2 essere applicata quando si ha una comprensione chiara del numero massimo possibile di richieste e del tasso di concorrenza, ma non risolve comunque i problemi dovuti ai cold start poich\u00e9 ogni nuova istanza creata per sostenere la concorrenzialit\u00e0 delle richieste incapper\u00e0 in quel problema.<\/span><\/p>\n Prima che AWS rilasciasse la feature di Provisioned Concurrency, cercare di evitare o almeno mitigare il problema del cold start per rendere le Lambda pi\u00f9 responsive era un compito difficile: bisognava far uso di codice custom lato utente per capire o stato di una Lambda se pronta on in stato di warming. Pi\u00f9 avanti alcune librerie sono salite alla ribalta come <\/span>lambda-warmer<\/span><\/a> e <\/span>serverless-plugin-warmup<\/span><\/a>, tuttavia non possono essere considerate una soluzione pulita e definitiva.<\/span><\/p>\n Per rendere capace una funzione di scalare senza fluttuazioni in latenza, si utilizza la <\/span>provisioned concurrency<\/b>. Quest\u2019ultima opzione permette di configurare un certo numero di istanze gi\u00e0 \u201ccalde\u201d fin dall\u2019inizio e che non richiede interventi sul vostro codice applicativo.<\/span><\/p>\n Il seguente esempio mostra una funzione con abilitata la provisioned concurrency elaborare un singolo picco di traffico.<\/span><\/p>\n Quando la <\/span>provisioned concurrency<\/b> \u00e8 allocata, la funzione \u00e8 in grado di scalare con la stessa politica di burst della concurrency di default.<\/span><\/p>\n Una volta allocata, la <\/span>provisioned concurrency<\/b>, gestisce le richieste in arrivo con una latenza molto bassa. Quando tutta la provisioned concurrency \u00e8 in uso, la funzione prender\u00e0 a scalare normalmente per gestire le ulteriori richieste. Queste richieste addizionali incorreranno nel cold start, ma saranno un numero significativamente basso se la Provisioned Concurrency \u00e8 stata configurata ad hoc.<\/span><\/p>\n Grazie a questa nuova feature \u00e8 ora possibile migrare ad un paradigma Serverless per servizi storicamente difficili da migrare, come:<\/span><\/p>\n Si pu\u00f2 configurare la Provisioned Concurrency sia per gli Alias che per una Versione specifica. Se viene configurata per un Alias, tutte le versioni ad esso associate erediteranno il valore di Provisioned Concurrency. Altrimenti ogni versione avr\u00e0 la propria configurazione indipendente. In questo modo \u00e8 possibile associare differenti versioni di Lambda , con differenti valori di Provisioned Concurrency, a diversi carichi di traffico, non solo, \u00e8 possibile sfruttare questa peculiarit\u00e0 per effettuare AB testing.<\/span><\/p>\n E\u2019 bene notare che non \u00e8 possibile configurare la Provisioned Concurrency per l\u2019Alias $LATEST o qualsiasi Alias che a lui faccia riferimento.<\/span><\/p>\n Per pubblicare una nuova versione di Lambda, \u00e8 sufficiente entrare nella pagina di dettaglio della Lambda desiderata e cliccare \u201cPublish new version\u201d dalle azioni del men\u00f9 a tendina in alto allo schermo, come mostrato in figura:<\/span><\/p>\n Dopo una pubblicazione, la nuova versione \u00e8 settata:<\/span><\/p>\n A questo punto \u00e8 possibile configurare la Provisioned Concurrency per la versione appena creata (Version 1) ma, per maggior esaustivit\u00e0, esploreremo il metodo per configurare la Provisioned Concurrency per un nuovo Alias che punta a \u201cVersion 1\u201d.<\/span><\/p>\n Procedendo, sotto la sezione <\/span>Aliases<\/b>, cliccare su \u201c<\/span>+ Create alias<\/b>\u201d. Questo comando apre un nuovo modale nel quale \u00e8 possibile creare un nuovo alias, che punter\u00e0 alla versione appena creata.<\/span><\/p>\n Quando l\u2019alias \u00e8 stato creato, \u00e8 possibile infine configurare la Provisioned Concurrency.<\/span><\/p>\n Puntiamo ora alla sezione \u201cProvisioned Concurrency\u201d. Qui possiamo configurare la <\/span>Provisioned Concurrency<\/b> per il nuovo alias, inserendo un valore che rappresenta quante esecuzioni parallele possiamo provisionare dal nostro Pool di Reservation. E\u2019 davvero molto semplice, basta solo avere l\u2019accortezza di sapere che questo ovviamente corrisponde ad un incremento dei costi cos\u00ec come specificato da AWS.<\/span><\/p>\n Una volta definita la Provisioned Concurrency, il valore della colonna \u201cStatus\u201d nella sezione apposita, cambier\u00e0 in <\/span>Ready<\/b>. Le invocazioni della Lambda a questo punto saranno gestite dalla Provisioned Concurrency a monte di quella on-demand Standard. <\/span><\/p>\n Nel grafico, possiamo vedere che le invocazioni di Lamdba sono effettivamente ora gestite da istanze provisionate e se analizziamo anche CloudWatch Logs, possiamo vedere chiaramente come tale invocazione di Lambda riporti la dicitura <\/span>Init Duration<\/b>, che corrisponde al tempo necessario per permettere a Lambda di effettuare il setup del numero richiesto di istanze per le esecuzioni. Oltre a questo abbiamo la voce classica di <\/span>Billed Duration<\/b>.<\/span><\/p>\n Possiamo notare la stessa cosa anche prendendo in esame la console di X-Ray per questa invocazione di Lambda.<\/span><\/p>\n Volendo compiere un ulteriore miglioramento possiamo attivare anche l\u2019opzione di <\/span>autoscaling per la provisioned concurrency<\/b>. Quando usiamo l\u2019Application Auto Scaling, possiamo creare una <\/span>target tracking scaling policy<\/b> che modifica il numero di esecuzioni contemporanee basandosi su metriche di utilizzo fornite da Lambda.<\/span><\/p>\n Nota a margine. Sembra non esserci un modo semplice per cancellare una configurazione di Provisioned Concurrency dalla console di AWS, per tale motivo si pu\u00f2 usare questo comando della cli di AWS:<\/span><\/p>\n E\u2019 possibile utilizzare l\u2019API di Application Auto Scaling per registrare un alias come un <\/span>scalable target <\/b>\u00a0a cui associare una<\/span> policy di scaling<\/b>.<\/span><\/p>\n Nell\u2019esempio seguente, una funzione scala tra un valore minimo e massimo di <\/span>provisioned concurrency <\/b>basandosi sull\u2019utilizzo della funzione stessa; al crescere del numero di richieste aperte , l\u2019Application Auto Scaling aumenta la Provisioned Concurrency a grossi step fino a raggiungere il massimo configurato.<\/span><\/p>\n La funzione prosegue scalando con la standard concurrency finch\u00e8 l\u2019utilizzo della stessa non comincia a calare. Quando l\u2019utilizzo \u00e8 di nuovo sufficientemente e consistentemente basso, l\u2019Application Auto Scaling diminuisce la Provisioned Concurrency in piccoli gradini periodici (met\u00e0 destra dell\u2019immagine).<\/span><\/p>\n A parte l\u2019auto scaling basato su metriche di utilizzo AWs consente anche di schedulare le politiche di Scaling. In entrambi i casi, \u00e8 prima necessario registrare un alias come scaling target.<\/span><\/p>\n Nuove e migliorate metriche di AWS Lambda sono ora disponibili per meglio aiutare a definire i picchi di traffico cos\u00ec come le Lambda possano rispondere in termini di concorrenza e invocazioni simultanee.<\/span><\/p>\n \u2013 Il numero di stanze per le funzioni che stanno processando gli eventi. Se il numero raggiunge la vostra <\/span>concurrent executions limit<\/span><\/a> per una regione specificata o il <\/span>reserved concurrency limit<\/span><\/a> che \u00e8 stato configurato per quella funzione, le richieste successive saranno scartate.<\/span><\/li>\nCold Start<\/h2>\n
<\/p>\n
Legenda<\/h6>\n
<\/p>\n
Istanze per le funzioni<\/span><\/h6>\n
<\/h6>\n
Richieste aperte<\/span><\/h6>\n
possibile Throttling<\/span><\/h6>\n
Reserved Concurrency<\/h2>\n
Limiti di Burst Concurrency<\/h3>\n
\n
\n
Legenda<\/h6>\n
<\/h6>\n
Istanze per le funzioni<\/span><\/h6>\n
<\/h6>\n
Richieste aperte<\/span><\/h6>\n
possibile Throttling<\/span><\/h6>\n
Provisioned Concurrency<\/span><\/h2>\n
Legenda<\/h6>\n
<\/h6>\n
Istanze per le funzioni<\/span><\/h6>\n
<\/h6>\n
Richieste aperte<\/span><\/h6>\n
<\/h6>\n
Provisioned concurrency<\/span><\/h6>\n
Standard concurrency<\/span><\/h6>\n
\n
<\/p>\n
<\/p>\n
<\/p>\n
<\/p>\n
<\/p>\n
<\/p>\n
<\/p>\n
aws lambda delete-provisioned-concurrency-config --function-name \r\n<\/span><function-name><\/span> --qualifier <\/span><version-number\/alias-name>\r\n<\/span><\/pre>\n
Auto Scaling API<\/h2>\n
Legenda<\/h6>\n
<\/h6>\n
Istanze per le funzioni<\/span><\/h6>\n
<\/h6>\n
Richieste aperte<\/span><\/h6>\n
<\/h6>\n
Provisioned concurrency<\/span><\/h6>\n
Standard concurrency<\/span><\/h6>\n
Metriche di Lambda<\/h2>\n
Metriche di Concurrency<\/h3>\n
\n
ConcurrentExecutions<\/span><\/pre>\n
ProvisionedConcurrentExecutions<\/span><\/pre>\n