Skip to content

[bug] 문제 등록이 커넥션 2개를 점유해 동시 요청 시 커넥션 풀이 고갈된다 #232

Description

@KiSeungMin

⚙️ 어떤 버그인가요?

문제 등록 요청 하나가 순간적으로 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건을 넘으면 폴더 삭제가 없어도 같은 고갈이 발생할 수 있습니다.

✅ 예상 결과

  • 문제 등록 요청 하나가 커넥션을 두 개 잡지 않아야 합니다.
  • 부가 작업(피드·리마인더)이 실패하더라도 조용히 사라지지 않고 관측 가능해야 합니다.

💡 검토할 만한 방향

세 가지가 있고 서로 배타적이지 않습니다.

  1. 커넥션 풀 크기 명시 — 가장 빠른 완화책이지만 근본 해결은 아닙니다. 애플리케이션이 커넥션을 2배로 쓴다는 사실은 그대로 남습니다.
  2. 부가 작업을 비동기로 분리 — 이미 RabbitMQ 를 쓰고 있으므로 피드 생성을 메시지로 넘기면 요청 스레드가 커넥션을 두 번 잡지 않습니다.
  3. AFTER_COMMIT + REQUIRES_NEW 조합 재검토 — 원 트랜잭션에 합류시키면 커넥션은 하나로 끝나지만, 피드 생성 실패가 문제 등록을 롤백시키게 됩니다. 어느 쪽이 맞는지는 정책 판단입니다.

📌 발견 경위

PR #231 (테스트 전면 개선) 에서 동시성 경합 테스트를 작성하다 발견했습니다. 3회 연속 재현됐습니다.

인프라 설정과 트랜잭션 설계에 동시에 걸쳐 있어 해당 PR 에서는 수정하지 않고 이슈로 남깁니다.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

fix버그, 오류 수정

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions