Skip to content

fix: 관리자 차단 해제(unbanUser) 시 동시 요청으로 활성 차단이 중복되면 크래시 발생 가능 #847

Description

@Hexeong

어떤 버그인가요

AdminUserBanService.unbanUser()가 사용하는 findActiveBan(long userId)UserBanRepository.findByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter(...)(Optional<UserBan> 반환, 단일 결과를 기대)를 호출합니다.

private UserBan findActiveBan(long userId) {
    return userBanRepository
            .findByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter(userId, ZonedDateTime.now(UTC))
            .orElseThrow(() -> new CustomException(ErrorCode.NOT_BANNED_USER));
}

user_ban 테이블에는 "유저당 활성 차단 1건" 제약이 DB 레벨에 없고, 차단 생성 시 중복 방지 로직도 check-then-act 방식입니다.

private void validateNotAlreadyBanned(long userId) {
    if (userBanRepository.existsByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter(userId, ZonedDateTime.now(UTC))) {
        throw new CustomException(ErrorCode.ALREADY_BANNED_USER);
    }
}

두 관리자가 동시에 같은 유저를 차단하면, 두 요청 모두 existsBy... 체크를 통과한 뒤 각자 UserBan을 저장할 수 있어(트랜잭션 격리/락 없음) 같은 유저에게 활성 차단이 2건 생길 수 있습니다. 이 상태에서 관리자가 해당 유저의 차단을 해제하려고 하면 findByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter가 2건을 반환하게 되어 Spring Data가 IncorrectResultSizeDataAccessException을 던지고, 차단 해제 요청이 실패합니다.

(#845/#846 쿼리 플랜 개선 작업 중 유사한 패턴 — SiteUserFilterRepositoryImpl.searchRestrictedUsers의 배치조회에서도 같은 원인으로 크래시가 날 수 있다는 리뷰를 받아 그쪽은 이미 수정했습니다. 이 이슈는 그 수정 범위에 포함되지 않은 언밴 플로우 쪽입니다.)

재현 방법(선택)

  1. 같은 유저에 대해 거의 동시에 관리자 제재(ban) API를 2번 호출(또는 user_ban에 같은 banned_user_id로 활성 행을 2건 직접 insert)
  2. 해당 유저에 대해 차단 해제(unban) API 호출
  3. IncorrectResultSizeDataAccessException 발생, 차단 해제 실패

참고할만한 자료(선택)

  • src/main/java/com/example/solidconnection/admin/service/AdminUserBanService.java (validateNotAlreadyBanned, findActiveBan, unbanUser)
  • src/main/java/com/example/solidconnection/siteuser/repository/UserBanRepository.java
  • src/main/resources/db/migration/V40__create_user_ban_table.sql (유저별 활성 차단 유일성을 보장하는 제약 없음)
  • 관련 PR: refactor: DB Repository 계층 쿼리 성능 개선 (쿼리 구조 개선 + 인덱스 추가) #846 (같은 근본 원인의 다른 코드 경로를 리뷰 중 발견)

개선 방향 제안(참고)

  • 근본 해결: user_ban에 "유저당 활성 차단 1건" 제약을 DB 레벨에서 강제(예: 부분 유니크 인덱스/애플리케이션 락)하거나, 차단 생성 시 비관적 락/유니크 제약 위반 처리로 race를 막기
  • 임시 완화: findActiveBanOptional<UserBan> 대신 List<UserBan>으로 조회해 여러 건이 있어도 안전하게 처리(예: 모두 해제하거나 최신 건만 해제)하도록 방어적으로 수정

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    버그Something isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions