概要
Slack の Event API + AWS Lambda で Bot を開発している際、
明確にリトライ処理を入れていないのにLambda が2回実行されるという問題に何度か遭遇しました。
結果としてわかったのは、
- Slack側の再送仕様
- Lambda側のデフォルト リトライ設定
- Workflowの副作用
が重なることで、意図せずイベントが複数回実行される状態が発生するということです。
実際ハマったことを備忘として残します。
この記事でわかること
- Slack イベントが2回実行される理由
- Slack Workflow を紐づけた時にイベントが倍増する理由
- Lambda がエラーを返すと Slack がイベントを再送する仕様
- Lambda のリトライ設定がデフォルトだと危険なケース
- 再発防止の設計パターン
- リトライ設定に関する正しい注意点(要件しだい)
1. Slack Workflow をチャンネルに紐づけるとイベントが2回送信される
まずはこの問題。
Slack Workflow(Workflow Builder)でWorkflowを作り、
Botがイベント購読しているチャンネルに紐づけると、
イベントが2回届くことがあります。
※環境の違いもあると思うので、必ず起こるわけではないと思う。
理由(推測)
Workflowをチャンネルに紐づけると内部処理によりイベントが増える。
その結果、同じメッセージが2回届く
対策
WorkflowとBotを同じチャンネルで併用しない。
または、同じメッセージが来た場合に重複排除する。
2. Lambda 側でエラーがあると Slack から同じイベントが再送される
Slack Event API は以下の仕様になっています:
Bolt アプリは Slack API サーバーからのリクエストに対して 3 秒以内に ack() メソッドで応答する必要があります。3 秒以内に応答しなかった場合、Slack API は一定時間経過後にリトライします。
https://docs.slack.dev/tools/java-slack-sdk/ja-jp/guides/events-api/
つまり Lambda 内でエラーが起きたり、
コードが構文エラーで落ちたりすると…
Lambda が 200 を返さない
Slack が「届いてない」と判断
同一イベントをもう1回送る
結果、Lambda が2回実行されます。
3. Lambda のリトライ設定がデフォルトだとさらに2回実行される
Slack がイベントを再送した上で、
Lambda側のデフォルト設定は以下の通り:
リトライ回数 = 2
つまり Slack 再送 × Lambda リトライ の組み合わせで、
最大 3〜6回 同じ処理が実行される可能性あり
副作用のある処理(メッセージ投稿/課金 API/外部サービス連携)を行っている場合は特に危険です。
4. 今回実際に発生した問題(実例)
症状
Slack → API Gateway → Lambda
Lambda コードに構文エラー
Lambda が 200 を返せない
Slack が再送 → Lambda が2回実行
修正後
構文エラー修正
Lambda が正常に 200 を返す
→ イベントは1回だけになった
5. 再発防止のためのベストプラクティス
① 重複排除を導入する
DynamoDBに投稿IDを登録しておいて、重複チェックすることはできました。
今回はメッセージ投稿のみなので、Slackの client_msg_id をキーにしてます。
import boto3
import time
import json
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("slack_msg_event_table")
def handler(event, context):
slack_event = json.loads(event["body"])
client_msg_id = slack_event["event"].get("client_msg_id")
ttl = int(time.time()) + 300 # 5分
# ここで重複排除(同時実行でも耐える)
try:
table.put_item(
Item={
"id": client_msg_id,
"ttl": ttl
},
ConditionExpression="attribute_not_exists(id)"
)
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
print("Duplicate event detected. Skipped.")
return { "statusCode": 200 }
② 3秒以内に ack を返す構造にする
Slack は 3 秒の制限が厳しいため、
• Lambda → SQS へ enqueue だけ行う
• 実処理は別 Lambda に任せる
という “即レス構成” が安定する。
③ Lambda のリトライは Slack の場合のみ 0 推奨
Slack の Event API は 送信側が再送してくる 仕様のため、
Lambda 側がリトライすると 多重実行の危険性 が高くなります。
(補足)Lambda のリトライ設定は要件に応じて決めるべき
「Slack の場合のみ0推奨」と書きましたが、
Lambda のリトライ設定は 常に 0 が正しいわけではありません。
リトライが必要なケース
• SQS / DynamoDB Streams / Kinesis
• バッチ処理やバックグラウンドジョブ
• データ損失が許されないワークロード
Slack でリトライ 0 を推奨する理由
• Slack の再送仕様(3 秒ルール)
• at-least-once delivery
• 多重実行すると副作用が発生しやすい
リトライ設定はイベントソース・副作用の有無・要件で決めることが重要。
まとめ
Slack × AWS Lambda でイベントが複数回実行される主な原因は以下の3つ:
| 原因 | 内容 |
|---|---|
| Workflow をチャンネルに紐づける | Slackが2つイベントを送る |
| Lambda がエラーで 200 を返せない | Slackが同じイベントを再送 |
| Lambda がデフォルトで2回リトライ | Slack 再送 × Lambda リトライで複数実行 |
Slack での開発では、
• 重複排除(Idempotency)
• 即時 ack(3秒以内)
• Slack のときだけリトライ 0
• CloudWatch でエラー早期検知
を意識すると事故を防ぎやすくなりそう。