# 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="/files/4HWlidIOLucOOm24T7cW" 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 보장이라는 트레이드 오프**를 선택한 결과입니다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wonjoon.gitbook.io/joons-til/trustay/documentation/resolving-the-reprocessing-problem.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
