이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- transactional data, event data, analytical data는 각각 무엇을 사실로 삼는가?
- 운영 DB row와 event log와 mart row를 같은 데이터로 보면 어떤 문제가 생기는가?
- Java/Spring 서비스에서 세 데이터 유형의 key와 생명주기는 어떻게 다르게 잡는가?
- 장애나 재처리 때 어떤 데이터를 기준으로 복원해야 하는가?
개요
transactional data는 현재 업무 상태다.
event data는 이미 발생한 사건의 기록이다.
analytical data는 의사결정과 조회 효율을 위해 재구성한 파생 데이터다.
세 데이터는 모두 주문이라는 같은 현실을 다룰 수 있지만, 목적과 수정 방식과 신뢰 기준이 다르다.
이 차이를 모르면 운영 DB를 리포트 원장처럼 쓰거나, event log를 현재 상태 table처럼 덮어쓰거나, mart를 서비스 정합성의 원본처럼 오해하게 된다.
원리
데이터 유형은 저장 위치보다 질문으로 구분한다.
- transactional data: “지금 주문 상태가 무엇인가?”
- event data: “어떤 일이 언제 발생했는가?”
- analytical data: “기간, 상품, 회원 기준으로 어떤 패턴이 보이는가?”
백엔드 개발자는 API 개발 중 이 세 질문을 섞지 않아야 한다.
운영 DB는 빠른 변경과 정합성이 중요하고, event log는 순서와 불변성이 중요하고, 분석 데이터는 재계산 가능성과 정의 일관성이 중요하다.
비교
| 구분 | 예시 | 핵심 key | 변경 방식 | 실패 시 기준 |
|---|---|---|---|---|
| Transactional Data | orders.status | order_id | update 가능 | 운영 DB transaction |
| Event Data | OrderPaid event | event_id, order_id | append | event log, offset |
| Analytical Data | fact_order_daily | order_date, product_id | 재계산 | source range, batch run |
같은 주문 취소라도 유형별 표현이 다르다.
- transactional data: 주문 row의 상태가
CANCELED로 바뀐다. - event data:
OrderCanceled사건이 append 된다. - analytical data: 일별 주문 금액 fact에서 취소 금액이 반영된다.
Spring 서비스 예시
@Transactional
public void payOrder(Long orderId, Money amount) {
Order order = orderRepository.getById(orderId);
order.pay(amount);
outboxRepository.save(new OutboxEvent(
"OrderPaid",
order.getId().toString(),
order.toPaidEventPayload()
));
}이 코드에서 orders row는 transactional data다.
OutboxEvent row는 event data로 넘어가기 위한 기록이다.
나중에 warehouse에 적재되는 fact_order_payment는 analytical data다.
흐름
orders table
order_id=10, status=PAID, paid_amount=50000
outbox_event
event_id=e-991, type=OrderPaid, aggregate_id=10
Kafka order.events
key=10, value={eventId:e-991, paidAt:...}
warehouse fact_order_payment
payment_date=2026-06-30, order_id=10, amount=50000
mart sales_daily
order_date=2026-06-30, gross_sales=...흐름이 길어질수록 “원본”이라는 말을 신중하게 써야 한다.
운영 판단의 원본은 transactional data일 수 있고, 재처리의 원본은 event data일 수 있으며, 리포트의 원본은 검증된 warehouse table일 수 있다.
판단 축
- 현재 상태를 묻는 요구사항이면 transactional data를 먼저 본다.
- 발생 이력을 묻는 요구사항이면 event data를 남긴다.
- 반복 집계와 분석을 묻는 요구사항이면 analytical data로 분리한다.
- event는 수정하지 않고 보정 event나 재처리 rule로 의미를 바로잡는다.
- analytical data는 source range와 transformation version을 남겨 다시 만들 수 있어야 한다.
실패 모델
- event payload에 현재 상태 snapshot만 담아 과거 사건의 의미를 잃는다.
- analytical mart를 API 응답의 원본으로 사용해 지연 데이터가 사용자 상태를 흔든다.
- 운영 DB row를 직접 집계해 리포트마다 취소, 환불, 테스트 주문 기준이 달라진다.
- event schema 변경 후 예전 event를 재처리할 수 없다.
- fact table의 집계 기준을 바꾸면서 과거 mart를 재계산하지 않는다.
운영 지표
event_append_count: transactional 변화가 event로 남았는지 확인한다.event_schema_version_distribution: 재처리 가능한 schema 버전 범위를 본다.source_to_warehouse_lag: 운영 DB commit과 warehouse 반영 사이의 지연이다.mart_rebuild_success_count: analytical data 재계산 성공 여부다.metric_definition_change_count: 지표 정의 변경이 운영에 미친 범위를 추적한다.
데이터 품질 관점
transactional data의 품질은 제약 조건과 transaction 경계에서 시작한다.
event data의 품질은 중복, 순서, schema compatibility에서 드러난다.
analytical data의 품질은 freshness, completeness, accuracy rule로 확인한다.
SELECT order_date, COUNT(*) AS fact_count, SUM(paid_amount) AS fact_amount
FROM fact_order_payment
WHERE order_date = DATE '2026-06-30'
GROUP BY order_date;이 SQL은 분석 결과를 검증하지만, 주문 상태 변경의 정합성을 보장하지는 않는다.
개인 프로젝트 기준
개인 프로젝트에서는 세 저장소를 모두 분리하지 않아도 된다.
하나의 PostgreSQL 안에 orders, order_events, order_daily_summary를 두어도 충분하다.
중요한 것은 table 이름보다 변경 가능한 현재 상태, append-only 사건, 재계산 가능한 요약을 구분하는 것이다.
기업 운영 기준
기업 운영에서는 각 데이터 유형의 owner가 다를 수 있다.
주문 서비스 팀은 transactional data와 domain event의 의미를 책임진다.
데이터 플랫폼 팀은 event ingestion, warehouse 적재, mart build를 책임진다.
분석 팀은 metric definition과 dashboard 해석을 책임진다.
경계가 명확해야 장애 때 “어느 데이터가 틀렸는가”를 빠르게 좁힌다.
실전 팁
- API 요구사항에 “통계”가 나오면 transactional query로 바로 풀기 전에 지연 허용치를 묻는다.
- event 이름은 과거형 사건으로 짓고, 현재 상태 이름으로 짓지 않는다.
- fact table에는 측정값과 grain을 먼저 정하고 dimension을 붙인다.
- 데이터 수정 요청이 들어오면 현재 상태 보정인지, event 보정인지, mart 재계산인지 분리한다.
- 재처리 runbook에는 어떤 유형의 데이터를 source of truth로 삼는지 적는다.
위험 신호!
- 운영 DB row 하나로 현재 상태, 이력, 분석 집계를 모두 해결하려 한다.
- event payload가 매번 다른 의미의 필드를 담는다.
- dashboard 수치가 틀렸는데 어떤 source range에서 다시 계산할지 모른다.
- mart table을 수동 update로 고친 뒤 transformation을 수정하지 않는다.
- 과거 event를 읽을 consumer가 schema version을 구분하지 못한다.
확인 질문
확인 질문
- 주문 취소를 transactional, event, analytical data로 각각 어떻게 표현하는가?
- 주문 row 상태 변경,
OrderCanceledevent append, 취소가 반영된 fact/mart 재계산으로 나눈다.- 리포트 수치가 틀렸을 때 바로 운영 DB를 수정하면 안 되는 이유는 무엇인가?
- 리포트 오류는 event 적재, transformation, mart build, metric definition 문제일 수 있어서 원본 상태 수정과 구분해야 한다.
- event data를 재처리 source로 쓰려면 무엇이 필요한가?
- event id, aggregate key, 발생 시각, schema version, 순서 또는 offset, idempotent consumer가 필요하다.
참고 문서
- Apache Kafka Documentation
- Debezium Documentation
- Martin Kleppmann, Designing Data-Intensive Applications
- Kimball Group, Dimensional Modeling Techniques