Skip to content

Test/ 동시성 이슈 테스트를 위한 Redis 락 구현 및 테스트#111

Merged
CheatIsKey merged 20 commits into
developfrom
Test/EventOrder-Redisson-AOP
Apr 24, 2026
Merged

Test/ 동시성 이슈 테스트를 위한 Redis 락 구현 및 테스트#111
CheatIsKey merged 20 commits into
developfrom
Test/EventOrder-Redisson-AOP

Conversation

@Max-1012
Copy link
Copy Markdown
Collaborator

💡 개요

동시성 이슈 테스트를 위한 Redis 락 구현 및 테스트

🛠️ 작업 내용

기본적으로 Event Order api 호출 시 락이 없는 버전 주문 메서드 호출하게 되어있습니다.
추후 최종 사용 버전 정하면 호출 메서드만 바꿔주면 되니 이건 신경 안쓰셔도 됩니다.

  1. Redis Lettuce 개발
  • FailFast 전략
  • Retry 전략
  • Blockging 전략
    을 위한 각각의 메서드 구현
  1. AOP 적용 방식으로 리팩토링
  • 3가지 전략 모두에 적용하여 메서드 3개 존재
  1. Redisson + AOP 를 사용한 방식 구현
    테스트코드에서 사용하기 용이하게 하기 위해
  • FailFast를 제외한 2가지 전략 + Watchdog 사용 여부에 따라 총 4가지 메서드 추가.

테스트 코드에는 총 11개의 테스트가 있습니다.

  • 락 미사용
  • Lettuce - FailFast
  • Lettuce - Retry
  • Lettuce - Blocking
  • Lettuce - FailFast + AOP
  • Lettuce - Retry + AOP
  • Lettuce - Blocking + AOP
  • Redisson - Retry - ttl + AOP
  • Redisson - Blocking - ttl + AOP
  • Redisson - Retry - watchdog + AOP
  • Redisson - Blocking - watchdog + AOP

테스트 코드를 돌리려면 env 파일에서 test 모드로 변경해야 하구요, application-test.yml 있는지 확인하시고,
도커 컴포즈에서 redis+mysql 만 띄우고 인텔리제이에서 실행하시면 됩니다.

낙관락은 구현 및 테스트 했지만 엔티티 자체를 변경해야 하기 때문에 업로드 하지 않습니다.

Max-1012 added 16 commits April 22, 2026 18:02
모든 테스트 코드 메서드 호출부 제외 통일
시간 측정 로직 추가
핵심 메서드를 제외한 부분은 시간 측정에서 제외
…니다. 추후 최후 사용버전이 확정되면 EventOrderController에서 EventOrderFacade에 있는 메서드 중 골라서 호출하면 됩니다.
@Max-1012 Max-1012 self-assigned this Apr 24, 2026
@Max-1012 Max-1012 added the enhancement New feature or request label Apr 24, 2026
@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 5위 (리뷰 85건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 필수 / Redisson 사용 금지): 🔴 위반
    • build.gradleorg.redisson:redisson-spring-boot-starter:3.51.0 의존성이 추가되었습니다.
    • 필수 요구사항에서 명시적으로 Redisson 사용을 금지하고 Lettuce 기반 Lock을 요구했습니다. Redisson은 오직 '도전 기능'에서만 허용됩니다. 필수 기능과 도전 기능의 경계를 무시했습니다.
  • 동시성 테스트 (ExecutorService + CyclicBarrier): 🔴 위반
    • 요구사항에서 명시한 CyclicBarrier를 사용하여 동시에 100개의 요청이 출발하도록 제어하지 않았습니다. 단순히 스레드 풀에 던져놓고 기다리는 방식(CountDownLatch 활용 부재)으로 작성되어 엄밀한 의미의 동시성 테스트로서의 가치가 떨어집니다.

🔴 보안 / 성능 주의

  1. 운영 환경 설정 파일에 민감 정보 하드코딩 (application-test.yml)
    • 데이터베이스 접속 비밀번호(soyoung7509)와 JWT 시크릿 키가 평문으로 커밋되었습니다. 테스트 환경이라 할지라도 절대 타협할 수 없는 사항입니다. 외부로 노출되면 서버 통제권을 넘겨주는 것과 같습니다. 즉시 환경 변수나 시크릿 매니저로 분리하십시오.
  2. 비즈니스 로직 내 락 키 하드코딩 (EventOrderFacade.java)
    • String key = "lock:product:" + productId; 형태의 키 생성 방식이 메서드마다 하드코딩 되어 있습니다. 휴먼 에러가 발생하기 딱 좋은 구조입니다.
    • AOP에서 SpEL을 파싱할 수 있게 구현해놓고 정작 Facade 클래스의 비즈니스 메서드에서는 AOP를 안 쓰고 직접 키를 조립하고 있습니다. 구조적 일관성이 전혀 없습니다.
  3. 바이너리 파일(.DS_Store) 커밋
    • Mac OS의 메타데이터 파일인 .DS_Store가 커밋되었습니다. 저장소를 불필요한 파일로 더럽히는 행위입니다. .gitignore에 추가하고 추적을 해제하십시오.

🟡 개선 제안

  1. DummyDataUser의 실행 조건 및 위치
    • @ComponentCommandLineRunner를 사용해 애플리케이션 구동 시점에 무조건 userRepository.count()를 실행합니다. 데이터가 없을 때만 동작하더라도, 운영 환경 서버가 뜰 때마다 불필요한 DB I/O를 발생시킵니다.
    • 데이터 초기화는 data.sql이나 마이그레이션 툴(Flyway)을 통해 통제 가능하게 관리하거나, 최소한 특정 프로파일(@Profile("local") 등)에서만 빈으로 등록되게 제한해야 합니다.
  2. 테스트 코드의 불확실성 (EventOrderFacadeConcurrencyTest.java)
    • createEventOrder_redisLettuce_failFast 테스트를 보면 assertThat(result.successCount()).isBetween(0, 1); 라고 검증하고 있습니다.
    • FailFast 전략이면 동시 요청 시 단 한 개만 성공하고 나머지는 실패하는 것을 보장하려는 의도겠지만, 테스트가 실행되는 하드웨어와 스레드 스케줄링에 따라 2개 이상 성공할 수도 있습니다. 이는 테스트가 됐다고 보기 어렵습니다.
    • 게다가 createEventOrder_withoutLock_fail 테스트는 "기대값과 달라야 한다(assertThat(isExactlyCorrect).isFalse())"는 논리를 사용하고 있습니다. 락이 없을 때 동시성 이슈가 "항상" 발생한다고 보장할 수 없기 때문에 이 테스트는 실행할 때마다 결과가 달라지는 플래키(Flaky) 테스트가 될 것입니다.

💡 학습 포인트

  • Lettuce vs Redisson의 철학 차이: Lettuce는 Redis의 단순 명령어를 비동기/동기로 제공하는 클라이언트일 뿐이며, 분산 락의 재시도 로직(Retry/Spin Lock)을 애플리케이션 레벨(Thread.sleep 등)에서 직접 구현해야 합니다. 반면 Redisson은 락 해제(Lua Script), 재시도, Watchdog 등을 프레임워크 단에서 추상화하여 제공합니다. 이 둘을 섞어 쓸 때 발생할 수 있는 유지보수 비용을 고민해 보십시오.
  • SpEL (Spring Expression Language): AOP에서 애노테이션의 속성값으로 메서드 파라미터를 동적으로 바인딩하기 위해 SpEL을 사용하는 패턴은 Spring 생태계에서 @Cacheable, @PreAuthorize 등 매우 흔하게 쓰이는 표준 방식입니다.

🤔 생각해보기

현재 executeWithLockBlocking 메서드는 락 획득을 위해 Thread.sleep(50)을 호출하며 루프를 도는 Spin Lock 방식입니다. 만약 1,000명의 사용자가 동시에 하나의 상품 락을 얻기 위해 이 메서드를 실행한다면, 애플리케이션의 스레드 풀과 CPU, 그리고 Redis 서버의 리소스에 어떤 연쇄적인 문제가 발생할까요?

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 5위 (리뷰 86건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 필수 / Redisson 사용 금지): 🔴 지속 위반
    • 이전 리뷰에서 필수 기능에서 Redisson 사용을 금지한다고 지적했으나, 현재 diff에서 build.gradleredisson 의존성과 RedissonConfig, RedissonLockService 등 관련 코드가 여전히 남아있습니다. 필수 요구사항과 도전 기능의 브랜치/PR을 분리하거나 명확히 구분해야 합니다.
  • 동시성 테스트 (ExecutorService + CyclicBarrier): 🔴 미반영
    • 요구사항에서 명시한 CyclicBarrier를 사용하여 스레드의 출발 지점을 통일하는 방식이 여전히 누락되어 있습니다. 엄밀한 동시성 제어 테스트를 위해 반드시 도입해야 합니다.

🟢 잘된 점

  1. Lettuce Lock의 이상적인 구조 설계: RedisLockRepository에서 SETNX와 Lua Script를 활용하여 락의 획득과 해제를 원자적으로 처리하고, RedisLockService에서 트랜잭션/예외 처리 흐름을 제어한 뒤, Aspect에서 이를 추상화한 3계층 분리 구조가 매우 훌륭합니다.
  2. 다양한 락 획득 전략 구현: 요구사항인 Fail Fast, Retry, Blocking 세 가지 전략을 모두 구현하고, AOP 기반의 커스텀 애노테이션(@RedisLock)까지 완성도 있게 적용한 점은 시니어 개발자의 코드 수준이라도 칭찬할 만큼 훌륭합니다.
  3. AOP와 SpEL의 완벽한 활용: 파라미터명을 파싱해 동적으로 Lock Key를 생성하는 SpEL 파싱 로직과, ProceedingJoinPoint를 람다(Supplier)로 전달하는 구조는 Spring AOP의 베스트 프랙티스를 잘 보여줍니다.

🔴 필수 수정

운영 환경 설정 파일에 민감 정보 하드코딩 (application-test.yml)

  • 이전 리뷰에서 지적했음에도 여전히 데이터베이스 접속 비밀번호(soyoung7509)와 JWT 시크릿 키가 평문으로 커밋되어 있습니다.
  • 테스트 환경이라 할지라도 GitHub에 비밀번호가 올라가는 것은 절대 타협할 수 없는 보안 취약점입니다. 즉시 환경 변수로 분리하거나 해당 내역을 강제 푸시(push -f)로 삭제해야 합니다.

🟡 권장 개선

  1. 테스트 코드의 불확실성 (Flaky Test) 개선

    • createEventOrder_withoutLock_fail 테스트에서 assertThat(isExactlyCorrect).isFalse()를 검증하고 있습니다. 동시성 이슈는 스레드 스케줄링에 따라 100번 중 1번은 정상 동작(성공 10건)할 수도 있습니다. 즉, 언제든 테스트가 실패할 수 있는(Flaky) 구조입니다.
    • 락이 없을 때의 동시성 이슈를 확인하려면, 성공 건수가 10을 초과하는지만 검증하는 것이 더 안전합니다.
    // 수정 전
    assertThat(isExactlyCorrect).isFalse();
    
    // 수정 후: 락 미사용 시 재고 감소 정합성이 깨져 성공 횟수가 초과되는지 확인
    assertThat(result.successCount()).isGreaterThan(TEST_TICKET_STOCK);
    assertThat(product.getStock()).isLessThan(0); // 재고가 음수로 떨어지는지 확인
  2. DummyDataUser의 실행 조건 제한

    • 이전 리뷰에서도 언급되었던 내용으로, @ComponentCommandLineRunner를 통해 애플리케이션 구동 시 항상 DB count 쿼리를 날립니다.
    • 데이터 주입은 로컬 개발 환경에서만 필요하므로 프로파일을 제한해야 합니다.
    @Component
    @RequiredArgsConstructor
    @Profile("local") // local 환경에서만 동작하도록 제한
    public class DummyDataUser implements CommandLineRunner {
        // ...
    }
  3. 바이너리 파일(.DS_Store) 커밋

    • .gitignore.DS_Store를 추가하신 것은 좋지만, 이미 Git이 추적 중인 파일은 .gitignore에 추가해도 무시되지 않습니다. 아래 명령어로 추적을 해제해야 합니다.
    git rm --cached .DS_Store

💡 학습 포인트

  • CyclicBarrier의 필요성: 단순히 스레드 풀에 작업을 던지는 것만으로는 "정확히 같은 시점"에 요청이 몰리게 재현할 수 없습니다. CyclicBarrier를 사용하면 모든 스레드가 출발선에서 대기하다가 일제히 달려나가게 만들 수 있어, 동시성 버그를 극대화하여 테스트의 신뢰도를 높일 수 있습니다.

🤔 생각해보기

현재 RedissonLockService에서는 lock.unlock()을 호출할 때 lock.isHeldByCurrentThread()를 확인하여 안전하게 해제하고 있습니다. 만약 락의 leaseTime이 비즈니스 로직의 실행 시간보다 짧아서, 로직이 끝나기도 전에 락이 만료되었다고 가정해 봅시다. 이때 다른 스레드가 이미 해당 락을 획득한 상태에서 원래 스레드가 finally 블록에서 unlock()을 시도한다면 어떤 문제가 발생할까요?

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta leaseTime이 짧아서 락이 풀렸을 때의 문제를 어떻게 방어할 수 있나요?

🔄 이전 피드백 반영 여부

  • Redisson 사용 금지 위반: ❌ 미반영 (여전히 Redisson 관련 코드가 다수 포함됨)
  • CyclicBarrier 기반 동시성 테스트: ❌ 미반영 (여전히 ExecutorService만 사용 중)
  • 민감 정보 하드코딩 (application-test.yml): ❌ 미반영 (비밀번호가 평문으로 남아있음)
  • 바이너리 파일(.DS_Store) 제거: 🟡 부분 반영 (.gitignore에는 추가했으나 캐시 삭제 필요)
  • DummyDataUser 실행 조건: ❌ 미반영 (여전히 전역적으로 실행됨)

AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 5위 (리뷰 86건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 필수 / Redisson 사용 금지): 🔴 지속 위반
    • build.gradle에 여전히 org.redisson:redisson-spring-boot-starter:3.51.0 의존성이 존재합니다. 필수 요구사항에서 명시적으로 금지한 기술을 과제 제출물에 포함시키는 것은 타협할 수 없는 중대 규정 위반입니다.
  • 동시성 테스트 (ExecutorService + CyclicBarrier): 🔴 미반영
    • 요구사항에서 명시한 CyclicBarrier를 사용하여 스레드의 출발 지점을 통일하는 방식이 여전히 누락되어 있습니다. 엄밀한 동시성 제어 테스트를 위해 반드시 도입해야 합니다.

🔴 보안 / 성능 주의

운영 환경 설정 파일에 민감 정보 하드코딩 (application-test.yml)

  • 이전 리뷰에서 지적했음에도 여전히 데이터베이스 접속 비밀번호(soyoung7509)와 JWT 시크릿 키가 평문으로 커밋되어 있습니다.
  • 테스트 환경이라 할지라도 GitHub에 비밀번호가 올라가는 것은 절대 타협할 수 없는 보안 취약점입니다. 즉시 환경 변수로 분리하거나 해당 내역을 강제 푸시(push -f)로 삭제해야 합니다.

🟡 개선 제안

  1. DummyDataUser의 실행 조건 제한
    • 이전 리뷰에서도 언급되었던 내용으로, @ComponentCommandLineRunner를 통해 애플리케이션 구동 시 항상 DB count 쿼리를 날립니다.
    • 데이터 주입은 로컬 개발 환경에서만 필요하므로 프로파일을 제한해야 합니다.
    @Component
    @RequiredArgsConstructor
    @Profile("local") // local 환경에서만 동작하도록 제한
    public class DummyDataUser implements CommandLineRunner {
        // ...
    }
  2. 바이너리 파일(.DS_Store) 제거
    • .gitignore.DS_Store를 추가하신 것은 좋지만, 이미 Git이 추적 중인 파일은 .gitignore에 추가해도 무시되지 않습니다. 아래 명령어로 추적을 해제해야 합니다.
    git rm --cached .DS_Store

🔄 이전 피드백 반영 여부

  • Redisson 사용 금지 위반: ❌ 미해결 (여전히 Redisson 관련 코드가 다수 포함됨)
  • CyclicBarrier 기반 동시성 테스트: ❌ 미해결 (여전히 ExecutorService만 사용 중)
  • 민감 정보 하드코딩 (application-test.yml): ❌ 미해결 (비밀번호가 평문으로 남아있음)
  • 바이너리 파일(.DS_Store) 제거: 🟡 부분 반영 (.gitignore에는 추가했으나 캐시 삭제 필요)
  • DummyDataUser 실행 조건: ❌ 미해결 (여전히 전역적으로 실행됨)

💬 리뷰에 대해 궁금한 점이 있나요? 코멘트에 @sparta 를 남겨보세요!
예: @sparta 이 코드에서 동시성 이슈가 발생할 수 있나요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 5위 (리뷰 88건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 필수 / Redisson 사용 금지): 🔴 지속 위반
    • 이전 리뷰에서 3회 연속 지적했으나 build.gradle에 여전히 org.redisson:redisson-spring-boot-starter:3.51.0 의존성이 존재합니다.
  • 동시성 테스트 (ExecutorService + CyclicBarrier): 🔴 미반영
    • CyclicBarrier를 사용한 엄밀한 동시성 재현 테스트가 여전히 누락되어 있습니다. 단순 스레드 풀 제출만으로는 "정확히 같은 시점"에 몰리는 것을 보장할 수 없습니다.

🔴 보안 / 성능 주의

운영 환경 설정 파일에 민감 정보 하드코딩 (application-test.yml)

  • 3회 연속 지적 사항. 데이터베이스 접속 정보(DB_URL, DB_USERNAME, DB_PASSWORD)와 JWT 시크릿 키가 평문으로 커밋되어 있습니다.
  • 테스트 환경 변수화(${ ... })를 적용했다고 착각할 수 있으나, 현재 평문 그대로 노출되어 있습니다. 원격 저장소에 올라간 이상 히스토리에 영구히 남습니다. 즉시 환경 변수 분리 및 Git 히스토리에서 민감 정보를 날리십시오.

🟡 개선 제안

  1. DummyDataUser의 무조건적 실행
    • CommandLineRunner를 통해 애플리케이션 구동 시 항상 DB count() 쿼리를 날립니다.
    • 데이터 주입은 로컬 개발 환경에서만 필요하므로 반드시 프로파일을 제한해야 합니다.
    @Profile("local") // local 환경에서만 동작하도록 제한
    @Component
    @RequiredArgsConstructor
    public class DummyDataUser implements CommandLineRunner { ... }
  2. 바이너리 파일(.DS_Store) 추적 해지
    • .gitignore에 추가한 것은 좋으나, 이미 커밋된 파일은 무시되지 않습니다. 반드시 아래 명령어로 캐시에서 제거하십시오.
    git rm --cached .DS_Store

🤔 생각해보기

3번의 리뷰에도 동일한 보안 취약점과 명백한 요구사항 위반(Redisson 사용, CyclicBarrier 누락)이 방치되고 있습니다. 만약 이 상태 그대로 운영 환경에 배포된다면, 해커가 GitHub에서 발견한 DB 접속 정보를 이용해 고객의 주문 및 결제 데이터를 몽땅 날려버리는 재앙이 발생할 수 있습니다. 코드 리뷰의 피드백을 무시하는 것이 비즈니스에 미치는 치명적인 연쇄 장애에 대해 어떻게 대비하시겠습니까?

🔄 이전 피드백 반영 여부

  • Redisson 사용 금지 위반: ❌ 미해결 (여전히 Redisson 관련 코드가 다수 포함됨)
  • CyclicBarrier 기반 동시성 테스트: ❌ 미해결 (여전히 누락)
  • 민감 정보 하드코딩 (application-test.yml): ❌ 미해결 (비밀번호가 평문으로 남아있음)
  • 바이너리 파일(.DS_Store) 제거: 🟡 부분 반영 (.gitignore 추가했으나 캐시 삭제 필요)
  • DummyDataUser 실행 조건: ❌ 미해결 (여전히 전역적으로 실행됨)

💬 리뷰에 대해 궁금한 점이 있나요? 코멘트에 @sparta 를 남겨보세요!
예: @sparta 이 코드에서 동시성 이슈가 발생할 수 있나요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 5위 (리뷰 88건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 필수 / Redisson 사용 금지): 🔴 지속 위반 (4회차)
    • build.gradleorg.redisson:redisson-spring-boot-starter:3.51.0 의존성이 여전히 존재합니다. 필수 요구사항에서 명시적으로 금지한 기술이 포함되어 있으므로 반드시 제거해야 합니다.
  • 동시성 테스트 (ExecutorService + CyclicBarrier): 🔴 미반영 (4회차)
    • 요구사항에서 명시한 CyclicBarrier를 사용하여 스레드의 출발 지점을 통일하는 방식이 여전히 누락되어 있습니다. 엄밀한 동시성 제어 테스트를 위해 반드시 도입해야 합니다.

🟢 잘된 점

  1. 훌륭한 락 추상화 및 3계층 분리 구조: RedisLockRepository(인프라) - RedisLockService(흐름 제어) - Aspect(선언적 추상화)로 계층을 명확히 나눈 설계가 매우 훌륭합니다.
  2. 체계적인 동시성 테스트 코드: 다양한 락 전략(FailFast, Retry, Blocking)에 대해 일관된 형식(ConcurrencyTestResult)으로 성능과 정합성을 검증하는 테스트 구조가 잡혀있어 학습 목적에 perfectly 부합합니다.
  3. SpEL 파싱 및 AOP의 완벽한 활용: 파라미터명을 동적으로 파싱해 Lock Key를 생성하는 로직과 Gradle에 -parameters 옵션을 추가한 디테일은 실무에서도 바로 쓰일 만큼 훌륭합니다.

🔴 필수 수정

운영 환경 설정 파일에 민감 정보 하드코딩 (application-test.yml)

  • 이전 리뷰에서 3회 연속 지적했음에도 데이터베이스 접속 정보와 JWT 시크릿 키가 평문으로 커밋되어 있습니다.
  • 테스트 환경이라 할지라도 GitHub에 비밀번호가 올라가는 것은 절대 타협할 수 없는 보안 취약점입니다. 즉시 환경 변수로 분리하거나 해당 내역을 강제 푸시(push -f)로 삭제해야 합니다.

🟡 권장 개선

  1. 테스트 코드의 불확실성 (Flaky Test) 개선

    • createEventOrder_withoutLock_fail 테스트에서 assertThat(isExactlyCorrect).isFalse()를 검증하고 있습니다. 동시성 이슈는 스레드 스케줄링에 따라 100번 중 1번은 정상 동작(성공 10건)할 수도 있습니다. 즉, 언제든 테스트가 실패할 수 있는 구조입니다.
    • 락이 없을 때의 동시성 이슈를 확인하려면, 성공 건수가 10을 초과하는지만 검증하는 것이 더 안전합니다.
    // 수정 전
    assertThat(isExactlyCorrect).isFalse();
    
    // 수정 후: 락 미사용 시 재고 감소 정합성이 깨져 성공 횟수가 초과되는지 확인
    assertThat(result.successCount()).isGreaterThan(TEST_TICKET_STOCK);
    assertThat(product.getStock()).isLessThan(0); // 재고가 음수로 떨어지는지 확인
  2. DummyDataUser의 실행 조건 제한

    • @ComponentCommandLineRunner를 통해 애플리케이션 구동 시 항상 DB count 쿼리를 날립니다. 데이터 주입은 로컬 개발 환경에서만 필요하므로 프로파일을 제한해야 합니다.
    @Component
    @RequiredArgsConstructor
    @Profile("local") // local 환경에서만 동작하도록 제한
    public class DummyDataUser implements CommandLineRunner {
        // ...
    }
  3. 바이너리 파일(.DS_Store) 커밋

    • .gitignore.DS_Store를 추가하신 것은 좋지만, 이미 Git이 추적 중인 파일은 .gitignore에 추가해도 무시되지 않습니다. 아래 명령어로 추적을 해제해야 합니다.
    git rm --cached .DS_Store

💡 학습 포인트

  • CyclicBarrier의 필요성: 단순히 스레드 풀에 작업을 던지는 것만으로는 "정확히 같은 시점"에 요청이 몰리게 재현할 수 없습니다. CyclicBarrier를 사용하면 모든 스레드가 출발선에서 대기하다가 일제히 달려나가게 만들 수 있어, 동시성 버그를 극대화하여 테스트의 신뢰도를 높일 수 있습니다.

🤔 생각해보기

현재 RedissonLockService에서는 lock.unlock()을 호출할 때 lock.isHeldByCurrentThread()를 확인하여 안전하게 해제하고 있습니다. 만약 락의 leaseTime이 비즈니스 로직의 실행 시간보다 짧아서, 로직이 끝나기도 전에 락이 만료되었다고 가정해 봅시다. 이때 다른 스레드가 이미 해당 락을 획득한 상태에서 원래 스레드가 finally 블록에서 unlock()을 시도한다면 어떤 문제가 발생할까요?

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta leaseTime이 짧아서 락이 풀렸을 때의 문제를 어떻게 방어할 수 있나요?

🔄 이전 피드백 반영 여부

  • Redisson 사용 금지 위반: ❌ 미반영 (여전히 Redisson 관련 코드가 다수 포함됨)
  • CyclicBarrier 기반 동시성 테스트: ❌ 미반영 (여전히 ExecutorService만 사용 중)
  • 민감 정보 하드코딩 (application-test.yml): ❌ 미반영 (비밀번호가 평문으로 남아있음)
  • 바이너리 파일(.DS_Store) 제거: 🟡 부분 반영 (.gitignore에는 추가했으나 캐시 삭제 필요)
  • DummyDataUser 실행 조건: ❌ 미반영 (여전히 전역적으로 실행됨)

AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

Copy link
Copy Markdown
Contributor

@DGAZA-max DGAZA-max left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다!!

@CheatIsKey CheatIsKey merged commit b2af723 into develop Apr 24, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Allday-Project-Board Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Feat] 동시성 이슈 테스트를 위한 다양한 락 구현

3 participants