Leader Job Safety Lab
English
Leader Job Safety Lab · Visual Companion

리스가 만료된 이전 작업자가 다시 실행돼도 최신 작업 결과만 PostgreSQL에 반영된다

Leader 선출은 실행 후보를 정할 뿐입니다. 지연된 작업자의 데이터 변경을 차단하려면 Redis가 순서를 발급하고 PostgreSQL이 최종 반영 여부를 판정해야 합니다.

6운영 시나리오
2SAFE / UNSAFE 비교
41 → 42작업 인계 시 펜싱 토큰

01 · 문제 파악

Leader 선출에 성공해도 데이터 변경은 안전하지 않을 수 있다

리스 TTL이 끝나도 이전 작업자의 스레드가 즉시 중단되는 것은 아닙니다. GC 일시 중지, 네트워크 단절, 긴 I/O가 끝나면 이전 작업자가 다시 실행될 수 있습니다.

실행 조정

Leader 리스만 사용

동시에 실행되는 작업자 수는 줄이지만, 이전 작업자의 재실행을 차단하지 못합니다.

자원 식별

작업 이름으로만 잠금

이름이 다른 작업이 같은 업무 자원을 변경하면 잠금 키가 충돌하지 않습니다.

결과 복구

외부 호출을 바로 재시도

첫 호출 결과가 불확실한 상태에서 새 요청을 만들면 외부 처리가 중복될 수 있습니다.

구상 단계(Brainstorming): 문제를 어떤 보장으로 나눌 것인가

동시 실행 제한Redis Leader 리스와 자원 리스한 시점의 실행 수를 줄입니다.
작업 인계리스 만료와 새 리스 획득새 작업자가 처리를 이어받습니다.
지연된 변경 차단펜싱 토큰 + PostgreSQL이전 토큰의 변경을 거부합니다.
재실행 안전성OperationId + 멱등 처리같은 외부 처리를 다시 확인할 수 있습니다.
처리 결과 영속화Exposed 트랜잭션 + Outbox업무 상태와 후속 작업을 함께 반영합니다.

02 · 해결 방안

해결 방안은 Redis와 PostgreSQL의 판정 책임을 분리한다

한 구성 요소가 모든 안전성을 담당하지 않습니다. 실행 조정, 최종 데이터 반영, 외부 처리 복구를 분리합니다.

검토 후 제외

Leader Owner Token 재사용

Owner Token은 순서를 비교할 수 없는 식별자입니다.

검토 후 제외

Redis에서 최종 반영 결정

리스 상태와 PostgreSQL 업무 상태가 불일치할 수 있습니다.

검토 후 제외

실패할 때 새 요청 ID 발급

처리 여부를 모르는 첫 요청과 새 요청이 모두 반영될 수 있습니다.

채택

단조 증가하는 펜싱 토큰

새 작업자가 항상 더 큰 순번을 받습니다.

채택

PostgreSQL 조건부 갱신

`incomingFence > lastAcceptedFence`인 경우에만 변경합니다.

채택

Outbox와 원래 OperationId 조회

불확실한 결과를 추측하지 않고 같은 요청을 조회합니다.

03 · 시스템 구조

Redis가 실행 순서를 조정하고 PostgreSQL이 데이터 반영 여부를 판정한다

한 구성 요소가 모든 안전성을 담당하지 않습니다. 실행 조정, 최종 데이터 반영, 외부 처리 복구를 분리합니다.

Leader Job Safety Lab 구성 요소와 데이터 흐름 한 구성 요소가 모든 안전성을 담당하지 않습니다. 실행 조정, 최종 데이터 반영, 외부 처리 복구를 분리합니다. API 작업 조정 데이터베이스 외부 처리 호출자 운영자 또는 Scheduler JobSafetyController 시나리오 API · 권한 검사 실행 요청 JobRunCoordinator 실행 순서와 리스 관리 FencedJobExecutionService 현재 구성과 토큰 재확인 실행 위임 자원 리스 · 토큰 Redis 8 실행 순서를 조정하는 저장소 Leader 리스 자원 리스 + 펜싱 토큰 Leader 리스 자원 리스 · 토큰 PostgreSQL 18 + Exposed 데이터 반영 여부를 최종 판정 하나의 트랜잭션 Outbox 처리 확인 및 결과 재확인 조건부 데이터 변경 OutboxEffectWorker 커밋된 요청 처리 외부 시스템 멱등 처리 · 결과 조회 Outbox 획득 · 결과 기록 안정적인 OperationId · 결과 조회
APIAPI 계층

시나리오 선택 · SAFE/UNSAFE 격리 · 권한 검사

작업 조정작업 조정 계층

Leader 리스 · 충돌 자원 리스 · 펜싱 토큰 발급

PostgreSQL데이터베이스 계층

현재 구성 재확인 · 조건부 갱신 · Checkpoint/Execution/Outbox

Outbox / 외부 시스템외부 처리 계층

외부 시스템 호출 · 처리 확인 기록 · 결과 재확인

04 · 대화형 시스템 흐름

시나리오를 선택해 안전 장치가 작동하는 지점을 확인한다

같은 시나리오를 SAFE와 UNSAFE로 전환하면 최종 상태가 달라지는 지점을 확인할 수 있습니다.

05 · 실제 구현

실제 클래스와 테스트가 각 안전 장치를 검증한다

화면의 단계는 설명을 위해 새로 만든 흐름이 아니라 실제 서비스와 시나리오 코드의 동작을 재구성한 것입니다.

구현 결과를 확인하는 테스트

  • LeaseOverrunScenarioTest토큰 41의 변경 거부와 토큰 42의 최종 상태 유지
  • FencedMutationPostgresIntegrationTestPostgreSQL 조건부 갱신에서 지연된 변경 차단
  • JobSafetyEndToEndIntegrationTest업무 상태, Checkpoint, 실행 이력, Outbox의 트랜잭션 반영
  • NonFenceableEffectScenarioTest안정적인 OperationId와 결과 재확인으로 중복 처리 방지
  • UnsafeJobSafetyControllerConditionTestUNSAFE API의 Profile과 설정값 격리

06 · 예제 실행

관련 서비스를 실행하고 SAFE 결과부터 확인한다

예제는 JDK 25, PostgreSQL 18 호환 서버와 Redis 8 호환 서버를 사용합니다. UNSAFE API는 비교 학습용으로만 별도 실행합니다.

07 · 적용 범위

이 예제가 해결하는 문제와 남는 한계

해결

지연된 데이터 변경

이전 펜싱 토큰의 조건부 갱신을 PostgreSQL이 거부합니다.

해결

구성 변경 중 실행

현재 Tenant, Region, 버전과 Namespace를 트랜잭션에서 다시 확인합니다.

해결

외부 결과 불확실

원래 OperationId를 조회하고 처리 확인 기록을 저장합니다.

한계

외부 시스템이 멱등 키와 조회 API를 제공하지 않음

정확히 한 번의 외부 처리를 보장할 수 없습니다. 수동 확인이나 보상 처리가 필요합니다.

한계

Redis 펜싱 순번 이력 유실

백업으로 순번을 보존하거나 Namespace Epoch를 올려야 합니다.

한계

스키마 마이그레이션

예제는 시작 시 SchemaUtils를 사용합니다. 운영 환경에서는 Flyway나 Liquibase가 필요합니다.