⚙️ 어떤 버그인가요?
문제 등록 요청 하나가 순간적으로 DB 커넥션을 2개 요구합니다. 커넥션 풀 크기는 설정이 없어 Hikari 기본값인 10이라, 동시 등록이 늘어나면 풀이 고갈되고 요청이 connectionTimeout 기본값인 30초를 꽉 채운 뒤에야 처리됩니다.
원인은 커밋 이후에 도는 이벤트 리스너입니다.
// StudyRoomFeedService.java:100
@TransactionalEventListener(phase = AFTER_COMMIT)
@Transactional(propagation = REQUIRES_NEW)
public void handleActivity(StudyRoomActivityEvent event) { ... }
registerProblem 의 트랜잭션이 커밋된 직후, 그 커넥션이 아직 반납되기 전에 리스너가 REQUIRES_NEW 로 두 번째 커넥션을 요청합니다. 같은 구조가 두 곳에 있습니다.
studyroom/service/StudyRoomFeedService.java:100 — 스터디룸 활동 피드
problem/reminder/ProblemReviewReminderService.java:39 — 복습 리마인더 생성
더 나쁜 점은 handleActivity 가 예외를 catch (Exception e) { log.error(...) } 로 삼킨다는 것입니다. 커넥션을 못 얻어 실패해도 사용자에게는 정상 응답이 나가고, 피드 생성과 챌린지 완료 판정이 조용히 유실됩니다.
🔎 어떤 상황에서 발생한 버그인가요?
Given 같은 폴더에 문제를 등록하는 요청이 동시에 8건 들어오고
When 그 폴더를 삭제하는 요청이 겹치면
Then 등록 요청 6~7건이 각각 30.2초 걸린 뒤에야 완료됩니다.
스레드 덤프로 확인한 대기 지점은 행 잠금이 아니라 HikariPool.getConnection 이었습니다. 무관한 폴더를 삭제하면 200ms 로 끝나므로, 같은 자원을 두고 경합할 때 드러납니다.
동시 등록이 10건을 넘으면 폴더 삭제가 없어도 같은 고갈이 발생할 수 있습니다.
✅ 예상 결과
- 문제 등록 요청 하나가 커넥션을 두 개 잡지 않아야 합니다.
- 부가 작업(피드·리마인더)이 실패하더라도 조용히 사라지지 않고 관측 가능해야 합니다.
💡 검토할 만한 방향
세 가지가 있고 서로 배타적이지 않습니다.
- 커넥션 풀 크기 명시 — 가장 빠른 완화책이지만 근본 해결은 아닙니다. 애플리케이션이 커넥션을 2배로 쓴다는 사실은 그대로 남습니다.
- 부가 작업을 비동기로 분리 — 이미 RabbitMQ 를 쓰고 있으므로 피드 생성을 메시지로 넘기면 요청 스레드가 커넥션을 두 번 잡지 않습니다.
AFTER_COMMIT + REQUIRES_NEW 조합 재검토 — 원 트랜잭션에 합류시키면 커넥션은 하나로 끝나지만, 피드 생성 실패가 문제 등록을 롤백시키게 됩니다. 어느 쪽이 맞는지는 정책 판단입니다.
📌 발견 경위
PR #231 (테스트 전면 개선) 에서 동시성 경합 테스트를 작성하다 발견했습니다. 3회 연속 재현됐습니다.
인프라 설정과 트랜잭션 설계에 동시에 걸쳐 있어 해당 PR 에서는 수정하지 않고 이슈로 남깁니다.
⚙️ 어떤 버그인가요?
문제 등록 요청 하나가 순간적으로 DB 커넥션을 2개 요구합니다. 커넥션 풀 크기는 설정이 없어 Hikari 기본값인 10이라, 동시 등록이 늘어나면 풀이 고갈되고 요청이
connectionTimeout기본값인 30초를 꽉 채운 뒤에야 처리됩니다.원인은 커밋 이후에 도는 이벤트 리스너입니다.
registerProblem의 트랜잭션이 커밋된 직후, 그 커넥션이 아직 반납되기 전에 리스너가REQUIRES_NEW로 두 번째 커넥션을 요청합니다. 같은 구조가 두 곳에 있습니다.studyroom/service/StudyRoomFeedService.java:100— 스터디룸 활동 피드problem/reminder/ProblemReviewReminderService.java:39— 복습 리마인더 생성더 나쁜 점은
handleActivity가 예외를catch (Exception e) { log.error(...) }로 삼킨다는 것입니다. 커넥션을 못 얻어 실패해도 사용자에게는 정상 응답이 나가고, 피드 생성과 챌린지 완료 판정이 조용히 유실됩니다.🔎 어떤 상황에서 발생한 버그인가요?
Given 같은 폴더에 문제를 등록하는 요청이 동시에 8건 들어오고
When 그 폴더를 삭제하는 요청이 겹치면
Then 등록 요청 6~7건이 각각 30.2초 걸린 뒤에야 완료됩니다.
스레드 덤프로 확인한 대기 지점은 행 잠금이 아니라
HikariPool.getConnection이었습니다. 무관한 폴더를 삭제하면 200ms 로 끝나므로, 같은 자원을 두고 경합할 때 드러납니다.동시 등록이 10건을 넘으면 폴더 삭제가 없어도 같은 고갈이 발생할 수 있습니다.
✅ 예상 결과
💡 검토할 만한 방향
세 가지가 있고 서로 배타적이지 않습니다.
AFTER_COMMIT+REQUIRES_NEW조합 재검토 — 원 트랜잭션에 합류시키면 커넥션은 하나로 끝나지만, 피드 생성 실패가 문제 등록을 롤백시키게 됩니다. 어느 쪽이 맞는지는 정책 판단입니다.📌 발견 경위
PR #231 (테스트 전면 개선) 에서 동시성 경합 테스트를 작성하다 발견했습니다. 3회 연속 재현됐습니다.
인프라 설정과 트랜잭션 설계에 동시에 걸쳐 있어 해당 PR 에서는 수정하지 않고 이슈로 남깁니다.