くりにっき

フルスタックキュアエンジニアです

Slackからのリクエストを処理するwebhookを1回だけ実行したい

最近そういうことを社内で何回か書いたのでメモ

前提

Slackには特定のイベントが発生した時に任意のwebhookにリクエストを飛ばすことができます。

f:id:sue445:20191017143638p:plain

具体的には下記のような使い方ができます

sue445.hatenablog.com

しかし普通にやると1回しかemojiを登録してないのにSlackに何回も投稿されて困ることがありました

なぜ重複投稿されるか?

公式ドキュメントを見つけきれてないので推測ですが、そういう仕様な気がしてます。

(Herokuだとdyno起動に時間がかかる時に重複リクエストになりやすい傾向があったけど、GASやLambdaでも同じことになる)

重複実行対策

Heroku + sinatra + puma構成の場合下記を行いました

1. 実行内容を一定時間キャッシュする

前述のemoy_webhookだとSlackの投稿内容を5分間redisにキャッシュしてたけど、Slackからのリクエストに含まれているevent_idを使った方がよかったかもしれないです

参考:

teppay.hatenablog.com

2. 排他ロックをする

redis-objectsにredisで排他ロックする機能があるのでそれを使ってました。(ruby以外にも同じようなライブラリがあるかは不明)

https://github.com/nateware/redis-objects#locks

3. サーバ側での並列処理を捨てる

1と2だけでは不完全で、完全同時にリクエストが飛んでくるとどうしようもなかったので多重実行されてしまいました。。。

そのためHerokuのdynoを1にしつつ、pumaのスレッド数を1にしてサーバ側で絶対に並列でリクエストを処理できない状態にしたら多重実行問題は解決しました。(普通のウェブアプリだと完全にアンチパターンなんだけどwebhookが叩かれる頻度は少ないので許容できる範囲内)