はじめに
こんにちは、commmnuneの新プロダクト「SuccessHub」のエンジニアをしている中野です。 今回は、最近取り組んだSlackAPIのレート制限対策についてまとめました。
(念の為前置きですが、サードパーティAPIのレート制限対策の方法は要件や環境次第で無数あると思うので、この記事に書いてあることが必ずしもベストプラクティスにはなりえません。あくまで一例として読んでいただけると幸いです。)
SuccessHubについて
本題に入る前にSuccessHubというプロダクトについて紹介します。 SuccessHubは、顧客管理や顧客へのアクションを一つのプラットフォームできるようにしたビジネス向けのツールです。
ユーザーは顧客に対してSuccessHub上でメールやSlackでメッセージを送信し、そのアクション履歴を管理することができます。 今回はその機能の一部を改善した時の話になります。
直面した課題
ユーザーがSuccessHub上から、顧客に対してSlackメッセージを送信できるようにするために、 予め顧客とやり取りしているチャンネル情報と顧客のユーザー情報(以下「コンタクト情報」)を取得したかったのですが そのためのAPIリクエストが一部レート制限にかかってしまっていました。
SlackのAPIはメソッドごとにレート制限のTierが公開されており 対象のAPI(users.info)はTier 4 に位置付けられています。
100+ per minute: Enjoy a large request quota for Tier 4 methods, including generous burst behavior.
とドキュメントに記載があるように基本的には1分間に100回以上のリクエストには耐えられるようなのですが 今回はユーザーが抱える顧客数(≒ Slackのチャンネル数)が多い場合にそれを上回るリクエストが発生していました。
(ちなみに、一回のリクエストで複数のSlackのユーザー情報を取得するAPI users.list もありますが Slackコネクト経由で接続されているユーザー情報が取れないため今回の要件とはマッチしませんでした。 そのため users.info を利用する方法のまま、リクエストのレートを管理する必要がありました。)
レート制限下でも、自前でリトライ処理を書くかSlackSDK利用すればリトライ処理を行うことは可能ですが、 大量のリトライを繰り返した結果、コンタクト情報の更新に時間がかかりすぎてしまうことが今回の問題でした。 また単に時間がかかるだけでなく、どのくらいまで更新が終わっているか取得する手段がないことも問題の一つとなっていました。
対策検討
整理すると、要件としては以下の二つの問題を解決することでした。
- 問題1: レート制限によりコンタクト情報が更新に時間がかかりすぎる
- 問題2: コンタクト情報が更新中なのか、更新完了なのか、あるいは失敗しているかなどのステータスがわからない
上記の問題を解決するには、一つのプロセスで全てのコンタクト情報を取得するのではなく 処理の一部をレート管理できる形で複数プロセスに分けて、更新途中のデータをどこかに格納しておく必要がありました。
実施した対策
レート管理を行いながらコンタクトを更新する
制限にかからないようにレート管理を行うためにCloud Tasksを利用しました。
もともとSuccessHubではメール送信等のレート管理に利用しており導入しやすかったことが背景にあります。 リトライ回数やレーティングの設定等も柔軟に行えるので、検証しつつキューの設定を行いました。
まず、必要な部分だけレート管理を行うため、コンタクト情報取得APIを処理だけCloud Tasks経由で行うようにします。 そのために非同期的に処理を行うことができるようにテーブル設計や全体のロジックにも手を加えました。
気をつけた点として、Cloud Tasksに非同期でコンタクト情報取得を行う形にするため、 一時的にコンタクト情報が空になることによるダウンタイムが発生してしまわないようにしました。 そのためデータをいきなり更新し始めるのではなく、一時テーブル利用してデータを貯めておき取得が全て完了したら更新をかけるような設計にしました。
コンタクト更新状態のステータスを参照できるようにする
実際には問題1の対策を行なった時点で副次的に問題2も解決したのですが 一時テーブルにデータを格納しておく設計になったことで、一時テーブルを参照すれば更新ステータスを取得することもできるようになりました。 また、複数のトランザクションが競合しステータスや更新結果に不整合がが起きないように排他制御に気を使いました。
フロー図
これらを踏まえて以下の図のような設計になりました。ちなみにSuccessHubではCloudRunを利用しています。
この変更によりレート制限を避けながらコンタクト情報の更新状態を取得することも可能になりました。 また仮にレート制限にかかってリトライ処理が発生しても、プロセスが分散しているため処理全体が遅延することを避けることができます。
次なる課題
レート制限に関する問題はひとまず解決しましたが、SlackAPIに合わせた最適なパフォーマンスとなるようにするには Cloud Tasksのキューの細かい構成についてはまだ改善の余地がありそうです。 また、今回のような複数プロセスにまたがる処理を監視するため、ロギングやエラーハンドリング周りを見直すきっかけとなりました。
終わりに
学んだこと
レート制限対策のような非機能的な課題ではベストプラクティスは要件によっても変わってくるため、要件を整理し問題を俯瞰して考える必要がありました。 個人的には難しい課題ではありましたがWebアプリケーションの設計に必要な知識を実務経験として学ぶ良い機会にもなりました。
コミューンやSuccessHubに興味を持っていただいた方へ
技術やプロダクト、コミューンという会社について興味を持っていただけた方は、ぜひ気軽に下記ページからカジュアル面談をお申込ください!