Data Engineering 실전 훈련북
이 문서의 사용법
이 문서는 01~13, 90, 91 문서를 다시 읽게 만드는 문제집이다.
먼저 정답을 읽지 않는다. 각 훈련에서 상황과 Evidence만 보고 내 판단을 쓴 뒤, 힌트와 해설을 확인한다. 그 다음 연결 문서를 열어 원래 문서의 판단 기준을 다시 읽는다.
91. Data Engineering 실전 가이드북이 어디로 가야 할지 알려주는 지도라면, 이 문서는 그 지도를 보지 않고도 손이 움직이게 만드는 반복 훈련장이다.
훈련 방식
- 상황과 Evidence만 읽는다.
먼저 써보기에 내 답을 짧게 적는다.- 힌트를 확인한다.
- 해설과 비교한다.
- 연결 문서를 실제로 다시 연다.
- 반복 기록표에 놓친 관점을 남긴다.
답안은 길 필요가 없다. 중요한 것은 무엇을 먼저 확인할지, 무엇을 아직 단정하면 안 되는지, 어떤 기준이 있어야 완료라고 말할 수 있는지를 직접 적는 것이다.
반복 학습 루프
| 회차 | 사용 방식 | 목표 |
|---|---|---|
| 1회차 | Evidence를 보고 틀려도 먼저 답한다. | 내가 모르는 기준을 드러낸다. |
| 2회차 | 연결 문서를 보기 전에 다시 답한다. | 판단 순서를 외운 지식이 아니라 습관으로 만든다. |
| 3회차 | 5분 제한을 두고 triage 또는 리뷰 코멘트를 쓴다. | 운영 상황에서 먼저 볼 것을 줄인다. |
| 4회차 | 내 프로젝트의 API, event, batch, mart에 대입한다. | 학습 문서를 개인 runbook으로 바꾼다. |
훈련 목록
| 번호 | 구분 | 훈련 | 반복해서 익힐 판단 |
|---|---|---|---|
| Lab 1 | 실습 랩 | 주문 결제 이벤트 스키마 고치기 | event와 state를 분리하고 compatibility를 지킨다. |
| Lab 2 | 실습 랩 | Outbox table 설계하기 | DB commit과 event publish 사이의 빈틈을 줄인다. |
| Lab 3 | 실습 랩 | processed event table과 ack 경계 잡기 | 중복 delivery를 정상 branch로 다룬다. |
| Lab 4 | 실습 랩 | Backfill dry run과 quality rule SQL 작성하기 | 재처리는 실행보다 범위, 속도, 검증이 먼저다. |
| Incident 1 | 장애 대응 | Kafka lag가 두 시간 쌓였다 | lag를 원인으로 단정하지 않고 source, consumer, target으로 나눈다. |
| Incident 2 | 장애 대응 | 야간 batch 실패로 dashboard가 stale이다 | rerun 전에 lock, range, downstream 노출을 확인한다. |
| Incident 3 | 장애 대응 | 매출 mart 품질 알림이 왔다 | freshness, completeness, accuracy를 분리한다. |
| Incident 4 | 장애 대응 | outbox pending이 증가하고 downstream이 비어 있다 | outbox 상태와 publisher, CDC, consumer 경계를 분리한다. |
| Review 1 | 설계 리뷰 | 신규 주문 mart pipeline 리뷰 | source 계약, grain, quality rule, replay 범위를 승인 조건으로 만든다. |
| Review 2 | 설계 리뷰 | Redis Stream vs Kafka 선택 리뷰 | 유실 허용, replay, consumer group, 운영 한계를 따진다. |
| Review 3 | 설계 리뷰 | 기능 PR 데이터 영향 리뷰 | 기능 배포가 reporting, search, recommendation을 흔드는 지점을 찾는다. |
| Final | 종합 훈련 | API는 정상인데 데이터가 틀리고 lag도 증가했다 | 여러 신호를 하나의 장애 대응 순서로 묶는다. |
Part 1. 실습 랩
Lab 1. 주문 결제 이벤트 스키마 고치기
상황
주문 결제 완료 시 order.paid 이벤트를 발행하려고 한다. consumer는 리포트, 검색, 추천 세 팀이다. 초안 payload는 API response 모델을 거의 그대로 복사했다.
제공된 Evidence
{
"eventName": "ORDER_PAID",
"orderId": 3912,
"user": {
"id": 77,
"nickname": "spring-user"
},
"status": "PAID",
"amount": 39000,
"couponDiscount": 3000,
"createdAt": "2026-07-01T09:12:31+09:00"
}- 검색 팀은 취소 이벤트도 나중에 받을 예정이다.
- 리포트 팀은 결제 시각 기준 일별 매출을 만든다.
- 추천 팀은 user profile 전체가 아니라 사용자 식별자만 필요하다.
- 다음 달에
payment_method가 추가될 수 있다.
해야 할 일
- event schema 초안을 수정한다.
- key, timestamp, event version, 금액 의미, producer 소유자를 명시한다.
- consumer별로 해석이 갈릴 수 있는 필드를 표시한다.
먼저 써보기
내 답안
수정한 payload:
key:
timestamp 기준:
versioning 판단:
consumer에게 미리 알려야 할 점:
힌트
API response는 현재 상태를 보여주지만 event는 이미 발생한 사실을 전달한다. createdAt이 주문 생성 시각인지 결제 완료 시각인지부터 분리한다.
해설
좋은 schema는 consumer가 같은 사실을 같은 뜻으로 읽게 만든다. event_id, event_type, event_version, occurred_at, order_id, user_id, paid_amount, currency처럼 event 자체의 계약과 business key를 분리한다. user nickname처럼 downstream 판단에 필요 없고 변경 가능성이 큰 값은 빼거나 별도 dimension으로 넘긴다.
payment_method 추가처럼 호환 가능한 추가는 version을 유지할 수 있지만, 기존 필드 의미 변경이나 timestamp 기준 변경은 새 version 또는 migration 계획이 필요하다.
연결 문서
- Event와 State 차이 상세: API response와 event payload를 분리한다.
- Event Schema 설계 상세: key, timestamp, payload 계약을 확인한다.
- Event Versioning과 Compatibility: 호환 변경과 비호환 변경을 나눈다.
- Domain Event Integration Event 분리: 내부 domain event와 외부 integration event를 나눈다.
- Data Engineering 실전 가이드북: Case 3, Case 13을 다시 본다.
가져갈 판단 기준
이벤트는 객체 덤프가 아니라 downstream과 맺는 사실 계약이다.
Lab 2. Outbox table 설계하기
상황
주문 결제 API에서 DB update 후 Kafka publish를 바로 호출하는 코드가 있다. 결제 상태는 바뀌었지만 event가 downstream에 없다는 장애가 한 번 있었다.
제공된 Evidence
10:00:01 orders.status = PAID commit success
10:00:02 kafka publish timeout
10:00:03 API returned 200
10:05:00 daily_order_mart has no row for order_id=3912현재 코드의 흐름:
begin transaction
update orders set status = 'PAID'
commit
publish order.paid to kafka
return 200해야 할 일
- outbox table schema를 설계한다.
- transaction 안에 무엇을 함께 저장할지 적는다.
- publisher가 어떤 status 전이를 가져야 하는지 적는다.
- pending과 failed를 운영자가 어떻게 볼지 정한다.
먼저 써보기
내 답안
outbox table columns:
transaction boundary:
status transition:
운영 지표:
cleanup 기준:
힌트
Outbox는 Kafka publish 성공을 보장하는 장치가 아니다. DB commit과 publish 요청 사이에서 event 발행 의도를 잃지 않게 만드는 장치다.
해설
outbox_events에는 event_id, aggregate_type, aggregate_id, event_type, event_version, payload, status, retry_count, available_at, published_at, created_at, last_error가 필요하다. 주문 상태 변경과 outbox insert는 같은 DB transaction에 들어가야 한다.
publisher는 pending을 가져가 publish를 시도하고, 성공 시 published로 바꾼다. publish 성공 후 상태 update에 실패하면 중복 발행이 가능하므로 consumer 멱등성이 같이 필요하다. 운영자는 pending age, failed count, retry 증가, publisher heartbeat를 봐야 한다.
연결 문서
- Dual Write 문제: DB write와 message publish를 분리할 때 생기는 빈틈을 확인한다.
- Transactional Outbox Pattern: outbox schema와 status 전이를 확인한다.
- Outbox 운영과 정리 전략: pending, failed, cleanup 운영 기준을 본다.
- API Transaction과 Event 발행 경계: API가 기다릴 것과 기다리지 않을 것을 나눈다.
- Data Engineering 실전 가이드북: Case 7을 다시 본다.
가져갈 판단 기준
Outbox의 핵심은 publish 성공이 아니라 발행 의도를 transaction 안에 남기는 것이다.
Lab 3. processed event table과 ack 경계 잡기
상황
포인트 적립 consumer가 같은 order.paid 이벤트를 두 번 처리해 포인트가 두 번 지급됐다. Kafka offset은 정상 commit된 것처럼 보인다.
제공된 Evidence
event_id=evt-3912-1
order_id=3912
consumer_group=point-reward-v2
10:00:01 poll event
10:00:02 insert point_ledger success
10:00:03 process crash before offset commit
10:00:08 consumer restarted
10:00:09 same event delivered again
10:00:10 insert point_ledger success해야 할 일
- idempotency key를 정한다.
- processed event table schema를 쓴다.
- business write와 processed insert의 transaction 경계를 정한다.
- ack 또는 offset commit 위치를 정한다.
먼저 써보기
내 답안
idempotency key:
processed table:
transaction boundary:
ack/commit 위치:
duplicate branch 처리:
힌트
중복 delivery는 예외 상황이 아니라 정상적으로 발생할 수 있는 branch다. 이미 처리됨을 성공으로 끝낼 수 있어야 한다.
해설
key는 보통 consumer_name + event_id 또는 business 의미가 더 강한 경우 consumer_name + order_id + reward_type으로 잡는다. 중요한 것은 재처리와 replay에서도 같은 side effect가 같은 key로 막히는 것이다.
business write와 processed insert는 같은 transaction 안에 있어야 한다. DB commit 후 offset commit 전에 crash가 나면 재전달될 수 있으므로, 재전달 시 processed table을 먼저 확인하고 이미 처리된 이벤트는 side effect 없이 ack한다.
연결 문서
- Consumer 실패 모델: DB commit과 ack 사이의 crash를 본다.
- Idempotency Key와 처리 이력 테이블: processed table 기준을 확인한다.
- Transaction Boundary와 Offset Commit: transaction과 offset commit 순서를 본다.
- Side Effect 중복 방지: 포인트, 이메일, 외부 호출을 보호한다.
- Data Engineering 실전 가이드북: Case 10을 다시 본다.
가져갈 판단 기준
중복을 없애려 하지 말고, 중복이 와도 같은 결과가 되게 만든다.
Lab 4. Backfill dry run과 quality rule SQL 작성하기
상황
지난 한 달 동안 coupon_discount가 mart에 반영되지 않았다. 백엔드 주문 원천에는 값이 있고, daily mart만 틀렸다. 운영자는 오늘 밤에 backfill을 하려고 한다.
제공된 Evidence
-- source
select count(*) as paid_orders, sum(paid_amount) as paid_amount, sum(coupon_discount) as coupon_discount
from orders
where paid_at >= '2026-06-01'
and paid_at < '2026-07-01'
and status = 'PAID';
-- target
select count(*) as rows, sum(gross_amount) as gross_amount, sum(discount_amount) as discount_amount
from daily_order_mart
where order_date >= '2026-06-01'
and order_date < '2026-07-01';해야 할 일
- backfill range와 batch size를 정한다.
- dry run SQL을 작성한다.
- 실행 중 stop condition을 적는다.
- 완료 검증용 quality rule을 작성한다.
먼저 써보기
내 답안
range:
dry run SQL:
batch size / rate limit:
stop condition:
완료 검증:
힌트
Backfill은 다시 돌리기가 아니라 정해진 범위의 데이터를 같은 결과로 다시 쓰기다. 실행 전 source-target 차이를 숫자로 잡아야 한다.
해설
먼저 기간, source 조건, target grain을 고정한다. dry run에서는 날짜별 count와 sum 차이를 뽑아 영향을 예측한다. 실행은 날짜 또는 id range로 나누고, target writer는 idempotent해야 한다. 완료 검증은 job success가 아니라 source-target count, gross amount, discount amount, freshness, rule result로 증명한다.
연결 문서
- Backfill 설계와 속도 제한: range, batch size, stop condition을 확인한다.
- Reprocessing과 Checkpoint: checkpoint와 재처리 범위를 본다.
- 운영 데이터 재처리 Runbook: 승인, 기록, 검증 흐름을 본다.
- 데이터 검증 SQL과 Rule: 완료 검증 rule을 작성한다.
- Backfill 재처리 Runbook: 실제 runbook 순서를 확인한다.
- Data Engineering 실전 가이드북: Case 9, Case 14를 다시 본다.
가져갈 판단 기준
Backfill은 실행 명령보다 범위, 멱등성, 완료 검증이 먼저다.
Part 2. 장애 대응 훈련
Incident 1. Kafka lag가 두 시간 쌓였다
Incident 상황
결제 완료 이벤트를 처리하는 order-mart-consumer의 lag가 증가했다. API error rate는 정상이고 주문 DB write도 성공하고 있다.
현재 보이는 증상
orders.paidtopic lag가 720,000까지 증가했다.- consumer 처리량이 평소 1,200 msg/min에서 150 msg/min으로 떨어졌다.
- DLQ 증가량은 아직 크지 않다.
- daily mart freshness가 90분 늦다.
로그/지표/SQL 단서
consumer_group=order-mart-v1
topic=orders.paid
partition=3 lag=380000
partition=4 lag=330000
partition=0 lag=2500
WARN slow query update daily_order_mart took 8.4s
INFO rebalance started
INFO rebalance completed in 31s5분 안에 확인할 것
내 답안
영향 범위:
먼저 볼 지표:
poison message 가능성:
target DB 병목 가능성:
임시 완화:
원인 후보 분류
- producer 급증 또는 partition key skew
- consumer 처리량 부족
- target DB write 병목
- rebalance 반복
- 일부 partition의 poison message
- downstream mart lock 또는 slow query
하면 안 되는 조치
원인 분류 전에 consumer instance만 늘리지 않는다. partition key skew나 DB 병목이면 instance 증설이 효과 없거나 부하만 키운다.
복구 절차
- lag가 특정 partition에 몰렸는지 확인한다.
- consumer error, retry, rebalance 빈도를 확인한다.
- target DB slow query와 lock을 확인한다.
- poison message가 있으면 DLQ 격리 또는 skip 정책을 문서화하고 적용한다.
- 처리량 부족이면 concurrency와 instance 수를 partition 수, DB capacity와 함께 조정한다.
- mart freshness와 quality rule을 통해 복구를 검증한다.
완료 검증
lag 감소만으로 종료하지 않는다. consumer throughput, retry/DLQ 안정화, mart freshness 회복, source-target count 검증까지 확인한다.
회고 질문
- lag alert가 partition별로 보였는가?
- consumer 처리량과 DB write latency가 함께 보였는가?
- DLQ 재처리 runbook이 있었는가?
연결 문서
- Kafka Topic Partition Consumer Group: partition, group, rebalance를 본다.
- Retry DLQ 중복 처리: poison message와 DLQ를 확인한다.
- Transaction Boundary와 Offset Commit: 재처리 시 중복 안전성을 본다.
- Kafka Lag와 Consumer 장애 대응: 첫 10분 runbook을 따른다.
- Data Engineering 실전 가이드북: Case 6, Case 15를 다시 본다.
가져갈 판단 기준
Kafka lag는 원인이 아니라 source, consumer, target 중 어디가 밀렸는지 묻는 신호다.
Incident 2. 야간 batch 실패로 dashboard가 stale이다
Incident 상황
매일 02:00에 도는 daily settlement batch가 실패했다. 아침 09:00에 운영 dashboard가 전날 값 그대로라는 제보가 들어왔다.
현재 보이는 증상
- API와 주문 DB는 정상이다.
daily_settlement_job마지막 성공은 전날 02:18이다.- dashboard cache는 정상 갱신됐지만 source mart가 stale이다.
- 같은 job을 수동 재실행할 수 있다.
로그/지표/SQL 단서
job_id=daily_settlement
scheduled_at=2026-07-01T02:00:00+09:00
status=FAILED
step=load_daily_settlement_mart
error=duplicate key value violates unique constraint daily_settlement_pkselect job_name, business_date, status, started_at, finished_at
from batch_job_history
where job_name = 'daily_settlement'
order by started_at desc
limit 5;5분 안에 확인할 것
내 답안
last success:
대상 business_date:
중복 실행 여부:
partial write 여부:
dashboard 완화:
원인 후보 분류
- 같은 business date 중복 실행
- lock lease 만료 후 overlap
- partial write 후 재시도
- idempotent writer 부재
- scheduler missed run 또는 잘못된 parameter
하면 안 되는 조치
대상 range와 partial write를 확인하기 전에 전체 월 데이터를 rerun하지 않는다. 중복 write와 dashboard 값 변동을 키울 수 있다.
복구 절차
- 실패한 business date와 last success를 확인한다.
- job lock과 job history에서 중복 실행 여부를 확인한다.
- target table에 partial row가 있는지 count와 sum으로 확인한다.
- writer가 upsert 또는 delete-and-insert 중 어떤 방식인지 확인한다.
- 안전한 재실행 범위를 정하고, 필요하면 dashboard에 stale 표시를 띄운다.
- 재실행 후 source-target 검증과 quality rule을 확인한다.
완료 검증
job status success만 보지 않는다. business date별 row count, amount sum, freshness, dashboard 노출 값이 모두 맞아야 한다.
회고 질문
- job parameter와 execution date가 분리되어 있었는가?
- lock이 heartbeat와 lease를 가지고 있었는가?
- 실패 알림에 business date와 영향 범위가 포함됐는가?
연결 문서
- Scheduler Cron 설계: 실행 시각과 대상 범위를 분리한다.
- Batch Lock 중복 실행 방지: overlap과 lock을 확인한다.
- 실패 재시도 알림 운영: 알림과 retry 기준을 본다.
- Batch 실패 대응 Runbook: 장애 대응 순서를 따른다.
- Data Engineering 실전 가이드북: Case 4, Case 14를 다시 본다.
가져갈 판단 기준
Batch 장애는 다시 돌리기 전에 어떤 business date가 얼마나 써졌는지부터 확인한다.
Incident 3. 매출 mart 품질 알림이 왔다
Incident 상황
데이터 품질 모니터링에서 daily_order_mart accuracy rule이 실패했다. 담당자는 mart가 늦은 것인지, 누락된 것인지, 금액 계산이 틀린 것인지 구분하지 못하고 있다.
현재 보이는 증상
- freshness rule은 20분 지연으로 warning이다.
- completeness rule은 정상이다.
- accuracy rule은 source 대비 target amount가 4.8% 낮다.
- 전날 주문 상태에
PARTIAL_REFUNDED가 새로 추가됐다.
로그/지표/SQL 단서
select status, count(*), sum(paid_amount), sum(refund_amount)
from orders
where paid_at >= '2026-06-30'
and paid_at < '2026-07-01'
group by status;PAID count=81200 amount=418000000 refund=0
CANCELED count=1200 amount=7300000 refund=7300000
PARTIAL_REFUNDED count=940 amount=51000000 refund=82000005분 안에 확인할 것
내 답안
품질 차원 분류:
source 변경:
mart 계산식:
dashboard 영향:
장애 등급:
원인 후보 분류
- freshness 지연
- completeness 누락
- accuracy 계산식 오류
- source enum 추가 영향
- dashboard cache 또는 semantic layer 오류
하면 안 되는 조치
quality alert를 단순 false positive로 닫지 않는다. 새 enum이나 계산식 변경은 값이 조용히 틀리는 형태로 남을 수 있다.
복구 절차
- freshness, completeness, accuracy 중 무엇이 실패했는지 분리한다.
- source enum, column, 의미 변경 여부를 확인한다.
- mart transform 로직이 새 status를 어떻게 처리하는지 본다.
- 영향 dashboard와 사용자 범위를 공지한다.
- transform 수정 후 작은 range로 backfill하고 검증한다.
- 새 enum에 대한 quality rule과 PR checklist를 추가한다.
완료 검증
source-target amount 차이가 허용 오차 안에 들어오고, 새 status별 count/sum rule이 통과해야 한다.
회고 질문
- enum 추가 PR에서 데이터 영향 체크리스트가 실행됐는가?
- accuracy rule이 status별 breakdown을 제공했는가?
- alert message만 보고 담당자가 owner를 찾을 수 있었는가?
연결 문서
- Data Quality Dimension: 품질 차원을 분리한다.
- Freshness Completeness Accuracy: 지연, 누락, 값 오류를 나눈다.
- 알림과 장애 등급: 장애 등급과 알림 메시지를 본다.
- 기능 개발 시 데이터 영향 체크리스트: enum 변경의 downstream 영향을 본다.
- 데이터 품질 이상 대응 Runbook: 품질 이상 대응 순서를 따른다.
- Data Engineering 실전 가이드북: Case 12, Case 13, Case 15를 다시 본다.
가져갈 판단 기준
품질 알림은 먼저 freshness, completeness, accuracy 중 무엇이 깨졌는지 분리해야 한다.
Incident 4. outbox pending이 증가하고 downstream이 비어 있다
Incident 상황
결제 API는 정상이고 orders table도 정상인데, reporting과 search downstream에 일부 주문이 보이지 않는다. outbox pending age가 45분을 넘었다.
현재 보이는 증상
outbox_events.status=PENDINGrow가 52,000개다.- publisher pod는 재시작을 반복한다.
- Kafka topic 생산량은 40분 전부터 급감했다.
- failed row는 적지만 pending이 계속 쌓인다.
로그/지표/SQL 단서
ERROR publisher failed to serialize payload
event_type=ORDER_PAID
event_version=1
field=payment_method
message=unknown field policy rejectselect status, count(*), max(now() - created_at) as max_age
from outbox_events
where created_at >= now() - interval '2 hours'
group by status;5분 안에 확인할 것
내 답안
pending age:
publisher heartbeat:
payload/schema 변경:
Kafka publish 여부:
downstream 영향:
원인 후보 분류
- publisher crash loop
- payload serialization 오류
- schema compatibility 위반
- Kafka broker publish 실패
- CDC connector lag 또는 offset 문제
- cleanup job이 pending을 잘못 건드린 문제
하면 안 되는 조치
pending을 수동으로 published 처리하지 않는다. downstream에 실제로 publish되지 않은 event를 완료 처리하면 누락을 숨긴다.
복구 절차
- pending age와 event_type 분포를 확인한다.
- publisher heartbeat와 crash log를 본다.
- 최근 schema 변경과 compatibility 정책을 확인한다.
- 안전한 patch 후 pending을 작은 범위로 publish한다.
- consumer 멱등성을 확인한 뒤 필요한 range만 재발행한다.
- reporting, search, recommendation별 반영 여부를 검증한다.
완료 검증
pending age가 정상 범위로 내려가고, Kafka 생산량과 downstream count가 source와 맞아야 한다.
회고 질문
- schema 변경이 outbox publisher 테스트를 통과했는가?
- pending age alert가 event_type별로 있었는가?
- downstream별 누락 검증 query가 있었는가?
연결 문서
- Transactional Outbox Pattern: outbox publish 흐름을 본다.
- CDC Debezium 기본: CDC를 쓰는 경우 connector offset을 확인한다.
- Outbox 운영과 정리 전략: pending과 failed 운영 기준을 본다.
- Event Versioning과 Compatibility: schema 변경 영향을 확인한다.
- Reporting Search Recommendation 연동: downstream별 영향 차이를 본다.
- Data Engineering 실전 가이드북: Case 7, Case 13을 다시 본다.
가져갈 판단 기준
Outbox pending은 API 성공 이후의 발행 의도가 아직 downstream 계약으로 전파되지 않았다는 신호다.
Part 3. 설계 리뷰 훈련
Review 1. 신규 주문 mart pipeline 리뷰
제안된 설계안
주문 DB에서 매일 새벽 2시에 전날 주문을 읽어 daily_order_mart를 만든다. DAG는 extract, transform, load 세 task로 구성한다. 실패하면 다음 날 다시 돌린다.
겉보기 장점
- 구조가 단순하다.
- 하루 한 번이면 비용이 낮다.
- API 처리 경로와 분리되어 있다.
숨은 리스크
- fact grain과 환불/취소 처리 기준이 없다.
- source schema 변경 감지 방식이 없다.
- partial write 후 재실행 기준이 없다.
- quality rule과 dashboard freshness 기준이 없다.
- backfill range와 checkpoint가 없다.
리뷰 질문
내 답안
source 계약 질문:
grain 질문:
failure mode 질문:
replay/backfill 질문:
quality rule 질문:
빠진 계약
- source owner와 column 의미
- fact grain
- business date와 schedule time 분리
- retry와 idempotent write 방식
- source-target 검증 SQL
- dashboard freshness SLA
- 재처리 승인과 기록 방식
개선된 설계 방향
DAG 자체보다 계약을 먼저 보강한다. source query, target grain, task boundary, checkpoint, quality validation task, rerun range, dashboard stale 처리 기준을 문서화한다.
승인 조건
source-target count/sum 검증, idempotent writer, quality rule, backfill runbook, owner와 alert 기준이 있어야 승인한다.
연결 문서
- Fact Dimension 기본: grain을 먼저 정한다.
- Extract Transform Load 단계: pipeline 단계를 분리한다.
- Pipeline Dependency와 Checkpoint: dependency와 checkpoint를 본다.
- Airflow DAG 예시: DAG boundary를 확인한다.
- 데이터 파이프라인 설계 리뷰 템플릿: 리뷰 템플릿을 적용한다.
- Data Engineering 실전 가이드북: Case 5, Case 11, Case 16을 다시 본다.
가져갈 판단 기준
Pipeline 설계 리뷰는 DAG 그림보다 source 계약, grain, 재처리, 품질 검증을 먼저 본다.
Review 2. Redis Stream vs Kafka 선택 리뷰
제안된 설계안
관리자 알림, 검색 색인 갱신, 추천 feature 갱신을 모두 Redis Pub/Sub으로 처리한다. 이유는 구현이 빠르고 운영 중인 Redis가 이미 있기 때문이다.
겉보기 장점
- Spring에서 붙이기 쉽다.
- 별도 Kafka cluster 운영이 필요 없다.
- 관리자 알림처럼 즉시성 있는 기능에 적합해 보인다.
숨은 리스크
- Pub/Sub은 subscriber가 내려가면 메시지를 잃을 수 있다.
- 검색 색인과 추천 feature는 replay와 재처리가 필요할 수 있다.
- Redis Stream을 쓰더라도 pending, ack, trim, persistence 한계가 있다.
- partition, retention, consumer group 운영 요구가 커지면 Kafka 쪽이 자연스럽다.
리뷰 질문
내 답안
유실 허용 여부:
replay 필요 여부:
consumer group 필요 여부:
backlog와 retention:
전환 조건:
빠진 계약
- 유실 허용 여부
- 메시지 보관 기간
- pending과 ack 처리
- replay 범위
- consumer별 처리 이력
- 장애 시 대체 경로
개선된 설계 방향
관리자 실시간 알림처럼 유실을 일부 허용할 수 있는 경로는 Pub/Sub을 쓸 수 있다. 검색 색인과 추천 feature처럼 재처리와 추적이 필요한 경로는 Redis Stream 또는 Kafka를 검토하되, backlog, replay, 운영 지표 기준을 먼저 정한다.
승인 조건
소비자별 유실 허용 여부와 replay 요구를 문서화하고, Redis Stream을 쓰는 경우 pending/claim/trim 정책과 Kafka 전환 조건이 있어야 한다.
연결 문서
- PubSub과 Stream 차이: 유실 허용 여부를 분리한다.
- Redis Stream Consumer Group: consumer group과 ack를 본다.
- Pending Entry와 Ack: pending과 claim을 확인한다.
- Redis 기반 이벤트 처리 한계: Redis 운영 한계를 본다.
- Kafka와 Redis Stream 선택 기준: 선택 기준을 정리한다.
- Data Engineering 실전 가이드북: Case 8을 다시 본다.
가져갈 판단 기준
메시징 도구 선택은 빠른 구현보다 유실 허용, replay, backlog, 운영 책임으로 결정한다.
Review 3. 기능 PR 데이터 영향 리뷰
제안된 설계안
주문 상태에 PARTIAL_REFUNDED를 추가한다. API response에는 새 status를 내려주고, 기존 reporting과 search는 나중에 필요하면 맞춘다.
겉보기 장점
- API 기능 변경 범위가 작아 보인다.
- DB column 추가 없이 enum만 확장하면 된다.
- 기존
REFUNDED와 비슷하게 처리하면 될 것 같다.
숨은 리스크
- mart 계산식이 새 status를 누락할 수 있다.
- search filter가 새 status를 알지 못할 수 있다.
- recommendation feature가 환불 상태를 잘못 학습할 수 있다.
- event schema compatibility와 consumer fallback이 없다.
- PR에 데이터 팀 협업과 배포 후 검증이 없다.
리뷰 질문
내 답안
영향을 받는 downstream:
event schema 변경:
mart 계산 변경:
품질 rule:
배포 후 확인:
빠진 계약
- status 의미와 전이 규칙
- event payload와 versioning 기준
- reporting/search/recommendation owner 확인
- 배포 전 source-target 검증
- 배포 후 quality rule과 alert
- backfill 필요 여부
개선된 설계 방향
PR에는 API 변경뿐 아니라 downstream 영향 체크리스트를 포함한다. 새 status가 fact 계산, event consumer, search index, recommendation feature에서 어떻게 처리되는지 owner별로 확인한다.
승인 조건
새 enum의 의미, event compatibility, mart 계산식, search/recommendation 처리, quality rule, 배포 후 검증 SQL이 있어야 승인한다.
연결 문서
- Schema Change와 Pipeline 영향: schema와 의미 변경 영향을 확인한다.
- 데이터 품질 회고와 재발 방지: 재발 방지 action으로 연결한다.
- Reporting Search Recommendation 연동: downstream별 차이를 본다.
- 데이터 팀과 백엔드 협업: 변경 협의 단위를 확인한다.
- 기능 개발 시 데이터 영향 체크리스트: PR checklist를 적용한다.
- Data Engineering 실전 가이드북: Case 13, Case 16을 다시 본다.
가져갈 판단 기준
백엔드 기능 PR은 API만 바꾸는 일이 아니라 downstream 데이터 계약을 함께 바꾸는 일일 수 있다.
최종 종합 훈련
상황
수요일 오전 09:30, 결제 API는 정상이다. 그런데 매출 dashboard 값이 전날보다 5% 낮고, orders.paid Kafka lag가 증가했으며, 전날 PARTIAL_REFUNDED status 배포가 있었다. outbox pending도 평소보다 높다.
제공된 Evidence
API error rate: 0.02%
orders update success rate: 99.98%
outbox pending max age: 38m
kafka lag: 480000
daily_order_mart freshness: 85m late
quality accuracy rule: failed, target amount -5.1%
new enum deployed: PARTIAL_REFUNDEDselect status, count(*), sum(paid_amount), sum(refund_amount)
from orders
where paid_at >= current_date - interval '1 day'
group by status;작성할 것
내 종합 답안
5분 triage:
가장 위험한 문제:
원인 후보 분류:
확인할 SQL/지표:
복구 절차:
완료 검증:
회고 action:
다시 읽을 문서:
해설
이 상황에서는 API 정상 여부로 장애를 닫으면 안 된다. 먼저 source write, outbox, Kafka, consumer, mart, quality rule을 분리한다. 새 enum 배포가 accuracy rule 실패와 연결될 수 있고, outbox pending과 Kafka lag는 freshness 지연을 만들 수 있다. 따라서 값이 틀린 문제와 늦는 문제를 동시에 보되 같은 원인으로 단정하지 않는다.
초기 대응은 영향 범위와 데이터 노출 제어다. dashboard에 stale 또는 degraded 상태를 표시하고, source와 target의 status별 count/sum 차이를 확인한다. 그 다음 outbox pending age, publisher error, Kafka partition lag, consumer retry/DLQ, mart transform 변경 여부를 순서대로 확인한다. 복구는 작은 range에서 patch와 재처리를 수행한 뒤 source-target 검증과 quality rule 통과로 종료한다.
연결 문서
- 서비스 데이터 흐름 전체 지도: API 이후 흐름을 한 줄로 그린다.
- Kafka Topic Partition Consumer Group: lag와 partition을 본다.
- Outbox 운영과 정리 전략: pending age를 본다.
- 운영 데이터 재처리 Runbook: 안전한 재처리를 준비한다.
- Freshness Completeness Accuracy: 늦음, 누락, 값 오류를 분리한다.
- 기능 개발 시 데이터 영향 체크리스트: PR 영향 누락을 회고한다.
- Data Engineering 실전 플레이북 압축 정리: incident 대응 지도를 본다.
- Data Engineering 실전 가이드북: Case 15, Case 16을 다시 본다.
가져갈 판단 기준
복합 장애에서는 신호를 하나로 뭉치지 말고 source, publish, stream, target, quality로 나눠 증명한다.
반복 기록표
| 날짜 | 훈련 | 처음 판단 | 놓친 관점 | 다시 볼 문서 | 다음 액션 |
|---|---|---|---|---|---|
| Lab 1 | |||||
| Lab 2 | |||||
| Lab 3 | |||||
| Lab 4 | |||||
| Incident 1 | |||||
| Incident 2 | |||||
| Incident 3 | |||||
| Incident 4 | |||||
| Review 1 | |||||
| Review 2 | |||||
| Review 3 | |||||
| Final |