Data Engineering 실전 가이드북
이 문서의 사용법
이 문서는 01~13, 90 문서를 다시 읽게 만드는 실전 동선이다.
목표는 Data Engineering 용어를 많이 아는 것이 아니라, API는 정상인데 데이터가 늦거나 틀린 상황에서 어디부터 봐야 하는지 판단하는 것이다. 각 Case를 읽을 때는 먼저 상황과 관찰할 증거 또는 확인할 단서만 보고, 바로 문서를 열기 전에 잠깐 스스로 원인과 확인 순서를 적어본다.
모든 Case를 한 번에 끝낼 필요는 없다. 하루에 1~2개만 잡고, 찾아볼 문서를 실제로 열어보며 자기 말로 runbook을 다시 쓰면 된다.
학습 흐름
- 전체 흐름을 먼저 잡는다.
- 운영 DB, event, batch, stream, pipeline, quality의 경계를 나눈다.
- 장애 상황에서 source, processor, target, quality rule 순서로 확인한다.
- 재처리와 backfill을 실행하기 전에 idempotency와 checkpoint를 확인한다.
- 마지막에는 설계 리뷰 템플릿으로 스스로의 파이프라인을 점검한다.
커버리지 지도
| 기존 문서 | 연결된 Case | 역할 |
|---|---|---|
| Data Engineering 전체 지도 압축 정리 | Case 1, Case 16 | 전체 지도, 흐름의 언어 |
| 서비스 데이터 흐름 전체 지도 상세 | Case 1, Case 16 | API 이후 데이터 흐름 추적 |
| Transactional Data Event Analytical Data 상세 | Case 1, Case 16 | 데이터 유형 구분 |
| Batch Stream Pipeline 구성 요소 상세 | Case 1, Case 16 | batch/stream/pipeline 구분 |
| 개인 프로젝트와 기업 데이터 운영 수준 비교 상세 | Case 1 | 개인 프로젝트 범위 조절 |
| Data Engineering 학습 로드맵 상세 | Case 1 | 학습 순서와 산출물 |
| OLTP OLAP Data Warehouse Data Mart 압축 정리 | Case 2 | 운영/분석 경계 |
| OLTP와 OLAP 차이 상세 | Case 2 | 운영 DB 직접 분석 위험 |
| Data Warehouse와 Data Mart 상세 | Case 2 | warehouse/mart 책임 |
| Fact Dimension 기본 상세 | Case 2 | grain, fact, dimension |
| 운영 DB와 분석 DB 분리 기준 상세 | Case 2 | 분리 기준과 영향 |
| 백엔드 개발자가 알아야 할 분석 요구사항 상세 | Case 2, Case 13 | 분석 요구 질문 |
| Event Log 데이터 모델링 압축 정리 | Case 3 | event modeling 지도 |
| Event와 State 차이 상세 | Case 3 | state와 event 구분 |
| Event Schema 설계 상세 | Case 3, Case 13 | event payload 계약 |
| Event Versioning과 Compatibility 상세 | Case 3, Case 13 | schema 변경 영향 |
| Audit Log와 Domain Event 차이 상세 | Case 3 | audit/domain 분리 |
| 이벤트 모델링 실수와 리뷰 기준 상세 | Case 3, Case 16 | 이벤트 리뷰 기준 |
| Batch Scheduler Cron 압축 정리 | Case 4 | batch 운영 지도 |
| Batch와 Online Request 경계 상세 | Case 4 | 요청 경로와 batch 분리 |
| Scheduler Cron 설계 상세 | Case 4 | cron 한계 |
| Spring Scheduler와 Spring Batch 기본 상세 | Case 4 | Scheduler/Batch 선택 |
| Batch Lock 중복 실행 방지 상세 | Case 4, Case 14 | lock과 idempotent writer |
| 실패 재시도 알림 운영 상세 | Case 4, Case 14 | retry, alert, runbook |
| ETL ELT Data Pipeline 압축 정리 | Case 5 | pipeline 지도 |
| ETL과 ELT 차이 상세 | Case 5 | raw 보존과 변환 위치 |
| Extract Transform Load 단계 상세 | Case 5, Case 14 | 단계별 실패 분리 |
| Pipeline Dependency와 Checkpoint 상세 | Case 5, Case 11 | dependency와 checkpoint |
| Schema Change와 Pipeline 영향 상세 | Case 5, Case 13 | schema 변경 영향 |
| Pipeline 장애 대응 상세 | Case 5, Case 14 | pipeline 장애 대응 |
| Kafka Message Queue Event Stream 압축 정리 | Case 6, Case 15 | Kafka 지도 |
| Message Queue와 Event Stream 차이 상세 | Case 6 | queue/stream 선택 |
| Kafka Topic Partition Consumer Group 상세 | Case 6, Case 15 | partition/group 해석 |
| Ordering Offset Commit 상세 | Case 6, Case 10 | ordering/offset commit |
| Retry DLQ 중복 처리 상세 | Case 6, Case 15 | retry/DLQ 판단 |
| Spring Kafka Producer Consumer 상세 | Case 6, Case 10 | Spring Kafka 경계 |
| CDC와 Outbox Pattern 압축 정리 | Case 7 | outbox/CDC 지도 |
| Dual Write 문제 상세 | Case 7, Case 13 | dual write 위험 |
| Transactional Outbox Pattern 상세 | Case 7, Case 13 | outbox 설계 |
| CDC Debezium 기본 상세 | Case 7 | connector, snapshot, offset |
| Exactly-once 환상과 At-least-once 현실 상세 | Case 7, Case 10 | at-least-once와 멱등성 |
| Outbox 운영과 정리 전략 상세 | Case 7 | pending, failed, cleanup |
| Redis Stream PubSub 압축 정리 | Case 8 | Redis messaging 지도 |
| PubSub과 Stream 차이 상세 | Case 8 | 유실 허용 판단 |
| Redis Stream Consumer Group 상세 | Case 8 | consumer group과 claim |
| Pending Entry와 Ack 상세 | Case 8, Case 10 | pending과 ack 위치 |
| Redis 기반 이벤트 처리 한계 상세 | Case 8 | Redis 한계 |
| Kafka와 Redis Stream 선택 기준 상세 | Case 8 | Kafka/Redis 선택 |
| 중복 처리 재처리 Backfill 압축 정리 | Case 9 | 중복/재처리 지도 |
| 중복 이벤트가 발생하는 이유 상세 | Case 9, Case 10 | 중복 원인 |
| Idempotency와 Deduplication 상세 | Case 9, Case 10 | idempotency/dedup |
| Backfill 설계와 속도 제한 상세 | Case 9, Case 14 | backfill 속도 제한 |
| Reprocessing과 Checkpoint 상세 | Case 9, Case 14 | checkpoint와 reprocessing |
| 운영 데이터 재처리 Runbook 상세 | Case 9, Case 14 | 재처리 runbook |
| Idempotent Consumer 설계 압축 정리 | Case 10 | consumer 멱등성 지도 |
| Consumer 실패 모델 상세 | Case 10, Case 15 | poll, DB commit, ack 실패 |
| Idempotency Key와 처리 이력 테이블 상세 | Case 10 | processed table |
| Transaction Boundary와 Offset Commit 상세 | Case 10, Case 15 | transaction/offset 경계 |
| Side Effect 중복 방지 상세 | Case 10 | side effect 보호 |
| Idempotent Consumer 코드 예시 상세 | Case 10 | 코드 배치 |
| Airflow Workflow Orchestrator 압축 정리 | Case 11 | orchestration 지도 |
| Workflow Orchestrator가 필요한 이유 상세 | Case 11 | scheduler/orchestrator 구분 |
| DAG Task Operator 개념 상세 | Case 11 | DAG/task/operator |
| Dependency Retry SLA 상세 | Case 11 | dependency/retry/SLA |
| Airflow DAG 예시 상세 | Case 11 | DAG 리뷰 |
| 백엔드 Batch와 Airflow 경계 상세 | Case 11 | Spring Batch/Airflow 경계 |
| Data Quality Monitoring 압축 정리 | Case 12, Case 15 | 품질 모니터링 지도 |
| Data Quality Dimension 상세 | Case 12, Case 15 | 품질 차원 분류 |
| Freshness Completeness Accuracy 상세 | Case 12, Case 15 | F/C/A 구분 |
| 데이터 검증 SQL과 Rule 상세 | Case 12, Case 14 | quality rule SQL |
| 알림과 장애 등급 상세 | Case 12, Case 15 | alert severity |
| 데이터 품질 회고와 재발 방지 상세 | Case 12, Case 15 | 회고와 재발 방지 |
| 백엔드 서비스와 데이터 파이프라인 연결 압축 정리 | Case 13, Case 16 | 백엔드/파이프라인 연결 지도 |
| API Transaction과 Event 발행 경계 상세 | Case 13 | API transaction/event 경계 |
| Domain Event Integration Event 분리 상세 | Case 13 | domain/integration event |
| Reporting Search Recommendation 연동 상세 | Case 13, Case 16 | downstream 소비자 차이 |
| 데이터 팀과 백엔드 협업 상세 | Case 13, Case 16 | 협업 계약 |
| 기능 개발 시 데이터 영향 체크리스트 상세 | Case 13, Case 16 | 기능 PR 영향 점검 |
| Data Engineering 실전 플레이북 압축 정리 | Case 14, Case 15, Case 16 | 장애 대응 지도 |
| Batch 실패 대응 Runbook 상세 | Case 14 | batch incident runbook |
| Kafka Lag와 Consumer 장애 대응 상세 | Case 15 | Kafka lag runbook |
| Backfill 재처리 Runbook 상세 | Case 14 | backfill runbook |
| 데이터 품질 이상 대응 Runbook 상세 | Case 15 | 품질 이상 runbook |
| 데이터 파이프라인 설계 리뷰 템플릿 상세 | Case 16 | 설계 리뷰 템플릿 |
케이스 목록
- Case 1. API는 정상인데 데이터 흐름을 설명하지 못하는 첫날
- Case 2. 매출 리포트를 운영 DB에서 바로 뽑자는 요청
- Case 3. 주문 이벤트를 만들었는데 consumer마다 다르게 해석한다
- Case 4. 야간 배치가 두 번 돌거나 아예 건너뛰었다
- Case 5. 파이프라인은 성공인데 mart 값이 어긋난다
- Case 6. Kafka consumer lag가 두 시간 쌓였다
- Case 7. DB commit은 성공했는데 이벤트가 downstream에 없다
- Case 8. Redis Pub/Sub으로 보낸 알림이 사라졌다
- Case 9. 누락된 한 달 데이터를 다시 채워야 한다
- Case 10. 같은 주문 이벤트가 두 번 들어와 포인트가 두 번 지급됐다
- Case 11. Airflow DAG는 성공했는데 데이터는 믿을 수 없다
- Case 12. 데이터 품질 알림이 왔지만 무엇이 깨졌는지 모른다
- Case 13. 기능 PR 하나가 리포트, 검색, 추천을 흔든다
- Case 14. 월요일 아침 batch 실패 incident를 처리한다
- Case 15. Kafka lag와 품질 이상이 동시에 터졌다
- Case 16. 새 주문 데이터 파이프라인 설계를 리뷰한다
Case 1. API는 정상인데 데이터 흐름을 설명하지 못하는 첫날
상황
새 프로젝트에서 주문 API, 결제 완료 이벤트, 일별 매출 리포트, 간단한 추천 feature를 붙이기로 했다. API controller와 service 코드는 어느 정도 보이지만, 결제 후 데이터가 어디로 흘러가고 어느 지점에서 늦거나 중복될 수 있는지 말로 설명하기 어렵다.
관찰할 증거 또는 확인할 단서
order-service
POST /orders/{id}/pay
orders table update
order-paid event
daily_order_mart
recommendation_feature- API 응답은 200이다.
- 리포트는 30분 뒤 반영된다.
- 추천 feature는 하루 한 번 갱신된다.
- 장애가 나면 API log, event log, batch history 중 무엇을 먼저 봐야 할지 불명확하다.
먼저 생각해보기
- 지금 보고 있는 데이터는 transactional data, event data, analytical data 중 무엇인가?
- API 성공과 리포트 반영 성공은 왜 다른 말인가?
- 개인 프로젝트에서는 어떤 운영 흔적만 남겨도 충분한가?
찾아볼 문서
실전 행동
- API transaction이 확정하는 사실을 적는다.
- event가 외부에 알리는 사실을 적는다.
- batch, stream, mart, feature가 각각 얼마나 늦어도 되는지 적는다.
- 각 단계의 key를 하나씩 연결한다.
- 최소 운영 흔적으로 처리 이력, 실패 로그, 검증 SQL을 정한다.
정리
Data Engineering의 시작점은 도구 이름이 아니라 흐름 지도다. API, event, batch, mart, quality rule을 한 줄로 연결할 수 있어야 뒤의 장애 대응이 가능하다.
가져갈 한 문장
API가 정상이라는 말은 downstream 데이터가 정상이라는 말이 아니다.
Case 2. 매출 리포트를 운영 DB에서 바로 뽑자는 요청
상황
기획자가 “주문 테이블에 결제 금액이 있으니 운영 DB에서 바로 일별 매출을 뽑으면 안 되나요?”라고 묻는다. 처음에는 단순한 SQL 하나로 보이지만, 월말 정산일에는 트래픽도 크고 리포트 사용자가 같은 데이터를 반복 조회한다.
관찰할 증거 또는 확인할 단서
SELECT date(paid_at), sum(paid_amount)
FROM orders
WHERE paid_at >= current_date - interval '30 days'
GROUP BY date(paid_at)
ORDER BY date(paid_at);- 운영 API latency가 정산일에 증가한다.
- 환불과 부분 취소가 들어오면 기존 SQL 의미가 흔들린다.
- 리포트 사용자에게 필요한 freshness는 실시간이 아니라 30분 이내다.
먼저 생각해보기
- 이 요청은 OLTP 요구인가, OLAP 요구인가?
- fact grain을 먼저 정하지 않으면 어떤 집계 오류가 생기는가?
- replica, warehouse, mart 중 어디까지 분리해야 하는가?
찾아볼 문서
실전 행동
- 리포트가 요구하는 freshness와 정확성 기준을 묻는다.
- 운영 DB에 직접 질의해도 되는 임시 조건인지 판단한다.
- fact grain과 취소/환불 표현을 먼저 정한다.
- mart가 필요하면 source, target, 재처리 범위를 문서화한다.
- source와 mart의 count/sum 검증 SQL을 함께 만든다.
정리
운영 DB에서 SQL이 실행된다는 사실과 분석 요구를 운영할 수 있다는 사실은 다르다. 리포트는 쿼리보다 grain, freshness, 재처리 기준이 먼저다.
가져갈 한 문장
분석 요구는 컬럼 요청이 아니라 지표의 의미와 지연 허용치를 합의하는 일이다.
Case 3. 주문 이벤트를 만들었는데 consumer마다 다르게 해석한다
상황
OrderChanged 이벤트를 만들었다. 검색 팀은 이 이벤트를 상품 노출 갱신에 쓰고, 데이터 팀은 매출 mart에 쓰고, 추천 팀은 구매 feature에 쓰려고 한다. 그런데 payload에 상태값과 금액이 섞여 있고, 취소와 환불이 같은 상태 변경으로 보인다.
관찰할 증거 또는 확인할 단서
{
"type": "OrderChanged",
"orderId": 1001,
"status": "DONE",
"amount": 39000,
"changedAt": "2026-06-30T10:15:30+09:00"
}DONE이 결제 완료인지 배송 완료인지 팀마다 다르게 말한다.amount가 결제 금액인지 할인 전 금액인지 모호하다.- 이벤트 version이 없다.
- audit log와 domain event, integration event가 섞여 있다.
먼저 생각해보기
- event와 state는 어떤 질문에 다르게 답하는가?
- 이 event는 내부 domain event인가, 외부 integration event인가?
- schema 변경이 생기면 과거 event replay는 어떻게 해석되는가?
찾아볼 문서
- Event Log 압축 정리
- event와 state 차이
- event schema 기본 구조
- versioning과 compatibility
- audit log와 domain event
- event modeling 리뷰
실전 행동
- 이벤트 이름을 상태 변경이 아니라 업무 사건으로 바꿀 수 있는지 검토한다.
- eventId, eventType, eventVersion, eventTime, aggregateId를 넣는다.
- 상태값과 금액의 business meaning을 문서화한다.
- audit log와 integration event를 분리한다.
- breaking change가 필요한 변경과 optional field 추가를 구분한다.
정리
event schema는 DTO가 아니라 producer와 consumer 사이의 장기 계약이다. 모호한 event는 장애가 나기 전까지 조용히 여러 해석으로 퍼진다.
가져갈 한 문장
이벤트는 “무엇이 바뀌었나”보다 “어떤 업무 사건이 확정됐나”를 말해야 한다.
Case 4. 야간 배치가 두 번 돌거나 아예 건너뛰었다
상황
매일 새벽 3시에 정산 batch가 돈다. 어느 날 job이 2시간 넘게 실행되었고, 다음 cron이 겹치면서 같은 날짜 partition을 두 번 썼다. 다른 날에는 서버 재시작 때문에 실행 자체가 빠졌다.
관찰할 증거 또는 확인할 단서
03:00 daily-settlement started
04:59 daily-settlement still running
05:00 daily-settlement started again
05:10 first run failed while writing target
05:21 second run succeeded- job request table이 없다.
- lock은 있지만 lease와 heartbeat가 없다.
- target writer가 append 방식이다.
- 최종 실패 알림에 재처리 범위가 없다.
먼저 생각해보기
- cron은 무엇을 보장하지 않는가?
- Scheduler와 Spring Batch 중 어떤 도구가 필요한 상황인가?
- lock이 있어도 idempotent writer가 필요한 이유는 무엇인가?
찾아볼 문서
- Batch Scheduler Cron 압축 정리
- batch와 online request 경계
- cron이 보장하지 않는 것
- Scheduler와 Batch 선택 기준
- lock, lease, heartbeat
- 실패 알림 내용
실전 행동
- 실행 시각과 처리 대상 범위를 분리한다.
- job request 또는 run history를 남긴다.
- lock, lease, heartbeat를 둔다.
- target writer를 partition overwrite나 upsert로 바꾼다.
- 알림에 job name, run id, target range, last success를 포함한다.
정리
Batch 운영은 cron 등록으로 끝나지 않는다. 실행 이력, 중복 실행 방지, 재시작 가능성, 완료 검증이 함께 있어야 한다.
가져갈 한 문장
Cron은 시간을 깨우지만, 운영 안전성은 깨워주지 않는다.
Case 5. 파이프라인은 성공인데 mart 값이 어긋난다
상황
Airflow에서는 daily_order_mart DAG가 성공이다. 그런데 source orders와 mart의 결제 금액 합계가 다르다. batch log에는 error가 없고, dashboard는 전날보다 매출이 7% 낮게 나온다.
관찰할 증거 또는 확인할 단서
-- source
SELECT count(*), sum(paid_amount)
FROM orders
WHERE paid_at >= date '2026-06-30'
AND paid_at < date '2026-07-01';
-- mart
SELECT count(*), sum(paid_amount)
FROM mart_order_daily
WHERE order_date = date '2026-06-30';- extract step count는 정상이다.
- transform step에서 새 환불 상태를 제외했을 가능성이 있다.
- load step은 성공했다.
- quality rule이 DAG success와 분리되어 있지 않다.
먼저 생각해보기
- ETL/ELT 중 어느 흐름에서 raw 보존이 복구를 쉽게 만드는가?
- Extract, Transform, Load 중 어느 단계에서 실패를 분리해야 하는가?
- task 성공과 데이터 검증 성공은 왜 다른가?
찾아볼 문서
실전 행동
- source, raw, staging, mart의 count/sum을 나란히 본다.
- extract 성공과 transform 정확성을 분리한다.
- schema 변경과 새 enum이 transform에 반영됐는지 확인한다.
- checkpoint와 validation status를 job status와 따로 남긴다.
- mart를 다시 만들 때 source range와 target partition을 명시한다.
정리
파이프라인 성공은 task가 끝났다는 뜻일 뿐이다. 데이터가 맞는지는 별도의 quality rule과 source-target 검증으로 확인해야 한다.
가져갈 한 문장
성공한 task가 항상 성공한 데이터를 만들지는 않는다.
Case 6. Kafka consumer lag가 두 시간 쌓였다
상황
주문 이벤트 consumer lag가 두 시간 쌓였다. API error rate는 정상이고 DLQ도 아직 비어 있다. 팀에서는 consumer instance를 늘리자는 이야기가 나오지만, 특정 partition만 lag가 큰지 전체 partition이 느린지 아직 모른다.
관찰할 증거 또는 확인할 단서
GROUP: order-mart-consumer
TOPIC: order-paid
PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
0 120300 120350 50
1 98410 143000 44590
2 119980 120010 30- partition 1의 lag만 크다.
- 최근 특정 대형 고객 주문이 몰렸다.
- consumer log에는 timeout이 간헐적으로 보인다.
먼저 생각해보기
- 전체 lag와 partition lag는 어떤 질문에 다르게 답하는가?
- partition key skew와 poison message를 어떻게 구분할 수 있는가?
- offset commit은 business 처리 완료와 같은가?
찾아볼 문서
- Kafka 압축 정리
- queue와 stream 차이
- partition key
- offset commit
- retry와 DLQ
- Spring Kafka consumer
- Kafka lag runbook
실전 행동
- partition별 lag와 consumer assignment를 본다.
- hot key인지 poison message인지 분류한다.
- downstream DB latency와 consumer processing time을 본다.
- 재시작 전에 DLT, retry policy, idempotent handler를 확인한다.
- lag가 줄어도 target freshness와 quality rule을 확인한 뒤 종료한다.
정리
Kafka lag는 하나의 원인이 아니다. partition별 분포, error log, downstream latency, offset commit 경계를 함께 봐야 한다.
가져갈 한 문장
Lag가 늘었다고 consumer부터 늘리면, 어떤 병목을 키우는지 모른 채 손을 대는 것이다.
Case 7. DB commit은 성공했는데 이벤트가 downstream에 없다
상황
결제 API는 성공했고 orders table 상태도 PAID다. 그런데 Kafka topic에는 OrderPaid가 없고, 검색과 리포트 모두 반영되지 않았다. 코드에는 @Transactional 안에서 DB 저장 후 kafkaTemplate.send()를 호출하는 부분이 있다.
관찰할 증거 또는 확인할 단서
@Transactional
public void pay(Long orderId) {
Order order = orderRepository.getById(orderId);
order.pay();
orderRepository.save(order);
kafkaTemplate.send("order-paid", new OrderPaidEvent(orderId));
}- broker 장애가 짧게 있었다.
- API 응답은 성공했다.
- outbox table은 없다.
- consumer 쪽에서는 event 자체를 받은 기록이 없다.
먼저 생각해보기
- DB commit과 Kafka publish를 같은 transaction처럼 볼 수 있는가?
- outbox는 어떤 실패를 줄이고 어떤 실패는 남기는가?
- CDC를 붙이면 consumer 멱등성이 필요 없을까?
찾아볼 문서
- CDC와 Outbox 압축 정리
- dual write 실패 matrix
- transactional outbox
- CDC Debezium
- at-least-once 현실
- outbox pending 운영
- API transaction과 event 발행 경계
실전 행동
- DB commit 성공과 event 발행 성공을 분리해서 기록한다.
- outbox table을 통해 업무 변경과 event 기록을 같은 DB transaction에 묶는다.
- publisher lag, failed event, pending age를 관측한다.
- consumer는 eventId 기반 processed table로 중복을 흡수한다.
- cleanup은 published 보관 기간과 재처리 가능성을 기준으로 정한다.
정리
Outbox는 “발행이 반드시 한 번만 된다”는 보장이 아니라, 발행해야 할 사실을 잃지 않게 만드는 운영 구조다.
가져갈 한 문장
Dual write 문제는 코드 순서 문제가 아니라 서로 다른 시스템의 commit 경계 문제다.
Case 8. Redis Pub/Sub으로 보낸 알림이 사라졌다
상황
주문 완료 후 내부 알림을 Redis Pub/Sub으로 발행했다. 평소에는 잘 동작했지만, consumer 배포 중 몇 분 동안 subscriber가 내려가면서 일부 알림이 사라졌다. 팀에서는 Redis Stream으로 바꾸면 충분한지, Kafka까지 가야 하는지 논의 중이다.
관찰할 증거 또는 확인할 단서
event: OrderPaid
expected notifications: 1400
sent notifications: 1321
subscriber downtime: 3 minutes
redis stream trim policy: MAXLEN 10000- 알림은 짧은 내부 비동기 처리다.
- 일부 알림은 다시 읽어야 한다.
- 장기 replay 요구는 아직 없다.
- pending entry를 누가 claim할지 정해져 있지 않다.
먼저 생각해보기
- Pub/Sub과 Stream은 유실 조건이 어떻게 다른가?
- Redis Stream의 pending entry는 어떤 상태를 뜻하는가?
- Redis Stream이 Kafka를 완전히 대체하지 못하는 지점은 무엇인가?
찾아볼 문서
- Redis Stream PubSub 압축 정리
- Sub과 Stream 차이
- consumer group과 pending
- pending entry와 ack
- Redis 기반 이벤트 처리 한계
- Kafka와 Redis Stream 선택
실전 행동
- 메시지가 사라져도 되는지 먼저 묻는다.
- 다시 읽어야 한다면 Stream ID, consumer group, pending 관리를 설계한다.
- business 처리 완료 후
XACK하도록 경계를 잡는다. - trim 정책이 replay 요구를 깨지 않는지 확인한다.
- 장기 보관, fan-out, partition 확장이 필요해지면 Kafka 전환 기준을 문서화한다.
정리
Redis Pub/Sub은 접속 중인 subscriber에게 빠르게 알리는 도구다. 다시 읽어야 하는 순간부터 Stream, pending, ack, trim 정책을 함께 설계해야 한다.
가져갈 한 문장
Redis를 쓴다는 말보다 메시지가 사라져도 되는지가 먼저다.
Case 9. 누락된 한 달 데이터를 다시 채워야 한다
상황
환불 금액 계산식이 잘못되어 최근 한 달 mart를 다시 만들어야 한다. 단순히 batch를 다시 돌리면 될 것 같지만, 운영 DB 부하와 target 중복, 실패 후 재시작 위치가 걱정된다.
관찰할 증거 또는 확인할 단서
target range: 2026-06-01 ~ 2026-06-30
source: orders, order_refunds
target: mart_order_daily
expected rows: 4,200,000
write mode: unknown
checkpoint: none- 기존 target row를 삭제할지 upsert할지 정해지지 않았다.
- dry run이 없다.
- 처리 속도 제한과 stop condition이 없다.
- 완료 검증 SQL이 없다.
먼저 생각해보기
- backfill은 단순 재실행과 무엇이 다른가?
- idempotency와 deduplication은 어떤 역할을 나누는가?
- checkpoint를 어디에 남겨야 실패 후 이어갈 수 있는가?
찾아볼 문서
- 재처리 압축 정리
- backfill과 replay
- idempotency와 deduplication
- backfill 실행 계획
- checkpoint 종류
- 재처리 runbook
- backfill 실전 runbook
실전 행동
- source, target, range, unit, write mode를 먼저 적는다.
- dry run으로 row 수와 예상 시간을 확인한다.
- checkpoint table과 run id를 만든다.
- batch size, sleep, stop condition을 정한다.
- source-target count/sum과 quality rule 통과를 완료 기준으로 둔다.
정리
Backfill은 과거 데이터를 다시 흘리는 운영 작업이다. 범위, 속도, 중단, 검증이 없으면 복구가 아니라 새로운 장애가 된다.
가져갈 한 문장
Backfill은 다시 돌리는 일이 아니라 다시 돌려도 안전하다는 것을 증명하는 일이다.
Case 10. 같은 주문 이벤트가 두 번 들어와 포인트가 두 번 지급됐다
상황
Kafka consumer가 주문 결제 이벤트를 받아 포인트를 지급한다. 어느 날 consumer가 DB commit 후 ack 전에 죽었고, 같은 이벤트가 다시 들어왔다. 포인트 지급은 두 번 실행되었지만 consumer offset만 보면 정상처럼 보인다.
관찰할 증거 또는 확인할 단서
eventId: evt-1001
orderId: order-1001
consumer crash: after DB commit, before offset commit
point_transactions:
- order-1001, 390 points
- order-1001, 390 points- processed event table이 없다.
- 외부 side effect idempotency key가 없다.
- offset commit을 처리 완료 기록으로 오해하고 있다.
먼저 생각해보기
- at-least-once 전제에서는 왜 중복 입력을 정상 조건으로 봐야 하는가?
- offset commit과 business side effect는 어떤 점에서 다르다?
- 이메일, 포인트, 외부 API 호출은 DB update보다 왜 위험한가?
찾아볼 문서
- Idempotent Consumer 압축 정리
- consumer 실패 모델
- processed table
- transaction과 offset commit
- side effect 중복 방지
- consumer 코드 예시
- ack 위치별 장애
실전 행동
- consumerName과 eventId로 처리 이력 table을 만든다.
- 처리 이력 insert와 business update를 같은 transaction에 둔다.
- duplicate branch에서는 side effect를 실행하지 않는다.
- provider idempotency key 또는 local side effect log를 둔다.
- 같은 event를 두 번 넣는 테스트와 ack 전 crash 테스트를 만든다.
정리
Consumer 멱등성은 중복을 피하는 기술이 아니라 중복이 와도 결과가 한 번으로 수렴하게 만드는 설계다.
가져갈 한 문장
Offset은 Kafka의 위치이고, 멱등성은 비즈니스 결과의 안전장치다.
Case 11. Airflow DAG는 성공했는데 데이터는 믿을 수 없다
상황
Airflow DAG는 초록색이다. extract, transform, load task 모두 성공했다. 그런데 리포트 사용자는 오늘 데이터가 늦다고 말한다. DAG에는 validation task가 없고, Spring Batch job parameter가 실행 시각에 묶여 있다.
관찰할 증거 또는 확인할 단서
DAG run: success
logical date: 2026-06-30
task: extract success
task: transform success
task: load success
quality rule: not executed
latest mart event_time: 2026-06-29 23:20- DAG 성공과 데이터 freshness가 분리되어 있지 않다.
- task boundary가 넓어 재처리 범위가 크다.
- Airflow DAG 안에 업무 로직이 들어가려 한다.
먼저 생각해보기
- scheduler와 orchestrator는 무엇이 다른가?
- DAG success와 data success는 왜 분리해야 하는가?
- Airflow는 Spring Batch의 어떤 책임을 가져가면 안 되는가?
찾아볼 문서
- Airflow 압축 정리
- DAG 성공과 데이터 성공
- task boundary
- SLA miss
- validation task
- 백엔드 Batch와 Airflow 경계
- pipeline validation
실전 행동
- DAG task 성공과 quality rule 성공을 별도 상태로 기록한다.
- logical date와 data interval을 parameter로 명시한다.
- task boundary를 extract/build/validate처럼 복구 가능한 단위로 나눈다.
- 업무 로직은 Spring Batch나 서비스 코드에 두고 Airflow는 orchestration을 맡긴다.
- SLA miss와 task failure 알림을 분리한다.
정리
Airflow는 workflow를 보이게 하지만 데이터의 의미를 대신 검증하지 않는다. DAG에는 validation task와 재처리 parameter가 함께 있어야 한다.
가져갈 한 문장
DAG가 초록색이어도 데이터가 초록색이라는 뜻은 아니다.
Case 12. 데이터 품질 알림이 왔지만 무엇이 깨졌는지 모른다
상황
Slack에 mart_order_daily quality failed 알림이 왔다. 메시지에는 실패했다는 말만 있고 freshness, completeness, accuracy 중 무엇이 깨졌는지 없다. 담당자는 대시보드를 보며 감으로 원인을 찾기 시작한다.
관찰할 증거 또는 확인할 단서
[Data Quality] mart_order_daily failed
dataset: mart_order_daily
rule: daily_order_check
status: failed
details: null
owner: unknown- rule dimension이 없다.
- severity가 없다.
- owner와 runbook URL이 없다.
- source-target count/sum 비교가 없다.
먼저 생각해보기
- freshness, completeness, accuracy는 각각 어떤 다른 장애를 뜻하는가?
- 좋은 quality rule metadata에는 무엇이 있어야 하는가?
- 회고에서 사람 실수보다 어떤 시스템 결함을 찾아야 하는가?
찾아볼 문서
실전 행동
- 실패 rule을 freshness, completeness, accuracy, validity, consistency로 분류한다.
- dataset, partition, owner, severity, runbook을 알림에 포함한다.
- source와 target의 count, sum, latest time을 비교한다.
- 영향이 크면 dashboard banner나 export pause 같은 완화 조치를 먼저 한다.
- 회고에서 누락된 rule, owner, alert routing을 action item으로 바꾼다.
정리
품질 알림은 실패 사실을 말하는 메시지가 아니라 다음 행동을 결정하게 만드는 운영 신호다.
가져갈 한 문장
품질 장애는 먼저 어떤 차원이 깨졌는지 이름 붙여야 복구 순서가 보인다.
Case 13. 기능 PR 하나가 리포트, 검색, 추천을 흔든다
상황
주문 상태에 PARTIALLY_REFUNDED가 추가된다. API 변경은 작아 보이지만, 매출 리포트, 검색 필터, 추천 feature가 모두 주문 상태를 사용한다. PR 설명에는 API 응답 변경만 적혀 있다.
관찰할 증거 또는 확인할 단서
change: orderStatus enum add PARTIALLY_REFUNDED
API: refund endpoint
event: OrderRefunded v1 unchanged
reporting: net revenue mart
search: order search filter
recommendation: purchase positive signal- integration event version 변경 여부가 없다.
- quality rule 변경 여부가 없다.
- backfill 필요성이 검토되지 않았다.
- 데이터 팀 owner가 리뷰어에 없다.
먼저 생각해보기
- API transaction과 event 발행 경계는 어디인가?
- domain event와 integration event를 분리해야 하는 이유는 무엇인가?
- 같은 상태 변경이 reporting, search, recommendation에 다르게 영향을 주는 이유는 무엇인가?
찾아볼 문서
- 백엔드-파이프라인 연결 압축 정리
- API transaction과 event 경계
- integration event 분리
- recommendation 차이
- 데이터 팀 협업
- 기능 개발 데이터 영향 체크리스트
- event compatibility
실전 행동
- enum 추가가 event schema, mart metric, search index, feature에 주는 영향을 적는다.
- integration event version 변경이 필요한지 판단한다.
- quality rule과 alert threshold 변경 여부를 확인한다.
- 기존 데이터 backfill 또는 reprocessing이 필요한지 확인한다.
- 데이터 팀과 business owner를 PR reviewer에 포함한다.
정리
백엔드 기능 변경은 운영 DB 안에서 끝나지 않는다. 이벤트와 downstream 소비자가 있으면 작은 enum 하나도 데이터 제품 전체의 의미를 바꿀 수 있다.
가져갈 한 문장
기능 PR에는 API 영향뿐 아니라 데이터 영향도 같이 적어야 한다.
Case 14. 월요일 아침 batch 실패 incident를 처리한다
상황
월요일 오전 9시, 재무팀이 “주말 매출 대시보드가 멈춰 있다”고 말한다. daily_order_mart batch는 토요일 새벽에 실패했고, 일요일 run은 skipped 상태다. 누군가 바로 rerun을 누르려 한다.
관찰할 증거 또는 확인할 단서
job: daily_order_mart
last_success: 2026-06-27 03:12
failed_run: 2026-06-28 03:00
skipped_run: 2026-06-29 03:00
target range: unknown
writer mode: append
quality rule: not checked- 마지막 정상 지점과 영향 범위가 불명확하다.
- writer가 append라 중복 위험이 있다.
- downstream 공개 중단 여부가 결정되지 않았다.
먼저 생각해보기
- 원인 분석 전에 무엇을 먼저 확인해야 하는가?
- 재실행과 재처리는 왜 다른가?
- batch success 후에도 어떤 품질 검증이 필요한가?
찾아볼 문서
- 실전 플레이북 압축 정리
- Batch 실패 첫 10분
- Backfill 실행 전 결정
- idempotent writer
- batch 실패 알림
- 단계별 기록
- 운영 데이터 재처리 runbook
- accuracy 검증 SQL
실전 행동
- rerun 전에 last success와 affected range를 확정한다.
- dashboard banner 또는 export pause가 필요한지 결정한다.
- target writer가 append인지 overwrite/upsert인지 확인한다.
- dry run으로 source row 수와 예상 처리량을 본다.
- 실행 command에 range, mode, run id를 남긴다.
- source-target count/sum과 quality rule 통과를 확인한 뒤 종료한다.
정리
Incident 중에는 원인보다 영향 범위와 안전한 복구 경계가 먼저다. 재실행 버튼은 runbook의 끝에 있어야 한다.
가져갈 한 문장
Rerun은 판단이 아니라 판단이 끝난 뒤의 실행이다.
Case 15. Kafka lag와 품질 이상이 동시에 터졌다
상황
주문 이벤트 consumer lag가 증가했고, 동시에 mart_order_daily freshness rule이 실패했다. API는 정상이다. 팀은 Kafka 문제인지 mart 품질 문제인지 나누지 못하고 있다.
관찰할 증거 또는 확인할 단서
consumer_group_lag: 180000
max_partition_lag: 120000
dlt_count: 0
latest_mart_event_time: 3 hours ago
quality_rule: freshness failed
dashboard impact: finance stale- lag가 특정 partition에 몰려 있다.
- freshness는 실패했지만 accuracy는 아직 확인하지 않았다.
- 재처리 완료 기준이 lag 0으로만 잡혀 있다.
먼저 생각해보기
- Kafka lag가 원인인지, 품질 이상이 결과인지 어떻게 연결할 수 있는가?
- freshness가 회복되어도 completeness와 accuracy를 봐야 하는 이유는 무엇인가?
- lag 복구 후 target 검증은 무엇으로 닫아야 하는가?
찾아볼 문서
- Kafka lag 원인 분류
- 데이터 품질 이상 첫 분류
- rebalance와 lag
- DLT 운영
- consumer 실패 matrix
- offset commit 원자성 한계
- freshness
- A 조합
- 장애 등급 기준
- 재발 방지 action
실전 행동
- partition lag 분포와 consumer error를 본다.
- freshness 장애가 어떤 dataset과 사용자에게 영향을 주는지 확인한다.
- lag 원인을 hot key, poison message, downstream DB 병목으로 나눈다.
- lag가 줄어든 뒤 source-target count/sum과 accuracy rule도 확인한다.
- 회고에서 lag alert, quality alert, owner routing이 충분했는지 바꾼다.
정리
Kafka lag와 데이터 품질 이상은 따로 보이지만 같은 흐름의 앞뒤일 수 있다. stream 상태와 target 품질을 함께 닫아야 한다.
가져갈 한 문장
Lag 0은 복구의 시작일 수 있지만, 품질 검증 통과가 복구의 끝이다.
Case 16. 새 주문 데이터 파이프라인 설계를 리뷰한다
상황
새 주문 데이터 파이프라인 설계 문서가 올라왔다. 구성도에는 API, Kafka, Airflow, warehouse, mart가 예쁘게 그려져 있다. 하지만 source contract, key, replay 범위, 품질 rule, owner가 충분히 적혀 있는지는 아직 모른다.
관찰할 증거 또는 확인할 단서
source: order-service
topic: order-paid
target: daily_order_mart
orchestrator: Airflow
freshness: near real time
replay: TBD
quality rules: TBD
owner: TBDnear real time의 의미가 모호하다.- key와 grain이 한 문장으로 정리되어 있지 않다.
- schema compatibility와 backfill 계획이 없다.
- 장애 때 어느 팀이 어떤 runbook을 실행할지 없다.
먼저 생각해보기
- 설계 리뷰에서 도구 구성도보다 먼저 봐야 할 것은 무엇인가?
- source, key, schema, SLA, replay, quality rule 중 비어 있는 항목은 무엇인가?
- 이 설계가 개인 프로젝트 수준인지 기업 운영 수준인지 어떻게 판단하는가?
찾아볼 문서
- 설계 리뷰 목적과 사용자
- key와 grain
- 운영자가 추적할 좌표
- pipeline 구성
- event modeling 리뷰
- quality rule 구성
- downstream별 계약
- 협업 계약 템플릿
- 데이터 영향 체크리스트
실전 행동
- 이 파이프라인의 사용자와 잘못된 데이터의 영향을 먼저 적는다.
- source owner, eventTime, schema version, delete policy를 확인한다.
- primary key, business key, partition key, target grain을 분리한다.
- failure mode마다 감지, owner, retry, DLT, replay 시작점을 적는다.
- freshness, completeness, accuracy rule을 최소 1개씩 요구한다.
- 리뷰 결과를 배포 조건과 runbook 보강 항목으로 남긴다.
정리
설계 리뷰는 도구 선택을 승인하는 자리가 아니다. 나중에 장애가 났을 때 어디부터 다시 처리하고 무엇으로 검증할지 미리 묻는 자리다.
가져갈 한 문장
운영 가능한 파이프라인은 그려진 구조가 아니라 다시 처리하고 검증할 수 있는 구조다.
마무리 점검
아래 질문에 답할 수 있으면 이 가이드북의 1회독은 끝난다.
- API는 정상인데 데이터가 늦거나 틀릴 때 어떤 순서로 source, event, processor, target, quality rule을 확인할 것인가?
- Kafka lag, batch failure, quality alert, backfill 요청은 각각 어떤 runbook으로 들어가야 하는가?
- event schema 변경과 기능 PR이 downstream에 주는 영향을 어떤 체크리스트로 확인할 것인가?
- 재처리 완료를 command 종료가 아니라 source-target 검증으로 닫을 수 있는가?
- 내가 만든 개인 프로젝트에는 최소한 어떤 처리 이력, 실패 로그, 품질 rule, runbook이 있는가?
마지막으로, 각 Case의 가져갈 한 문장만 따로 적어보면 좋다. 그 문장들이 실제 장애나 설계 리뷰 때 바로 떠오르면, 문서는 읽은 것이 아니라 몸에 들어온 것이다.