핵심은 사용자별 기능 사용량을 측정하는 것
Dugi 🎈
2023년 12월 15일, 채널톡에 새로운 과금 모델이 적용되었습니다. 이번 개편의 가장 중심이 되는 아이디어는 "사용량 기반 과금"으로, 채널톡에서 제공하는 서비스를 얼마나 많이 이용했는가에 따라 비용이 결정됩니다. 개편 이전에는 고정된 구독료를 지불하면 부가 서비스를 사용할 수 있었습니다. “마케팅” 부가서비스를 구독하면 마케팅 기능을 사용할 수 있고, 마케팅 메시지를 한 달에 1건을 보내든 1억 건을 보내든 동일한 구독료를 지불했습니다. 새로운 과금 모델에서는 마케팅 메시지를 보낸 양이 많을수록 사용료가 올라갑니다.
2023.12.15 이후 적용된 과금 모델. (https://channel.io/ko/pricing)
이 글에서는 이번 과금 모델 개편의 비즈니스적 효과에 관해서는 잠시 접어두고, 기술적 관점에서 이 문제에 어떻게 접근했는지에 대해 풀어보고자 합니다. 이번 개편에서 구현되어야 하는 가장 중요한 기술 중 하나는 사용자별로 어떤 기능을 언제 얼마나 사용했는지 측정하는 것입니다. 이렇게 월별로 집계한 사용량을 근거로 하여 고객별 청구 금액을 결정하기 때문입니다. 사용량을 측정하고 집계하는 잘 알려진 문제이지만, 돈과 결제에 관련된 문제이기 때문에 특히 이 기능을 정확하게 구현해야 합니다.
사용자가 어떤 기능을 사용할 때마다 측정이 이루어져야 합니다.
한 고객사가 마케팅 메시지를 25만 건 보냈다면, 25만 건을 보냈다고 기록되어야 합니다. 한 달 동안 사용한 양을 집계할 수 있어야 합니다.
사용량 한도 기능이 필요합니다.
사용량 한도는 어떤 기능이 너무 많이 사용되지 않도록 제한할 수 있는 기능입니다. “마케팅” 기능에 대한 한도를 월 100만 건으로 설정하면, 마케팅 메시지는 한 달에 100만 건까지만 보낼 수 있으며 더 이상의 사용은 거부되어야 합니다.
이 기능은 AWS에서 제공하는 Budget Threshold 처럼 사용자 스스로가 너무 많은 금액이 청구되는 것을 막기 위해 한도를 설정하는 데 이용할 수 있습니다. 또는 저희 팀에서 시스템적으로 특정 기능의 사용을 제한하기 위해 쓰기도 합니다.
사용자는 현재 사용량을 조회할 수 있어야 합니다.
기능 사용량이 특정 값에 도달하는 것을 트리거로 하는 비즈니스 로직을 붙일 수 있어야 합니다.
현재 사용량이 한도에 거의 도달하면 알림 이메일이 전송되는 기능을 예로 들 수 있습니다. 이러한 로직은 몇십 분이나 수 시간 뒤에 실행되어도, 또는 로직 실행이 가끔 누락되어도 그렇게 큰 문제는 아니라서 조금 더 유연합니다. 하지만 이것 또한 실시간으로 이루어질 수 있다면 좋습니다.
1, 2, 3, 4번에서 다룬 사용량 측정, 한도 적용, 현재 사용량 조회는 최대한 정확하게 이루어져야 합니다.
두 가지 측면의 정확도를 들 수 있습니다. 실시간으로 볼 수 있는 수치의 정확도와, 최종적으로 집계된 수치의 정확도입니다. (실시간으로는 부정확한 수치를 보게 되나 결과적으로 보정이 이루어져 정확한 결과가 집계되도록 하는 구조가 여럿 알려져 있습니다.)
청구 금액을 결정하는 수치에 대해 정확함을 희생해야 한다면, 채널톡에서 손해를 보는 방향으로 이루어져야 합니다. 즉, 사용량이 실제보다 적게 측정되어 결과적으로 고객에게 더 적은 비용이 청구되는 것은 허용할 수 있으나, 그 반대의 경우는 허용하지 않습니다.
실시간으로 고객이 조회하는 사용량이나, 한도 적용의 경우 청구 금액보다 더 큰 유연함을 적용할 수 있습니다. 하지만 역시 최대한 정확한 결과를 얻을 수 있다면 좋습니다.
비용 청구는 고객사가 지정한 일자에 이루어지므로, 사용량 집계 또한 고객사가 결정한 윈도우에서 이루어져야 합니다.
예를 들면, 한 고객사는 1월 8일 ~ 2월 8일, 다른 고객사는 1월 20일 ~ 2월 20일 동안 집계가 이루어져야 한다는 것입니다. 따라서 집계 윈도우는 시스템에서 결정하거나 고정할 수 없고, 사용자가 임의로 변경하도록 허용해야 합니다.
참 재미있는 시스템 디자인 문제입니다! 😂
많은 시스템 디자인 문제에서 정확성과 효율의 트레이드오프를 다룹니다. 하지만 이번 업데이트에서 필요한 사용량 측정의 경우, 둘 다 잡아야 하는 경우입니다. 트레이드오프가 불가피하더라도, 비즈니스적 요구사항에 따라 결정 방향이 잡혀 있으며 기술적인 선호가 가장 높은 우선순위가 되기 어렵습니다.
-
추가적으로, 제가 원하는 개발자 관점의 요구조건은 다음과 같았습니다.
채널톡에서 사용량이 측정되는 부가 서비스별로 담당하는 팀과 엔지니어가 서로 다릅니다.
이 엔지니어들이 모두 사용량 측정과 집계 도메인에 대해 잘 알고 있는 상태로 각자의 비즈니스 로직을 관리한다고 기대하기 어렵습니다. 따라서 “기능 사용”이라는 사건을 발생시키는 인터페이스는 잘 알지 않아도 직관적으로 사용할 수 있는 형태로 제공되어야 합니다.
추후 저희가 새로운 기능을 출시하여 다른 부가 서비스에 대해 측정과 집계가 이루어져야 할 때, 자연스럽게 확장하여 적용할 수 있는 형태여야 합니다.
이러한 기획이 이루어지던 당시, 데이터를 보니 저희가 처리해야 하는 사용량은 월간 1억 건을 약간 밑도는 수치였습니다. 즉, 평균 시간당 10만 건, 약 40 TPS에 해당합니다. 단, 실제로는 균일한 트래픽이 들어오지 않습니다. 현재 데이터를 보면 주간에는 평균보다 높은 50 TPS 정도를 처리하고 있습니다. 또, 일회성 메시지 (특정 고객 세그먼트에 한 번에 메시지를 보내는 기능) 와 같은 서비스의 경우 최대 수십만 ~ 수백만 건의 사용량이 발생합니다. 따라서 짧은 시간 동안 수백~수천 TPS의 요청이 특정 고객사로부터 발생할 수 있습니다.
이번 과금 모델 개편은 추후 저희가 제공하는 부가기능이 늘어남에 따라 더 확장되어야 합니다. 채널톡을 이용하는 고객사 수와 크기도 계속해서 늘어나고 있으므로, 현재 사용량의 10배까지는 처리할 수 있도록 설계해야 향후 1~2년 동안 구조를 변경하지 않아도 버틸 수 있다고 생각했습니다.
문제의 크기에 대해서는 잠시 잊고, 먼저 가장 간단한 구현 방법을 구성해봅시다.
저희 팀에서 primary database로 사용하고 있는 RDS에 사용량을 기록합시다. 먼저 사용량 테이블을 만들어둡니다. 어떤 사용자가 기능을 사용할 때마다, 그 사용자에 대한 row에 write를 수행합니다. 부가서비스를 사용하는 행위와 사용량 테이블에 대한 쓰기를 트랜잭션으로 묶으면 한 쪽에 대한 쓰기가 실패하는 경우에도 정확한 결과를 얻을 수 있습니다.
아래 의사코드를 보면 useFeature
procedure에서 사용량 한도 체크 → 사용량 increment하여 기록 → 기능 사용에 대한 비즈니스 로직 실행 순서로 이루어지는 트랜잭션을 표현하고 있습니다.
useFeature
가 여러 스레드에서 동시에 실행되거나, 여러 개의 인스턴스에서 실행될 수 있다면 약간의 조치가 필요합니다. 바로 위의 의사코드는 다음과 같은 문제가 있기 때문입니다.
n
을 읽은 후 n+1
을 쓰는 것은 race condition을 설명할 때 자주 등장하는 교과서 예시입니다. 동시에 실행되는 스레드가 있다면 실제로 기록되어야 할 수치와 다른 값이 기록될 수 있습니다.
먼저 사용량을 읽어온 후 그 수치에 기반한 판단 (currentUsage ≥ 한도 라면, 한도 초과로 기능 사용을 거부
) 을 합니다. 동시에 실행되던 스레드가 있었다면 그 스레드의 작업으로 인해 실제로는 한도를 초과했지만 기능 사용은 그대로 통과될 수 있습니다. 따라서 한도 기능이 정확하게 동작하지 않습니다.
첫 번째로 짚은 race condition 문제의 해결 방법입니다.
Atomic write 오퍼레이션을 사용하는 방법이 있습니다. RDS라면 writeCurrentUsage
내부에서 실행될 UPDATE
문에서 usage = usage + 1
과 같은 syntax를 이용하는 것이 이 방법에 해당합니다.
Mutex를 사용합니다. RDS라면 fetchCurrentUsage
에서 실행될 read를 SELECT FOR UPDATE
문을 이용하여 그 row에 대한 exclusive lock을 획득하면 됩니다. 그러면 한 번에 하나의 스레드만 접근이 일어나므로 data race가 발생하지 않습니다.
버전 필드를 활용하여 optimistic update를 구현하는 방법도 있습니다. 먼저 사용량 테이블에 버전 column을 추가합니다. 그리고 writeCurrentUsage
가 이루어질 때 기존에fetchCurrentUsage
에서 읽었던 버전과 비교하여 같았을 때만 UPDATE
를 수행하고, 버전을 증가시킵니다. 그러면 동시에 수행되는 작업 중 하나만 성공하기 때문에 다른 작업들은 재시도하도록 유도할 수 있습니다.
(저희 팀에서는 락 기능을 제공하지 않는 DynamoDB와 같은 데이터베이스에 대한 접근에 optimistic update를 활용하고 있습니다.)
지금까지 저희 팀에서 활용하던 패턴에도 어떤 사건이 발생할 때마다 통계값을 집계하여 사용자에게 제공하는 선례가 여럿 있었습니다. 앞서 소개한 나이브한 전략을 사용한 적도 있고 그렇지 않은 경우도 있는데, 기존 사례를 보며 전략을 적용할 수 있을지 알아보겠습니다.
1) (과금 모델 개편 이전) 문자메시지 요금 부과
앞서 제시한 간단한 구현 전략의 의사코드의 형태와 같았습니다. 정확히는 이 중 mutex를 활용하여 락을 건 트랜잭션을 이용하여 data race를 해결하도록 했습니다.
저희 팀은 이 방법에 근본적인 한계가 있다는 것을 알고 있습니다. 매번 RDS에 1번의 읽기와 1번의 쓰기 쿼리를 실행해야 하기 때문입니다. 특히 락을 걸고 쓰기를 수행하는 경우, 어떤 row에 대해 모든 스레드에서 단 1개의 스레드만 앞으로 진행할 수 있습니다. 따라서 한 사용자에 대한 throughput 상한이 있다는 점이 분명하고, 앞서 언급한 “일회성 메시지” 처럼 특정 고객사로부터 많은 이벤트가 발생하는 경우 적절한 구조가 아닙니다.
실제로, 과금 모델 개편 이전 문자메시지 전송 파이프라인의 병목 지점이 바로 요금 부과 단계입니다. 문자메시지를 실제로 전송하는 것은 내부적인 요청 분산을 통해 처리량을 높일 수 있습니다. 하지만 요금 부과 처리 속도는 수평 확장을 통해 높일 수 없기 때문에 throughput에 한계가 있었습니다.
(이 한계점이 사용자 경험에 영향을 주는 원인이 될 수 있습니다. 예를 들어, 고객사에서 프로모션 이벤트가 있어 3시에 이벤트 안내 메시지가 한번에 나가야 하는데, 처리가 지연되어 3시 30분에서야 문자를 받았다면 광고 메시지는 받았는데 프로모션은 이미 끝나버렸으니 문자를 받은 사람과 고객사의 경험 모두 좋지 않을 것입니다.)
하지만 요금 부과의 경우 가장 높은 정확성이 요구되기 때문에 불가피하게 데이터베이스 트랜잭션과 락을 통해 처리하고 있었습니다.
2) 일회성메시지, 캠페인, 서포트봇, 자동화규칙 등 부가기능의 집계값
1) 방법은 데이터베이스에 대한 읽기 및 쓰기 요청이 매 사건마다 이루어지기 때문에 처리 속도에 한계가 있습니다. 그러면 이것을 뛰어넘기 위해서는 요청 수를 줄이면 됩니다.
어떤 기능이 5번 사용되었을 때, 수치를 1 더하는 쓰기를 5번 수행하는 것이 아니라, 그것을 처리하는 프로세스가 중간 결과를 적절히 기억하고 있다가 5를 한 번에 더하면 됩니다. 그렇다면 데이터베이스에 대한 요청 횟수는 프로세스가 개별 요청을 압축하는 만큼 1/n 으로 줄어듭니다.
이 접근의 한계점은 정확도입니다 (그것도 상당한 희생이 필요합니다). 프로세스는 압축된 요청을 인메모리에 기억하고 있습니다. 따라서 프로세스가 종료되기 전 기억하고 있는 것을 모두 flush해줄 수 있도록 프로세스의 graceful termination을 충실하게 구현해주어야 합니다. 프로세스가 갑자기 종료된다면 인메모리에 쌓였던 요청은 손실되며 복원할 길이 없습니다. 이렇게 손실되는 정보를 줄이려면 flush 주기를 자주 가져야 하지만, 그만큼 데이터베이스에 대한 요청을 줄이는 효과는 잃어버립니다. 즉, 정확도와 데이터베이스 로드(속도)에 대한 트레이드오프가 됩니다.
저희 팀에서 이 패턴에 따라 구현한 것 중, 특정 기능의 경우 3분의 flush 주기를 가지고 있습니다. 이전에 이 전략으로 측정한 집계 데이터와 더 믿을 수 있는 다른 데이터를 비교해 정확성을 검증해본 적이 있었습니다. 프로세스의 비정상 종료에 의한 손실로 인해 수치가 서로 10% 이상 맞지 않는 경우가 상당했습니다. 특히 주로 백그라운드 작업을 진행하는 worker 프로세스는 OOM 등이 원인이 되어 불귀의 객이 되는 경우가 더 잦습니다. 예고없는 프로세스의 종료 사고 하나하나가 정확도의 손실이 된다면 모니터링하는 팀원의 부담은 더 커질 것입니다.
채널톡의 마케팅, 서포트봇, 오퍼레이션 등 부가 서비스의 통계 집계값은 이벤트 발생량이 많아 데이터베이스 부담을 줄이기 위해 이 형태로 구현된 경우가 많았습니다. 이 통계값은 사용자에게 참고 및 모니터링 용도로 제공되므로 아주 높은 정확도로 구현되지 않아도 괜찮았기 때문에 이러한 타협이 이루어졌습니다. (물론 10% 이상의 오차는 조금 심하긴 합니다만..) 하지만 과금 모델 개편에서 이루어지는 사용량 집계의 경우 측정된 수치에 기초하여 비용 산정이 이루어지므로, 우리가 이미 관찰한 10% 이상의 오차는 수용할 수 없어 이 전략을 선택하지 않았습니다.
3) 상담 통계
상담 통계 기능은 채널톡에서 스스로 개발한 특별한 데이터베이스를 통해 제공되고 있습니다.
이 데이터베이스는 secondary storage로, primary storage에 이루어지는 변경사항을 구독하여 데이터를 쌓습니다. 그리고 저장하고 있는 데이터에 대한 다양한 집계 쿼리에 꽤 빠르게 응답할 수 있습니다. 상담 통계 뿐만 아니라, 이미 저희 팀의 여러 코어 기능이 이 데이터베이스를 기반으로 하고 있기 때문에 신뢰가 있는 기술이기도 합니다.
그러나, 이 데이터베이스에서 제공하는 기능과 사용량 집계에서 요구하는 것은 서로 지향점이 맞지 않습니다. 먼저, 이 데이터베이스는 복잡한 검색이나 집계 쿼리를 꽤 빠르게 수행할 수 있으나, 개별 요청의 처리 자체는 아주 빠르지 않습니다. 사용량 한도를 적용하기 위해서 현재 사용량을 자주 읽어오는 것도 필요하므로, 이 사용례에서는 적절하지 않다고 생각했습니다.
또한, 이 데이터베이스는 저장할 수 있는 데이터의 크기에 제한이 있습니다. 기획 단계에서 사용자들의 월별 기능 사용량을 추정해본 결과 이 데이터베이스에서 지원하는 크기를 넘어설 것으로 보였습니다.
-
저희 팀에서 좋아하는 전략은 아니지만 데이터베이스로 RDS를 사용하는 것보다 Redis와 같은 인메모리 저장소를 활용하면 더 높은 속도를 누릴 수 있습니다. (Redis의 경우 충분히 강력한 인스턴스를 사용하면 수만 TPS 까지 도달할 수 있다고 알려져 있고, 이것은 웬만하면 차고 넘치는 수치입니다.)
하지만 기능 사용량을 집계한 데이터는 단순히 기능이 사용할 때마다 기록하는 것 뿐만 아니라 다른 로직에서 읽거나 쓰기도 하기 때문에 RDS에 데이터를 쌓는 것이 훨씬 용이합니다. 예를 들면 데이터 분석팀에서 이미 메인 데이터베이스(RDS)를 바라보는 파이프라인을 구성하고 있기 때문에 별도의 작업이 필요 없게 됩니다. 그리고 팀에서 Redis를 영속성 있는 저장소로 이용하지 않고, 휘발성 있는 데이터를 보관하는 용도로만 사용하고 있습니다. 물론 옵션을 잘 설정하면 커맨드 단위로 데이터의 복원이 가능합니다. 하지만 팀에서 Redis 복원에 대한 검증이나 사례가 충분히 쌓이지 않은 상태입니다.
이러한 이유로 기존에 사용하던 전략은 이번 과금 구조 개편에 필요한 사용량 집계 기능에 어울리지 않는다고 판단하여, 새로운 패턴을 제안하고 실험을 통해 검증하여 사용하기로 했습니다.
다행히 이벤트를 집계하는 파이프라인을 구성하는 문제는 이미 잘 알려진 문제입니다. 과금 모델 개편 개발 진행중에는 잘 몰랐지만, 글을 준비하면서 이 문제가 풀려온 역사에 대해 알아보았습니다. 2000년대 후반부터 2010년대 중반까지 이 분야에서 연구가 활발했고 현재는 GCP의 Dataflow 등의 제품, Apache Spark, Apache Kafka 와 같은 오픈소스 프레임워크가 잘 알려져 있습니다.
스트리밍 데이터를 처리하는 파이프라인으로 사용량 측정 및 집계 문제에 접근하는 것이 이 문제의 요구사항을 만족하기에 적절하다고 생각한 이유는 다음과 같습니다.
배치 처리 시스템과 달리, 스트리밍 시스템은 데이터를 처리할 때 지연 시간이 적습니다. (”지연 시간”이란 이벤트 발생 시점으로부터 집계 결과에 그 이벤트가 반영될 때까지의 지연을 말합니다.) 따라서 사용량 한도를 적용하거나, 사용자나 우리가 필요로 할 때 실시간 사용량을 읽을 때 수 초 이내의 지연 시간을 기대할 수 있습니다. 이것은 높은 정확도로 데이터를 제공할 수 있다는 것과 연결됩니다.
각 기능별 로직을 구현하는 코드와 사용량을 집계하는 파이프라인을 물리적으로 분리할 수 있습니다. 비즈니스 로직에서는 “사용 이벤트” 를 발생시키기만 하면 됩니다. 뒤쪽의 집계를 담당하는 파이프라인에서 사용량을 기록하는 것과의 의존성이 사라집니다.
따라서 각 기능을 개발하는 엔지니어들은 사용량 집계에 대해서는 관심을 끄고 “사용 이벤트”를 적절한 시점에 발생시키는 데 집중하는 것으로 충분합니다.
스트림 데이터 처리 시스템의 관점으로 바라봤을 때의 큰 그림은 다음과 같습니다.
어떤 부가 서비스 기능의 사용을 표현하는 “사용 이벤트” 라는 개념을 정의합니다.
각 기능을 표현하는 비즈니스 로직에서 이 이벤트를 발행합니다.
“사용 이벤트”의 스트림을 따라가면서 집계한 수치를 기록하고 데이터베이스에 씁니다.
-
큰 그림은 괜찮지만, 디테일을 채워 나가는 과정은 그렇게 간단하지 않았습니다. 고민했던 점들을 발제하면서 문제편 글은 마무리해보겠습니다.
정확도 검증
스트림 데이터 처리 시스템에서 제공하는 보장은, 아주 간단하게 분류하자면, “최대 한 번 처리”, “최소 한 번 처리”, “정확히 한 번 처리” 보장으로 나뉩니다.
이번 과금 모델 변경 업데이트에서 요구하는 조건을 보면, 우리가 사용량 집계 파이프라인에서 제공해야 할 보장은 “정확히 한 번 처리”를 최대한 지향하되, 사용 이벤트가 “최대 한 번 처리” 될 수 있도록 구성하는 것입니다. 이벤트가 한 번보다 많이 처리될 경우 고객에게 실제보다 큰 금액이 청구되므로 이 경우를 피해야 합니다.
속도와 확장성
최종적으로 고객별 기능 사용량을 집계한 결과는 RDS에 기록되어야 합니다. 하지만 나이브 구현 전략에서 살펴보았듯, 스트림 집계 파이프라인을 구성하더라도 매 이벤트를 받을 때마다 데이터베이스 접근이 일어난다면 처리 속도에 한계가 있습니다.
다행히도 많은 스트림 처리 시스템의 경우 이 문제에 대한 고민이 잘 이루어져 있습니다. 여러 이벤트를 압축하여 저장하는 형태로 요청 횟수를 줄이면서, worker의 비정상 종료가 발생하더라도 정확한 결과를 보장하도록 지원합니다. 집계를 담당하는 worker의 수평 확장으로 파이프라인의 총 처리 속도도 원하는 만큼 지원할 수 있도록 되어 있습니다.
저희 팀에서는 스트림 처리 시스템의 원리를 바탕으로, 저희가 처리해야 하는 트래픽 패턴에 적절한 처리 속도에 도달할 수 있었으며, 실험을 통해 이것을 검증했습니다.
이벤트 처리 지연
스트림 시스템은 이벤트가 비동기적으로 처리되기 때문에 필연적으로 처리 지연이 발생합니다. 관건은 이 지연이 실제로 얼마나 되는지 측정하고 지연 시간을 우리가 허용할 수 있는 범위 내로 유지할 수 있도록 모니터링하는 것입니다. 수 초 이내로 안정적으로 유지된다면 좋겠지만 수십 초, 수 분 이상 처리가 지연되면 비상 상황입니다.
이벤트 처리 기준 시각의 문제
이것은 이벤트 처리 지연과 연결되는 문제입니다.
스트리밍 시스템에서 각 이벤트를 시점에 따라 모을 때, “이벤트의 발생 시각”을 기준으로 하거나 “이벤트의 처리 시각”을 기준으로 하는 두 가지 접근이 있습니다.
“이벤트의 처리 시점”을 기준으로 하는 것은 다른 방법에 비해 간단합니다. 파이프라인에서 이벤트를 수신하는 것이 기준이 되기 때문에 자연스럽게 순서대로 이벤트가 처리된다는 강력한 보장이 있기 때문입니다. 하지만 반대로 “이벤트의 발생 시점”을 기준으로 한다면, 파이프라인 입장에서는 이벤트의 순서가 뒤죽박죽일 수 있기 때문에 문제가 됩니다. 처리 지연 시간도 예측하기 어렵습니다.
우리가 구현해야 하는 사용량 집계의 경우, 다음과 같은 상황을 예로 들 수 있습니다.
어떤 사용자에 대해 2024년 1월 동안의 사용량을 집계해야 한다고 합시다. 이 사용자는 1월 31일에 채널톡을 활용하여 문자 메시지를 전송했습니다. 문자 메시지의 도달 성공이 확인되면, 기능 사용으로 인식되어 이에 대한 요금을 부과해야 합니다.
하지만 문자 메시지의 도달 여부를 확인하는 것은 최대 수 시간까지 소요될 수 있습니다. 수신자의 기기가 꺼져 있을 수도 있으면 이 기기가 켜져야 수신이 확인되고, 중간에 통신사나 여러 벤더를 거치기 때문에 최종적인 확인 시점이 밀릴 수 있기 때문입니다.
2월 2일에 이르러서야 도달 여부가 확인되었다고 합시다. “이벤트의 처리 시점”을 기준으로 한다면 2월 사용분에 이 내역이 포함됩니다. 하지만 사용자는 1월에 이 메시지를 보냈기 때문에 이러한 처리에 납득하지 못할 수 있습니다. (더욱이 이러한 제약이 우리의 기술적 한계로 인해 적용되어야 한다면 고객에게 이유를 설명하기가 더 어려울 것입니다.)
그래서, 반대로 “이벤트의 발생 시점”을 기준으로 합니다. 그러면 이 내역은 1월 사용분에 올바르게 포함될 것입니다. 문자메시지 수신 확인 지연으로 인해 2월 2일에 이르러서야 1월의 사용량을 100% 확정할 수 있었습니다. 하지만 1월 사용분에 대한 정산과 비용 청구, 결제는 2월 1일에 이루어져야 합니다. 그렇다면 일단 2월 1일에 비용 산정을 하고, 늦게 들어오는 이벤트에 대해 추후 오차 사용분을 보정하는 절차가 있어야 합니다.
스트림 집계가 이루어지는 윈도우의 변동
위의 문제도 복잡하지만, 집계가 이루어지는 윈도우가 변경될 수 있기 때문에 더 꼬여버립니다. 😭 고객이 결제일을 변경하면, 이벤트가 집계되어야 하는 위치가 이전 월이나 다음 월로 바뀔 수 있기 때문입니다.
새로운 기술 도입 시 수반되는 운영 비용
채널톡은 기존에 스트림 데이터 시스템을 사용하고 있지 않았습니다.
예를 들어 Apache Kafka처럼 새로운 시스템을 이번에 도입하기로 결정한다고 합시다. 팀 내에서 이를 운용해본 경험이 없기 때문에 운영 비용을 무시할 수 없습니다. 집계 파이프라인을 구현하는 저만 힘든 것이 아니라 이것을 모니터링해야 하는 백엔드팀과 데브옵스팀 팀원들도 함께 삽질을 할 수밖에 없습니다.
그래서 우리 팀에서 이미 활용하고 있는 기술을 확장하여 해결할 수 있다면 그렇게 하는 편이 좋습니다.
-
이어지는 해결편에서는 이 문제에 접근하기 위해 활용한 기술과 구현의 디테일, 진행한 실험과 이후 운영 및 모니터링 과정에 대해 소개하겠습니다.
We Make a Future Classic Product
채널팀과 함께 성장하고 싶은 분을 기다립니다