Slack Event APIで同じEventが何回も発行される問題の原因と対処


Slack Botを開発するためにEvent APIを使用してEventをSubscribeすると、同一Eventのはずがリクエストが複数回発生してしまうという現象が発生したのでその原因と対処方法を検討した。

原因

Slack Event APIは到達率を上げるためにEventを発行してから指定時間以内にEventの発行先からレスポンスが返ってこないと3回リトライ処理を繰り返す。指定時間は以下のドキュメントによると3000ミリ秒とされている。

Your app should respond to the event request with an HTTP 2xx within three seconds. If it does not, we'll consider the event delivery attempt failed. After a failure, we'll retry three times, backing off exponentially.
https://api.slack.com/events-api

FaaSなど常時起動ではない実行環境の場合、インスタンスの割り当てを行っているときに3000ミリ秒経ってしまって、Eventのリトライが発生してしまう。
Slack側はEventが到達していないと判断されて複数回リトライをするが、FaaS側はインスタンスが割当たったあとに1回目から実行してしまうので、結果として1回のEventにつき複数回の処理をしてしまう。

対処方法

とりあえず複数回処理したくない(簡易編)

リトライされたリクエストには X-Slack-Retry-Num ヘッダーがついている。

"X-Slack-Retry-Num": ["1"]

数字がリトライされた回数になる。
このヘッダーがなければ初回、あれば2回目以降のリトライ処理と判別できるので、C#なら以下のコードで判別できる。

req.cs
if (req.Headers.TryGetValue("X-Slack-Retry-Num", out StringValues val))
{
  //リトライなら何も処理せずに200を返す
  log.LogInformation("Retry request" + val);
  return (ActionResult)new OkResult();
}
// リトライでなければこれ以降の処理を続行

確実に処理をしたい場合

上記の方法だと本当のリトライ処理も捨ててしまうのでサーバーが一時的に利用不可でその後復帰しても処理は行われない。FaaSはステートレスなので、これを厳密に処理するには外部サービスをつかって前後のリクエストを比較する必要がある。リクエストに付加されてくるclient_msg_idが固有のIDなのでこれを外部テーブルなどに入れておき、すでに処理したリクエストかどうかを判断してリトライ処理を受け入れるか破棄するかを判断する必要がある。

自分は1つ目のゆるい方法でやったので実際のコードはなし。