동시성 문제를 해결하기 위해 뮤텍스(Mutex), 세마포어(Semaphore)나 데이터베이스의 비관적/낙관적 락을 떠올리게 된다.
하지만 이 기법들은 모두 모놀리식(Monolithic) 아키텍처와 같이 하나의 시스템 경계(Boundary) 안에서만 동작한다.
- 뮤텍스와 세마포어는 단일 서버, 즉 하나의 프로세스 내부에서 스레드 간의 충돌을 막는 OS 수준의 기법이다.
- 비관적/낙관적 락 역시 단일 데이터베이스 인스턴스 내에서 트랜잭션 간의 데이터 정합성을 보장하는 방식이다.
하지만 MSA 환경에서는 각 서비스가 독립된 서버와 데이터베이스를 사용하는 것이 일반적이다.
프로세스 간에는 메모리를 공유하지 않기 때문에 서로의 락 상태를 확인하거나 제어할 수 없다.
즉, 한 서버에서 설정한 락을 다른 서버가 인식할 방법이 없다.
따라서 여러 대의 서버가 네트워크를 통해 동일한 공유 자원(예: 재고, 쿠폰, 포인트 등) 에 접근할 수 있는 상황에서는
분산 락(Distributed Lock) 이 필요하다.
분산 락(distributed lock) 이란?
분산 락은 공통 저장소에 락의 상태를 저장하고 각 프로세스가 이 상태를 읽어 락을 획득하거나 해제하는 방식으로 동작한다.
즉, 모든 노드가 공통된 저장소를 통해 락 상태를 공유하여 서로 다른 서버 간에도 자원 접근을 순차적으로 제어할 수 있게 된다.
분산 락의 동작 원리
- 공통 저장소에 락의 상태(소유자 식별자, 만료 시간 등)를 저장한다.
- 락을 얻고자 하는 프로세스는 저장소에 락 생성을 시도한다.
- 성공하면 해당 자원에 대한 접근 권한을 가지며, 작업이 끝나면 락을 해제한다.
- 네트워크 장애/크래시로 락이 영구적으로 남는 것을 막기 위해 TTL(만료시간)을 둔다.
자주 사용되는 공통 저장소
Redis: 인메모리라 빠르고 SET NX PX 같은 원자적 연산 또는 RedLock 알고리즘을 활용해 분산 락을 구현한다. 응답성이 중요한 시나리오에 자주 사용된다.
Zookeeper: 분산 코디네이터로서 노드 간 상태 동기화, ephemeral node 기반의 락, 순서 노드 등을 제공한다. 일관성(Consistency)을 중시할 때 유리하다.
Database (MySQL, Postgres 등): GET_LOCK() 같은 Named Lock, 혹은 행을 이용한 낙관/비관적 락으로 구현 가능. 영속성과 ACID 트랜잭션의 장점이 있지만 성능이 떨어질 수 있다.
구현 방식과 원리
Redis 기반 분산 락
Redis에서는 SET 명령의 NX(존재하지 않을 때만)와 PX(만료시간 설정) 옵션을 활용하여 락을 구현한다.
락을 요청하는 노드는 고유한 토큰(UUID 등)을 함께 저장해 락의 소유권을 나타낸다.
이 방식은 원자적으로 락을 획득하므로 여러 노드가 동시에 요청하더라도 한 노드만 성공하게 된다.
락 해제 시에는 저장된 토큰과 자신의 토큰을 비교해 일치할 때만 해제하는 방식으로 안전성을 보장한다.
Redis — RedLock 알고리즘
RedLock은 여러 개의 독립된 Redis 인스턴스에 동일한 키로 락을 요청하고,
과반수 이상에서 성공하면 락을 획득한 것으로 간주하는 알고리즘이다.
이를 통해 단일 Redis 장애에 대한 내성을 확보할 수 있다.
하지만 네트워크 지연이나 시계 불일치로 인한 잠재적 위험이 있으므로, 복잡도가 높은 환경에서는 주의 깊은 검토가 필요하다.
Zookeeper 기반 락
Zookeeper는 분산 코디네이터로, ephemeral(임시) znode를 이용해 락을 구현한다.
각 클라이언트는 순차 노드를 생성하고 자신의 바로 앞 순서 노드가 삭제될 때까지 감시한다.
가장 작은 번호의 노드를 가진 클라이언트가 락을 획득하며, 세션이 끊기면 해당 노드가 자동 삭제되어 락이 해제된다.
이 방식은 강한 일관성을 보장하지만, 설정과 관리가 비교적 복잡하다.
Database 기반 락
DB에서는 GET_LOCK() 같은 Named Lock을 활용하거나 특정 행에 비관적 락(SELECT ... FOR UPDATE)을 거는 방식으로 구현할 수 있다. 단순하고 트랜잭션 일관성을 보장하지만, DB 부하가 높아질 수 있으며 대규모 트래픽에는 적합하지 않을 수 있다.
핵심 고려사항
분산 락이 제대로 동작하려면 다음을 만족해야 한다.
- 락 획득의 원자성 (Atomicity)
- 락을 획득하거나 실패하는 과정이 반드시 원자적으로 수행되어야 한다.
(여러 프로세스가 동시에 락을 요청하더라도, 단 하나만 성공해야 함)
- 락을 획득하거나 실패하는 과정이 반드시 원자적으로 수행되어야 한다.
- 락 소유권 (Ownership)
- 어떤 노드가 락을 획득했는지 식별할 수 있어야 하며,
다른 노드가 실수로 해당 락을 해제하지 않도록 해야 한다.
(예: UUID, 스레드 ID, 토큰 값 등으로 식별)
- 어떤 노드가 락을 획득했는지 식별할 수 있어야 하며,
- 락 만료 (Expiration)
- 네트워크 장애나 서버 다운 등의 이유로 락이 해제되지 않는 상황을 방지하기 위해
일정 시간이 지나면 자동으로 락이 해제되도록 설정한다.
- 네트워크 장애나 서버 다운 등의 이유로 락이 해제되지 않는 상황을 방지하기 위해
- 락 해제의 안전성 (Safety)
- 락을 해제할 때, 반드시 “내가 획득한 락인지” 확인한 뒤 해제해야 한다.
그렇지 않으면 다른 노드의 락을 해제해버리는 위험이 있다.
- 락을 해제할 때, 반드시 “내가 획득한 락인지” 확인한 뒤 해제해야 한다.
'DB' 카테고리의 다른 글
낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock) (0) | 2025.10.17 |
---|---|
[Redis] 레디스 자료형 (0) | 2025.06.24 |
[DB] Connection Pool, HikariCP (0) | 2025.03.27 |
FETCH JOIN (0) | 2025.03.18 |
[CS] DBMS (0) | 2023.07.08 |