[Lock-9][Optimization]네임드 락(Named Lock)의 구현과 트랜잭션 분리 및 성능 최적화

2025. 12. 26. 16:26·Database/MySQL

1. 네임드 락(Named Lock)이란?

 네임드 락(Named Lock)은 MySQL에서 제공하는 GET_LOCK() 및 RELEASE_LOCK() 함수를 사용하여 사용자가 지정한 임의의 문자열에 대해 잠금을 획득하는 기능이다. 기존의 레코드 락(X-Lock, S-Lock)이 특정 데이터 행을 잠근다면, 네임드 락은 "유저의 주문", "이벤트 응모"와 같이 특정 '행위'나 '논리적 리소스'를 잠글 수 있다는 점이 특징이다. 이는 데이터베이스 레벨에서 제공하는 락이기에 분산 환경에서도 효과적인 동시성 제어가 가능하다.

  • 장점: 별도의 인프라(Redis 등) 없이 분산 환경에서 동시성 제어 가능
    • 데이터베이스 레벨의 강력한 정합성 제공
    • 특정 리소스 식별자(문자열) 기반의 유연한 관리
  • 단점: 락 획득 및 해제를 명시적으로 관리해야 함
    • 세션 관리에 신경을 써야 하며, 잘못 사용할 경우 커넥션 풀 고갈 위험이 있음

2. 네임드 락의 구현

2.1. Repository 구현

MySQL의 네이티브 쿼리를 직접 호출하여 락을 획득하고 해제하는 인터페이스를 정의한다.

public interface EventNamedLockRepository extends JpaRepository<EventWithLock, Long> {
    // 락 획득: 성공 시 1, 타임아웃 0, 에러 시 NULL 반환
    @Query(value = "SELECT GET_LOCK(:lockName, :timeoutSeconds)", nativeQuery = true)
    Integer getLock(@Param("lockName") String lockName, @Param("timeoutSeconds") int timeoutSeconds);

    // 락 해제: 성공 시 1, 실패 시 0 반환
    @Query(value = "SELECT RELEASE_LOCK(:lockName)", nativeQuery = true)
    Integer releaseLock(@Param("lockName") String lockName);
}

2.2. Facade 패턴을 이용한 역할 분리

낙관적 락과 마찬가지로, 네임드 락 역시 락을 획득하는 행위와 비즈니스 로직을 분리하기 위해 Facade 패턴을 활용한다.

@Component
@RequiredArgsConstructor
public class NamedLockEventFacade {
    private final EventWithLockService eventWithLockService;
    private final EventWithLockRepository eventWithLockRepository;

    public void joinEvent(Long eventId, Long memberId) throws InterruptedException {
        String lockName = String.format("event_%d", eventId);

        try {
            // 1. 락 획득 시도 (3초 타임아웃)
            Integer lockResult = eventWithLockRepository.getLock(lockName, 3);
            if (lockResult == null || lockResult <= 0) {
                throw new RuntimeException("락 획득 실패");
            }

            // 2. 비즈니스 로직 실행
            eventWithLockService.joinEventWithNamedLock(eventId, memberId);
        } finally {
            // 3. 반드시 락 해제 수행
            eventWithLockRepository.releaseLock(lockName);
        }
    }
}

3. 직면한 문제: 락 해제와 커밋 시점의 불일치

위와 같이 구현한 뒤 100명의 동시 참가 테스트를 수행하면 예상과 달리 데이터가 꼬이는 현상이 발생한다. 원인은 트랜잭션 커밋과 네임드 락 해제의 타이밍 차이에 있다.

문제 발생 시나리오

  1. Thread 1: 네임드 락을 획득하고 데이터를 수정한다.
  2. Thread 1: 비즈니스 로직이 끝난 뒤 finally 블록에서 락을 해제한다.
  3. Thread 2: 해제된 락을 즉시 획득하여 작업을 시작한다.
  4. 문제: Thread 1의 트랜잭션은 아직 커밋되지 않은 상태이므로, Thread 2는 Thread 1의 변경 사항이 반영되지 않은 과거 데이터를 읽게 된다.

4. 해결 방법: 트랜잭션 분리 (REQUIRES_NEW)

  문제를 해결하려면 트랜잭션이 완전히 커밋된 후에 락이 해제되도록 보장해야 한다. 이를 위해 서비스 로직에 Propagation.REQUIRES_NEW를 적용하여 독립적인 트랜잭션을 실행한다.

public class EventWithLockService {
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 새로운 트랜잭션 시작
    public void joinEventWithNamedLock(Long eventId, Long memberId) {
        // 비즈니스 로직 수행 및 즉시 커밋
    }
}

이렇게 하면 joinEventWithNamedLock이 종료되는 시점에 즉시 트랜잭션이 커밋되고, 이후 Facade의 finally 구문에서 락이 해제된다. 따라서 다음 스레드는 항상 최신 데이터를 읽을 수 있게 된다.


5. 새로운 복병: 커넥션 풀(Connection Pool) 고갈

트랜잭션을 분리(REQUIRES_NEW)하자 테스트 중 Connection is not available 에러가 발생한다. 이는 하나의 요청이 2개의 커넥션을 점유하게 되었기 때문이다.

  1. 커넥션 1: 네임드 락을 유지하기 위한 커넥션 (Facade 범위)
  2. 커넥션 2: 실제 데이터를 수정하기 위한 새로운 트랜잭션 커넥션 (Service 범위)

최적화 및 해결 방안

커넥션 풀 고갈을 방지하기 위해 HikariCP 설정과 DB 설정을 조정해야 한다.

  • 커넥션 풀 크기 확장: 필요한 커넥션 수를 계산하여 maximum-pool-size를 늘려준다.
  • DB 설정 동기화: DB의 max_connections 값이 HikariCP 풀 크기보다 충분히 큰지 확인해야 한다.
  • 풀 분리(Pool Separation): 네임드 락 전용 풀과 일반 비즈니스용 풀을 분리하면 안정성을 더욱 높일 수 있다.

'Database > MySQL' 카테고리의 다른 글

[Index-8][Optimization] FullText Index와 n-gram 파서  (1) 2026.01.20
[Lock-10][Optimization] Lock: 실무 적용(⭐) - 트랜잭션 범위 축소와 이벤트 기반 관심사 분리  (0) 2025.12.26
[Lock-8][Optimization] 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock) 구현 및 비교  (0) 2025.12.26
[Lock-7][Optimization] 데드락(Deadlock)의 유령과 갱신 분실(Lost Update)의 함정  (0) 2025.12.25
[Lock-6][Optimization] 데이터 정합성을 보장하는 락(Lock)의 종류와 전략  (0) 2025.12.25
'Database/MySQL' 카테고리의 다른 글
  • [Index-8][Optimization] FullText Index와 n-gram 파서
  • [Lock-10][Optimization] Lock: 실무 적용(⭐) - 트랜잭션 범위 축소와 이벤트 기반 관심사 분리
  • [Lock-8][Optimization] 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock) 구현 및 비교
  • [Lock-7][Optimization] 데드락(Deadlock)의 유령과 갱신 분실(Lost Update)의 함정
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[Lock-9][Optimization]네임드 락(Named Lock)의 구현과 트랜잭션 분리 및 성능 최적화
상단으로

티스토리툴바