CQRS - Apache Druid

jaewonnow_·2026년 1월 19일

DataEngineering

목록 보기
15/19

CQRS 의 아키텍쳐로써 Druid를 사용해보자.

https://toss.tech/article/payments-legacy-7

토스 기술블로그를 참고하였습니다.


실제 데이터 사용 사례

1️⃣ 넓은 기간 조회 — 몇 개월, 혹은 몇 년치 데이터를 한 번에 보는 것.
2️⃣ 특정 조건 검색 — 모든 결제 내역 중 특정 구매자 이름이 포함된 거래를 찾는 것.
3️⃣ 정렬과 집계 — 정렬, 페이지네이션, 집계 결과를 한눈에 보는 것.
4️⃣ 도메인 간 결합 — 결제부터 매입, 정산까지 특정 거래 건의 모든 단계를 연결해서 보는 것.

이는 단순한 조회 쿼리로는 처리하기 어렵다. 특히 도메인 간 결합의 경우 다중 테이블 조인을 유발한다. 이러한 요청을 그대로 원장 테이블에서 수행할 경우 대량의 데이터 스캔, 성능 저하, 비효율적인 쿼리 실행이 불가피하다.

결론적으로 실시간 응답속도 ( 온라인 서비스 ) 를 유지하기 어려워지고 이 문제를 해결하기 위해 더 효율적인 데이터 처리 아키텍쳐와 성능 최적화 전략을 설계해야한다.

실시간 데이터 가공

데이터 결합하려면 많은 테이블을 각 도메인의 DB로부터 옮겨와야 한다.
토스에서도 은행 시스템을 많이 사용한 것 같다. 특히 MSA 구조에서는 CDC 를 많이 사용할 수 밖에 없다.

CDC

CDC를 사용하면 도메인 팀과의 의존성은 끊을 수 있다. 왜냐하면 도메인 로직과 상관없이 데이터 테이블 변경 기록만 가지고도 쿼리 전용 테이블이 업데이트 되기 때문이다.

그러나 조회 친화적인 역정규화 테이블을 만들려면 데이터 팀에서 승인팀의 도메인 로직 일부를 들고 있어야 한다고 한다.

CDC 의 단점

토스의 말 : 결제 승인팀의 수단별 원장(ledger)은 20개 이상으로 매우 복잡했습니다.

결제수단마다 기록해야 하는 항목/정산 규칙/상태가 다른 상태

→ 그래서 역정규화 테이블을 만들려면 이 모든 경우를 정확히 풀어서 합치는 로직이 필요하고, 그 로직을 데이터팀이 들고 있게 된다.

메세지 발행 방식

토스의 말 : 메시지 발행(Message Publishing) 방식을 채택

완성된 데이터는 각 도메인 팀에서 직접 발행, 이를 통해 데이터팀의 도메인 의존성을 감소시키고, 시스템 의존성을 Kafka로 단순화할 수 있다.


Druid란?

대규모 데이터 세트에 대한 빠른 분석을 위해 설계된 실시간 분석 데이터베이스.
Druid는 실시간 수집, 빠른 쿼리 성능을 위해 사용되며 빠른 집계가 필요한 동시성이 높은 API 백엔드로 사용된다.

쉽게 말해 OLAP 로 이해하면 된다. ( NoSQL 인가? 틀린 건 아닌듯,, )

Druid 특징

  • 수평 확장성(Scalability) — 데이터 증가에 따라 자유롭게 확장 가능할 것

  • Sub-second 수준의 빠른 집계 연산 성능 — 실시간 분석이 가능할 것

  • RDB로는 구현이 어려운 OLAP 쿼리 지원 — 복합 집계를 안정적으로 처리할 것

전통적인 데이터 베이스 vs 아파치 드루이드

  • 전통적인 데이터베이스: 데이터 적재 -> 쿼리 -> 프로세싱 -> 결과확인

  • 아파치 드루이드: 데이터 적재 -> 프로세싱(인덱싱) -> 쿼리 -> 결과확인

빠르게 데이터를 취합해야하고 결과값을 통해 비즈니스의 발전을 창출해야할 경우에는 이전에 발생한 이벤트들에 대한 쿼리를 최대한 빠르게 가져오는 것이 중요하다. 따라서 드루이드는 빅데이터의 이벤트 처리에 강점을 가진다고 말할 수 있다.

Druid 운영의 도전과제

1️⃣ 과거 데이터 재적재의 어려움 문제

2001년부터 메시지 발행이 시작된 2024년 직전까지, 모든 결제 수단의 과거 데이터를 조합해 역정규화 테이블을 생성해야 했다. 특히 무반납 가상계좌의 경우, 최초 발급 시점부터 현재까지의 모든 이력을 보관해야 했기 때문에 데이터량이 방대했다. 데이터를 만드는 과정도 복잡했지만, 무엇보다 데이터 정합성을 100% 확보하는 과정이 가장 큰 난관이었다.

2️⃣ 멱등성 처리 문제

또 다른 문제는 멱등성(idempotency) 처리의 부재. 메시지 중복 발행이나 거래 상태 변경이 발생할 경우, 조회 시점마다 해당 거래의 최종 상태를 정확히 반영해야 했다. 이를 위해 보정 내역과 무효화 내역을 별도로 적재한 뒤, 매번 Merge on Read 방식으로 조인하여 최신 상태를 계산해야 했다. 이 구조는 쿼리를 복잡하게 만들었고, 개발자가 해당 로직을 일부 놓칠 경우 존재하지 않는 데이터가 노출되는 위험도 있다. 결국 이러한 한계들은 Druid의 유지 보수성과 신뢰성 확보에 지속적인 부담으로 작용한다.

3️⃣ 데이터 파편화 문제

취소 거래 테이블의 경우, 승인 거래와의 연계 조회를 위해 원 승인일자 기준으로 동일하게 파티셔닝되어 있었다. 예를 들어, 일주일 전에 발생한 거래가 환불될 경우, 취소 거래는 현재 시점에 발생하더라도 데이터는 원 승인일자(즉, 일주일 전 파티션)에 적재된다. 이로 인해 데이터가 시점별로 분산되어 테이블 단위의 파편화(Fragmentation)가 발생하고, 조회 시 더 많은 컴퓨팅 자원이 소모되어 읽기 쓰기 속도 성능 저하가 나타난다.

이 문제를 해결하기 위해 60초 주기 파편화 탐지 프로세스를 도입한다. 해당 프로세스는 파편화된 테이블을 자동으로 식별한 후, Compaction 작업을 주기적으로 수행함으로써 데이터 세트를 지속적으로 정리하고 통합한다. 이를 통해 조회 성능을 안정적으로 극대화할 수 있다.

4️⃣ 복잡한 아키텍처를 가진 시스템 운영의 어려움 문제

Druid는 구성요소가 많고 알아야 할 점이 많았다.

Zookeeper: 세그먼트 관리 및 노드 상태 관리
Broker: 메타데이터 기반 쿼리 라우팅
Historical: 세그먼트 데이터 보유
MiddleManager: 세그먼트 재정렬, 입수 작업(peon) 담당
Coordinator/Overlord: 세그먼트 분배, compact 작업 결정
Router: 브로커에 쿼리 전달

모든 서비스를 알고 튜닝하고 필요에 따라 각 서비스를 스케일링해야 해야하며, 시스템 운영 난이도가 높았습니다.

Rollup으로 집계 성능 극대화

여러 뷰 중 일부는 집계형 데이터를 포함하며, 동시에 실시간 조회 성능을 요구한다. 기존에는 특정 기간의 거래 데이터를 조회하기 위해 여러 Druid 테이블을 UNION ALL로 합친 뒤 최종 집계를 수행한다.

이 방식은 원천 데이터를 유지한 채 실시간 조회 (온라인 조회) 가 가능하지만, 데이터 규모가 커질수록 스캔 비용이 급격히 증가하여 성능 저하가 발생한다.

-> 집계형 데이터는 실시간 조회 성능을 요구하고 , 집계에는 여러 방식이 있다.

  • Druid Rollup 기능
    Rollup은 동일한 차원(Dimension) 값을 가지는 데이터를 자동으로 집계(Aggregation)하여 저장하는 방식. 쿼리 실행 시 별도의 GROUP BY와 같은 집계 연산을 수행할 필요 없이, 미리 실시간으로 집계된 데이터를 즉시 조회할 수 있다. 조회해야 하는 데이터 스캔이 줄어들면서 조회 성능이 향상된다.

Druid Rollup vs DB 제공 Rollup

미리 집계해 저장한다는 점은 같지만, 어디서/언제/어떤 제약으로 집계하느냐가 다르다.

1) Druid Rollup

어디서? Druid ingestion(적재) 시점에

언제? 데이터가 들어올 때(스트리밍/배치 ingest)

무엇을? 시간 버킷(예: 1분/5분/1시간) + 차원(dimension) 기준으로 미리 집계된 형태로 저장

특징

  • 저장 데이터 자체가 “이미 집계된 레코드”라서 쿼리가 매우 빨라짐

  • 대신 세밀한 원본 이벤트 수준은 희생될 수 있음(롤업 수준 아래로는 못 봄)

  • 롤업 “그레인(시간 단위/차원 조합)”을 잘못 잡으면 나중에 요구사항 바뀔 때 곤란

2) DB 제공 Rollup (보통 RDB/OLAP DB 기능)

A) SQL ROLLUP/CUBE 같은 쿼리 문법

어디서? 조회 시점에 DB가 계산

언제? 쿼리 날릴 때마다

특징

  • 소계/합계를 한 번에 뽑는 문법일 뿐이라 원본을 매번 스캔/집계할 수 있으면 빠르고, 아니면 느려짐 - 즉 미리 저장해두는 롤업이 아님

B) Materialized View / Summary Table 같은 “미리 집계 저장”

어디서? DB 내부 기능(또는 배치 작업)으로

언제? 주기적 리프레시(또는 증분 갱신)

특징

  • Druid rollup처럼 “미리 집계”가 맞지만, 갱신 방식/일관성/성능은 DB마다 다르고, 대규모 실시간 이벤트 집계는 DB 종류에 따라 한계가 올 수 있음

실시간 대시보드에서 “최근 1분/5분/시간대 지표”가 핵심이고 트래픽 많음 → Druid Rollup 강함

RDB에서 단순 리포트/소계 정도, 데이터량 크지 않음 → SQL ROLLUP

분석 DB/웨어하우스에서 정기 리포트/집계 테이블 관리 → MV/요약테이블

결론

대규모 실시간 서빙은 만능 데이터베이스를 찾는 일이 아니라, 도메인을 이해하고 각 엔진의 강점을 정확히 조합하는 일임을 다시 확인.

저희는 검색(Elasticsearch), 시계열 집계(Druid), 조인/통합 원장(StarRocks)을 한 레이어에서 일관된 경험으로 묶었고, 그 과정에서 얻은 가장 큰 배움은 단순합니다.

적게 읽도록 설계하면, 빠르고 효율적이며 정확해진다.
1) 정확성을 잃지 않고, 2) 적게 읽도록, 3) 효율적인 운영이 가능한 방식으로 문제를 풀었는가?

profile
0 to 100 Data Engineer

0개의 댓글