데이터 수집 파이프라인에서
실시간으로 데이터를 처리하는 스트리밍 패턴에서
장애 발생시 어떻게 데이터를 처리할까? 어떻게 처리해야 정합성을 유지할까?
이때 두가지 방식이 혼합해서 사용한다.
체크포인트, 로깅
(1) 체크포인트와 로깅
- 체크포인트
- 무엇을 저장하나? 특정시점의 전체 상태를 저장(Snapshot) + 그 체크포인트까지 처리된 이벤트 인덱스값(offset)
- 활용
- 장애 발생 시 가장 최근의 체크포인트로 복구 + 로깅의 이벤트 기록을 순차적으로 재처리함
- 로깅
- 시간순으로 발행된 이벤트를 순차적으로 그대로 저장하는 것
- 활용
- 장애 발생 시 이벤트 기록들을 replay해서 데이터를 복구
여기까지만 봐도 뭔말인지 모를 용어들이 난무한다.
체크포인트, 로깅뭔지 알려면 좀더 구체적인 예시로 이해하는게 좋다.
그전에 이벤트, 상태라는게 뭔지 알고 가야한다.
그다음 오프셋, 스냅샷이 의미하는바가 뭔지, 왜 체크포인트를 유지하는게 어려운지,
로깅이 그냥 로그를 저장하는 정도로 알았지만 replay가 도대체 뭔지는 실제 예시를 보면 한번에 이해된다.
(2) 예시로 이해하기
<사전지식>
이벤트 vs 상태
- 이벤트: "무슨 일이 일어났다"는 사실 (불변) - 시스템으로 들어오는 입력데이터
- 상태: 이벤트들을 처리한 현재 결과값 (변함)
실무 예시:
이벤트: "유저A가 기생충에 좋아요를 눌렀다" 상태: 유저A의 liked_movies = ["기생충", "올드보이", "타짜"]
이벤트는 고객행동으로 인해 프로듀서가 발행한 로그 데이터 이고
상태는 이벤트를 기반으로 처리해서 사용자의 좋아요누른 영화 리스트를 업데이트 한 결과값을 의미한다.
이걸 반드시 집고 넘어가야 체크포인트, 로깅을 활용하는 걸 정확히 이해가능하다.
체크포인트, 로깅 예시로 알아보자
실무 상황: 실시간 추천 시스템
우리가 Netflix 같은 스트리밍 서비스에서 실시간 추천 시스템을 운영한다고 해보자.
사용자 행동 데이터가 계속 들어옴:
15:30:01 - 유저A가 "기생충" 시청 시작
15:30:15 - 유저A가 "기생충" 30초 시청 후 skip
15:30:20 - 유저B가 "오징어게임" 좋아요 클릭
15:30:25 - 유저A가 "범죄도시" 시청 시작
로깅 방식:
이벤트를 순서대로 계속 쌓아놔:
Event1: {user: A, action: "start_watch", movie: "기생충", time: 15:30:01}
Event2: {user: A, action: "skip", movie: "기생충", time: 15:30:15}
Event3: {user: B, action: "like", movie: "오징어게임", time: 15:30:20}
Event4: {user: A, action: "start_watch", movie: "범죄도시", time: 15:30:25}
체크포인트(스냅샷) 방식:
15:30:20 시점의 전체 사용자 상태를 통째로 저장:
{
userA: {preference: ["액션", "스릴러"], recently_watched: ["기생충"], skip_count: 1},
userB: {preference: ["드라마", "스릴러"], liked_movies: ["오징어게임"]},
userC: {preference: ["코미디"], watching_now: "극한직업"}
... (전체 사용자 데이터)
}
핵심 차이점:
1. 저장하는 내용이 다름
로깅: "무슨 일이 일어났는지" 이벤트 기록
체크포인트: "현재 상태가 어떤지" 전체 스냅샷
그럼 이제 복구를 이걸 이용해서 어떻게 할까?
각 방식으로만 복구를 한다고 생각해보자
로깅:
전체 이벤트를 처음부터 다시 쭈욱 처리해서 상태값을 업데이트 해야한다.
(이벤트양이 많으면 처리할게 너무 많아진다)
체크포인트:
장애발생과 체크포인트의 시점이 일치하지 않는게 일반적이다.
그 사이의 기간동안의 유실된 이벤트로 인해 상태값이 정확하지 않게 된다.
결론 및 개선된 복구전략:
두가지 방식을 혼용해서 사용함.
장애 발생 시 가장 최근 체크포인트로 복구하고
그때를 기준으로 처리된 이벤트 오프셋(offset)값을 활용해서
로깅에서 저장된 이벤트를 처리해서 상태값을 업데이트 한다.(replay)
예시상황에 적용해보자,
15:30:01 - 유저A가 "기생충" 시청 시작
15:30:15 - 유저A가 "기생충" 30초 시청 후 skip
15:30:20 - 유저B가 "오징어게임" 좋아요 클릭
15:30:25 - 유저A가 "범죄도시" 시청 시작
로깅 방식:
이벤트를 순서대로 계속 쌓아놔:
Event1: {user: A, action: "start_watch", movie: "기생충", time: 15:30:01}
Event2: {user: A, action: "skip", movie: "기생충", time: 15:30:15}
Event3: {user: B, action: "like", movie: "오징어게임", time: 15:30:20}
Event4: {user: A, action: "start_watch", movie: "범죄도시", time: 15:30:25}
체크포인트(스냅샷) 방식:
15:30:20 시점의 전체 사용자 상태를 통째로 저장:
{
userA: {preference: ["액션", "스릴러"], recently_watched: ["기생충"], skip_count: 1},
userB: {preference: ["드라마", "스릴러"], liked_movies: ["오징어게임"]},
userC: {preference: ["코미디"], watching_now: "극한직업"}
... (전체 사용자 데이터)
}
시스템이 15:30:30에 장애가 났다고 가정해보자:
1. 체크포인트 생성시 Offset를 저장 (Kafka 예시)
처리 상태를 따로 기록:
Topic: user-events Partition 0: [event1, event2, event3, event4, event5]
↑ offset=2 (여기까지 처리완료)
엔지니어의 독백:
"아, 시스템이 죽었네. 어디까지 처리했는지 확인해보자...
Consumer group의 offset을 보니까 offset=2까지 처리했구나.
그럼 offset=3부터 다시 처리하면 되겠다."
2. 실제 복구 과정:
1. 시스템 재시작 - 가장 최근 체크포인트 스냅샷으로 복구
2. 마지막 처리된 offset 확인 (offset=2)
3. offset=3부터 이벤트 재처리 시작
4. event3, event4, event5... 순차 처리
'데이터 엔지니어' 카테고리의 다른 글
| [Delivery guarantee] at most once, at least once, exactly once (0) | 2025.07.18 |
|---|---|
| [실시간] RBML, SBML HML (3) | 2025.07.14 |