인프런 커뮤니티 질문&답변

세진:)님의 프로필 이미지
세진:)

작성한 질문수

재고시스템으로 알아보는 동시성이슈 해결방법

Named Lock 활용해보기

네임드락에서 부모 트랜잭션과 별도로 실행해야하는 이유

작성

·

389

·

수정됨

0

네임드락에서 Propagation.REQUIRES_NEW을 설정하는 이유로 부모 트랜잭션과 별도로 실행되어야 한다고 하셨는데, 왜 별도로 실행해야하는지 궁금합니다.

다른분들 질문에서 아래처럼 답변해주셨는데 제가 이해한바가 맞을까요?

부모의 트랜잭션과 동일한 범위로 묶인다면 Synchronized 와 같은 문제가 발생합니다.

Database 에 commit 되기전에 락이 풀리는 현상이 발생합니다.

그렇기때문에 별도의 트랜잭션으로 분리를 해주어 Database 에 정상적으로 commit 이 된 이후에 락을 해제하는것을 의도하였습니다.

핵심은 lock 을 해제하기전에 Database 에 commit 이 되도록 하는것입니다.

 

  1. Synchronized와 같은 문제 : 트랜잭션이 시작 -> 락 획득 -> 로직 수행 -> 락 반납 -> 트랜잭션 커밋과 같이 커밋되기 전에 락 반납한 상황에서 다른 요청이 들어올 수 있기 때문에 동시성 이슈는 여전히 발생

  2. 위의 내용대로라면 부모 트랜잭션의 로직은 보류해두고, 동시성이 발생하는 로직만 따로 분리해서 새로운 트랜잭션으로 로직 실행후 commit하고, 부모 트랜잭션의 나머지 로직 수행으로 이해했는데, 제가 응용한 프로젝트는 왜 네임드락의 정합성이 안맞는지 궁금합니다..동시성 테스트할때 시간도 엄청 오래걸립니다.

  3. 2번 질문에 이어서 제가 위에서 이해한대로 트랜잭션 로그도 확인해봤는데, 부모 트랜잭션 이후로 새로운 트랜잭션 완료한 뒤 기존 트랜잭션을 처리하는걸로 로직은 잘 동작하는데 부정합이 이유가 무엇일까요..?

    20240302 22:10:41.691 [http-nio-8080-exec-1] INFO o.a.c.c.C.[.[.[/] - Initializing Spring DispatcherServlet 'dispatcherServlet' 
    20240302 22:10:41.691 [http-nio-8080-exec-1] INFO o.s.w.s.DispatcherServlet - Initializing Servlet 'dispatcherServlet' 
    20240302 22:10:41.695 [http-nio-8080-exec-1] INFO o.s.w.s.DispatcherServlet - Completed initialization in 4 ms 
    20240302 22:10:41.957 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.flab.offcoupon.service.CouponIssueService.issueCoupon] 
    20240302 22:10:41.957 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - 트랜잭션 1 : 쿠폰 발급 요청. eventId : 1, couponId : 1, memberId : 1 
    20240302 22:10:41.988 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. SELECT id,
                   category,
                   description,
                   start_date,
                   end_date,
                   daily_issue_start_time,
                   daily_issue_end_time,
                   created_at,
                    updated_at
            FROM  event
            WHERE id = 1;
     {executed in 14 msec} 
    20240302 22:10:41.998 [http-nio-8080-exec-1] INFO j.resultsettable - 
    |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------|
    |id |category |description  |start_date |end_date   |daily_issue_start_time |daily_issue_end_time |created_at          |updated_at          |
    |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------|
    |1  |바디케어     |바디케어 전품목 할인
     |2024-02-27 |2024-02-29 |13:00:00               |15:00:00             |2024-02-27T22:34:01 |2024-02-27T22:33:57 |
    |---|---------|-------------|-----------|-----------|-----------------------|---------------------|--------------------|--------------------|
     
    20240302 22:10:42.012 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. SELECT GET_LOCK('namedLock', 3000);
     {executed in 9 msec} 
    20240302 22:10:42.013 [http-nio-8080-exec-1] INFO j.resultsettable - 
    |----------------------------|
    |get_lock('namedlock', 3000) |
    |----------------------------|
    |1                           |
    |----------------------------|
     
    20240302 22:10:42.014 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - getLock = 1 
    20240302 22:10:42.014 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.flab.offcoupon.repository.IncreaseIssuedCoupon.increaseIssuedCouponQuantity] 
    20240302 22:10:42.014 [http-nio-8080-exec-1] INFO c.f.o.r.IncreaseIssuedCoupon - 트랜잭션 2 쿠폰 발급 수 증가. couponId : 1 
    20240302 22:10:42.015 [http-nio-8080-exec-1] INFO c.f.o.r.IncreaseIssuedCoupon - 트랜잭션 2 쿠폰 조회. couponId : 1 
    20240302 22:10:42.029 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. SELECT id,
                   event_id,
                   discount_type,
                   discount_rate,
                   discount_price,
                   coupon_type,
                   max_quantity,
                   issued_quantity,
                   validate_start_date,
                   validate_end_date,
                   created_at,
                   updated_at,
                   version
            FROM coupon
            WHERE id = 1;
     {executed in 13 msec} 
    20240302 22:10:42.041 [http-nio-8080-exec-1] INFO j.resultsettable - 
    |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------|
    |id |event_id |discount_type |discount_rate |discount_price |coupon_type             |max_quantity |issued_quantity |validate_start_date |validate_end_date |created_at       |updated_at       |version |
    |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------|
    |1  |1        |PERCENT       |50            |[null]         |FIRST_COME_FIRST_SERVED |500          |10              |2024-02-01T00:00    |2024-02-05T00:00  |2024-02-01T00:00 |2024-02-01T00:00 |0       |
    |---|---------|--------------|--------------|---------------|------------------------|-------------|----------------|--------------------|------------------|-----------------|-----------------|--------|
     
    20240302 22:10:42.058 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. UPDATE coupon
            SET
                issued_quantity = 11
            WHERE id = 1
     {executed in 15 msec} 
    20240302 22:10:42.059 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.flab.offcoupon.repository.IncreaseIssuedCoupon.increaseIssuedCouponQuantity] 
    20240302 22:10:42.090 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. SELECT RELEASE_LOCK('namedLock');
     {executed in 29 msec} 
    20240302 22:10:42.092 [http-nio-8080-exec-1] INFO j.resultsettable - 
    |--------------------------|
    |release_lock('namedlock') |
    |--------------------------|
    |1                         |
    |--------------------------|
     
    20240302 22:10:42.093 [http-nio-8080-exec-1] INFO c.f.o.s.CouponIssueService - releaseLock = 1 
    20240302 22:10:42.138 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. SELECT EXISTS (SELECT 1
                           FROM coupon_issue
                           WHERE member_id = 1
                             AND coupon_id = 1
                             AND DATE (created_at) = 2024-02-29
                LIMIT 1);
     {executed in 25 msec} 
    20240302 22:10:42.139 [http-nio-8080-exec-1] INFO j.resultsettable - 
    |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    |exists (select 1
                           from coupon_issue
                           where member_id = 1
                             and coupon_id = 1
                             and date (created_at) = '2024-02-29'
                limit 1) |
    |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    |false                                                                                                                                                                                                                              |
    |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
     
    20240302 22:10:42.176 [http-nio-8080-exec-1] DEBUG j.sqltiming -  com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
    1. INSERT INTO coupon_issue (member_id, coupon_id, coupon_status, created_at, updated_at)
            VALUES (1, 1, 'NOT_ACTIVE', 2024-03-02T22:10:42.141176, 2024-03-02T22:10:42.141176)
     {executed in 32 msec} 
    20240302 22:10:42.178 [http-nio-8080-exec-1] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.flab.offcoupon.service.CouponIssueService.issueCoupon]

 

 

답변 2

0

최상용님의 프로필 이미지
최상용
지식공유자

세진:) 님 안녕하세요.
먼저 질문 주신 사항에 대해서는 이해하고 계시는 바가 맞습니다.

첨부해주신 소스에 대해서는 제가 전체적인 로직을 모르고, 설정이 어떻게 되어있는지도 알지 못하기 때문에 어떤 문제로 인해 발생하는지는 유추하기가 힘든것 같습니다.

감사합니다.

0

안녕하세요, 인프런 AI 인턴입니다. 세진님의 질문에 답변드리겠습니다.

데이터베이스 트랜잭션에서 동시성 제어를 위해 네임드락(명명된 락)을 사용할 때, Propagation.REQUIRES_NEW 속성을 설정하여 부모 트랜잭션과 별도로 락을 관리하는 이유는 다음과 같습니다.

  1. 부모 트랜잭션과 별도로 실행하여 더세분화된 제어가 가능합니다. 각각의 트랜잭션을 독립적으로 관리하여, 하나의 트랜잭션이 실패하더라도 다른 트랜잭션의 성공 여부에 영향을 주지 않습니다.

  2. 트랜잭션의 격리성을 향상시킵니다. 부모 트랜잭션에서 락을 획득한 후 로직을 처리하고 락을 해제하는 과정에서 커밋이 이루어지기 전에 락이 해제되면 다른 트랜잭션들이 동기화 문제를 일으킬 수 있습니다. 하지만 별도로 실행되는 트랜잭션에서는 락을 획득하고 데이터 조작 후 커밋까지 완료한 뒤에 락을 해제하게 되어, 해당 데이터에 대한 정합성을 보장할 수 있습니다.

  3. 성능 최적화를 도모할 수 있습니다. 부모 트랜잭션의 실행 중 다른 자원이 필요하지 않은 불필요한 대기 시간을 줄여주며, 별도의 트랜잭션을 빠르게 처리하고 커밋하여 자원을 효율적으로 사용할 수 있습니다.

세진님의 프로젝트에서 네임드락의 정합성 문제가 발생하는 이유에 대해서는, 로그 내용으로만으로는 정확한 원인 분석이 어렵지만, 일반적으로 고려해볼 수 있는 몇 가지는 다음과 같습니다.

  • 락 획득과 해제 로직에서의 문제: 락을 획득하고 해제하는 과정에서 예상치 못한 오류로 인해 정합성이 깨질 수 있습니다.
  • 트랜잭션의 격리수준(Isolation level) 설정: 데이터베이스의 트랜잭션 격리수준에 따라 동시성 문제가 달라질 수 있으며 올바르게 설정되어 있는지 검토가 필요합니다.
  • 트랜잭션 스프레드 관리: 새로운 트랜잭션과 기존 트랜잭션의 전파 방식이 올바르게 설정되었는지 확인하는 것도 중요합니다.
  • 데드락(Deadlock) 감지 및 제어: 데드락이 발생하면 시스템의 일부가 서로를 기다리는 상태가 되어 시간이 오래 걸릴 수 있습니다.

성능 문제와 일관성 문제를 해결하기 위해서는 트랜잭션의 경계를 명확히 설정하고, 충분한 테스트를 통해 시스템이 올바르게 동작하는지 확인하는 것이 중요합니다. 실제 시스템에서 발생하는 문제를 해결하기 위해서는 로그 분석, SQL 모니터링, 격리 수준의 이해, 그리고 동시성 기법에 대한 깊은 이해가 필요합니다.

추가로 전문적인 도움이 필요하다면 데이터베이스 관리자(DBA)나 시스템 아키텍트의 조언을 구하시는 것도 좋은 방법일 것입니다.

세진:)님의 프로필 이미지
세진:)

작성한 질문수

질문하기