Slack Events APIを利用しているのですが、そのリクエスト先をGAS(Google Apps Script)に設定しています。GASで処理させることで、自前でサーバを立てることなく、SlackのWebhookを簡単に実現できます。
なのですが、急に問題が発生しました。
サービスで 1 日に使用しているコンピュータ時間が長すぎます
GASをたくさん利用している方なら見たことがあると思われる文字列ですね。GASで処理させ過ぎると制限に引っかかってしまいます。いつからか、急にこの問題が多発するようになってしまいました。
今回の記事では、その問題が起き始めた原因とその解決策を紹介したいと思います。
長時間使ってしまっている問題について
では早速、実際にあった問題について説明していきたいと思います。
同じ内容のリクエストが大量に来る問題
まずはGASのログを確認していました。すると、Slackへ1回投稿するだけで何度もリクエストが飛んできてました。
あれ、どこにそんな仕様があるんだ、と。1回のリアクションとかで複数回もリクエストが飛んできたら、どれを処理すればいいのか判定しなきゃいけません。これは前に処理したリクエストだから何もしないーみたいな。いや、そもそもそんなSlackの仕様なんてどこにも書いてません。では何が問題なのでしょうか?
ドキュメントを何度も確認していると見つけました。
雑訳:サーバからのレスポンスが3秒以上かかると処理できなかったとして失敗扱いにするよ。
GASの実行数から処理時間を確認してみると、たしかに3秒以上かかっている。
雑訳:失敗したリクエストは最大3回リトライするよ。タイムアウトしてリトライした場合はhttp_timeout
をつけるよ。
なるほど!こういうことか。
「3秒以上かかる」 → 「リトライで3回くらい同じリクエストが飛んでくる」 → 「時間がかかる処理が通常の3〜4倍実行される」 → 「1日の実行時間制限に引っかかる」 → \(^o^)/
ということで、3秒以内に処理を抑えないといけません。
3秒以内に処理を終わらせる
3秒以上かかっているからタイムアウトしてSlackがリトライを何度もしてきてDOS攻撃みたいになって制限にかかっているということがわかりました。
じゃあ、3秒以内に処理をさせればいいんだ!
ということで、細かい処理の見直しをはじめていきました。ループ処理でスプレッドシートに値をsetするのではなく、ループ処理で一度変数に入れておいて、ループ処理が終わったら1回でスプレッドシートに値をsetする、みたいになるべくAPIを叩く回数を減らしていきました。しかしあまり効果はありませんでした。。
処理のキューイング
ということで、まず思いついたのは処理のキューイングです。Slackからのリクエストを受け取り、外部APIを叩いて処理させていました。外部APIは大きなボトルネックになります。外部サービスのレスポンス時間でこちらの処理時間も決まってしまいます。外部サービスの処理が3秒以上かかる場合はそこで終わりです。最近流行りのものを例にあげると、ChatGPTのようなAI系の処理をさせたらなかなか計算結果が返ってこないですよね。そういうものをイメージしていただければわかりやすいかなと思います。
そこで、Slackからのリクエストを一度スプレッドシートにキューとしてpushしておきます。スプレッドシートAPIのappend()で必要な情報をpushするイメージです。
そして、時間主導型のトリガーを設定して、キューに積まれた処理を1つずつ処理していきます。
このように、SlackからのWebhookで処理するものはスプレッドシートに書き込むだけ。それだけでSlackからのリクエストは終わりです。その先の重い処理は別のトリガーで実行されるのでちょっと重くてもSlackには影響ありません。
結構改善しました!が、まだまだ3秒以内の壁には届かなかったのです。。
ライブラリの削除
そこで調べてみました。GASで処理時間がかかってしまう原因。
こちらのサイトに助けられました。
外部ライブラリを読み込んでいると時間がかかるようです。ちなみにこのときはCheerioというライブラリを使用していました。
1つ上で紹介したキューイングで処理の分離ができています。なので、Slackからのリクエストを受け取る処理とCheerioというライブラリを使用する処理は分離されています。
ということは、プロジェクトファイルも分離できます。
ということで、このようにwebhookと処理させたいところをファイル自体の分離させました。こうすることで、Webhookのプロジェクトファイルからはライブラリの利用をなくすことができました。
すると、なんということでしょう。
ライブラリを削除しただけなのに、3秒以上かかっていた処理が1秒以内に収まるようになりました!
まとめ
このように、うまく処理を分け、定期実行のトリガーを利用しつつ、必要最低限のライブラリにすることで、Slackからのリクエストのタイムアウト扱い問題を解消することができました。GASでこういう処理の分割は重要ですね。