pile·
DB / 데이터·channel-talk채널톡·

고객 타게팅을 좀 더 정교하게, ClickHouse 도입기

채널톡 CRM 마케팅의 고객 타게팅을 커머스 행동 데이터까지 넓히기 위해 ClickHouse를 도입한 과정과, 그 과정에서 부딪힌 한계를 함께 적었다. "A 상품 쿠폰을 가진 고객이 장바구니에 A를 담았지만 아직 구매하지 않은 경우" 같은 조건을 RDBMS로 풀기에는 데이터량이 감당하기 어려웠다. 고객사들이 발급한 쿠폰만 최소 20억 건 이상으로 추정된다.

핵심 포인트
  • 워크로드가 쓰기와 읽기 비율 100:1에 append-only 성격이고, 웹훅으로 받는 주문 정보는 LWW 정도의 일관성만 지원해도 되며 준 실시간 처리로 충분해 OLAP 전환을 내포하고 있었다.
  • ClickHouse를 고른 이유는 성숙도와 레퍼런스, 그리고 한 달 이내에 프로덕션 안정성을 확보해야 하는 상황에서 매니지드 서비스로 빠르게 갈 수 있다는 판단이었다.
  • MergeTree의 단순함이 매력이자 단점이다. ReplacingMergeTree는 중복을 즉시 제거하지 않고 Merge 시점에 정리해, 매 쿼리마다 중복 제거 처리를 얹어야 해 성능을 잡아먹는다.
  • ClickHouse는 대규모 JOIN에 강한 시스템이 아니지만, 실제 쿼리 대상이 원본 수십억 건이 아니라 필터링된 고객 집합이라 감당할 수 있었다.
  • 정말 어려웠던 것은 Point Query다. granule 단위로 읽는 구조라 아이템 하나를 읽기 위해 큰 범위를 읽는다.
상세 정리
  • 제품 맥락: CRM 마케팅은 채널톡·문자·카카오 메시지를 발송해 상담으로 쌓인 고객을 다시 온사이트로 끌어들이고, 전환되면 그 행동 데이터가 다시 채널톡에 반영돼 더 나은 상담으로 이어지는 선순환을 만든다.
  • 한계: 여러 빌더사를 통해 커머스를 연동할 수 있지만 저장 구조의 한계로 고객이 장바구니에 무엇을 담았는지, 어떤 쿠폰을 갖고 있는데 안 썼는지를 타게팅하기 어려웠다.
  • 기존 구성: 기본은 RDBMS이고 특정 쿼리를 위해 원본 테이블의 보조 저장소를 따로 구성해 쿼리하기도 했다.
  • RDBMS로 풀 때의 문제: 이 쿼리 형식을 최적화하기에는 부담이 크고 I/O 병목에 따라 꼬리 지연이 길어져 제품 안정성이 떨어진다.
  • 워크로드 성격 정리: append-heavy하고 시의성에 따라 특정 시간대나 블랙 프라이데이 같은 날짜에 트래픽이 몰릴 수 있으며, 모든 이벤트와 히스토리를 하나의 테이블로 비정규화하기 어렵다.
  • OLTP와 OLAP 구분: OLTP는 은행 송금이나 주문 처리처럼 짧고 빈번한 읽기/쓰기가 중심이고 일관성이 중요한 반면, OLAP은 준 실시간 데이터 쿼리에 초점이 있고 트랜잭션이나 개별 row 업데이트에는 비교적 약하다.
  • 저자는 만능 데이터베이스가 없다는 점을 짚는다. 높은 TPS가 필요하면 Redis의 인메모리 강점을, 단일 노드로 안 되면 파티셔닝을, 그 위에 트랜잭션까지 보장하려면 분산 트랜잭션이라는 복잡한 문제를 풀어야 한다.
  • MergeTree의 동작: 매 쓰기를 디스크에 작은 Part로 바로 적재하고 주기적으로 병합하는 방식이라 append 성격 쓰기에 유리하다. 기본 MergeTree는 Update 시 in-place가 아닌 mutation으로 Part가 재작성되는 문제가 있어 ReplacingMergeTree가 제안됐다.
  • ReplacingMergeTree의 대가: 중복 제거가 Merge 시점에 일어나므로 매 쿼리에서 특정 아이템의 중복된 데이터가 조회될 수 있다. 예컨대 특정 주문이 환불이면서 구매 완료인 상태로 동시에 존재할 수 있다.
  • 빈번한 업데이트는 주문 데이터에서 발생한다. 주문은 취소나 환불로 언제든 바뀔 수 있지만, 주문 확정 등 특정 시점 이후에는 상태가 변경되지 않는다는 성질이 있다.
  • 그래서 Update 가능한 영역을 분리하고 Cold Storage와 함께 쿼리하는 방식으로 개선했다.
  • JOIN 제약: 복잡한 대규모 JOIN은 다른 시스템이 더 잘 지원하며, ClickHouse는 브로드캐스트 가능한 크기의 right-hand side 테이블이나 양쪽이 같은 키로 샤딩되지 않은 경우에 대한 권장 패턴을 따로 안내한다.
  • 감당 가능했던 이유: 원본 주문·쿠폰·장바구니는 수억에서 수십억 건이지만 실제로 쿼리하는 것은 특정 조건을 만족하는 고객의 집합이다. 필터링된 집합 간의 메모리 연산이라면 커버 가능했고 실제 지연도 수백 ms 수준에서 관리됐다.
  • 다만 JOIN은 시스템 자원을 많이 소모하는 연산이라 개선이 필요하다고 인정한다.
  • Point Query 문제: 최종 마케팅 발송 시 처음 세그먼트에 등록된 고객이 여전히 포함되는지 확인하는 Membership Check가 있는데, 이는 "이 유저가 이 세그먼트에 속해 있나"를 묻는 형태다.
  • ClickHouse는 기본 8192개 아이템을 담는 granule 단위로 데이터 범위를 파악해 skip하거나 읽는다. 수십억 건 스캔에는 효율적이지만, 아이템 하나를 읽기 위해 큰 범위의 파일을 읽어야 한다.
  • 해법: Redis를 함께 운영하는 하이브리드 아키텍처를 택했다. 동기화 과정의 엔지니어링 비용과 복잡도가 올라가는 선택이었지만 레퍼런스를 찾기 쉬웠고 Hot/Cold 분리와 같은 성격의 보조 저장소 유지는 어렵지 않은 결정이었다고 밝힌다.
  • 회고: ClickHouse의 단순 명료함이 이점이자 트레이드오프였고, 한계는 도입 전부터 인지하고 있었다. 그 단순함 때문에 하이브리드 설계가 필요해져 전체 아키텍처 복잡도는 오히려 올라가는데, 이는 복잡도를 어디에 둘 것이냐의 선택 영역이라고 정리한다.
  • 이후 계획: 업데이트가 매우 빈번한 워크로드에도 Analytics를 제공해야 하는 요구를 반영하기 위해 커스텀 리포트에서는 다른 엔진을 도입했고, 이 글의 Hot Storage 개선도 이어서 다룰 예정이라고 예고한다.
왜 읽나RDBMS로는 감당 안 되는 세그먼트 조건을 OLAP으로 옮기려는 팀이 ClickHouse의 구조적 이점과 ReplacingMergeTree·JOIN·Point Query라는 세 한계를 미리 가늠할 수 있다.
channel-talk
채널톡 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. DB / 데이터·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    분석가가 직접 만든 마케팅 자동화 시스템 — 오디언스 빌더 제작기

    여기어때 데이터 분석팀(UB팀)이 개발 경험 없이 AI 에이전트 바이브 코딩으로 마케팅 자동화 시스템 '오디언스 빌더'를 구축한 과정이다. SQL 없이 UI에서 타겟 조건을 설정하면 BigQuery에서 수천만 행 대상자를 추출해 Kafka·S3를 통해 마케팅 도구로 전달하는 파이프라인을 직접 만들어, 캠페인 준비 시간을 2~3일에서 10분으로 단축했다.

    요약 이어보기
    #data-pipeline#kafka#postgres+2
  2. DB / 데이터·vercel-blogVercel Blog·

    모든 Vercel 빌드를 지탱하는 데이터베이스 마이그레이션 이야기

    Vercel이 빌드 웜 풀(warm pool) 상태 데이터를 Redis에서 DynamoDB로 마이그레이션한 과정을 다룬다. 빌드 풀의 청구 매핑 데이터가 Redis 장애 시 복구 불가능하다는 구조적 위험이 이주를 결정한 핵심 이유였고, 24/7 프로덕션 트래픽 속에서 다운타임 없이 5단계 점진적 전환을 완료했다.

    요약 이어보기
    #database-migration#zero-downtime#redis+2
  3. DB / 데이터·channel-talk채널톡·

    DynamoDB 핫 파티션을 해결하는 3가지 방법 (3): 조회를 인덱스 테이블로 옮기기

    채널톡이 DynamoDB 핫 파티션을 해결하려 GSI를 별도 인덱스 테이블로 떼어낸 연재의 마지막 편으로, 조회 경로를 GSI에서 인덱스 테이블로 옮긴 과정을 다룬다. 2편에서 쓰기 파이프라인이 완성돼 WriteThrottleEvents가 월 110만 회에서 0회가 됐지만, 조회 코드가 여전히 GSI를 읽고 있어 GSI를 지울 수 없었다. GSI가 읽기에서 대신해주던 키 해석·정확 일치·삭제 필터·페이지 채우기를 사내 공통 라이브러리의 조회 규칙으로 흡수해, 호출부는 GSI든 인덱스 테이블이든 같은 코드를 쓰게 만들었다.

    요약 이어보기
    #migration#dynamodb#hot-partition+5