채널코퍼레이션의 Amazon DynamoDB와 함께한 아키텍처 현대화 여정 – 1부
채널코퍼레이션은 올인원 AI 메신저 ‘채널톡’을 운영하는 B2B SaaS 스타트업입니다. 채널코퍼레이션의 비전은 “고객과 기업 사이의 모든 문제를 해결하는 것”으로, 첫 제품인 채널톡은 고객과 기업 사이의 커뮤니케이션 문제를 해결하기 위해 등장했습니다.
채널톡은 채팅상담, 챗봇, AI 전화, CRM 마케팅, 사내 메신저가 합쳐진 통합 솔루션으로, 기업과 고객의 커뮤니케이션을 돕고 있습니다. 한국, 일본 그리고 미국을 포함한 총 22개국에서 약 15만 개 이상의 고객이 채널톡을 사용 중에 있으며, 매월 7천만 건 이상의 상담이 발생하고 있습니다. 그 결과 2018년부터 매년 2~5배의 속도로 매년 비즈니스가 성장하고 있습니다.
이번 블로그에서는 모든 비즈니스 규모에서 사용할 수 있는 Amazon DynamoDB를 이용한 채널코퍼레이션의 비즈니스 현대화 여정의 동기, DynamoDB를 선택한 이유, 그리고 PostgreSQL로 부터 마이그레이션 시 고민했던 포인트들을 공유합니다.
채널톡은 초기에 팀 안에서 커뮤니케이션을 도와주는 아래 첫번째 사진의 ‘팀챗’과 고객과 기업 사이의 커뮤니케이션을 도와주는 아래 두번째 사진의 ‘유저챗’으로 구분되어 있습니다.
비즈니스가 빠르게 성장하며 자연스럽게 제품의 핵심인 채팅 기능에서 처리해야 하는 RPS(Requests per second, 초당 처리량)도 빠르게 늘어나기 시작했습니다. 기존엔 PostgreSQL을 기반으로 쓰기 트래픽이 많은 특징을 갖는 채팅 기능을 처리하고 있었기 때문에 자연스럽게 인스턴스 스케일업으로 대응해왔습니다.
하지만 캠페인 메시지와 일회성 메시지를 전송하는 마케팅 기능의 등장과 함께 기존의 스케일업 전략은 수정이 불가피했습니다. 캠페인 메시지는 규칙 기반으로 고객이 특정 행동을 할 때 자동으로 메시지를 보내는 기능이며, 예를 들면 회원가입을 했을 때 환영 인사를 보내거나, 가격 페이지를 보고 있을 때 할인 쿠폰을 보내는 것입니다. 일회성 메시지는 타겟 고객을 대상으로 원하는 시점에 메시지를 1회 전송하는 기능으로, 예를 들면 현재 온라인 상태인 고객들을 대상으로 한정 세일 쿠폰을 발급하는 등으로 활용할 수 있습니다.
채널톡의 기존 워크로드는 주로 채팅 기반의 1:1 고객 상담이었기 때문에 피크 타임 트래픽 예측이 어렵지 않았지만, 마케팅 기능은 고객이 타임 세일 이벤트를 하거나 대량의 타겟에 일회성 메시지를 발송하는 경우 순간적으로 여러 테이블에 스파이크 트래픽이 발생할 것으로 예상되었습니다. 기존 전략으로 대응한다면, 첫째로 스파이크 트래픽의 피크(peak) 트래픽에 맞춰 RDS 인스턴스 사이즈를 선택해야 하기 때문에 오버 프로비저닝으로 인한 ‘비용 비효율’이 예측되었습니다. 둘째는 특정 테이블들의 부하가 RDS 인스턴스 전체에 영향을 끼치는 ‘테이블 간 부하 전파 문제’로 전체 서비스가 불안해질 수 있다고 생각되었습니다. 셋째는 PostgreSQL에서 사용 중인 오퍼레이션의 NoSQL 대체 가능 여부였고, 대표적으로는 채팅 워크로드의 특성 상 여러 채팅 방의 “안 읽은 메시지” 개수 합을 구하는 문제가 있었습니다. 마지막은 PostgreSQL에서 DynamoDB로의 데이터 마이그레이션 전략이었습니다.
채널코퍼레이션은 위의 네가지 문제 해결과 동시에 다음 세가지 주요 이유로 DynamoDB를 선택했습니다. 첫째로 데이터베이스의 변경 사항을 쉽게 다른 서비스로 이벤트 소싱 할 수 있어야 하며, 둘째로 기존 AWS 서비스들과 풍부한 연동, 마지막으로 ACID 트랜잭션이 필요했습니다.
마케팅 기능과 함께 출현한 스파이크 트래픽의 첫 번째와 두 번째 문제는 DynamoDB를 선택한 것만으로 간단히 해결할 수 있었습니다.
첫 번째, 비용 비효율 문제는 DynamoDB는 처리량이 동일한 30분 이내에 이전 피크의 2배를 초과하지 않는 한에서 트래픽을 즉시 수용하는 온디맨드 모드를 사용할 수 있고, 필요 시에는 프리 워밍도 가능했기 때문에 유연한 대응이 가능했습니다.
또한, 프로비저닝 모드에서는 오토 스케일링을 지원하여 꾸준한 워크로드에 대한 대응 및 과도한 사용 방지를 위한 상한도 설정할 수 있었습니다. 다음 그림은 유저챗과 메시지 DynamoDB 테이블에서 대량의 일회성 메시지가 발생했을 때의 쓰기 트래픽 패턴을 나타냅니다.
두 번째, DynamoDB 테이블은 한 테이블의 워크로드가 다른 테이블에 영향을 주지 않기 때문에 쉽게 테이블 간 부하 전파 문제를 해결할 수 있고, 전체 서비스를 안정적으로 운영하면서 유연한 트래픽을 처리하는 구조로 개선할 수 있다고 판단했습니다.
세 번째, 기존 PostgreSQL 오퍼레이션의 NoSQL 대체 가능 여부를 확인하기 위해 채널톡 채팅의 주요 요소를 간략히 살펴보겠습니다.
위 그림에서 볼 수 있는 채널톡 채팅의 주요 구성요소를 추상화 해보면 다음과 같습니다.
채팅 방(Chat) : 채팅 방 안에는 메시지가 존재합니다.
참여 정보(ChatSession): 특정 채팅 방에서 읽지 않은 메시지 숫자와 마지막 읽은 시간 등의 정보가 기록됩니다.
참여 정보에서 읽지 않은 메시지의 수를 ‘ChatBadge’라고 합니다. 그리고 특정 사용자는 여러 채팅 방에 동시에 속할 수 있기 때문에 여러 채팅 방에 있는 안 읽은 메시지 수의 총 합은 ‘ManagerBadge’라고 합니다.
이 구성요소들은 다음과 같이 별도로 동작하는 두 개의 요구조건이 존재하며, 원자성(atomicity)과 Count 연산이 필요했습니다.
각 채팅 방에서 메시지가 발생할 때 작성자 외의 사용자들에게 원자적(atomic)으로 ChatBadge가 증가합니다.
각 채팅 방에서 발생한 모든 ChatBadge의 합은 ManagerBadge가 됩니다.
기존에는 PostgreSQL을 사용했기 때문에 ChatBadge는 원자적 연산을 사용했고, ManagerBadge는 ChatBadge들의 합을 구하면 됐었습니다. 하지만 DynamoDB에서 기존과 같은 방식으로 구현한다면 채팅 방이 많은 사용자는 ManagerBadge를 계산하는 속도가 점점 느려질 것으로 예측되었습니다. 이 문제를 해결하기 위해 DynamoDB 트랜잭션을 사용하기로 했으며, 아래의 첫번째 그림은 before/두번째 그림은 after의 테이블 디자인을 나타냅니다.
특정 채팅 방에서 메시지가 작성되면, 작성자를 제외한 각 참여자에겐 다음과 같은 오퍼레이션이 수행됩니다.
참여자의 ChatBadge를 증가시키는 UpdateItem을 생성합니다.
참여자의 ManagerBadge를 증가시키는 UpdateItem을 생성합니다.
두 개의 UpdateItem 오퍼레이션을 TransactWriteItems API를 이용해 처리합니다.
이를 통해, ChatBadge들의 합은 ManagerBadge가 됨을 보장할 수 있으며, 각각을 O(1)의 시간 복잡도로 조회할 수 있게 됩니다. 물론 이 방법은 메시지가 동시다발적으로 지속적으로 발생하는 상황에서 트랜잭션 충돌을 일으킬 수 있지만, 채널톡의 워크로드는 대부분의 경우 고객 상담이 1:1 대화로 진행되기 때문에 동시다발적 메시지가 발생하는 경우는 많지 않습니다. 또한, 동시에 다량의 메시지가 발생할 땐 다음과 같이 처리 할 수 있습니다.
아래 그림에서 보시는 바와 같이 충돌이 발생했을 때는 exponential backoff retry 전략을 활용하며, DynamoDB 트랜잭션은 ClientRequestToken 파라메터를 사용하는 경우 멱등성을 지원하기 때문에 연결 시간 초과 등의 문제로 동일 요청이 여러 번 제출된 경우에도 10분 이내라면 정확하게 ChatBadge와 ManagerBadge를 관리할 수 있어 세 번째 문제를 해결할 수 있었습니다.
네 번째, 마이그레이션은 새벽 시간을 활용해 서버를 중지하고 진행했습니다. DynamoDB Parallel Scan의 세그먼트 아이디어를 차용해 PostgreSQL의 id를 해싱(hashing)한 후 여러 개 쓰레드에서 세그먼트 별 병렬로 BatchWriteItem API를 사용해 마이그레이션 했었습니다. 현재는 다운타임 없는 서비스를 제공하기 위해 DynamoDB Streams와 AWS Lambda를 활용한 마이그레이션 방법을 사용하고 있습니다.
무한에 가까운 수평 확장성, 스키마의 유연함, 이벤트 소싱을 쉽게 만들어주는 DynamoDB Streams 그리고 여러 개 테이블에 대한 ACID 트랜잭션을 지원하는 DynamoDB 도입 후 채널톡은 채팅 비즈니스 영역에서 대규모 인프라 변화 없이 지속적으로 신규 기능을 추가하며 비즈니스를 운영하고 있습니다. 채널코퍼레이션이 DynamoDB를 통해 얻은 주요 장점을 정리해보면 다음과 같습니다.
인프라나 코드 구조 개선없이 지속적으로 신규 기능을 추가할 수 있었습니다. 예를 들어, 규칙 기반으로 봇이 메시지를 생성하는 기능과 같은 메시지 수가 급격히 증가할만한 기능이 출시되어도, 인프라 문제없이 서비스를 운영할 수 있게 되었습니다.
별도의 인스턴스 유지비용 없이 사용한 만큼 지불하는 DynamoDB의 비용 모델은 기존의 고정 비용이었던 데이터베이스 비용을 가변 비용으로 쉽게 만들 수 있습니다. 트래픽이 감소해도 사용한 비용 역시 줄어드는 구조가 되어 비즈니스 상황이 좋지 않을 때도 비용 절감을 위해 별도 아키텍처 수정이 필요 없어졌고, 반대로 트래픽이 증가하는 경우도 동일합니다.
지속적으로 비즈니스 영역이 확장되며, 채팅 외에도 모든 채널톡 고객의 사용량을 측정하는 부분과 같이 수평 확장이 필요한 영역에서도 DynamoDB를 활용해 다양한 문제를 해결하고 있습니다.