이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 서비스 데이터는 API 응답 이후 어떤 저장소와 처리 단계를 지나가는가?
  • 동기 transaction, event 발행, batch 보정, warehouse 적재의 경계는 어떻게 나누는가?
  • 장애가 났을 때 어떤 key, 로그, 지표로 영향 범위와 재처리 범위를 찾는가?
  • 개인 프로젝트에서는 어느 수준까지 단순화하고, 기업 운영에서는 무엇을 추가해야 하는가?

개요

백엔드 서비스 데이터는 API transaction에서 끝나지 않는다.

주문 생성 API가 성공하면 운영 DB에는 주문 row가 남는다. 그러나 검색 색인, 알림, 추천 feature, 정산 리포트, 일별 매출 mart는 같은 순간에 완성되지 않는다.

Data Engineering 전체 지도는 이 차이를 숨기지 않고 표시하는 문서다.

핵심은 “어디에 저장했는가”보다 “어디까지 전달되었고, 실패하면 어디서 다시 시작할 수 있는가”다.

원리

서비스 데이터 흐름은 보통 다섯 경계로 나뉜다.

  • 요청 경계: controller, service, transaction, 운영 DB commit이 있는 곳이다.
  • 발행 경계: domain event나 outbox row가 기록되는 곳이다.
  • 처리 경계: consumer, scheduler, batch job, stream processor가 실행되는 곳이다.
  • 적재 경계: search index, cache, warehouse, mart 같은 downstream 저장소다.
  • 검증 경계: count, freshness, checksum, rule SQL로 완료를 확인하는 곳이다.

이 경계를 한 그림에 넣어야 장애 설명이 가능하다.

API가 200을 반환했는데 리포트가 비어 있다면 API 장애가 아니라 발행, 소비, 적재, 검증 중 어디가 끊겼는지 봐야 한다.

전체 흐름

Client
  -> Order API
  -> order-service transaction
      -> orders table commit
      -> outbox_event insert
  -> outbox relay
  -> Kafka topic: order.events
  -> consumers
      -> search index
      -> notification
      -> feature store
      -> warehouse staging
  -> daily mart build
  -> data quality check
  -> dashboard / analyst query

이 흐름에서 운영 DB는 사실의 출발점이고, 이벤트는 사실이 흘러가는 운송장이다.

warehouse와 mart는 서비스 운영의 원본이 아니라 의사결정을 위해 재구성한 결과다.

백엔드 코드에서 보이는 지점

@Transactional
public Long placeOrder(PlaceOrderCommand command) {
    Order order = orderRepository.save(Order.create(command));
    outboxRepository.save(OutboxEvent.from(order.placedEvent()));
    return order.getId();
}

이 코드는 주문 저장과 이벤트 발행 요청을 같은 transaction 안에 둔다.

Kafka 발행 자체를 transaction 안에서 직접 호출하지 않고 outbox row를 남기면, API 성공과 이벤트 발행 실패 사이의 dual write 위험을 줄일 수 있다.

운영자가 추적할 좌표

서비스 데이터 흐름에는 공통 좌표가 필요하다.

  • business key: orderId, paymentId, memberId
  • event key: partitioning과 중복 판단에 쓰는 key
  • event id: 한 번 발생한 사건을 식별하는 id
  • source commit time: 운영 DB에 사실이 기록된 시각
  • published time: broker나 downstream으로 넘어간 시각
  • processed time: consumer나 batch가 처리한 시각
  • checkpoint: offset, 날짜 범위, cursor, DAG run id

좌표가 없으면 “어제 주문 몇 건이 리포트에서 빠졌다”는 문제를 재처리 범위로 바꾸기 어렵다.

예시 로그

event=order_placed
event_id=01JZ1M3K9V8S4
order_id=392018
source_commit_at=2026-06-30T09:01:12+09:00
published_at=2026-06-30T09:01:13+09:00
topic=order.events
partition=7
offset=1839921
consumer=order-search-indexer
status=failed
reason=search_cluster_timeout
retry_count=3
dlq_topic=order.events.dlq

이 로그는 API 성공 여부보다 downstream 처리 위치를 보여준다.

장애 대응자는 order_id, event_id, partition, offset, consumer를 기준으로 재처리 대상을 좁힌다.

실패 모델

  • API transaction은 성공했지만 outbox relay가 멈춰 이벤트가 발행되지 않는다.
  • broker에는 이벤트가 있지만 consumer lag가 커져 검색 색인이 늦어진다.
  • consumer가 성공 로그를 남겼지만 target 저장소 write가 일부 실패한다.
  • batch가 전날 범위를 다시 계산하면서 오늘 데이터까지 덮어쓴다.
  • mart build는 성공했지만 품질 SQL이 실패해 지표 신뢰성이 떨어진다.

이 실패들은 모두 “API가 정상인지”만 보면 놓친다.

운영 지표

  • outbox_unpublished_count: commit 이후 아직 발행되지 않은 이벤트 수다.
  • event_publish_lag_seconds: source commit과 broker publish 사이의 지연이다.
  • consumer_lag: broker 기준 처리 지연이다.
  • target_write_failures: downstream 저장소 write 실패 수다.
  • mart_freshness_lag_minutes: mart가 최신 운영 데이터와 얼마나 떨어져 있는지다.
  • quality_rule_failed_count: count, sum, null rate 같은 검증 실패 수다.

지표는 도구별로 흩어져도 하나의 흐름으로 읽어야 한다.

개인 프로젝트 기준

개인 프로젝트에서는 Kafka, Airflow, warehouse를 모두 도입할 필요가 없다.

다만 다음 네 가지는 남겨야 한다.

  • 처리 대상 table 또는 event table
  • 처리 이력 table
  • 실패 로그
  • 수동 재실행 방법

Spring Scheduler로 하루 한 번 mart table을 갱신하더라도 job_run_id, from, to, status, error_message를 남기면 운영 연습이 된다.

기업 운영 기준

기업 운영에서는 흐름마다 owner와 지연 허용치를 둔다.

검색 색인은 1분 지연까지 허용될 수 있지만, 정산 mart는 D+1 오전 8시 이전 완료가 중요할 수 있다.

이 차이를 문서화해야 장애 등급과 알림 채널이 달라진다.

기업 기준에서는 다음도 필요하다.

  • producer와 consumer schema 변경 절차
  • DLQ 보관 기간과 재처리 권한
  • batch 재실행 승인 절차
  • 품질 rule 실패 시 downstream 공개 여부
  • 데이터 팀, 백엔드 팀, 인프라 팀의 연락 경계

실전 팁

  • API sequence diagram에 downstream 데이터 흐름을 한 줄이라도 추가한다.
  • 이벤트 이름보다 source table, business key, 재처리 key를 먼저 확인한다.
  • 비동기 처리를 도입하면 retry와 DLQ까지 함께 설계한다.
  • batch는 “언제 실행되는가”보다 “어느 범위를 다시 실행할 수 있는가”를 먼저 정한다.
  • dashboard가 틀렸다는 신고를 받으면 운영 DB, staging, mart의 count와 freshness를 같이 본다.

위험 신호!

  • 데이터 흐름도가 controller, service, repository에서 끝난다.
  • 이벤트 발행 성공 여부를 API 로그만으로 판단한다.
  • batch 실패 후 전체 재실행만 가능하다.
  • consumer가 중복 이벤트를 받으면 결과가 달라진다.
  • 품질 검증 없이 dashboard 갱신 완료를 성공으로 본다.

확인 질문

확인 질문

  • 주문 생성 API가 성공했는데 검색에 보이지 않을 때 어느 경계를 순서대로 확인해야 하는가?
    • 운영 DB commit, outbox 미발행 수, broker publish lag, consumer lag, search index write 실패를 순서대로 본다.
  • 재처리 범위를 좁히려면 어떤 좌표가 필요한가?
    • business key, event id, partition/offset, 날짜 범위, job run id, source commit time이 필요하다.
  • 개인 프로젝트에서 최소한의 Data Engineering 흐름을 만들려면 무엇을 남겨야 하는가?
    • 처리 대상, 처리 이력, 실패 로그, 수동 재실행 방법, count/freshness 검증을 남긴다.

참고 문서