'2020/11'에 해당되는 글 1건

  1. 2020.11.22 Slack File Upload API 발송 횟수 제한(Rate Limit) 해결하기

슬랙에는 여러가지 방식으로 다른 서비스로부터의 외부 호출을 제공하는데, API Call을 통한 파일 업로드, 메세지 발송과, 심플하게 메시지 전송을 호출할 수 있는 WebHook 등을 제공하고 있다.

 

우리도 각종 알림용도로 여러 서비스에 연동해서 사용하는데, (메일 알림.. 장애 알림.. 등) 요청이 많지 않던 그 동안은 별 무리 없이 사용 하던 중 몇가지 제한사항이 발생했다.

 

Slack API 자체에서 일정 발송 횟수 이상이 넘어갈 경우 Too Many Request(429) 오류를 내뱉으며 잠시간 Block 상태가 되버려 요청들이 다 막히게 된다. 일반 메시지의 경우 여러개의 WebHook을 번갈아 가며 사용(WebHook 1개당 초당 1회 제한)하는 방식으로 어느정도 잦은 요청도 극복이 가능했지만, File upload의 경우 별도의 API로만 제공되어있어 제한을 피할 방법이 필요해졌다. 이에 429 오류 발생 시 일정 시간 이후 재시도 등의 처리로 극복하려 했으나, 일부 서비스에서 점점 더 잦은 요청으로 파일 메시지의 누락 등이 발생해서 다른 방안이 필요해졌다.

 

우선 Slack이 제공하는 File upload API의 호출 제한 사항을 보자

Slack files.upload API의 사양 (출처 : https://api.slack.com/methods/files.upload)

POST 방식으로 두가지 방식을 제공하는데, 실질적인 파일을 전송하는 방식은 multipart/form-data를 사용한다. (다른 방식의 경우 사이즈가 큰 본문이 파일처럼 전송된다.) 

 

여기서 문제가 되었던 중요 사항인 Rate limiting을 보면 Tier 2 로 되어있다.

 Tier 2와 다른 일부 Tier의 전송 제한(출처 : https://api.slack.com/docs/rate-limit)

20+ per minute로 되어있다. Note를 보면 1분당 20회, 약 3초당 1회 정도의 요청을 기본적으로 허용하고 일부 불가피하게 넘는 요청도 봐준다(?) 정도로 이해하면 된다.

 

우리의 서비스에서 해당 수치는 사실 파일 업로드를 사용하는 부서에서 전송하는 현황으로 생각하면 넉넉한 수치라고 생각했다. 다만 가끔 초당 수십회 이상 파일 업로드가 필요한 그런 경우가 생기게 되었고 이런 상황은 1분당 20회로는 하루에도 여러번 TooManyRequest(429) 와 맞닥뜨릴 수밖에 없는 상황이었다.

 

이에 처음에는 별도의 파일 서버를 두고 해당 파일의 링크만 담아, 전송 제한으로부터 훨씬 여유로운 WebHook을 이용하려 했다. 하지만 내부 논의 끝에 새로운 방식을 고안해냈다. (논의는 참가하지 않아서 선택기준은 자세히 전달 듣지 못했지만.. 아마 내부서비스이기도 하고 추가 파일서버의 필요함, 파일 전송 및 다운로드간 리소스 소모 등등의 이유였지 않을까 싶다.) 새로운 방식은 늦게 메시지를 받아보더라도 수신률은 최대로 유지하는 걸 기본 전략으로 삼아 Queue에 요청을 쌓아서 3초에 한개씩 전송하고, 오류나 실패시에도 다시 Queue에 적재하여 재시도하는 방식으로 신뢰성을 유지하는 방식을 선택하기로 했다.

 

이에, Queue 역할을 할 Redis와 알림 API 서비스 내부에 Scheduler를 구현하기로 하였다.

(Redis는 RabbitMQ 등으로 대체해도 된다.)

 

기존 방식

일반적인 API 호출 구조

기존에는 요청을 받으면 알림 서비스가 슬랙 API를 호출하는 간단한 구조였다.

 

새로운 방식

새로 Redis와 Scheduler를 적용한 방식

새로운 방식을 표현한 구조다. 기존 방식과 다르게 Queue역할을 해주는 Redis와, 주기적으로 Redis로부터 전송 데이터를 받아오는 Scheduler가 추가되어있다.

 

간단하게 플로우를 설명하면 다음과 같다.

 

1. 사용자가 파일을 알림 서비스로 업로드한다.

2. 알림 서비스는 파일을 받아 로컬 서버에 저장하고

3. 그 파일 정보와 전송정보를 Redis에 적재한다.

(여기까지가 사용자가 실질적으로 전송 요청을 마무리하는 단계)

4. 사용자 응답

5 알림 서비스의 스케쥴러가 3초마다 돌며 Redis에 파일 전송 정보가 있는지 체크하고 가져온다.

6. 전송 정보가 있을 경우 Slack File Upload API를 호출한다.

7. Slack으로부터 파일 업로드 결과를 받는다.

8. 오류가 발생하는 등, 전송에 실패하면 재시도를 위해 Redis에 다시 정보를 적재한다.

 

API의 기본 제한인 3초 1회 룰을 정확히 지키다 보니, 더이상 429 오류는 발생하지 않았다. 다만 약간의 burst한 요청을 Slack API가 허용하는 만큼, 파일 업로드 API를 집중 포화 할 일이 없는 부서가 해당 기능을 사용 할 때의 부득이한 지연을 방지하기 위해서 내부에서 429를 발생시키는 잦은 요청과, 일반적인 파일 업로드 요청을 두개의 API로 분리하기로 했다. 

 

결과적으로 일반 파일 업로드 요청과 지연 파일 업로드 요청으로 나뉘었는데, 실제 우리 서비스에 적용된 상세 플로우는 다음과 같다

2개로 나누어진 API의 상세 플로우

API : 알림 서비스의 전송 API
Scheduler : 알림 서비스의 일반 파일 업로드를 담당하는 스케쥴러
Scheduler-delayed : 알림 서비스의 지연 파일 업로드를 담당하는 스케쥴러
Sender : 알림 서비스에서 Slack 서버 API를 호출하는 주체
Redis server : Queue 역할을 담당하는 Redis

 

파일 관련 API가 두개로 나뉜 만큼 SchedulerRedisListKey도 두개로 늘어났다. (실제 서비스에는 일반 메세지를 담당하는 것 까지 3개씩 동작하고 있다)

 

지연 파일 업로드 뿐 아니라, 일반 파일 업로드 기능도 제공하는 만큼 3초당 1회 호출 룰이 약간 초과할 여지가 있기에 429가 발생할 여지 또한 남아있지만, 기존과 다르게 Queue형식을 채용함으로서 429가 발생하더라도 약 1분간은 내부에서 글로벌한 static 변수로인해 Slack File Upload API 호출을 제한시키면서, Redis로 전송 관련 정보 적재만 하는 방식을 사용하였고 이후 Slack API에서 제한이 풀릴 쯤 스케쥴러에 의해 자동으로 전송되도록 했다. 

 

결론

전송 횟수 제한이 있는 Slack API를 사용하면서 이에 대한 우회하는 방안을 생각하고 구현하여 적용해보았다.

해당 방식과 동일하게 적용할 경우 3초 1회보다 빠른 요청이 Redis에 쌓이는 만큼 로컬에 파일이 쌓이는 파일들에 의해 디스크 용량이 부족할 수 있기 때문에 디스크 용량을 넉넉하게 준비해놔야 하는 단점이 있다. 그리고 지연 요청의 경우 많이 쌓인 상태라면, 그 이후 보낸 요청의 경우 늦게 도달하기 때문에 염두할 필요가 있다.

각 자 본인의 서비스에 맞는 구현 방식으로 변경해서 이용해야 할 것이다. 만약 조금 더 서비스 규모가 크거나 파일 전송이 잦아진다면, 별도의 파일서버를 이용하고 WebHook 방식을 사용하여 3초 1회란 제한에서 벗어나, 좀 더 빠르고 안정적으로 대응할 수 있을 것이다.

 

더 나은 방안이 있다면 언제라도 댓글 주세요 ^^

 

Posted by 에스엠에스

댓글을 달아 주세요