고객은 절대 기다려주지 않는다: 빠른 데이터 서빙으로 고객 만족도를 수직 상승 시키는 법
안녕하세요, 토스페이먼츠 Data Engineer 이세찬입니다.
2020년 8월, PG 사업을 인수하며 출범한 토스페이먼츠는 이후 가파른 성장세를 보였습니다. 2022년 대비 2024년, 거래 건수 기준으로 약 2배 증가했죠. 하지만 이런 폭발적인 성장은 우리에게 새로운 도전을 안겨주었습니다. 바로 "어떻게 하면 늘어나는 데이터를 안정적이고 빠르게 서빙할 수 있을까?"라는 문제였어요.
모놀리식에서 MSA로: 첫 번째 도전
2020년 8월 이후, 토스페이먼츠는 더 나은 개발 환경을 위해 MSA(Microservices Architecture) 환경으로 전환을 시작했습니다. 하지만 애플리케이션 레벨의 분리는 이루어졌어도, DB 레벨의 분리까지는 도달하지 못했습니다.
모놀리식 구조에서는 모든 도메인이 하나의 시스템과 DB에서 돌아갔습니다. 결제, 정산 등 도메인별 원장 조회는 크게 문제되지 않았어요. DB 내부에서 원장 간 JOIN으로 해결하거나, 원장 자체가 통합 관리되어 데이터 조회가 단순한 쿼리로 가능했기 때문이에요. 하지만 MSA 환경으로 전환하면서 상황은 완전히 달라졌습니다.
Elasticsearch로 해결한 첫 번째 검색 니즈
2021년, 가맹점의 거래내역 확인, 환불, 정산 등을 위한 새로운 제품이 출시되었습니다. 그러면서 검색에 대한 니즈가 커졌어요. 당시 우리의 해결책은 명확했습니다. 검색에 필요한 필드들을 Elasticsearch(ES)에 색인하여 문제를 해결했죠.
하지만 이것만으로는 충분하지 않았습니다. 거래량은 계속 늘어났고, 단순 검색을 넘어 복잡한 집계 연산과 실시간 조회가 필요해졌어요.
CQRS 아키텍처 도입: 명령과 조회의 분리
2024년 6월, 중요한 전환점이 찾아왔습니다. 거래 승인 원장이 기존 Oracle에서 MySQL로 완전히 마이그레이션되었고, 이를 계기로 CQRS(Command Query Responsibility Segregation) 아키텍처를 본격적으로 도입하게 되었어요. 이전까지는 읽기(DB Read)와 쓰기(DB Write)를 단순히 분리하는 수준이었지만, 우리가 만들고자 한 것은 그보다 훨씬 발전된 구조였습니다.
다음과 같은 요구사항을 동시에 충족할 수 있는 시스템을 필요로 했습니다.
이 세 가지 목표를 모두 충족할 수 있는 해답이 바로 Apache Druid였어요.
왜 Apache Druid였을까?
1️⃣ 시계열 데이터에 최적화
거래 내역은 본질적으로 시계열 데이터입니다. 금액 합산 집계와 같은 OLAP성 쿼리에서 Druid는 매우 유리했습니다.
2️⃣ 낮은 러닝 커브
DSL도 있지만 Druid SQL을 통해 누구나 쉽게 구현할 수 있어요. 기존 SQL 지식만 있으면 빠르게 적응할 수 있었습니다.
3️⃣ 자동 인덱싱의 강력함
모든 컬럼이 bitmap index화됩니다. 심지어 컬럼이 nested JSON이라도 모든 key가 bitmap index 처리돼요. 이는 사용자나 운영자 입장에서 별도 가이드 없이도 어느 정도 성능을 보장받을 수 있다는 의미였습니다.
4️⃣ 비용 효율적인 아키텍처
Cloud-native 아키텍처이며, Computing과 Storage가 분리된 구조로 비용 측면에서 저렴하게 운영이 가능해 보였습니다.
Druid 도입의 성과
읽기와 쓰기가 분리되면서 놀라운 일이 일어났습니다. 승인 플랫폼 팀의 컴퓨팅/휴먼 리소스를 쓰지 않고도, 데이터 조회팀에서 수십억 건의 데이터를 저지연으로 부담 없이 조회하고 집계할 수 있게 되었어요.
이로 인한 결과는 아래와 같습니다.
MSA 환경에서의 진짜 조회 니즈
하지만 여기서 끝이 아니었습니다. 실제 고객들이 원한 것은 단순한 데이터 조회를 훨씬 넘어서는 것이었어요.
고객들의 실제 요구사항
1️⃣ 넓은 기간 조회 — “몇 개월, 혹은 몇 년치 데이터를 한 번에 보고 싶어요.” 2️⃣ 특정 조건 검색 — “모든 결제 내역 중 특정 구매자 이름이 포함된 거래를 찾고 싶어요.” 3️⃣ 정렬과 집계 — “정렬, 페이지네이션, 집계 결과를 한눈에 보고 싶어요.” 4️⃣ 도메인 간 결합 — “결제부터 매입, 정산까지 특정 거래 건의 모든 단계를 연결해서 보고 싶어요.”
이러한 요구사항들은 단순한 조회 쿼리로는 처리하기 어려운 복잡한 조회 패턴을 동반했습니다. 대규모 범위 조회, 문자열 검색, 정렬과 페이징 및 집계 연산, 다중 테이블 조인이 필요했어요. 문제는, 이러한 요청을 그대로 원장 테이블에서 수행할 경우 대량의 데이터 스캔, 성능 저하, 비효율적인 쿼리 실행이 불가피하다는 점이었습니다. 실시간 응답 속도를 유지하기가 점점 어려워졌고, 결국 저희는 이 문제를 해결하기 위해 더 효율적인 데이터 처리 아키텍처와 성능 최적화 전략을 새롭게 설계해야 했어요.
실시간 데이터 가공: CDC vs 메시지 발행
데이터 결합을 위해 많은 테이블을 각 도메인의 DB로부터 옮겨와야 했습니다.
CDC의 단점
CDC를 사용하면 도메인 팀과의 의존성은 끊을 수 있지만, 조회 친화적인 역정규화 테이블을 만들기 위해 데이터팀에서 승인팀의 도메인 로직 일부를 들고 있어야 했어요. 결제 승인팀의 수단별 원장은 20개 이상으로 매우 복잡했습니다. 시스템 의존성은 낮아지지만 도메인 의존성이 높아지는 문제가 있었어요.
메시지 발행 방식의 선택
저희는 메시지 발행(Message Publishing) 방식을 채택했습니다. 완성된 데이터는 각 도메인 팀에서 직접 발행하며, 이를 통해 데이터팀의 도메인 의존성을 감소시키고, 시스템 의존성을 Kafka로 단순화할 수 있었어요. JsonArray 형태를 제외한 데이터 발행 시 Druid의 Bitmap Index가 자동으로 생성되어 효율적인 질의가 가능했습니다.
Druid의 핵심 장점은 모든 컬럼이 Bitmap Index로 처리되는 구조적 특성에 있습니다. 이로 인해 별도 조치 없이도 JSON 필드 단위로 테이블화가 용이하며, 일정 수준 이상의 조회 성능이 자연스럽게 보장되었어요.
AWS 환경에서의 비용 최적화
Computing과 Storage 분리의 장점
1️⃣ 저렴한 스토리지 비용
AWS EBS와 같은 고가의 네트워크 스토리지를 사용할 필요가 없습니다. 모든 데이터는 S3에 안전하게 저장되고, 쿼리 수행에는 고성능 로컬 스토리지를 가진 인스턴스를 활용해요. 테스트 결과, 네트워크 스토리지 대비 로컬 스토리지는 최대 9배 이상 빠른 읽기와 쓰기 성능을 보였어요.
2️⃣ 가용영역 분산의 용이성
네트워크 스토리지 의존성을 제거하면서 가용 영역(AZ) 단위로의 분산 배치가 훨씬 유연해졌습니다. 결과적으로 장애 발생 시에도 더 유연하게 복구할 수 있는 고가용성(HA) 환경을 확보할 수 있었어요.
3️⃣ Spot Instance 활용
데이터가 S3에 안전하게 보관되어 있기 때문에 각 장비의 로컬에 많은 데이터를 보유하고 있을 필요가 없습니다. 이런 특성을 활용해, 개발과 테스트 환경에서는 Spot Instance를 적극 활용하고 있어요. 이를 통해 더 저렴한 비용으로 서비스를 운영하면서도 일부 노드가 무너지는 상황에서의 서비스 안정성에 대한 평가를 자연스럽게 할 수 있는 환경을 마련했습니다.
이러한 구조적 최적화를 통해 월 약 5천만 원 이상의 클라우드 비용 절감 효과를 달성했습니다. 비용 효율성과 성능, 안정성을 모두 확보한 셈이죠.
Druid 운영의 도전 과제들
장점이 많았지만, 단점도 분명했습니다.
1️⃣ 과거 데이터 재적재의 어려움 문제
2001년부터 메시지 발행이 시작된 2024년 직전까지, 모든 결제 수단의 과거 데이터를 조합해 역정규화 테이블을 생성해야 했어요. 특히 무반납 가상계좌의 경우, 최초 발급 시점부터 현재까지의 모든 이력을 보관해야 했기 때문에 데이터량이 방대했습니다. 데이터를 만드는 과정도 복잡했지만, 무엇보다 데이터 정합성을 100% 확보하는 과정이 가장 큰 난관이었죠.
2️⃣ 멱등성 처리 문제
또 다른 문제는 멱등성(idempotency) 처리의 부재였어요. 메시지 중복 발행이나 거래 상태 변경이 발생할 경우, 조회 시점마다 해당 거래의 최종 상태를 정확히 반영해야 했습니다. 이를 위해 보정 내역과 무효화 내역을 별도로 적재한 뒤, 매번 Merge on Read 방식으로 조인하여 최신 상태를 계산해야 했어요. 이 구조는 쿼리를 복잡하게 만들었고, 개발자가 해당 로직을 일부 놓칠 경우 존재하지 않는 데이터가 노출되는 위험도 있었습니다. 결국 이러한 한계들은 Druid의 유지 보수성과 신뢰성 확보에 지속적인 부담으로 작용했습니다.
3️⃣ 데이터 파편화 문제

취소 거래 테이블의 경우, 승인 거래와의 연계 조회를 위해 원 승인일자 기준으로 동일하게 파티셔닝되어 있었어요. 예를 들어, 일주일 전에 발생한 거래가 환불될 경우, 취소 거래는 현재 시점에 발생하더라도 데이터는 원 승인일자(즉, 일주일 전 파티션)에 적재됩니다. 이로 인해 데이터가 시점별로 분산되어 테이블 단위의 파편화(Fragmentation)가 발생하고, 조회 시 더 많은 컴퓨팅 자원이 소모되어 성능 저하가 나타났어요.
이 문제를 해결하기 위해 60초 주기 파편화 탐지 프로세스를 도입했어요. 해당 프로세스는 파편화된 테이블을 자동으로 식별한 후, Compaction 작업을 주기적으로 수행함으로써 데이터 세트를 지속적으로 정리하고 통합했습니다. 이를 통해 조회 성능을 안정적으로 극대화할 수 있었어요.
4️⃣ 복잡한 아키텍처를 가진 시스템 운영의 어려움 문제
.png)
Druid는 구성요소가 많고 알아야 할 점이 많았는데요,
모든 서비스를 알고 튜닝하고 필요에 따라 각 서비스를 스케일링해야 해야하며, 시스템 운영 난이도가 높았습니다.
실제 장애 사례
한 번은 파편화된 대량의 데이터를 한꺼번에 적재하는 과정에서 문제가 발생했습니다. 데이터 적재 시 생성된 수많은 세그먼트(segment) 정보가 Zookeeper로 한꺼번에 전달되면서, Zookeeper 트랜잭션 로그의 디스크 공간이 가득 차고, 결국 Druid 전체가 다운되는 장애가 발생한 것이죠.
해결
저희의 해결 방법은 다음과 같았어요. Zookeeper 트랜잭션 로그 보존 주기(retention)를 조정하여 로그 축적을 방지했고, 데이터 전송 과정에서 압축을 적용해 트래픽과 로그 크기를 최소화했습니다. 이후 유사한 상황에서도 안정적으로 데이터가 적재되도록 구조를 개선했어요.
또한, Druid가 조인 문법을 지원하긴 하지만 시스템 구조상 조인 기능이 매우 제한적이라는 점도 확인했어요.
PG 비즈니스가 생각보다 복잡한 자료구조를 가지고 있기 때문에 많은 양의 테이블에 대한 조인 처리는 필수적이었는데, PG 비즈니스에서의 여러 도메인 데이터 결합 문제를 풀어나감에 있어서는 Druid가 지원하는 테이블 조인 개수의 한계는 우리의 비즈니스 문제를 푸는데 문제가 되었어요.
검색의 필수성: Elasticsearch와의 협업
대고객 서비스에서는 검색 요건이 필수적이에요. 5년이 넘는 기간 동안 축적된 50억 건에서 100억 건에 이르는 방대한 데이터 중 일부 단서만을 기반으로 특정 거래를 신속하게 찾아내는 것이 중요합니다. 단일 시스템으로도 해당 문제를 해결하는 것은 가능해요. 그러나 고속의 검색 성능을 안정적으로 보장하기는 어려워요.
하이브리드 아키텍처

저희의 해결책은 명확했습니다.
이 구조를 통해 Druid의 데이터 스캔 범위를 최소화하였으며, 결과적으로 불필요한 연산을 줄이고 조회 성능을 안정적으로 개선할 수 있었어요.
Rollup으로 집계 성능 극대화
토스페이먼츠 제품에서 제공하는 여러 뷰 중 일부는 집계형 데이터를 포함하며, 동시에 실시간 조회 성능을 요구합니다. 기존에는 특정 기간의 거래 데이터를 조회하기 위해 여러 Druid 테이블을 UNION ALL로 합친 뒤 최종 집계를 수행했어요. 이 방식은 원천 데이터를 유지한 채 실시간 조회가 가능했지만, 데이터 규모가 커질수록 스캔 비용이 급격히 증가하여 성능 저하가 발생했습니다.
Druid Rollup 기능
Rollup은 동일한 차원(Dimension) 값을 가지는 데이터를 자동으로 집계(Aggregation)하여 저장하는 방식이에요. 쿼리 실행 시 별도의 GROUP BY와 같은 집계 연산을 수행할 필요 없이, 미리 실시간으로 집계된 데이터를 즉시 조회할 수 있습니다. 조회해야 하는 데이터 스캔이 줄어들면서 조회 성능이 향상되죠.

성과
Rollup 적용 전후의 성능 차이는 매우 뚜렷했어요. 적용 이전에는 수십억 건에 달하는 원천 데이터를 직접 조회해야 했기 때문에, 평균 응답 시간이 수십 초에 이르렀습니다. 반면, Rollup을 적용한 이후에는 같은 쿼리가 평균 0.5~1초 수준으로 단축되었어요. 결과적으로 최대 99%의 성능 향상을 확인할 수 있었으며, 이는 데이터 접근 경로 단축과 사전 집계 구조의 효율성이 결합된 결과였습니다.
도메인 간 데이터 결합의 필요성
제품이 고도화되면서 사장님들의 요구사항은 점점 더 복잡해졌습니다. 예를 들어, 결제와 매입, 정산 같은 타 도메인 간 데이터 결합에 대한 수요가 있었어요. 각 도메인 간 데이터 결합이 불가피한 상황이 왔죠. 요구사항을 충족하기 위해서는 서로 다른 데이터베이스의 데이터를 결합해야 하는 상황이 발생한 것입니다. 토스페이먼츠 결제의 Lifecycle을 한눈에 볼 수 있는 더 큰 범위의 통합 원장이 필요하게 됐습니다. 실제 구매가 얼마였고, 실제 순이익이 얼마인지 알 수 있어야 했어요.
StarRocks 도입: 새로운 전환점
토스페이먼츠는 점점 복잡해지는 원장 데이터 구조 속에서, Druid가 가진 멱등성 처리 한계와 조인 제약을 넘어서기 위해 새로운 방향을 고민했습니다. 그 결과, 데이터 일관성을 지키면서도 빠르고 유연한 조회가 가능한 StarRocks를 도입하게 됐어요. 이 결정은 단순히 시스템을 교체하는 수준이 아니라, 데이터 서빙 구조 전반을 '통합과 실시간성 중심'으로 재설계한 전환점이었습니다.
왜 StarRocks였을까
StarRocks를 선택한 이유는 다음과 같아요.
첫째, Druid의 한계를 보완할 수 있었습니다. 멱등성 처리가 안정적으로 지원되기 때문에 중복 데이터나 불안정한 이벤트를 관리하기 훨씬 수월해졌어요.
둘째, 분석 시스템과의 일관성을 확보할 수 있었습니다. StarRocks는 기존 분석 인프라와 구조적으로 유사한 형태를 가지고 있어, 데이터 흐름을 단순화하고 유지 보수 부담을 크게 줄일 수 있었습니다.
셋째, 조인(Join) 성능이 압도적으로 높았어요. 이 덕분에 여러 도메인의 데이터를 쉽게 결합해볼 수 있었습니다.
멱등성 처리의 강력함
멱등성 처리가 되면 장점이 매우 많아집니다. CDC(Change Data Capture) 기반 메시지를 활용해 원장 테이블을 그대로 실시간 복제할 수 있고, 중복 메시지가 들어오더라도 재처리가 간단해요. 이 구조 덕분에 데이터 정합성을 유지하기 쉬워졌고, 테이블 설계 시 Append Only 제약을 고려하지 않아도 되어 개발자 입장에서 훨씬 자유로운 설계가 가능해졌습니다. 결국, 시스템 복잡도는 낮추면서도 신뢰성과 안정성을 동시에 확보하게 됐죠.
분석 시스템과의 통일성
Druid 시절에는 Spark나 Trino를 통해 여러 원장 테이블을 조합하고, 그 결과를 S3에 저장한 뒤 다시 Druid로 불러오는 번거로운 과정이 필요했습니다. StarRocks로 전환한 이후에는 훨씬 단순해졌어요. 여러 카탈로그(Catalog)를 쉽게 연결할 수 있고, Hive 카탈로그에서의 작업 결과를 StarRocks 카탈로그로 바로 가져올 수 있습니다. 또 모든 필드를 비트맵 인덱스로 처리하지 않아 데이터 적재 속도도 훨씬 빨라졌어요.
단순한 아키텍처
StarRocks는 Druid와 달리 매우 단순한 구조를 가지고 있습니다:
.png)
- FE (Frontend): 메타정보를 관리하고, 쿼리를 라우팅합니다.
- CN (Computing Node): S3에서 내려받은 핫 캐시(hot cache)를 보유한 계산 노드로, 실제 연산을 수행합니다.
이 구조는 S3 기반의 공유형 구조(shared architecture)로 이루어져 있는데요. 구조가 단순하여 유지 보수가 용이하고, 수평 확장이 자유로워요.
유연한 JOIN 지원
과거 Druid 환경에서는 JOIN 제약으로 인해 역정규화(denormalization)된 테이블 구조를 유지해야 했습니다. 반면, StarRocks에서는 다음과 같은 개선이 이루어졌어요.
이를 통해 데이터 일관성을 유지하면서도, 복잡한 조인 쿼리를 유연하게 처리할 수 있는 구조를 확보했습니다.
StarRocks 성능 최적화 전략
1️⃣ Colocation Group: 조인 성능 극대화
대규모 테이블 간 조인 연산에서 가장 큰 성능 병목은 네트워크 간 데이터 이동(Shuffle)입니다.

Colocation Group이란?
Colocation Group은 두 개 이상의 테이블을 특정 컬럼(예: 거래 ID)을 기준으로 동일한 해시 알고리즘을 적용해 데이터를 분산 저장하는 방식이에요. 이를 통해 동일한 값을 갖는 데이터가 반드시 동일한 노드 및 동일한 버킷(Bucket)에 위치하도록 강제합니다. 이 구조는 조인(Join) 연산 시 데이터의 네트워크 이동을 최소화하여 성능을 향상시키는 역할을 해요. 즉, 조인 연산이 노드 내부에서만 수행되므로 추가적인 네트워크 전송이 불필요하게 되는 것이죠.
성능 테스트 결과
이 실험 결과를 통해 Colocation Group 적용이 대규모 조인 연산의 처리 효율을 크게 향상시킴을 확인했습니다.
주의사항
Colocation Group을 적용할 때에는 다음 사항에 유의해야 합니다.
2️⃣ Prefix Index: 데이터 스캔 최소화
StarRocks의 Prefix Index를 활용한 데이터 스키핑(data skipping) 기법입니다.

Sort Key 설정
StarRocks에서 Sort Key를 설정하면 다음과 같은 동작이 수행됩니다.
토스페이먼츠의 적용 사례

Sort Key 순서는 다음과 같아요.
- 가맹점 ID — 필수적이며 가장 빈번하게 사용되는 조회 조건
- 거래 시각(Timestamp) — 각 가맹점 ID에 대응되는 세부 정렬 기준
이러한 구조를 적용함으로써 다음과 같은 성능상의 이점을 확보했어요.
Prefix Index를 활용하여 특정 가맹점의 데이터 시작 위치를 즉시 탐색할 수 있게 되었고, 조회 조건과 무관한 데이터 블록을 스캔하지 않고 건너뛰는(Data Skipping) 방식을 적용하여 데이터 스캔 범위를 최소화할 수 있었습니다.
결과적으로, 전체 데이터 읽기량(RawRowsRead)이 약 99.68% 감소했어요. 이는 Sort Key 기반의 정렬 구조와 Prefix Index 최적화가 결합된 결과로, 대용량 거래 데이터 조회 성능을 획기적으로 개선하는 데 기여하였습니다.
3️⃣ 전체 성능 개선 비교
성능 테스트 환경: MySQL, Oracle, Apache Druid, 최적화 전 StarRocks, 최적화 후 StarRocks
응답 속도 비교
최적화 전 StarRocks 대비 약 81.2% 감소한 CPU 사용률로 동일한 쿼리 부하를 처리할 수 있었어요. 이는 데이터 스캔 효율성 향상과 연산 경로 단축의 결과로 분석됩니다.
Prefix Index를 활용한 테이블 최적화는 데이터 스캔 범위를 효과적으로 축소하여 쿼리 응답 성능을 향상시키고, 동시에 시스템 자원(CPU) 사용량을 대폭 절감하는 매우 효율적인 접근 방식으로 평가됩니다. 이를 통해 대용량 거래 데이터 환경에서도 안정적이고 일관된 조회 성능을 확보할 수 있었어요.
현재 토스페이먼츠의 데이터 서빙 구조

현재 토스페이먼츠(Toss Payments)의 데이터 서빙 구조는 다음과 같아요.
이러한 구조를 통해 경영자 및 가맹점 사업자(사장님)에게 보다 다양하고 심층적인 인사이트를 신속하게 제공할 수 있게 되었습니다.
마치며
대규모 실시간 서빙은 “만능 데이터베이스를 찾는 일”이 아니라, 도메인을 이해하고 각 엔진의 강점을 정확히 조합하는 일임을 다시 확인했어요. 저희는 검색(Elasticsearch), 시계열 집계(Druid), 조인/통합 원장(StarRocks)을 한 레이어에서 일관된 경험으로 묶었고, 그 과정에서 얻은 가장 큰 배움은 단순합니다.
적게 읽도록 설계하면, 빠르고 효율적이며 정확해진다.
실전에서 통했던 원칙은 세 줄로 요약할 수 있어요.
앞으로는 AWS + IDC 분산 환경에서의 일관성 모델과 지연 편차 관리라는 숙제가 남아 있어요. 하지만 문제의 크기와 무관하게, 저희는 같은 질문으로 시작하려고 합니다.
- 정확성을 잃지 않고, 2) 적게 읽도록, 3) 효율적인 운영이 가능한 방식으로 문제를 풀었는가?
이 글이 대규모 데이터 서빙을 고민하는 데이터 엔지니어들에게 설계의 체크리스트이자 실패를 줄이는 힌트가 되길 바랍니다. 더 좋은 방법과 새로운 시도를 함께 실험해 보고 싶습니다.
✅ 이번 아티클은 Toss Makers Conference의 세션을 바탕으로 재구성되었습니다.
