AWS 기술 블로그
채널톡의 Amazon SQS를 이용한 효율적인 Spike 트래픽 처리 방법
이 게시물은 채널코퍼레이션의 황동욱 Software Engineer가 작성한 것입니다.
채널코퍼레이션은 올인원 AI 메신저 ‘채널톡’을 운영하는 B2B SaaS 스타트업(Start-up) 입니다. 채널톡은 채팅상담, 챗봇, AI 전화, CRM마케팅, 사내 메신저가 합쳐진 통합 솔루션으로, 기업과 고객의 커뮤니케이션를 돕고 있습니다. 최근에는 인터넷 전화 기능 ‘미트’를 출시했고, 다양한 AI기능을 추가하여 솔루션을 고도화하고 있습니다. 현재 한/미/일 포함 총 22개국에서 약 15만 개 이상의 고객사가 ‘채널톡’을 사용 중에 있으며, 채널톡으로 매월 4억건의 채팅 메시지를 처리하고 있습니다
이 게시글에서는 채널톡 팀의 완전 관리형 메시지 큐(Message Queue) 서비스인 Amazon Simple Queue Service(SQS)의 도입 과정과 실제 운영 경험을 공유합니다.
Spike 트래픽과 메시지 큐
채널톡 애플리케이션 서버는 대부분 예측 가능한 트래픽 패턴을 가지고 있으며, 오전 9시를 시작으로 주중과 주간에 높은 요청량을 받고 업무 시간이 끝나면 요청량이 점차 줄어듭니다. 이러한 패턴에 따라 오토스케일링을 적용하여 서버 자원을 효율적으로 관리하고 있습니다. 현재, 채널톡은 초당 약 4~5천 건의 요청을 처리하며, 일반적인 요청 상황에서는 오토스케일링을 통해 원활하게 대응할 수 있었습니다.
그러나 예측 불가능한 트래픽 패턴, 특히 순간적으로 대량의 요청이 들어오는 경우 오토스케일링만으로는 즉각적으로 대응하기 어려웠습니다. 채널톡에서 제공하는 “일회성 메시지” 기능이 이런 상황에 해당하는데, 이는 고객사가 특정 고객 세그먼트에 대해 대규모의 메시지를 한 번에 발송하는 기능입니다. 이로 인해 순간적으로 수십~수백만 건의 메시지 전송 요청을 처리해야 할 필요가 생깁니다.
오토스케일링의 경우 새로운 인스턴스를 준비하는 데 일정 시간이 소요되므로, 예고 없는 트래픽 증가에 적극적으로 대처하기 어렵습니다. Spike 트래픽을 예측할 수 있는 경우에는 사전에 서버 자원을 증설할 수 있지만, 채널톡을 사용하는 고객사들이 “일회성 메시지” 기능을 언제, 어느 규모로 사용할지 예측하기 어렵습니다.
Spike 트래픽에 대응하는 문제는 이미 손님으로 가득한 식당에 새로 온 손님들의 주문을 처리하는 상황으로 비유할 수 있습니다. 테이블이 꽉 차 있는데 손님들이 들어오고 있다면, 해법은 두 가지입니다.
- 사람들을 앞에서 기다리게 하고, 테이블이 비워지는 대로 차례로 들어오게 합니다.
- 테이블이 꽉 찼다고 하고 바로 사람들을 돌려보냅니다.
이것을 요청을 받는 서버 문제에 대입해 보면, 다음과 같습니다.
- 요청을 큐에 쌓아 두고, 요청을 큐에서 빼면서 차례대로 처리하기
- 허용된 용량 이상의 요청은 즉시 거절하기
1번 방법을 선택하면, 모든 요청은 언젠간 처리될 것이라는 보장을 얻을 수 있습니다. (모든 손님은 결국 식사를 할 수 있습니다.) 하지만 비동기적으로 처리되기 때문에, 손님들이 20~30분씩 기다렸다가 식사를 해야만 할 수도 있습니다.
반면에 2번 방법에서는 테이블이 꽉 찬 후에 도착한 손님들은 돌아가야 합니다. 하지만 빈 테이블이 있을 때, 도착한 손님들은 기다리지는 않고, 즉시 식사를 할 수 있습니다.
그림1. Spike 트래픽에 대응하는 전략 – 메시지 큐로 요청 버퍼링
그림2. Spike 트래픽에 대응하는 전략 – 요청량 한도 이상의 요청은 거부
위의 2가지 접근 방법에는 각각 장단점이 존재하기 때문에 문제의 성격을 고려하여 적절한 방식을 선택하는 것이 중요합니다. 채널톡의 경험을 예로 들면 다음과 같습니다:
- 내부 서비스 요청 로깅: 로깅 작업은 즉각적으로 완료될 필요가 없으며, 개발자가 나중에 로그를 확인할 수 있으면 충분합니다. 이에 따라, 요청 로그를 큐에 저장하고, 이 큐를 구독하는 워커(Worker)가 나중에 로그를 저장소로 이동시키는 방식을 도입하였습니다.
- Open API: 채널톡의 Open API는 기존에 동기적 응답을 제공하고 있었습니다. Open API를 비동기적으로 처리하려면, 요청의 결과를 polling할 수 있도록 인터페이스를 변경해야 합니다. 이렇게 되면 Open API에 breaking change가 생기기 때문에, 이 정책을 즉시 적용할 수 없습니다. 따라서 기존의 처리 방식을 유지하되, 허용 범위를 초과하는 요청은 즉시 거절하는 방식을 채택하였습니다.
이 중 Amazon SQS를 활용해 내부 서비스 요청 로깅을 수행한 사례는 더욱 상세히 설명하겠습니다.
적용 사례 – 서비스 요청 로깅
채널톡은 외부로부터의 웹훅(webhook)이나, 채널톡 Open API 등 여러 서비스 요청에 대한 로그를 기록하고 있습니다. 이 로그를 Amazon DynamoDB에 기록한다면, 갑자기 많은 로그 record를 추가하려는 경우 DynamoDB의 ProvisionedThroughput을 넘어서 문제가 발생합니다. 어떤 로그의 경우 분석의 편의성 때문에 Amazon RDS로 보내는데, 로그를 insert하는 쿼리가 거부되지는 않겠지만 Spike 트래픽이 발생할 경우 RDS의 부하가 증가할 수 있습니다. 따라서 Amazon SQS를 이용해서 서비스 요청 로그를 버퍼링하고, Amazon SQS에서 일정한 속도로 로그를 읽어서 DynamoDB나 RDS와 같은 저장소에 저장하는 아키텍처를 설계했습니다.
그림3. Amazon SQS를 활용한 서비스 요청 로그 수집 시스템 구조
이러한 구조를 선택하고 나서 운영상 아래와 같은 효과를 얻을 수 있었습니다.
- Amazon SQS가 중간에서 버퍼 역할을 하기 때문에, 각각의 어플리케이션 (API Server와 log-writer) 을 배포할 때 선택지가 훨씬 넓었습니다. Amazon SQS는 별도로 설정하지 않아도 메시지를 무한히 많이 저장할 수 있고, 최대 14일까지 보관할 수 있기 때문에 먼저 publish하는 쪽만 배포하고, 나중에 consume하는 쪽을 배포하는 것도 가능합니다. 실제로 메인 api server를 배포하면서 여러 문제가 있어 개발팀에서 긴급 대응을 해야 할 때, 상대적으로 덜 중요한 log-writer 쪽은 스케줄에 맞춰 배포되지 않아도 상관없다 보니 더 여유 있게 대응할 수 있었습니다.
- Amazon SQS에서 메시지를 읽다가 어플리케이션이 비정상적으로 종료되어도 안전하게 재시도할 수 있는데, 이 특징 때문에 log-writer는 Amazon EC2 Spot instance에 띄울 수 있었습니다. Spot instance는 일반 EC2 instance에 비해 저렴한 대신 언제든지 종료될 수 있습니다, 이 때문에 일반 어플리케이션 서버에서는 사용하기 어렵지만, 로그를 처리하는 부차적인 작업의 경우에는 사용할 수 있었습니다.
Amazon SQS를 사용하며 고민한 점
인-메모리의 큐 자료구조를 사용할 때와 비교하여, 외부의 메시지 큐 서비스를 이용하며 다음과 같은 문제에 대한 해답이 필요했습니다.
- 메시지 큐에서 제공하는 인터페이스는 어떤 형태인가?
- 메시지를 처리하던 프로세스가 예고 없이 종료되거나, 처리 중 에러가 발생한 경우를 고려하여 손실 없이 메시지를 안전하게 처리하려면 어떻게 해야 하는가?
- 요청의 전달이 보장되지 않는 네트워크를 통해 메시지 발행과 소비를 안전하게 처리하려면 어떻게 해야 하는가?
- 메시지의 처리 순서를 어떻게 보장할 수 있는가?
- 큐에서 충분히 빠른 속도로 메시지가 처리되지 않고 있는 상태를 어떻게 찾을 수 있고, 어떻게 대처할 수 있는가?
Amazon SQS에서 이러한 문제를 어떻게 해결할 수 있는지 고민한 과정에 대해 소개하겠습니다.
Amazon SQS의 인터페이스
애플리케이션은 Amazon SQS에 대해 메시지를 생산하는 publisher와 메시지를 소비하여 처리하는 consumer 역할 모두를 수행할 수 있습니다. 각각의 입장에서의 API는 다음과 같습니다.
Publisher:
- 메시지 발행하기 (SendMessage)
Consumer:
- 메시지 가져오기 (ReceiveMessage)
- 메시지 삭제하기 (DeleteMessage)
- 메시지 가져오기 취소 (ChangeMessageVisibility (visibilityTimeout=0))
API 목록을 보면 publisher API에 비해 consumer 쪽의 오퍼레이션이 다양합니다. 그 이유는 Amazon SQS에서 메시지를 처리하는 방식 때문입니다.
그림4. Amazon SQS Consumer의 인터페이스와 메시지 처리 흐름
Amazon SQS에서는 consumer가 읽은 메시지를 바로 삭제하지 않습니다. 이 메시지를 처리한 후, 처리가 완료되었다(ACK)는 것을 Amazon SQS에 다시 요청해야 Amazon SQS에서 메시지가 삭제됩니다. 반대로, 메시지 처리가 실패한다면, 이 메시지 처리를 재시도할 수 있도록 SQS에 취소 요청(NACK)합니다.
Amazon Kinesis Data Streams, Amazon Managed Streaming for Apache Kafka(Amazon MSK)와 같은 다른 메시지 큐 서비스는 “어디까지 처리했다”와 같은 checkpoint 방식으로 메시지의 처리를 관리하는데, 메시지 하나하나에 대해 개별적으로 ACK/NACK를 보낼 수 있다는 점이 Amazon SQS의 특징입니다.
Writing a safe consumer
Amazon SQS의 consumer가 receive, ack, nack 3가지 액션을 할 수 있다는 사실을 알았으니, 이제 이것을 가지고 큐에서 지속적으로 메시지를 처리할 수 있는 워커를 만들어 보겠습니다.
가장 간단하게 작성해보자면, 다음과 같은 pseudo-code가 됩니다.
function worker() {
while (true) {
const message = sqs.receive()
try {
process(message) // 요청을 실제로 처리하는 부분 (비즈니스 로직)
sqs.ack(message)
} catch (e) {
sqs.nack(message)
}
}
}
이 구현에는 어떤 문제가 있을까요?
process(message), sqs.ack(message), sqs.nack(message) 도중에 consumer process가 어떤 이유로 종료되었다면 어떤 일이 일어날까요? Amazon SQS는 receive 요청에 의해 메시지를 전달했지만, 이 메시지에 대해 ack도, nack도 받지 못한 채 계속 기다리고 있을 것입니다.
따라서, 메시지를 받을 때는 consumer의 응답에 의존하지 않고 이 메시지를 처리할 수 있는 fallback 수단이 필요합니다. Amazon SQS에서는 메시지를 받을 때 VisibilityTimeout 파라미터 값을 설정할 수 있습니다.
VisibilityTimeout 파라미터는 VisibilityTimeout 시간 동안 하나의 consumer만 Amazon SQS에서 메시지를 읽을 수 있도록 보장해줍니다. 하지만, VisibilityTimeout 이 지난 후에도 메시지를 읽은 consumer로 부터 ack 또는 nack 응답을 받지 못한 경우, Amazon SQS는 해당 메시지를 다른 consumer들이 읽을 수 있도록 허용합니다. 그래서, VisibilityTimeout을 이용해서 비정상적으로 종료된 워커에 의해서 처리되지 못한 메시지를 재처리할 수 있습니다. 즉, 어떤 워커가Amazon SQS에서 읽은 메시지를 제대로 처리하지 못하고, 비정상적으로 종료되더라도, VisibilityTimeout 시간이 지나면, 다른 워커가 동일한 메시지를 Amazon SQS에서 읽어서 재처리 할 수 있습니다.
function worker() {
while (true) {
const message = sqs.receive({ timeout }) // VisibilityTimeout(=timeout)이 지나면 다른 worker에서 메시지를 수신할 수 있다.
try {
process(message) // 여기나
sqs.ack(message) // 여기나
} catch (e) {
sqs.nack(message) // 여기에서 exit 해버려도,
}
}
}
VisibilityTimeout을 사용하는 경우, 위의 코드에서 process(message) 수행 시간이 VisibilityTimeout을 초과한다면, process(message)를 수행하는 워커 외에 다른 워커도 이 메시지를 처리 할 수 있게 됩니다. 이러한 경우, 동일한 메시지가 서로 다른 워커에 의해서 중복 처리될 수도 있습니다. 따라서 process(message) 에도 VisibilityTimeout 과 같은 timeout을 설정하는 것이 안전합니다.
function worker() {
while (true) {
const message = sqs.receive({ timeout })
try {
withTimeout(process(message), timeout)
sqs.ack(message)
} catch (e) {
if (e instanceof TimeoutException) {
/* just pass, no need to do something. */
continue
}
sqs.nack(message)
}
}
}
이렇게 Amazon SQS로부터 계속해서 메시지를 polling하면서 각 메시지를 동기적으로 처리하는 워커를 작성할 수 있습니다.
앞선 pseudo-code에서 process(message) 부분을 작성할 때도 유의할 점이 있습니다. 이 부분은 큐의 메시지를 처리하는 비즈니스 로직에 해당할 것입니다. process(message)를 모두 수행한 다음 Amazon SQS에 ACK을 보내기 전에도 consumer 프로세스가 비정상적으로 종료될 수 있고, process(message) 도중에 종료되는 것도 가능한 시나리오입니다. 다른 워커가 이 메시지의 처리를 재시도할 수 있으므로, process(message) 내부에서 일어나는 비즈니스 로직 중 일부 또는 전부가 다시 실행될 수 있습니다.
따라서, 우리는 큐의 메시지를 처리하는 비즈니스 로직을 작성할 때 하나의 메시지가 여러 번 처리될 수 있음을 염두에 두어야 합니다. 예를 들어, 데이터베이스에 item을 upsert하는 행위는 멱등성 동작 이기 때문에 여러 번 처리 되어도 관계 없습니다. 하지만, 사용자에게 푸시 메시지를 보내는 동작이었다면 푸시 메시지가 2번 전달되어 사용자 경험이 좋지 않을 것입니다. 이 경우 중복 제거 필터를 통해 사용자 경험을 개선할 수 있습니다.
그림5. 메시지 중복 처리 문제
Message Deduplication
그림 5를 보면 push service 앞에 중복 제거 필터가 있어 같은 내용의 메시지가 여러 번 나가지 않도록 방지해주는 안전장치가 있었습니다. Amazon SQS도 네트워크 문제로 인해 메시지 전송이 재시도 되는 경우 동일한 메시지가 여러 번 publish되지 않도록 방지하는 메커니즘을 가지고 있습니다.
그림6. 네트워크 상의 손실로 인해 Amazon SQS로의 메시지 발행이 실패하는 두 가지 시나리오
메시지 전송이 실패했을 때, 이것이 Amazon SQS에 전송 요청이 도달 조차 못한 것인지, 아니면 Amazon SQS에 요청이 도달하여 메시지는 publish되었으나 Amazon SQS로부터 응답을 받지 못한 것인지 판단하기 어렵습니다. (그림 6) 이러한 문제 상황은 네트워크를 통해 통신해야 하는 서비스 간에 흔히 빈번하게 발생 할 수 있습니다.
따라서 메시지 전송 실패 원인을 알 수 없기 때문에, 메시지 전송 요청이 실패하면 publisher는 메시지 전송을 재시도할 수밖에 없습니다. (AWS 클라이언트 SDK도 메시지 전송 요청이 실패하면 대부분 몇 번 재시도하는 정책을 내부적으로 구현하고 있습니다.) 즉, 메시지 재전송 과정에서 동일한 메시지가 중복 발행될 수 있는 것입니다.
그림7. MessageDeduplicationId 파라미터를 활용한 메시지 중복 요청 제거
이것을 방지하기 위해, Amazon SQS에 메시지를 publish할 때 MessageDeduplicationId
파라미터를 추가할 수 있습니다. 네트워크 오류로 인해 메시지 발행을 재시도할 때는, 같은 MessageDeduplicationId
를 지정하는 것입니다. 그러면 Amazon SQS는 수신한 publish 요청이 이전에 시도된 적 있는 것임을 인지하고 중복 요청을 무시합니다. (그림 7)
Message ordering
다른 메시지 큐와 마찬가지로, Amazon SQS도 메시지 간의 순서를 관리하는 메시지 그룹(Message group)이라는 개념이 있습니다. Amazon SQS는 같은 message group id를 가지는 메시지끼리 처리 순서를 보장합니다. 어떻게 순서를 지킬 수 있는지, 아래 그림을 통해 더 살펴 보겠습니다.
그림8. Amazon SQS (FIFO queues) 의 추상화 모델
그림8은 Amazon SQS의 추상화 모델입니다. 메시지 그룹마다 논리적인 큐가 존재한다고 생각하면 편리합니다. 하지만, 실제로 물리적인 큐가 provisioning 되는 것은 아닙니다. Amazon SQS는 message를 publish 할 때마다 새로운 메시지 그룹을 지정할 수 있고, 메시지 그룹은 무한히 많이 만들 수 있습니다.
그림9. 메시지 그룹을 지정하여 Amazon SQS에 메시지 발행
Amazon SQS에서 같은 메시지 그룹에 있는 메시지는 논리적인 FIFO 큐에 묶여 있습니다. 따라서, 한 메시지 그룹에서는 가장 앞에 있는 메시지 하나만 읽을 수 있습니다.
그림10. Amazon SQS 메시지 그룹에 따라 Consumer가 접근 가능한 메시지
가장 앞에 있는 메시지를 가져왔다고 해서 뒤에 있는 메시지를 바로 볼 수 있는 것 또한 아닙니다. 가장 앞에 있는 메시지를 가져온 후, 처리 완료 (ACK) 까지 되어야 그 뒤에 있는 메시지를 가져올 수 있게 됩니다. 맨 앞의 메시지를 처리 실패하면 (NACK), 뒤에 있는 메시지는 가져올 수 없습니다. 그래서, 어떤 이유로 인해 맨 앞의 메시지를 계속 처리할 수 없다면 그 뒤에 있는 메시지들은 모두 영원히 처리할 수 없기 때문에 문제가 발생할 수 있습니다. 그래서 Amazon SQS는 메시지 처리를 재시도할 수 있는 최대 횟수를 정해두고, 이 이상으로 실패하면 해당 메시지를 Dead Letter Queue (DLQ)로 보낼 수 있는 기능을 제공합니다.
그림11. Amazon SQS 메시지 그룹에서 메시지 ACK/NACK 여부에 따라 consumer가 접근 가능한 메시지
채널톡은 Amazon SQS 메시지 그룹의 특징을 분산 락(Distributed Lock)의 일부분을 대체하는 데 활용하고 있습니다. 같은 메시지 그룹에 속한 메시지는 동시에 하나만 처리 중일 수 있다는 조건을 만족하기 때문입니다. 추가로 동일한 메시지 그룹에 속한 메시지들의 처리 순서가 보장됩니다.
메시지 그룹에서 제공하는 보장을 활용하여, 채널톡 서비스에서 제공하는 다양한 기능을 구현하는 데 활용할 계획을 가지고 있습니다.
- 사용자 이벤트 추적: 채널톡은 채널톡 서비스 플러그인을 이용해서 웹사이트 페이지 방문, 스크롤 내리기 등 사용자 행동 이벤트를 수집하고 관리하고 있습니다. 이러한 이벤트들은 특히 이벤트 발생 순서가 중요합니다. 한 사용자로부터 발생한 이벤트에서 발행된 메시지를 메시지 그룹으로 관리하면, 여러 대의 서버 인스턴스에서 동시에 이벤트를 처리해도 lock 경합이 발생하지 않습니다. 동시에 이벤트의 발생 순서는 유지되기 때문에 Amazon SQS를 사용하기에 적합한 사례입니다.
- 채팅 메시지 스트림: 채팅도 메시지의 순서가 유지되는 것이 중요합니다. 사용자는 채널톡 앱 안에서 채팅상담을 열고 메시지를 작성할 수 있지만, 문자메시지, 이메일, 카카오톡 등 외부 메신저로부터 들어오는 문의도 채널톡 서비스 내에서의 채팅과 메시지로 변환됩니다. 다양한 웹훅으로 외부 연동이 이루어져 있는데, 동일한 채팅으로부터 발생하는 요청을 메시지 그룹으로 관리하면 메시지의 순서를 유지하면서 동시에 많은 요청을 처리할 수 있습니다.
메시지 처리 속도
Amazon SQS를 도입하기 전에는, 채널톡 서비스를 개발하는 과정 중에 메시지 처리 속도를 조절하는데 많은 어려움이 있었습니다. 애플리케이션 서버에 갑자기 많은 요청이 들어오는 경우, 저희 팀은 보통 다음과 같이 대응을 했습니다.
- 오토스케일링 정책에 의해 새로운 서버가 추가 됩니다. 그리고, 새로운 서버들에서도 신규 요청이 처리되기 때문에 기존 서버의 부하가 줄어들게 됩니다.
- 이미 들어온 요청들은 서버 내부의 작업 큐(Job Queue)에 쌓여 있습니다. 작업 큐에 담긴 요청이 처리 될 때까지 서버의 상태를 모니터링 합니다.
- 작업 큐에서 모든 요청이 처리되면, 긴급 모니터링 상황을 해제 합니다.
위와 같은 대응 프로세스는 문제가 있었습니다.
- 이미 서버 안의 작업 큐까지 들어온 요청은 이 서버 내부에서 처리될 수 밖에 없습니다. 그런데, 이 서버가 Out Of Memory(OOM) 등의 이유로 갑자기 종료되기라도 한다면, 모든 작업 요청이 유실될 수 있습니다.
- 같은 이유로, 작업 큐에 작업 요청이 많이 쌓인 상황에서 서버를 더 추가한다고 해도 처리 속도가 그만큼 늘어나지는 않습니다.
이 프로세스를 개선하기 위해서는 서버 내부의 작업 큐를 바깥으로 분리할 필요가 있었습니다. 그러면 서버가 종료되더라도 작업 요청이 유실되는 문제가 없습니다. 또한, 새로운 서버가 실행되자 마자, 서버 외부의 큐로부터 작업 요청을 가져와서 처리할 수 있기 때문에, 서버가 추가될 수록 처리 속도를 높일 수 있습니다. 여러 consumer들이 동일한 Amazon SQS를 함께 polling할 수 있기 때문에, Amazon SQS를 작업 큐로 사용하기에 적합했습니다.
Consumer worker를 더 띄우면 속도가 빨라지는지 확인해보기 위해, 다음과 같은 실험을 진행해보았습니다.
그림12. Amazon SQS Consumer 애플리케이션의 메시지 처리 속도 측정 실험 결과
(위) Consumer thread 수에 따른 전체 메시지 처리 속도
(아래) Consumer thread 수에 따른 thread별 메시지 처리 속도
먼저, Amazon SQS에서 메시지를 가져와 삭제하는 기능만을 수행하는 워커를 생성합니다. 이후, 워커 스레드(Worker Thread)의 수를 변화시켜가며 메시지 처리의 최대 속도를 측정해 보았습니다. 결과적으로, 워커의 수와 관계없이 처리 속도는 대략 300 TPS로 일정하게 유지되는 것을 확인할 수 있었습니다. Amazon SQS를 polling하는 워커는 다른 워커의 영향을 받지 않고 독립적으로 동작하기 때문에, 워커의 수가 n개일 경우 Amazon SQS에서의 메시지 읽기 속도도 이론적으로 n배 증가하는 것으로 나타났습니다.
이 실험을 통해 얻은 결과로, Amazon SQS에 메시지가 급속히 유입될 때 아래의 전략이 효과적임을 확인할 수 있었습니다.
- Spike 트래픽 발생 시, 서버 인스턴스를 추가하여 갑작스런 트래픽 증가에 신속히 대응할 수 있었습니다.
- 트래픽이 증가할 때, 추가해야 할 인스턴스 수를 예측할 수 있게 되었습니다. 예를 들어, 평상시 4대의 서버가 Amazon SQS를 polling하는 상황에서 평소의 3배에 달하는 트래픽으로 인해 Amazon SQS에 메시지가 누적되고 있다면, 8대의 추가 서버를 배치함으로써 증가한 트래픽을 처리할 수 있습니다. 개발 환경에서는 소규모 트래픽으로 필요한 워커의 수를 실험해보고, 이를 바탕으로 실제 운영 환경에서의 트래픽에 맞춰 적절한 워커 수를 결정할 수 있습니다.
마무리
지금까지 채널톡에서 겪고 있던 문제를 해결하기 위해 메시지 큐를 도입하게 된 이유와 도입 과정에서 고려했던 사항들을 살펴봤습니다. 채널톡 서비스를 통해 제공하는 기능 중 일부는 예상하지 못한 Spike 트래픽을 발생시키며, 서버에 내장된 인메모리 큐는 이러한 상황에 효과적으로 대응할 수 없었습니다. 그래서 서버 외부에 존재하는 메시지 큐로 Amazon SQS를 도입해서 이러한 Spike 트래픽 문제를 해결하고자 했습니다.
Amazon SQS를 효과적으로 활용하기 위해서는 프로세스의 비정상 종료, 중복 처리, 순서 보장, 처리 속도와 같이 외부 메시지 큐 서비스를 이용하는 데 있어 발생하는 공통적인 문제를 해결해야 합니다. 채널톡에서는 Amazon SQS에서 제공하는 기능을 적절하게 활용하여 이러한 문제에 대한 해결 방법을 고민했습니다. Amazon SQS를 도입한 채널톡 팀의 엔지니어들은 메시지 큐라는 컴포넌트를 효과적으로 활용하고 있으며, 이를 이용하여 채널톡 서비스의 다양한 비즈니스 요구사항을 해결하고 있습니다.