# Resolving the Reprocessing Problem

## 배경 및 문제

#### 실패 시 **어디까지 처리됐는지 알 수 없는** 구조

기존 대량 알림 생성 구조에서는

요청 발생 = 즉시 실행 이라는 단순한 구조를 가지고 있었고,

* 스케줄러 트리거
* 관리자 콘솔 액션
* 예약작업

각 트리거는 서로 인지하지 못한 채 독립적으로 실행되었고,\
알림 센터 적재와 푸시 생성이 하나의 실행 흐름으로 묶여 있었습니다.

이로 인해 작업 도중 장애가 발생하면 아래와 같은 문제가 발생하였습니다.

* 작업이 어느 지점까지 성공했는지 추적할 수 없음
* 재시작 시 전체 재실행 또는 일부 누락/중복을 감수
* 운영자는 로그와 기억에 의존해 복구 판단.

즉, 작업이 실패했다는 사실은 인지할 수 있지만,\
**무엇을 다시 해야하는지**는 시스템에서 알 수 없는 상태입니다.

이 구조에서 재처리는 기술적인 문제가 아니라,\
**누락을 감수할 것인지, 중복 발송을 감수할 것인지의 선택 문제가 되었습니다.**

## 고민

#### 단순 재시도가 아닌 **안전한 재시작**

**처음에는 실패 시 전체 작업을 다시 실행하는 방식도 고려했습니다.**\
**하지만 대량 알림의 특성상 아래 문제가 있습니다.**

* 전체 재실행은 이미 성공한 대상에 중복 알림을 유발
* 작업 크기가 클수록 재실행 비용과 DB 부하 증가
* 실패 지점이 반복되면 동일한 장애를 계속 재현

결국 필요한 것은 재시도(retry)가 아니라\
**중단 지점 이후부터 실행할 수 있는 재시작**입니다.

이를 위해서는 시스템이 아래 사항들을 트래킹하고 있어야합니다.

* 이 작업의 현재 상태는?
* 어디까지 처리가 됐는가?
* 다시 시작한다면 어디부터 처리해야하는가?

## 해결 방법 검토

### 1. 전체 재실행 (X)

가장 단순한 방법이자, 작업을 처음부터 다시 실행하는 것.

하지만 이 방식은,

* 이미 성공한 대상에 **중복 알림 발생**
* 작업 규모가 **클수록 재실행 비용 급증**
* **반복 실패 가능성**

문제들을 가지고 있었고, 정확성과 신뢰성을 보장하지 못합니다.

### 2. 메시지 큐 재시도 / DLQ 활용 (X)

메시지 큐 기반 구조에서는\
실패 메시지 재시도나 DLQ를 통한 재처리가 가능합니다.

하지만 이 방식은 **실패 처리에는 적절하지만, 재시작 관점에서는 적절치 않았습니다**.

* 재처리 단위가 **메시지 수준**
* 하나의 대량 작업이 **어디까지 진행됐는지 파악이 어려움**
* 특정 작업 전체를 기준으로 **이어 재시작하기 어려움**

결국 재처리를 위해서는

* 작업 단위 상태 저장
* 진행도(cursor) 관리)

가 필요했습니다.

### 3. 상태 기반 재처리 모델 (O)

#### 핵심 설계: 상태와 진행도를 데이터로 남긴다

재처리 시스템의 책임으로 만들기 위해,\
각 대량 알림 생성 요청을 하나의 Job으로 정의했습니다.

각 Job은 아래 정보를 가집니다:

* 작업 상태
* 처리 진행도(cursor)

이 설계를 통해 작업 실패 시에도:

* **어디까지 성공했는지** 명확히 남아있고,
* cursor 이후부터 **안전하게 재시작 가능**해졌습니다.

### Cursor기반 재시작

대량 알림 대상은 chunk 단위로 처리되며,

* chunk 처리 후 cursor 업데이트
* 장애 발생 시 마지막 cursor 이후부터 재개

이 방식으로:

* 전체 재실행 제거
* 부분 재처리 가능
* chunk 단위 중복 생성 최소화

가 가능하게 됐습니다.

### 자동 감지: 중단된 작업을 복구 대상으로 전환

**재처리는 사람이 아닌 시스템이 감지하는 상태 변화**여야합니다.

이를 위해:

* PROCESSING 상태 이지만
* 일정 시간 이상 갱신되지 않은 Job을 stuck job으로 판단

하고,

* 시스템에서는 복구 대상으로 인지
* 마지막 cursor 기준 재시작
* 새로운 job이 처음 등록됬을때는 0부터 시작
* 기존 작업 처리 방식은 수정없이 그대로 사용 가능

하도록 설계했습니다.

### 재처리 로직

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FFT5q6PQU181TXLJTCkwl%2Fimage.png?alt=media&#x26;token=85bec503-4ba6-4b75-b10d-07b083e068a2" alt=""><figcaption></figcaption></figure>

* RUNNING 상태의 job이 있으면 신규 job 실행 금지
* 오래 갱신되지 않은 RUNNING job은 stuck job으로 판단
* 복구 대상 job은 cursor 기준으로 재시작

## 트레이드 오프: at-least-once 보장 선택

이 구조는 exactly-once 재처리를 목표로 하지 않았습니다.

* 중간 실패 시 동일 chunk 재처리 가능성 존재.&#x20;
  * 이때는 해당 chunk는 중복 발송될 여지가 있습니다.
* 이론적으로 중복 가능성은 완전 배제 불가.&#x20;
  * 효율성과 성능을 포기한 단건 처리시에는 가능

하지만 대량 알림의 특성상,

* **완벽한 중복 제거**보다
* **안정적인 재시작**과 **운영 안전성**

이 더 중요하다고 판단하였고, 재처리 설계는\
**chunk 단위 at-least-once 보장이라는 트레이드 오프**를 선택한 결과입니다.
