분산락을 적용해보자!
- 개발
- 2026. 2. 18. 23:56
분산락은 도대체 무엇일까?
이전에도 Lock에 대해 학습을 진행한적이 있었습니다. DB락 부터, 낙관 락, 비관 락에 대해 학습을 진행하였죠. RDB vs Nosql크게 DB에는 2가지 종류가 존재합니다. SQL을 사용하는 RDB와 SQL도 사용하는 N
b-programmer.tistory.com
이전 장에서 분산락이 무엇인지 학습했습니다. 그런데 왜 분산락을 써야 하는가에 대해서는 아직 완전히 납득이 되지 않았습니다.
제가 이해한 분산락은, 여러 인스턴스에서 같은 공유 자원에 대한 임계영역이 동시에 실행되지 않도록 보장하는 메커니즘입니다.
그렇다면 이런 질문이 남습니다. 왜 우리는 "동시에 실행되는 것"을 막아야 할까요?
이번 장에서는 이 질문에 집중해보려고 합니다. DB 락과 Redis 락을 직접 적용해보면서, 각각이 어떤 문제를 해결하고 어떤 한계를 가지는지 경험해보겠습니다.
시작하기 앞서 일반 DB 락과의 차이점을 알아봅시다.
락이란, 동일한 공유 자원에 대해 여러 작업이 동시에 접근하지 못하도록 조절하는 메커니즘입니다.
여기서 말하는 일반적인 DB 락은 낙관적 락과 비관적 락을 의미합니다.
겉으로 보기에는 일반 DB 락과 분산락이 크게 다르지 않아 보입니다.
둘 다 결국 동일한 자원에 대한 동시 접근을 제어하기 때문입니다.
하지만 차이점은 적용되는 범위에 있습니다.
일반 DB 락은 하나의 데이터베이스를 기준으로 동시성을 제어합니다.
수많은 사용자가 특정 자원에 동시에 접근하더라도, DB가 해당 자원에 대해 상호 배제를 보장합니다.
반면, 분산락은 여러 인스턴스가 동시에 실행되는 환경에서, 동일한 자원에 대한 작업이 동시에 수행되지 않도록 조정하는 메커니즘입니다.
즉, 차이는 "사용자 수"가 아니라 단일 프로세스 내부의 동시성 제어인지, 여러 인스턴스 간의 동시성 제어인지에 있다고 볼 수 있습니다.
그렇다면, 일반 DB락은 분산락으로 사용할 수 없을까?
여러 인스턴스가 동일한 DB를 사용하고 있다면, DB 락을 통해 분산 환경에서도 동시성 제어를 할 수 있습니다.
즉, 여러 실행 환경이 존재하더라도 하나의 DB를 기준으로 상호 배제를 보장할 수 있다는 의미입니다.
다만 제가 느끼기에 쉽지 않다고 생각했던 이유는,
여러 인스턴스가 동시에 같은 자원에 접근하는 상황을 직접 구성하고 검증하는 과정이 단순하지 않기 때문입니다.
인스턴스가 여러 개라는 것은,
각각이 독립적으로 실행되며 동일한 자원에 접근을 시도할 수 있다는 뜻입니다.
이 상황에서 실제로 충돌이 발생하는지, 그리고 DB 락이 이를 제대로 제어하는지를 확인하는 과정이 생각보다 복잡하게 느껴졌습니다.
결론적으로, 일반 DB 락도 분산 환경에서 사용할 수 있습니다.
다만 이를 직접 실험하고 체감하기 위해서는 여러 실행 환경을 구성하고 경쟁 상황을 만들어보는 과정이 필요하다고 느껴졌습니다.
그래서.. 제가 생각하는 분산락을 사용하는 이유는?
동시성을 제어한다는 측면에서 보면, 일반 락과 분산락은 크게 다르지 않다고 생각합니다.
둘 다 동일한 자원에 대해 동시에 접근하지 못하도록 제한하는 메커니즘이기 때문입니다.
다만 차이점은 적용 범위에 있다고 봅니다.
일반 락은 하나의 실행 환경 내부에서의 동시성을 제어하는 반면, 분산락은 여러 인스턴스가 존재하는 환경에서 동시성을 제어합니다.

예를 들어 서버가 3대가 존재한다고 가정해봅시다.
서버1도 특정 동작을 수행하고, 서버2도 수행하고, 서버3도 수행합니다.
그런데 그 동작이 동일한 작업이라면, 굳이 3번 실행할 필요가 있을까요?
한 번만 수행해도 되는 작업이라면, 여러 번 실행하는 것은 불필요한 비용입니다.
대표적으로 스케줄링 작업이 있을 수 있고, 또는 외부 API와 통신하는 작업도 이에 해당할 수 있습니다.
이를 한 단어로 정리하면 부하 제어라고 볼 수 있습니다.
분산락을 구현하는 방식은 여러 가지가 있지만, 대표적으로 두 가지를 들 수 있습니다.
- DB 기반(ShedLock 등)으로 관리하는 방법
- Redis 기반으로 락을 관리하는 방법
여기서 말하는 서버 부하는 애플리케이션 부하뿐만 아니라, DB 부하까지 포함합니다.
핵심은 DB가 이 부하를 어느 정도까지 감당할 수 있는지 확인하는 것입니다.
만약 DB에 락/배치 트래픽이 집중되어 부담이 커지는 상황이라면,
DB에 계속 락 책임을 두기보다는 Redis를 이용해 락을 분리하는 방향도 충분히 고려할 수 있습니다.
다만 Redis를 도입하게 되면 운영 및 관리 포인트가 하나 더 늘어나게 됩니다.
따라서 단순히 성능만을 이유로 선택하기보다는, 전체 구조에서 어떤 방식이 더 이득인지 비교한 뒤 신중하게 결정해야 한다고 생각합니다.
이 글에서는 가장 간단하게 확인 할 수 있는 스케쥴링으로 진행합니다.
먼저 분산락을 적용하지 않은 버전으로 진행해보겠습니다.
서버는 2개를 띄워두고, 디버깅을 걸어둔 상태에서 동시에 요청을 보내볼 예정입니다.
분산락이 없다면 두 서버가 동시에 같은 작업을 수행할 수 있습니다.
반대로 분산락이 정상적으로 적용되어 있다면,
같은 키(공유 자원)에 대해서는 한 서버만 락을 획득해서 작업을 진행하고 나머지 서버는 실패하거나 대기하게 되겠죠.
즉, 결과적으로 "한 번만 실행"되는 형태가 됩니다.
분산락을 적용하지 않는 형태

역시, 서버 두대가 디버깅이 걸려있네요.
shedlock 적용
분산락을 적용하지 않은 형태에서는 역시 서버 두 대 모두 디버깅이 걸렸습니다.
ShedLock을 적용한 뒤에는, 스케줄러에 아래처럼 락을 추가했습니다.
@SchedulerLock(name = "sampleJob",
lockAtMostFor = "PT20S",
lockAtLeastFor = "PT3S")
name은 어떤 작업인지 구분하기 위한 키이고, lockAtMostFor, lockAtLeastFor는 락이 유지되는 시간을 의미합니다.
적용 후 실행해보니, "한 번에 한 서버만 실행된다"는 점에서는 동작하는 것처럼 보였습니다.
다만 기대와 다르게 특정 서버만 실행되는 게 아니라, 두 서버가 번갈아가며 실행되는 형태였습니다.
특히 lockAtLeastFor를 3초로 설정해 둔 영향으로, 약 3초 단위로 실행 주체가 바뀌는 패턴이 관찰되었습니다.
제가 느낀 점은, 인스턴스마다 스케줄러가 각각 반복적으로 동작하고 있다는 느낌이 강했습니다.
서버가 여러 대라면, 각 서버가 동일한 스케줄러를 가지고 있고, 정해진 시간에 모두 실행을 시도합니다.
즉, 스케줄러가 하나만 존재하는 것이 아니라, 서버 개수만큼 각각 존재하고 독립적으로 동작하는 구조라는 점이 인상적이었습니다.
ShedLock을 적용하더라도 스케줄러 자체가 하나로 합쳐지는 것은 아니었습니다. 각 인스턴스가 여전히 실행을 시도하지만,
그중 하나만 실제로 작업을 수행하도록 조정되는 구조였습니다.
그래서 저는 "스케줄러가 여러 개 존재하고, 그중 하나만 실행되는 구조"라는 느낌을 받았습니다.
이번엔 레디스 락을 사용해 봅시다.
이번에는 레디스 락을 사용해봅시다.
Redis 락은 DB 기반(ShedLock)과 느낌이 다를까 싶었는데, 체감상 동작 방식은 유사했습니다.
두 인스턴스 모두 스케줄러는 실행을 시도하지만,
락을 획득한 인스턴스만 실제 작업을 수행하고 다른 인스턴스는 실행되지 않는 방식이었습니다.
그래서 결과적으로는 번갈아가며 동작하는 것처럼 보였습니다.
DB 기반에서는 테이블을 통해 락 상태를 확인할 수 있었는데,
Redis는 키를 확인해보면 되지만 현재는 시간 관계상 직접 확인하지는 못했습니다.
그렇다면 Redis 락은 어떻게 구현할 수 있을까요?
public <T> Optional<T> executeWithLock(String key, Duration ttl, Supplier<T> task) {
String token = UUID.randomUUID().toString();
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(key, token, ttl);
if (!acquired) {
return Optional.empty();
}
try {
return Optional.ofNullable(task.get());
} finally {
redisTemplate.delete(key);
}
}
위 코드는 SET NX + TTL 방식으로 락을 획득하고,
락을 획득한 경우에만 task를 실행하도록 구성했습니다.
락 획득에 실패하면 작업을 수행하지 않고 Optional.empty()를 반환합니다.

원래 락 해제(delete)는 안전하게 처리하려면 Lua 스크립트로 작성하는 것이 권장되지만,
이번에는 우선 Redis 락을 "사용해보는" 수준에서 단순하게 구현했습니다.
아직 코드를 전반적으로 분산락으로 고치지는 않았습니다. 그래서 아마 한 개더 글이 더 작성이 되어질 예정입니다.
결론
처음에는 ShedLock과 Redis 락이 서로 다르게 동작할 것이라고 생각했습니다.
하지만 직접 적용해보니 체감상 동작 방식은 크게 다르지 않았습니다.
곰곰이 생각해보면, 둘 다 결국 분산 환경에서 상호 배제를 구현하기 위한 방법이기 때문에
동작 원리가 완전히 다를 이유도 없었습니다.
이번 파트에서는 직접 ShedLock과 Redis 락을 적용해보며
두 방식이 어떤 점에서 유사하고, 또 어떤 부분에서 차이가 있는지를 확인하는 시간을 가졌습니다.
그 과정을 통해 분산락을 왜 사용하는지에 대해 어느 정도 납득하게 되었습니다.
특히 "정합성"보다는 "중복 실행을 방지하고 부하를 제어하기 위한 장치"라는 점에서 의미를 이해하게 되었습니다.
끝으로, ShedLock과 Redis 락의 차이점은 다음과 같습니다.
| ShedLock | Redis 락 |
| DB 사용 | Redis 사용 |
| 스케줄러 전용 | 범용 락 |
| 설정 간단 | 구현 책임 큼 |
| 안정적 | TTL/해제 설계 중요 |
'개발' 카테고리의 다른 글
| Redis 분산락은 왜 완전히 안전하다고 보기 어려울까? (0) | 2026.02.20 |
|---|---|
| EDA에도 패턴들이 이렇게나 많이 존재 하다니.. (0) | 2026.02.19 |
| 어째서 JVM을 알아야 하는가? (0) | 2026.02.15 |
| 어째서 메시지 브로커가 왜 필요한가? (0) | 2026.02.13 |
| read-only일때 replica 사용하게 하기 (0) | 2026.02.12 |