
Spring WebFlux 환경에서 Redisson을 이용해 분산 락을 구현할 때,
락을 해제하는 방법으로 보통 아래 두 가지가 있습니다
forceUnlock()unlock(threadId)두 메서드는 겉보기엔 비슷하지만, 내부적으로 동작 방식과 안전성 측면에서 꽤 큰 차이가 있습니다.
이 차이를 정확히 이해하지 않으면, 의도치 않은 락 해제나 데이터 경합(race condition) 같은 문제가 발생할 수 있습니다.
unlock(threadId) — 스레드 단위 안전한 해제Redisson의 락(RLock)은 내부적으로 스레드 ID 기반 소유권(ownership) 을 관리합니다.
특정 스레드가 락을 획득하면 Redis 내부 키에 해당 스레드의 ID 정보가 기록됩니다.
unlock(threadId)는 이 정보를 비교해서,
“이 락을 실제로 잡은 스레드인지”
를 검증한 뒤에만 해제합니다.
lock.unlock(threadId)
Redisson 3.15.x 이하 버전에서는 threadId 기반 unlock이 제공되지 않음WebFlux + 코루틴 환경에서는 “스레드 기반 검증” 자체가 의미가 약해질 수 있습니다.
forceUnlock() — 소유권 검증 없이 강제 해제forceUnlock()은 이름 그대로 락 소유자 검증 없이 Redis에서 해당 락 키를 삭제합니다.
lock.forceUnlock()
→ 락의 owner threadId가 누구든 상관없이 무조건 해제합니다.
| 상황 | 권장 방식 | 이유 |
|---|---|---|
| 동기 환경 (Spring MVC) | unlock(threadId) | 동일 스레드에서 락 획득/해제 일관성 보장 |
| 비동기 환경 (WebFlux, Coroutine) | forceUnlock() | 실행 스레드가 달라져도 해제 보장 |
| 강제 정리(에러 복구, Deadlock 회피 등) | forceUnlock() | 임시 조치로 사용 가능 |
WebFlux에서는 Reactor가 스레드를 자유롭게 스위칭합니다.
즉, 락을 잡을 때와 해제할 때의 Thread ID가 다를 수 있습니다.
예를 들어:
Lock acquired by Thread-A
Transaction committed on Thread-B
이런 경우 unlock(threadId)는 "다른 스레드에서 해제를 시도" 하므로 실패하거나 예외를 던질 수 있습니다.
그래서 WebFlux 기반 시스템에서는 forceUnlock() 으로 일관되게 해제하는 게 현실적인 선택입니다.
| 항목 | unlock(threadId) | forceUnlock() |
|---|---|---|
| 소유자 검증 | 있음 | 없음 |
| 안전성 | 높음 | 낮음 |
| 비동기 환경 호환성 | 낮음 | 높음 |
| Deadlock 회피용 | 비권장 | 가능 |
| WebFlux 환경 추천 | 사용불가 | 현실적방안 |
unlock(threadId)는 “안전하지만 스레드 일관성”이 필요한 방식 forceUnlock()은 “비동기 환경에서의 현실적 선택” forceUnlock()으로 락 해제 시점을 명시적으로 관리하는 것이 안정적