learning raphael draft 2026-05-22

Redis 분산락 + DB 트랜잭션 분리 시 정합성 문제

TL;DR — Redis lock과 DB 트랜잭션은 서로 다른 레이어다. Lock 해제 시점이 DB commit 시점보다 빠르면, 후속 트랜잭션이 롤백된 결과를 정상으로 가정하고 동작해서 정합성이 깨진다. 면접 정답 라인: "Lock은 DB commit 이후에 해제하거나, 애초에 DB 비관적 락(`SELECT FOR UPDATE`)을 쓴다."

개념 설명

핵심 아이디어

면접관이 던진 질문 원문:

> "Redis lock을 '안전 장치'로 쓰신다고 하셨는데, DB 트랜잭션 커밋 실패 시 Redis lock이 이미 해제된 상태라면 데이터 정합성을 어떻게 보장하시나요?"

무슨 일이 일어나는가 (잘못된 흐름):

```

시간 →

T1: [Redis lock 획득] → [DB UPDATE 실행] → [Redis lock 해제] → [DB COMMIT 실패 → ROLLBACK]

T2: [Redis lock 획득] → [잔액 조회 = 차감 전 상태]

→ [의사결정] → [DB UPDATE] → [COMMIT]

```

왜 중요한가

두 메커니즘의 레이어가 다르다:

항목DB 비관적 락 (`SELECT FOR UPDATE`)Redis 분산락
범위트랜잭션 범위와 **자동 일치**별도 해제 호출 필요
해제 시점COMMIT/ROLLBACK 시 자동개발자가 명시적 호출 또는 TTL
장애 시DB 트랜잭션과 운명 공유TTL 만료 시 멋대로 풀림
인프라단일 DB별도 Redis 클러스터

Redis lock의 장점은 분산 환경(여러 서비스/DB 간 동시성 제어)인데, 단일 DB 내 동시성 제어용으로 쓰면 트랜잭션 경계 불일치 문제가 생긴다.

실전 적용

해결 방안 4가지 (면접 답변 라인)

1. Lock 해제를 DB Commit 이후로 (Spring 패턴)

```java

@Service

public class PaymentService {

@Transactional

public void processPayment(Long userId, BigDecimal amount) {

// 1. Redis lock 획득

RLock lock = redisson.getLock("payment:" + userId);

lock.tryLock(3, 10, TimeUnit.SECONDS);

try {

// 2. DB 작업

updateBalance(userId, amount);

// 3. Commit은 메서드 종료 시 자동

// 4. Lock 해제도 commit 이후로 미룸

} finally {

// ❌ 여기서 해제하면 commit 전이라 위험

// lock.unlock();

}

}

}

// ✅ Commit 이후 이벤트 리스너에서 해제

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

public void releaseLock(LockReleaseEvent event) {

event.getLock().unlock();

}

```

트레이드오프: Lock 보유 시간 늘어남 → 동시성 ↓. 짧은 트랜잭션에만 권장.

2. DB 비관적 락 사용 (`SELECT FOR UPDATE`)

```java

@Repository

public interface AccountRepository extends JpaRepository {

@Lock(LockModeType.PESSIMISTIC_WRITE)

@Query("SELECT a FROM Account a WHERE a.id = :id")

Account findByIdForUpdate(@Param("id") Long id);

}

```

트레이드오프: DB 부하 ↑, 분산 환경(여러 서비스)에는 부적합.

3. Idempotency Key + DB Unique Constraint

```java

@Entity

public class PaymentTransaction {

@Column(unique = true)

private String idempotencyKey; // 클라이언트가 전달

// ...

}

```

트레이드오프: 클라이언트가 idempotency key 관리 필요. 락보다 깔끔하지만 책임이 클라이언트로 이동.

4. Outbox Pattern + Retry

트레이드오프: 즉시 일관성 X, 구현 복잡도 ↑.

면접 답변 권장 라인

> "Redis 분산락은 분산 환경 전제일 때 의미가 있고, 단일 DB 내라면 DB 비관적 락이 더 자연스럽습니다. Redis lock을 쓴다면 반드시 DB commit 이후에 해제해야 하는데, Spring에서는 @TransactionalEventListener(AFTER_COMMIT)로 처리합니다. 다만 결제 같은 케이스는 idempotency key + unique constraint 조합이 더 명시적이고 안전해서 실무에서는 그쪽을 선호했습니다."

함정 / 주의점

복습 일정

단계날짜완료
Day 0 (초학습)2026-05-22
Day 72026-05-29
Day 372026-06-28
Day 1272026-09-26

참고