이 글은 누구를 위한 것인가
- 실시간 판매 현황을 모니터링하고 빠른 의사결정을 하고 싶은 이커머스 운영팀
- 이커머스 데이터 파이프라인을 처음 구축하는 데이터 엔지니어
- 현재 배치 처리 방식에서 실시간 스트리밍으로 전환하려는 팀
들어가며
"지금 얼마나 팔리고 있나?"라는 질문에 즉각 답할 수 있는가? 많은 이커머스가 전날 데이터를 오전에 확인하거나, 심지어 주간 리포트로 현황을 파악한다. 이런 지연은 프로모션 효과 측정, 재고 문제 감지, 시스템 장애 탐지를 늦춘다.
실시간 분석 파이프라인은 주문, 결제, 반품, 재고 이벤트를 수초 내에 집계하여 의사결정 속도를 높인다. 블랙프라이데이에 특정 카테고리가 폭발적으로 팔리는 순간 재고 보충과 프로모션을 즉시 조정할 수 있다.
이 글은 bluefoxdev.kr의 데이터 엔지니어링 파이프라인 설계 를 참고하고, 이커머스 실시간 분석 관점에서 확장하여 작성했습니다.
1. 파이프라인 전체 아키텍처
[이커머스 서비스]
주문 서비스 결제 서비스 재고 서비스 회원 서비스
↓ ↓ ↓ ↓
[Kafka]
order-events payment-events inventory-events
↓
[Kafka Consumer]
Flink / Spark Streaming
↓
[저장소]
ClickHouse (실시간) BigQuery (배치/ML)
↓
[시각화]
Grafana Metabase 자체 대시보드
↓
[알림]
Slack / PagerDuty (이상 탐지)
2. 이벤트 스키마 설계
2.1 주문 이벤트
interface OrderCreatedEvent {
eventType: 'ORDER_CREATED';
eventId: string;
occurredAt: string; // ISO 8601
orderId: string;
userId: string;
totalAmount: number;
itemCount: number;
channel: 'web' | 'mobile_app' | 'mobile_web';
couponUsed: boolean;
paymentMethod: string;
items: Array<{
productId: string;
categoryId: string;
quantity: number;
unitPrice: number;
}>;
shipping: {
region: string; // 배송 권역
type: 'standard' | 'express' | 'pickup';
};
}
2.2 Kafka Producer 구현
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'order-service',
brokers: ['kafka-1:9092', 'kafka-2:9092', 'kafka-3:9092'],
});
const producer = kafka.producer({
idempotent: true, // 중복 방지
});
export async function publishOrderEvent(event: OrderCreatedEvent): Promise<void> {
await producer.send({
topic: 'order-events',
messages: [{
key: event.orderId,
value: JSON.stringify(event),
headers: {
eventType: event.eventType,
version: '1.0',
},
}],
});
}
3. 실시간 집계: ClickHouse
3.1 ClickHouse 테이블 설계
-- 주문 이벤트 원본 테이블
CREATE TABLE order_events (
event_id String,
occurred_at DateTime64(3), -- 밀리초 정밀도
order_id String,
user_id String,
total_amount Decimal(12, 2),
item_count UInt16,
channel LowCardinality(String),
payment_method LowCardinality(String),
region LowCardinality(String),
category_ids Array(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(occurred_at)
ORDER BY (occurred_at, order_id);
-- 실시간 집계 뷰 (분 단위)
CREATE MATERIALIZED VIEW orders_per_minute
ENGINE = SummingMergeTree()
ORDER BY (minute, channel)
AS SELECT
toStartOfMinute(occurred_at) AS minute,
channel,
count() AS order_count,
sum(total_amount) AS total_revenue,
avg(total_amount) AS avg_order_value
FROM order_events
GROUP BY minute, channel;
3.2 실시간 KPI 쿼리
-- 지난 1시간 채널별 매출 현황
SELECT
channel,
count() AS orders,
sum(total_amount) AS revenue,
avg(total_amount) AS aov,
countIf(item_count > 1) / count() * 100 AS multi_item_rate_pct
FROM order_events
WHERE occurred_at >= now() - INTERVAL 1 HOUR
GROUP BY channel
ORDER BY revenue DESC;
-- 1분 단위 매출 추이 (최근 2시간)
SELECT
toStartOfMinute(occurred_at) AS minute,
count() AS orders,
sum(total_amount) AS revenue
FROM order_events
WHERE occurred_at >= now() - INTERVAL 2 HOUR
GROUP BY minute
ORDER BY minute;
4. Grafana 대시보드 설계
4.1 필수 패널 구성
[이커머스 실시간 대시보드]
Row 1: 핵심 KPI (Big Number)
- 오늘 주문 수 - 오늘 매출
- 전일 대비 주문 - 전일 대비 매출
- 현재 진행 주문 - 결제 성공률
Row 2: 시계열 트렌드 (Time Series)
- 분당 주문 수 추이
- 분당 매출 추이
- 채널별 주문 분포
Row 3: 카테고리/상품 (Bar Chart, Table)
- 실시간 인기 카테고리 Top 10
- 실시간 인기 상품 Top 20
- 지역별 주문 히트맵
Row 4: 시스템 상태
- 결제 성공/실패율
- 평균 결제 처리 시간
- 재고 부족 알림
4.2 이상 탐지 알림 설정
# Grafana Alert Rule 예시
- name: "주문량 급감 감지"
condition: |
orders_per_minute <
avg(orders_per_minute offset 1w) * 0.5
for: 5m
annotations:
summary: "주문량이 평소 대비 50% 이하"
description: "결제 시스템 또는 서비스 장애 가능성"
labels:
severity: critical
notification:
channels: ["slack-ops", "pagerduty"]
5. 성능 최적화 팁
| 항목 | 권장 설정 | 이유 |
|---|---|---|
| Kafka 파티션 수 | 주문 서비스 인스턴스 × 3 | 병렬 처리 극대화 |
| ClickHouse 샤드 | 일일 데이터 100GB당 1샤드 | 쿼리 병렬화 |
| Grafana 새로고침 | 30초 간격 | DB 부하 방지 |
| 데이터 보존 기간 | 원시 3개월, 집계 2년 | 스토리지 비용 절감 |
마무리: 구축 우선순위
- 1주 차: Kafka 클러스터 구성 + 주문 이벤트 발행
- 2주 차: ClickHouse 설치 + Consumer 구현 + 기본 쿼리
- 3주 차: Grafana 연동 + 기본 대시보드 완성
- 4주 차: 이상 탐지 알림 + 슬랙 연동
실시간 파이프라인은 완벽하게 시작하려 하지 말고, 일단 주문 이벤트 하나로 시작해서 점진적으로 확장하는 것이 현실적이다.