Projects/hub-eleven

[트러블슈팅] Redisson 분산 락 적용 중 트랜잭션 커밋 순서 문제를 해결한 과정 (2)

annovation 2026. 6. 23. 23:00

추가 해결 사항

💡 문제 상황

  • Redisson 분산 락 적용 중 트랜잭션 커밋 순서 문제를 해결하는 과정에서 다수의 동시 요청이 하나의 재고 락에 몰리면서 일부 요청이 tryLock의 대기 시간 안에 락을 획득하지 못하는 상황이 발생했고, 이 요청들이 락 획득 실패 예외로 처리되어 동시성 테스트 통과가 이뤄지지 않는 문제가 발생했다.

💡 문제 증상

  • 현재 tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS)에서  WAIT_TIME_SECONDS 동안 락 획득을 기다리지만, 그 시간 안에 락을 얻지 못하면 false를 반환한다.
  • 이를 곧바로 STOCK_LOCK_TIMEOUT 예외로 변환했기 때문에 일부 요청이 재시도 없이 실패 처리되어 동시성 테스트가 통과하지 못했다.
package com.hubEleven.stock.infrastructure.lock;

@Component
@RequiredArgsConstructor
public class RedissonStockLockManager implements StockLockManager {

	private static final long WAIT_TIME_SECONDS = 3L;
	private static final long LEASE_TIME_SECONDS = 5L;

	private final RedissonClient redissonClient;

	@Override
	public <T> T executeWithLock(String lockKey, Supplier<T> supplier) {
		RLock lock = redissonClient.getLock(lockKey);
		boolean locked = false;

		try {
			locked = lock.tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS);
			if (!locked) {
				throw new GlobalException(STOCK_LOCK_TIMEOUT);
			}

			return supplier.get();
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			throw new GlobalException(STOCK_LOCK_TIMEOUT);
		} finally {
			if (locked && lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}
}

해결 방안 검토

💡 해결 방안

  • 대략적으로 해결 가능한 락 획득 대기 시간 초과 시 예외 처리 정책을 요약해보면 아래와 같다.
정책 요약 세부 내용 장점 단점
A 너무 오래 기다리는 요청은 실패시킨다 - 트래픽 폭주 시 일부 요청은 빠르게 실패시키고 재시도를 유도한다.
- WAIT_TIME을 제한적으로 설정
- 락 획득 실패 시 409 CONFLICT 반환
- 클라이언트가 재시도하거나 사용자에게 안내
- 서버가 오래 붙잡히지 않음
- 트래픽 폭주 시 빠르게 실패 처리 가능
- 사용자 요청 일부가 실패할 수 있음
- 클라이언트 재시도 설계가 필요함
B 서버 내부에서 재시도 처리 1. 락 획득 시도
2. 실패
3. 짧게 대기
4. 다시 시도
5. 몇 회 실패하면 최종 실패
- 순간적인 경합은 흡수 가능
- 사용자 실패율 감소
- 응답 시간이 길어질 수 있음
- 재시도 폭주가 생기면 서버 부하 증가
- 재시도 횟수/간격 정책이 필요함
C 주문 요청을 큐로 보내 순차 처리 - Kafka, RabbitMQ 같은 큐를 사용
- 주문 요청
 → 큐에 적재
 → 재고 감소 consumer가 순서대로 처리
- 대량 트래픽을 안정적으로 흡수 가능
- 재고 감소를 순차 처리하기 쉬움
- 락 경합 감소
- 구현 복잡도 증가
- 비동기 처리 구조 필요
- 사용자에게 즉시 확정 응답을 주기 어려울 수 있음

해결 방법

💡 해결 방법 : 락 획득 실패 시 제한적 재시도 후 명시적인 예외를 반환

최대 3번 반복:
  tryLock 시도
  성공하면 supplier 실행
  실패하면 짧게 대기 후 재시도

3번 모두 실패:
  STOCK_LOCK_TIMEOUT 예외
  • 락 획득 실패를 곧바로 비즈니스 실패로 처리하면 순간적인 트래픽 집중 상황에서 정상 처리 가능한 요청까지 실패할 수 있다고 판단했다.
  • 이에 제한된 횟수의 재시도를 통해 일시적인 락 경합을 완화하고, 재시도 후에도 락을 획득하지 못한 경우에는 STOCK_LOCK_TIMEOUT 예외를 명시적으로 반환하여 클라이언트 또는 상위 계층에서 재시도 여부를 판단할 수 있도록 설계했다.

Redisson 재시도 구조가 가능한 이유

💡 Redis Pub/Sub 구조

  •  

검증

💡 동시성 테스트

 

1. 테스트 진행 방식

  • 동시에 100개의 요청이 같은 상품 재고를 1개씩 감소시키는 테스트를 작성했다.

2. 테스트 통과 결과

  • Redisson 분산 락과 트랜잭션 경계 분리 후 테스트가 통과했다.


개선할 사항

  • 추후 확작성을 고려하다면, 모든 요청이 순차적으로 처리될 수 있게 Kafka, RabbitMQ 같은 기술을 활용해 재시도 로직을 개선하면 좋을 것 같다.

참고 자료