-
Notifications
You must be signed in to change notification settings - Fork 0
[YS-532] refactor: 실험 공고 키워드 자동완성 동시성 문제 해결을 위한 Redis INCR 연산 로직 추가 #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Walkthrough일일 사용 제한 검증 로직이 기존의 로그 카운팅 방식에서 Redis 기반의 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UseCase
participant UsageLimitGateway (Redis)
participant LogGateway
User->>UseCase: 키워드 추출 요청
UseCase->>UsageLimitGateway (Redis): incrementAndCheckLimit(memberId, dailyLimit)
UsageLimitGateway (Redis)-->>UseCase: 사용 가능 여부(Boolean)
alt 사용 가능
UseCase->>LogGateway: 로그 저장
UseCase-->>User: 키워드 추출 결과 반환
else 사용 제한 초과
UseCase-->>User: 일일 사용 제한 예외 발생
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
domain/src/main/kotlin/com/dobby/gateway/UsageLimitGateway.kt (1)
3-5
: 인터페이스 설계가 우수하나 문서화가 필요함
UsageLimitGateway
인터페이스는 단일 책임 원칙을 잘 따르고 있으며,incrementAndCheckLimit
메서드명이 원자적 연산을 수행한다는 의도를 명확히 전달합니다. 동시성 문제 해결에 적합한 설계입니다.다만 메서드의 정확한 동작(반환값의 의미, 예외 상황 등)을 명확히 하기 위해 KDoc 추가를 권장합니다.
interface UsageLimitGateway { + /** + * 회원의 일일 사용량을 증가시키고 제한을 확인합니다. + * + * @param memberId 회원 ID + * @param dailyLimit 일일 사용 제한 수 + * @return 제한 내라면 true, 제한 초과라면 false + */ fun incrementAndCheckLimit(memberId: String, dailyLimit: Int): Boolean }application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (2)
57-57
: 테스트 정확성 향상을 위한 구체적인 파라미터 사용 권장
any()
대신 실제 일일 제한값(예:2
)을 사용하면 올바른 제한값이 전달되는지 검증할 수 있어 테스트의 정확성이 향상됩니다.- every { usageLimitGateway.incrementAndCheckLimit(memberId, any()) } returns true + every { usageLimitGateway.incrementAndCheckLimit(memberId, 2) } returns true
81-81
: 테스트 정확성 향상을 위한 구체적인 파라미터 사용 권장마찬가지로 여기서도 구체적인 일일 제한값을 사용하는 것이 좋습니다.
- every { usageLimitGateway.incrementAndCheckLimit(memberId, any()) } returns false + every { usageLimitGateway.incrementAndCheckLimit(memberId, 2) } returns false
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt
(2 hunks)application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt
(4 hunks)domain/src/main/kotlin/com/dobby/gateway/UsageLimitGateway.kt
(1 hunks)domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt
(0 hunks)infrastructure/build.gradle.kts
(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt
(1 hunks)infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt
(0 hunks)infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt
(1 hunks)infrastructure/src/main/resources/db/migration/V202507311527__remove_idx_experiment_keywords_log.sql
(1 hunks)infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
(1 hunks)
💤 Files with no reviewable changes (2)
- domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt
- infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt:33-46
Timestamp: 2025-07-27T12:26:47.814Z
Learning: Gradmeet-BE 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 DB 조회 기반 검증 방식을 선호한다. 분산 락은 나중에 최적화가 필요할 때 고려한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt (2)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Learnt from: chock-cho
PR: #93
File: src/main/kotlin/com/dobby/backend/infrastructure/coroutine/TransactionCoroutineExecutor.kt:15-24
Timestamp: 2025-02-12T04:30:30.826Z
Learning: In Spring transactions with Kotlin coroutines, using runBlocking
within transactionTemplate.execute
ensures that the transaction context is active during the execution of suspend functions. While this approach blocks the thread, it guarantees transaction safety by preventing async operations from executing outside the transaction boundary.
application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (1)
Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
🪛 detekt (1.23.8)
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
[warning] 149-149: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (13)
infrastructure/build.gradle.kts (1)
70-70
: 새로운 테스트 의존성 추가가 적절함
mockito-kotlin
의존성 추가는 새로운UsageLimitGateway
인터페이스를 모킹하기 위한 테스트 요구사항을 지원합니다. 버전 4.1.0은 안정적이며 기존 테스트 인프라와 잘 어울립니다.infrastructure/src/main/resources/db/migration/V202507311527__remove_idx_experiment_keywords_log.sql (1)
1-6
: 외래키 제약조건 삭제·재생성은 적절합니다InnoDB에서는 외래키 제약조건이 참조 컬럼(member_id)에 대한 인덱스를 필요로 하기 때문에, 복합 인덱스(idx_experiment_keywords_log)를 제거하려면
- 기존 외래키를 삭제하고
- 복합 인덱스를 DROP
- 외래키를 재생성하여 단일 컬럼(member_id) 인덱스를 자동 생성
하는 순서가 필수적입니다. 해당 마이그레이션은 위 절차에 맞게 올바르게 작성되었습니다.
infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt (1)
6-6
: 리포지토리 단순화가 적절함
countByMemberIdAndCreatedAtBetween
메서드 제거는 새로운 Redis 기반 사용량 제한 아키텍처와 일치합니다. 리포지토리가 기본 CRUD 연산에만 집중하도록 단순화된 것이 적절합니다.application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (1)
26-26
: 새로운 게이트웨이 모킹 추가가 적절함
UsageLimitGateway
모킹 추가는 아키텍처 변경사항을 잘 반영하고 있습니다.application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (2)
5-5
: 새로운 의존성 추가가 적절합니다.
UsageLimitGateway
의존성 추가가 리팩터링 목표와 일치하며, 의존성 주입 패턴을 올바르게 따르고 있습니다.Also applies to: 17-17
50-51
: 동시성 문제 해결을 위한 핵심 로직 개선이 우수합니다.기존의 데이터베이스 카운팅 방식에서 Redis의 원자적 연산을 활용한 방식으로 변경하여 동시성 문제를 효과적으로 해결했습니다. API 호환성을 유지하면서 성능과 정확성을 모두 개선한 좋은 리팩터링입니다.
infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt (4)
13-19
: 클래스 구조와 의존성 설정이 적절합니다.Spring DI 어노테이션과 Redis 템플릿, 환경 프로파일을 활용한 구조가 멀티 환경 배포에 적합하게 설계되었습니다.
21-33
: Redis INCR 연산을 활용한 동시성 안전한 구현입니다.원자적 INCR 연산과 첫 번째 사용 시에만 만료 시간을 설정하는 최적화된 로직이 우수합니다. 동시성 문제를 효과적으로 해결하면서 성능도 고려한 구현입니다.
35-38
: 캐시 키 구조가 잘 설계되었습니다.프로파일별 분리와 날짜 기반 파티셔닝으로 환경 간 충돌 방지와 일일 리셋이 올바르게 구현되었습니다.
26-26
: 시간대 일관성 확인을 권장합니다.
LocalDateTime.now()
와LocalDate.now()
가 동일한 시간대를 사용하는지 확인해주세요. 배포 환경에서 시간대 설정이 일관되지 않으면 예상치 못한 만료 시간 계산이 발생할 수 있습니다.infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt (3)
35-43
: 동시성 테스트 설정이 체계적으로 구성되었습니다.모든 필요한 의존성의 모킹과 실제 사용 시나리오를 반영한 테스트 데이터 설정이 우수합니다. 새로운
UsageLimitGateway
의 동작을 올바르게 시뮬레이션하고 있습니다.Also applies to: 59-112
114-136
: 동시성 테스트 로직이 정확하게 구현되었습니다.스레드 풀을 활용한 동시 요청 시뮬레이션과 원자적 카운터 동작 모킹이 실제 Redis 동작을 잘 반영하고 있습니다. 일일 한도 검증도 정확합니다.
149-153
: 예외 처리가 테스트 목적에 적합합니다.정적 분석 도구가 예외 삼키기를 경고했지만, 이 테스트에서는 예상되는 일일 한도 초과 예외를 의도적으로 카운팅하는 것이므로 올바른 구현입니다. 테스트의 목적상 예외를 다시 던지거나 로깅할 필요가 없습니다.
override fun incrementAndCheckLimit(memberId: String, dailyLimit: Int): Boolean { | ||
val key = getCacheKey(memberId) | ||
val count = redisTemplate.opsForValue().increment(key, 1) ?: 1L | ||
|
||
if (count == 1L) { | ||
val expireSeconds = Duration.between(LocalDateTime.now(), LocalDate.now().plusDays(1).atStartOfDay()).seconds | ||
redisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS) | ||
} | ||
|
||
logger.debug("Usage count for key=$key is $count") | ||
|
||
return count <= dailyLimit | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동시성 문제 해결을 위해 비관적/낙관적 락과 분산 락을 고려할 수 있었으나, Redis의 INCR 연산을 선택한 이유는 다음과 같습니다.
- 비관적 락과 낙관적 락은 주로 UPDATE 작업에서 발생하는 동시성 이슈에 적합합니다.
- 분산 락은 단순히 사용자 사용 횟수를 제어하는 데에는 과도한 비용과 복잡성을 가져온다고 생각합니다.
INCR 연산은 Redis 내부에서 단일 명령으로 처리되므로 여러 요청이 동시에 실행되어도 정확한 카운트가 보장되어 race condition을 방지할 수 있습니다. 그래서 별도의 락 없이도 안전하게 사용 횟수를 제한할 수 있어 효율적입니다.
지금처럼 단순한 로직에 최소 비용으로 동시성 문제를 해결하기 위해, Redis의 INCR 연산을 통한 원자적 처리가 가장 적합하다고 판단했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redis의 INCR 연산은 해당 작업을 통해 처음 공부해봤는데요, 지수님 덕분에 좋은 지식을 많이 얻어가네요!
@@ -0,0 +1,6 @@ | |||
ALTER TABLE experiment_post_keywords_log DROP FOREIGN KEY fk_experiment_post_keywords_log_member; | |||
|
|||
DROP INDEX idx_experiment_keywords_log ON experiment_post_keywords_log; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 COUNT
쿼리를 사용하지 않게 되어, 매번 추가되는 행에 대해 인덱스를 생성하는 것이 불필요한 오버헤드로 작용한다고 판단하여 해당 인덱스를 삭제했습니다~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 테스트 코드는 모킹을 통해 동시성 이슈 해결 여부를 확인하는 테스트입니다. 실제 Redis와 연결해 테스트하는 것이 더 정확하지만, 테스트 시간이 길고 무거워서 로컬에서만 통합 테스트를 진행했습니다. 그래서 실제 통합 테스트 코드는 삭제하고 모킹 기반 테스트 코드만 유지했습니다. 🥲
실제 통합 테스트 진행 내용은 블로그에 참고용으로 올렸습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FE에서 자동완성 기능 개발을 완료할 때까지 사용 횟수 제한을 풀어달라고 요청을 해서 임시로 관련 기능/테스트 코드는 주석처리했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt (2)
25-32
: 불필요한 @SpringBootTest 어노테이션 제거 고려모킹 기반 단위 테스트에서
@SpringBootTest
는 불필요하며 테스트 실행 속도를 느리게 만들 수 있습니다.@ExtendWith(MockitoExtension::class)
만으로 충분합니다.@ExtendWith(MockitoExtension::class) -@SpringBootTest -@ActiveProfiles("test") class ExtractExperimentPostKeywordsConcurrencyTest {
84-89
: 사용되지 않는 usageCount 변수 제거
usageCount
변수가 증가하기만 하고 실제로 사용되지 않습니다. 불필요한 코드는 제거하는 것이 좋습니다.- val usageCount = AtomicInteger(0) - whenever(experimentPostKeywordsGateway.save(any())).thenAnswer { - usageCount.incrementAndGet() null }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt
(2 hunks)application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt
(4 hunks)infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt
- application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt:33-46
Timestamp: 2025-07-27T12:26:47.814Z
Learning: Gradmeet-BE 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 DB 조회 기반 검증 방식을 선호한다. 분산 락은 나중에 최적화가 필요할 때 고려한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
📚 Learning: ai가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(illegalargumentexception)는 정상적인 상황에 가깝다. 따라서 experimentpostkeywo...
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Applied to files:
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
📚 Learning: in the gradmeet-be project, redis lock operations should use lua scripts for atomic operations to en...
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#106
File: src/main/kotlin/com/dobby/backend/infrastructure/lock/RedisLockManager.kt:23-32
Timestamp: 2025-02-17T06:19:55.617Z
Learning: In the Gradmeet-BE project, Redis lock operations should use Lua scripts for atomic operations to ensure thread safety in multi-threaded environments, particularly in the unlock operation where check-and-delete needs to be atomic.
Applied to files:
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
📚 Learning: gradmeet-be 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 db 조회 기반 검증 방식을 선호한다. 분산 락...
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt:33-46
Timestamp: 2025-07-27T12:26:47.814Z
Learning: Gradmeet-BE 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 DB 조회 기반 검증 방식을 선호한다. 분산 락은 나중에 최적화가 필요할 때 고려한다.
Applied to files:
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
📚 Learning: in spring transactions with kotlin coroutines, using `runblocking` within `transactiontemplate.execu...
Learnt from: chock-cho
PR: YAPP-Github/Gradmeet-BE#93
File: src/main/kotlin/com/dobby/backend/infrastructure/coroutine/TransactionCoroutineExecutor.kt:15-24
Timestamp: 2025-02-12T04:30:30.826Z
Learning: In Spring transactions with Kotlin coroutines, using `runBlocking` within `transactionTemplate.execute` ensures that the transaction context is active during the execution of suspend functions. While this approach blocks the thread, it guarantees transaction safety by preventing async operations from executing outside the transaction boundary.
Applied to files:
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Check Code Quality
- GitHub Check: build
package com.dobby.concurrency | ||
|
||
import com.dobby.enums.MatchType | ||
import com.dobby.enums.experiment.TimeSlot | ||
import com.dobby.enums.member.GenderType | ||
import com.dobby.enums.member.MemberStatus | ||
import com.dobby.enums.member.ProviderType | ||
import com.dobby.enums.member.RoleType | ||
import com.dobby.gateway.OpenAiGateway | ||
import com.dobby.gateway.UsageLimitGateway | ||
import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway | ||
import com.dobby.gateway.member.MemberGateway | ||
import com.dobby.model.experiment.keyword.ApplyMethodKeyword | ||
import com.dobby.model.experiment.keyword.ExperimentPostKeywords | ||
import com.dobby.model.experiment.keyword.TargetGroupKeyword | ||
import com.dobby.model.member.Member | ||
import com.dobby.usecase.experiment.ExtractExperimentPostKeywordsUseCase | ||
import com.dobby.util.IdGenerator | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.mockito.junit.jupiter.MockitoExtension | ||
import org.mockito.kotlin.any | ||
import org.mockito.kotlin.mock | ||
import org.mockito.kotlin.whenever | ||
import org.springframework.boot.test.context.SpringBootTest | ||
import org.springframework.test.context.ActiveProfiles | ||
import java.time.LocalDateTime | ||
import java.util.concurrent.atomic.AtomicInteger | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
누락된 import 추가 필요
테스트 실행을 위해 필요한 import가 누락되어 있습니다.
import com.dobby.util.IdGenerator
+import com.dobby.exception.experiment.ExperimentPostKeywordsDailyLimitExceededException
import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
추가로 다음 import도 필요합니다:
import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
package com.dobby.concurrency | |
import com.dobby.enums.MatchType | |
import com.dobby.enums.experiment.TimeSlot | |
import com.dobby.enums.member.GenderType | |
import com.dobby.enums.member.MemberStatus | |
import com.dobby.enums.member.ProviderType | |
import com.dobby.enums.member.RoleType | |
import com.dobby.gateway.OpenAiGateway | |
import com.dobby.gateway.UsageLimitGateway | |
import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway | |
import com.dobby.gateway.member.MemberGateway | |
import com.dobby.model.experiment.keyword.ApplyMethodKeyword | |
import com.dobby.model.experiment.keyword.ExperimentPostKeywords | |
import com.dobby.model.experiment.keyword.TargetGroupKeyword | |
import com.dobby.model.member.Member | |
import com.dobby.usecase.experiment.ExtractExperimentPostKeywordsUseCase | |
import com.dobby.util.IdGenerator | |
import org.junit.jupiter.api.BeforeEach | |
import org.junit.jupiter.api.extension.ExtendWith | |
import org.mockito.junit.jupiter.MockitoExtension | |
import org.mockito.kotlin.any | |
import org.mockito.kotlin.mock | |
import org.mockito.kotlin.whenever | |
import org.springframework.boot.test.context.SpringBootTest | |
import org.springframework.test.context.ActiveProfiles | |
import java.time.LocalDateTime | |
import java.util.concurrent.atomic.AtomicInteger | |
package com.dobby.concurrency | |
import com.dobby.enums.MatchType | |
import com.dobby.enums.experiment.TimeSlot | |
import com.dobby.enums.member.GenderType | |
import com.dobby.enums.member.MemberStatus | |
import com.dobby.enums.member.ProviderType | |
import com.dobby.enums.member.RoleType | |
import com.dobby.gateway.OpenAiGateway | |
import com.dobby.gateway.UsageLimitGateway | |
import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway | |
import com.dobby.gateway.member.MemberGateway | |
import com.dobby.model.experiment.keyword.ApplyMethodKeyword | |
import com.dobby.model.experiment.keyword.ExperimentPostKeywords | |
import com.dobby.model.experiment.keyword.TargetGroupKeyword | |
import com.dobby.model.member.Member | |
import com.dobby.usecase.experiment.ExtractExperimentPostKeywordsUseCase | |
import com.dobby.util.IdGenerator | |
+import com.dobby.exception.experiment.ExperimentPostKeywordsDailyLimitExceededException | |
import org.junit.jupiter.api.BeforeEach | |
+import org.junit.jupiter.api.Test | |
import org.junit.jupiter.api.extension.ExtendWith | |
import org.mockito.junit.jupiter.MockitoExtension | |
import org.mockito.kotlin.any | |
import org.mockito.kotlin.mock | |
import org.mockito.kotlin.whenever | |
import org.springframework.boot.test.context.SpringBootTest | |
import org.springframework.test.context.ActiveProfiles | |
import java.time.LocalDateTime | |
import java.util.concurrent.atomic.AtomicInteger | |
+import java.util.concurrent.Executors | |
+import java.util.concurrent.TimeUnit | |
+import kotlin.test.assertEquals |
🤖 Prompt for AI Agents
In
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
at the beginning of the file (lines 1 to 29), there are missing import
statements required for the test to run correctly. Add the necessary import
statements that are currently omitted to ensure all dependencies and annotations
used in the test class are properly imported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FE에서 자동완성 기능 개발을 완료할 때까지 사용 횟수 제한을 풀어달라고 요청을 해서 임시로 관련 기능/테스트 코드는 주석처리한 상태야~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ji-soo708 아, 그런 상황이었군요! FE 팀의 자동완성 기능 개발 완료를 기다리는 동안 임시로 사용 횟수 제한을 비활성화한 거네요.
그렇다면 제가 지적한 missing import 문제는 현재로서는 해결할 필요가 없겠습니다. 테스트 코드가 주석처리되어 있는 상태에서는 해당 import들이 불필요하니까요.
나중에 FE 팀의 자동완성 기능 개발이 완료되어 사용 횟수 제한을 다시 활성화할 때, 그때 테스트 코드의 주석을 해제하시면서 필요한 import들을 추가하시면 될 것 같습니다.
임시 조치에 대한 명확한 설명 감사합니다! 🙂
🧠 Learnings used
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt:33-46
Timestamp: 2025-07-27T12:26:47.814Z
Learning: Gradmeet-BE 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 DB 조회 기반 검증 방식을 선호한다. 분산 락은 나중에 최적화가 필요할 때 고려한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt:10-14
Timestamp: 2025-07-17T08:06:13.596Z
Learning: Ji-soo708의 팀은 현재 OpenAI 공식 API만 사용할 예정이므로, FeignClient URL을 외부화하지 않고 하드코딩하는 것을 선호한다. 팀의 요구사항에 따라 단순함을 우선시하는 접근 방식을 취한다.
Learnt from: chock-cho
PR: YAPP-Github/Gradmeet-BE#148
File: domain/src/main/kotlin/com/dobby/enums/University.kt:288-296
Timestamp: 2025-07-12T18:17:29.186Z
Learning: 사용자 chock-cho는 자동완성 API의 서버 부하를 줄이기 위해 FE와 합의하에 디바운싱 처리로 설계했고, 사용자가 입력하지 않은 상태에서의 의도치 않은 API 호출을 방지하기 위해 @NotBlank 어노테이션을 적용했다. 사용자 행동 패턴까지 고려한 세심한 BE 설계 접근을 보여준다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
// @Test | ||
// fun `동시에 여러 요청 시 최대 2번까지만 성공하고 나머지는 제한 예외가 발생해야 한다`() { | ||
// val executor = Executors.newFixedThreadPool(THREAD_COUNT) | ||
// | ||
// val successCount = mutableListOf<Unit>() | ||
// val failCount = mutableListOf<Unit>() | ||
// val lock = Any() | ||
// | ||
// repeat(THREAD_COUNT) { | ||
// executor.submit { | ||
// executeKeywordExtraction(successCount, failCount, lock) | ||
// } | ||
// } | ||
// | ||
// executor.shutdown() | ||
// val finished = executor.awaitTermination(10, TimeUnit.SECONDS) | ||
// if (!finished) { | ||
// throw RuntimeException("Thread pool shutdown timeout occurred") | ||
// } | ||
// | ||
// assertEquals(DAILY_LIMIT, successCount.size) | ||
// assertEquals(THREAD_COUNT - DAILY_LIMIT, failCount.size) | ||
// } | ||
// | ||
// private fun executeKeywordExtraction( | ||
// successCount: MutableList<Unit>, | ||
// failCount: MutableList<Unit>, | ||
// lock: Any | ||
// ) { | ||
// try { | ||
// val input = ExtractExperimentPostKeywordsUseCase.Input(memberId, text) | ||
// useCase.execute(input) | ||
// synchronized(lock) { | ||
// successCount.add(Unit) | ||
// } | ||
// } catch (e: ExperimentPostKeywordsDailyLimitExceededException) { | ||
// synchronized(lock) { | ||
// failCount.add(Unit) | ||
// } | ||
// } | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석 처리된 테스트 코드 활성화 필요
PR 목적이 동시성 문제 해결인데 관련 테스트가 주석 처리되어 있습니다. 모킹 기반 테스트라도 동시성 검증을 위해 활성화해야 합니다.
-// @Test
-// fun `동시에 여러 요청 시 최대 2번까지만 성공하고 나머지는 제한 예외가 발생해야 한다`() {
+ @Test
+ fun `동시에 여러 요청 시 최대 2번까지만 성공하고 나머지는 제한 예외가 발생해야 한다`() {
테스트 전체를 주석 해제하여 동시성 문제 해결을 검증할 수 있도록 해주세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt
from lines 109 to 149, the entire concurrency test code is commented out, which
prevents verification of the concurrency issue fix. Remove the comment markers
to reactivate the test and ensure it runs during testing to validate the
concurrency behavior as intended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FE에서 자동완성 기능 개발을 완료할 때까지 사용 횟수 제한을 풀어달라고 요청을 해서 임시로 관련 기능/테스트 코드는 주석처리한 상태야~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ji-soo708, 이해했습니다! FE 팀의 자동완성 기능 개발 완료를 기다리기 위해 임시로 사용 횟수 제한을 비활성화한 상태군요.
동시성 문제 해결을 위한 Redis INCR 로직은 구현되어 있고, 테스트 코드도 준비되어 있으니 FE 개발이 완료되면 주석을 해제하여 다시 활성화하면 되겠네요. 비즈니스 요구사항에 맞춘 합리적인 접근 방식입니다.
✏️ Learnings added
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#162
File: infrastructure/src/test/kotlin/com/dobby/concurrency/ExtractExperimentPostKeywordsConcurrencyTest.kt:109-149
Timestamp: 2025-08-02T13:37:08.629Z
Learning: Gradmeet-BE 프로젝트에서 FE 팀의 자동완성 기능 개발 완료를 기다리기 위해 사용 횟수 제한 기능과 관련 테스트 코드를 임시로 주석 처리한 상태이다. 동시성 문제 해결을 위한 Redis INCR 로직은 구현되어 있고, FE 개발 완료 후 다시 활성화할 예정이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt:33-46
Timestamp: 2025-07-27T12:26:47.814Z
Learning: Gradmeet-BE 프로젝트에서 하루 최대 2회 사용 제한이 있는 기능의 경우, unique constraint나 낙관적 락 대신 DB 조회 기반 검증 방식을 선호한다. 분산 락은 나중에 최적화가 필요할 때 고려한다.
Learnt from: chock-cho
PR: YAPP-Github/Gradmeet-BE#148
File: domain/src/main/kotlin/com/dobby/enums/University.kt:288-296
Timestamp: 2025-07-12T18:17:29.186Z
Learning: 사용자 chock-cho는 자동완성 API의 서버 부하를 줄이기 위해 FE와 합의하에 디바운싱 처리로 설계했고, 사용자가 입력하지 않은 상태에서의 의도치 않은 API 호출을 방지하기 위해 @NotBlank 어노테이션을 적용했다. 사용자 행동 패턴까지 고려한 세심한 BE 설계 접근을 보여준다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
💡 작업 내용
기존 문제

여러 스레드가 동시에 요청을 보내면 5개의 요청 모두가 처리되어 동시성 문제가 발생함을 확인함
DB 조회 대신 Redis의 INCR 연산을 사용하여 처리 후 테스트 진행
✅ 셀프 체크리스트
🙋🏻 확인해주세요
🔗 Jira 티켓
https://yappsocks.atlassian.net/browse/YS-532
Summary by CodeRabbit
신규 기능
버그 수정
테스트
기타