AWS Study Note

AWS Lambda: Reuse, Retry Behavior and Invocation Type

討論 AWS Lambda 的重用、重試行為與 Invocation Type,以及在開發上需要注意的事。

Azole (小賴)
10 min readJul 10, 2019

之前被問說要不要分享 lambda,我還想說 lambda 有什麼好分享的,不就是開發函式而已嗎?但隨著雷愈踩愈多(通常就是自己沒有好好地閱讀文件),才開始認真地去看一些細節。

最近就看了 Retry Behavior 跟 Reuse,做了一個很簡單的函式來試試看。

Reuse

AWS Lambada 在叫用函式時,會啟動執行內容(execution context),這是一個暫時的環境,包含了一些依賴或宣告的初始化,而這個啟動會需要一些時間。而在執行過函式後,這個執行內容會被保持一段時間,AWS 稱之為凍結,當再次呼叫時,會解凍這個執行內容並且重複使用(reuse)。而當 reuse 時,函式中在 handler 以外的宣告都會保持初始化,這樣在再次呼叫時,就不用重新初始化,可以提供較好的效能。例如可以建立資料庫連線,這樣在 reuse 時,就不用重建連線,可以使用上一次的連線。

這邊做了一個很簡單的程式,在 handler 外部宣告一個函式,初始化為 0,然後在 handler 裡印出且遞增。如果 reuse lambda function 時,每次都是重頭跑到尾,那應該每次印出來都是 0,但實驗顯示,是會隨著呼叫次數而遞增。

let outside = 0;exports.handler = (event, context, callback) => {
console.log(JSON.stringify({
'RequestId': context.awsRequestId,
'outside': outside++
}));

return {
statusCode: 200
};
};

雖然這樣做可以提升效能,但這樣就有了一點雷區,這個函式放久一點,例如一個小時後再去執行,又會被初始化為 0。這樣就有可能會誤用了初始宣告,而且 AWS 也沒有說這個執行內容會被保持多久。

另外,例如我們在 handler 外部宣告了資料庫連線,根據後端工程師的「良好習慣」,有 create 就有 end、有 open 就有 close,如果在 handler 裡用完後,就順手把 connection end 掉了,或是 timeout 了,connection 自己關閉了,結果 reuse 的時候要用,就會拿到一個已關閉的連線,就會有問題。

建議的做法是,不要對 Lambda Function 的 state 有任何的假設,也就是不要假設 state 會被保留下來。每次都乖乖地初始化,如果有必要的話,在初始化的地方做一些檢查,例如,以資料庫建立連線為例,可以在 handler 之外就初始化以善用它重用的效率,但使用前或建立前,先去確認一下這個連線是否已被建立且連線中。

Retry Behavior

現在稍微改一下程式,模擬函式執行時發生錯誤:

let outside = 0;exports.handler = (event, context, callback) => {
console.log(JSON.stringify({
'RequestId': context.awsRequestId,
'outside': outside++
}));
// 發出錯誤
return callback('Error', 'retry test')
};

或是

let outside = 0;exports.handler = async (event, context, callback) => {
console.log(JSON.stringify({
'RequestId': context.awsRequestId,
'outside': outside++
}));

return new Promise((resolve, reject) => {
reject(Error("ERROR"));
});
};

我們用 console 畫面的測試功能進行測試,會收到一個執行錯誤的 log。

同步叫用

現在改用 cli 來叫用函式,在呼叫時把 invocation type 設定為 Event (註1),這是用非同步的方式來叫用,叫用後 log 一樣看到失敗的訊息,但我們等一點時間後,會看到這個 function 又被重複叫用一次,依舊錯誤後,再等一下,又會再叫用一次。根據 AWS 文件,會被重試兩次(所以總共執行了 3 次)(註2)。

aws lambda invoke --function-name test-freeze --invocation-type Event result.txt

注意觀察 log,發生錯誤後的重試叫用的 request id 會跟原本的一樣(註3),然後 outside 變數還是被累加上去了。AWS 文件中特別提醒,程式碼必須要有能重複處理相同事件的能力,以免發生問題,例如重複 insert 資料等。

這邊觀察到錯誤重試時的 request id 跟原本的一樣,應該可以試著用這個當判斷是否為錯誤重試、事件是否已經處理過。

  • 註1: invocation type 預設是 RequestResponse,這是同步叫用函式,叫用時會保持連線直到有回覆,或是 timeout,見下節。
  • 註2: 如果想要在三次嘗試都失敗後,你想要知道怎麼了,可以設定 Dead Letter Queue (DLQ)來捕捉這些失敗的事件,DLQ 可以將這些未處理的事件轉送到 SQS 或 SNS。如果沒有設定 DLQ,那在三次執行後,這個事件就會被捨棄。
  • 註3: Request Id 是從 context 中取得的,context 是一個讓我們的程式碼可以跟 Lambda 互動的物件。

Invocation Types

AWS CLI 叫用 Lambda Function 時,有一個參數叫做 invocation type,他可以設定成 RequestResponse, Event, DryRun,DryRun 只是測試目前這個 caller 是否能叫用這個 Lambda Function,並不會真的執行,所以就略過不討論。

RequestResponse

當我們用這個模式呼叫 Lambda Function 時,Lambda Function 會立即被執行,且我們需要等待這個 Lambda Function 執行完畢、回覆 response。這個就是同步叫用 (Synchronous Invokes),在這個模式底下,Lambda 不會自動幫我們重試,我們需要自己確認這個回覆,看看執行結果是否成功,如果有錯誤的話,是否需要重新再呼叫一次 (retry) 等。

Event

當我們用這個模式叫用 Lambda Function 時,Lambda 送一個 event 到一個 queue 去,然後就會回覆你成功(除非是新增這個 event 到 queue 去發生問題)。然後另外一個 process 會從這個 queue 讀出這個 event,並且叫用我們的 function。

這個就是 Asynchronous Invokes。

而當我們的 function 發生錯誤時(function 回覆錯誤代碼、runtime 錯誤或 timeout 等),Lambda 會再等一分鐘後,幫我們重試一次,如果還是有錯誤,等 2 分鐘後,會再試一次。如果三次都失敗了,這個 event 就會被捨棄,或是你可以設定 DLQ 來捕捉這個執行失敗的事件。

所以我們在上一節做的實驗,需要把 invocation-type 設定成 event,好觸發這個 retry 的行為供我們觀察。

上述討論的是用 CLI 叫用的時候,那如果是用其他 AWS Services 呢?

答案是不同的 AWS Services 叫用的模式並不同,所以這點在開發上要特別注意,是否要自行處理錯誤以及自己開發的 lambda function 要能被 retry。

  • 同步: ELB(ALB), API Gateway (也可以是非同步的), CloudFront, Kinesis Data Firehose, Cognito, Lex, Alexa, DynamoDB, Kinesis, SQS
  • 非同步: S3, SNS, SES, CloudFormation, CloudWatch Log, CloudWatch Event, Config, CodeCommit

那為什麼我們需要知道哪個服務是用哪一種方式來叫用 Lambda Function 呢?還記得上一節自動 retry 的問題嗎,如果我們今天開發一個 Lambda Function 預期是會用非同步的方式呼叫,那這個 Lambda Function 就得好好地處理重複事件了,因為一但發生錯誤,他自己就會自動重試,我們應該不想資料被重複 insert 或其他類似的情況發生吧。

除了上述的 invocation type,還有一種分類模式,分為 Push Model 與 Pull Model,AWS Services 中的 Kinesis 與 DynamoDB Stream 就是屬於 pull model,本文原本是想記錄 Reuse 與 Retry,這部分超過這邊的討論,就暫且不表。

結論

在開發 Lambda 時,要注意自己參數的初始化與相關判斷,也要注意是哪個服務會叫用這個 function,並且要讓自己的 lambda function 有重複執行(處理相同事件)的能力。

References

--

--

Azole (小賴)
Azole (小賴)

Written by Azole (小賴)

As a passionate software engineer and dedicated technical instructor, I have a particular fondness for container technologies and AWS.

No responses yet