この記事は、SlackアプリをAWS Lambdaで作っている人向けの記事です。
Slack APIの3秒ルールとLambdaのコールドスタート問題
Slack API(特にEvents APIやSlash Commands)をAWS Lambdaで受けていると、3秒ルールとLambdaのコールドスタートの相性問題にほぼ必ずぶつかります。
同じことでハマった人の助けになれば幸いです。
忙しい人向けのまとめ
- まずはメモリを128MB → 256MBに上げる。だいたい解決します。
- Slack Bolt for Pythonを使うと楽(Python限定)
Slack APIの「3秒ルール」とは
SlackのEvents APIやSlash Commandsでは、リクエストを受け取ってから3秒以内にackを返す必要があります。
- 3秒以内に
200 OKを返す → 成功 - 3秒を超える → タイムアウト扱いでSlack側が再送 or エラー
処理自体が重いかどうかは関係なく、とにかく3秒以内にackすることが最優先です。
厳しいですね。
Lambdaで起きがちな問題:コールドスタート
AWS Lambdaを使っていると、以下のような構成はよくあります。
- API Gateway / Lambdaの関数URL → Lambda → Slack API
このとき問題になるのが Lambdaのコールドスタート です。
- 初回起動時
- しばらく呼ばれていなかった関数
こうしたタイミングでは、
- ランタイム起動
- ライブラリロード
- 初期化処理
などが走り、それだけで数秒かかることがあります。
結果として、「処理は軽いのに、Slackからのリクエストはたまに失敗する」 という現象が起きます。困ります。
実際によくある症状
- たまにSlack側で
timeoutエラー - CloudWatch Logsを見ると処理自体は終わっている
- 再実行すると成功する
処理は軽いからすぐ返るはずなのに……という場合はだいたい コールドスタート + 3秒ルール が主原因です。
対策①:Lambdaのメモリを256MBに上げる
結論から言うと、メモリをデフォ値の128MB → 256MBに上げるだけで、ほとんどのケースは解決します。
なぜ効くのか
Lambdaでは、vCPUがメモリに比例して割り当てられるという仕様になっています。
そのため、
- ランタイム起動
- 依存ライブラリのimport
が高速化され、コールドスタート時間が目に見えて短くなります。
他の対策と比べてどうか
コールドスタート対策としては、
- プロビジョニング済み同時実行を使う
- cron(EventBridge)で定期的に実行してウォームアップする
といった方法もあります。
ただしこれらは、コストや管理ポイントが増え設定や運用が面倒というデメリットがあります。
それに比べると、メモリを256MBに上げるだけという対策は、
- 設定が一瞬
- 運用不要
- コスト増が少ない
と、かなり手軽です。おすすめです。
コストは?
実行時間が短くなる恩恵もありますし、リクエスト数も少ないSlack Bot用途なら、コスト増はほぼ無視できるレベル なんじゃないかなと思います。
アプリがなぜか動かないひどいユーザー体験よりはずっと良いはずです。
対策②:Slack Bolt for Pythonを使う
Lambdaにはもう一つ重要なポイントがあります。
レスポンスを返した時点で、処理は終了する
つまり、
- ackを返した後に
- 重い処理を続ける
ということは 素直に書くとできません。(そして返す前に重い処理をしようとすると3秒を超過します)
SQS / EventBridge / Step Functions などを使えば話は変わりますが、まあ面倒です。
ここで役に立つのが Slack Bolt for Python です。
Slack Bolt for Pythonには、lazy listener という仕組みがあります。
- まず即座にackを返す
- 重い処理は別スレッドで実行
これにより、
- Slackの3秒ルールを守れる
- Lambdaの制約とも相性がいい
という構成が作れます。
なぜPythonなのか
現時点では他言語(Node.jsなど)には同等の仕組みがありません。Slack公式ドキュメントでも明言されています。
普段TypescriptやJavaを触っていると型安全が恋しくなる瞬間はありますが…
“Lazy Listeners are a feature which make it easier to deploy Slack apps to FaaS (Function-as-a-Service) environments. Please note that this feature is only available in Bolt for Python, and we are not planning to add the same to other Bolt frameworks.”
まとめ
- Slack APIには3秒ルールある
- Lambdaのコールドスタートで普通に破られる
- メモリを上げるだけで、だいたい解決する
- Slack Bolt for Python + lazy listener が相性抜群
それでもダメな場合
- 3秒以上にかかりそうな処理は別途やること前提で、provisioned concurrencyを1にする
- リトライの回数を示すヘッダを見るようにして二重実行を回避
いいねやストックで応援いただけると励みになるので、ぜひポチっとお願いします!