事情是這樣的:
前陣子工作上開發的 API 上線後,在當天的數千次 request 中,發生 5 次不明原因的錯誤(從 CloudWatch Dashboard 看到 API Gateway 的 5XX Error 出現大於零的數字)。
後來為了重現錯誤,用 K6 做了 soak test。在 30 分鐘內也的確出現數次錯誤。
不過 API Gateway 在 response payload 之中只給出「Internal Server Error」的訊息,為了進一步了解錯誤的原因,要參考 $context Variables for access logging only 章節在 Access Log 之中加入額外的資訊。比如說:
$context.authorize.error
$context.authorizer.error
$context.authenticate.error
$context.integration.error
$context.waf.error
線索與解法
後來我在 $context.integration.error
中看到我要的錯誤訊息,該錯誤訊息指出 Lambda timeout 了。
不過,首先出問題的 Lambda function 很輕量,僅僅向 RDS 做了 CRUD。再來這個錯誤出現的比較隨機。
不過後來打開 knex.js 的 connection pool,並把 pool max 設為 7 之後就沒有再發生這個情況。
我的推測
為了解釋 connection pool 與 Lambda timeout 的關聯,我找到 RDS Proxy 的文件中有關 Session Pinning 的章節。它的大意是,多數情況下 RDS Proxy 會盡可能地重複利用連線,除非你做了一些事情,導致該連線暫時不適合被共用。
RDS Proxy automatically pins a client connection to a specific DB connection when it detects a session state change that isn’t appropriate for other sessions. Pinning reduces the effectiveness of connection reuse. If all or almost all of your connections experience pinning, consider modifying your application code or workload to reduce the conditions that cause the pinning.
AWS RDS Proxy Docs – Managing an RDS Proxy – Avoiding pinning

首先,Lambda function 會建立與 RDS Proxy 的連線(稱為 client connection),而 RDS Proxy 會維護與 RDS 的連線(稱為 database connection)。我認為:當 Lambda function 觸發 session pinning 時,原本使用中的 client connection 會綁死某個 database connection,而 Lambda function 在每服務完一次請求,當下的環境會被 reuse,應該包括已經建立的 client connection,因此要是 client connection 因為上次請求而被 pinned,便有可能無法服務下一個請求(在我的程式中,會先做基本的查詢,再做寫入)。因此,若是在 knex.js 加上 connection pool,使得 knex.js 可以在需要的時候建立額外的 client connection,便能避開這個問題。
延伸思考
每個 RDS instance 能提供的 database connection 數量都會因為自身擁有的記憶體,而有不一樣的限制。舉例來說,t2.micro 只能提供 40 個 database connection。所以當 40 個 database connection 都被 pinned 之後,即使 Lambda 能輕易的 scale 到幾百幾千,也能正常連上 RDS Proxy,但是好像有可能:RDS Proxy 不能立即提供可用的 database connection?
發佈留言