차단은 한쪽이 했는데 매칭은 양쪽을 막아야 했다

차단 목록은 보통 “내가 차단한 사람”으로 저장한다. 그런데 매칭에서 그 목록만 보면 이상한 일이 생긴다. A가 B를 차단했어도, B가 먼저 상대를 찾으면 A가 후보로 나올 수 있다.

차단이 선착순 혜택이 되면 곤란하다. VoiceLink의 Java 17·Spring Boot 3.2.5 코드를 보면 누가 요청했든 한쪽이 거부한 만남은 제외한다. 이 규칙 때문에 저장 방향과 조회 방향이 달라진다.

저장은 한 방향, 매칭은 양방향

A → B라는 관계를 저장하면 누가 차단했고 누가 해제할 수 있는지 남는다. 매칭할 때는 요청자 순서에 기대지 않고 양쪽 관계를 확인해야 한다. canMatch의 null·자기 자신 검사를 뺀 부분이다.

return !userBlockRepository.existsByBlockerIdAndBlockedId(
            userA.getId(), userB.getId())
        && !userBlockRepository.existsByBlockerIdAndBlockedId(
            userB.getId(), userA.getId());

B의 목록이 비어 있어도 A가 B를 차단했다면 연결하지 않는다. canMatchRejectsEitherDirectionBlock 테스트도 요청자 방향은 false, 역방향은 true로 주고 거절되는지 검사한다.

해제도 방향을 지켜야 한다. A가 차단을 풀어도 B → A 관계가 남아 있다면 아직 매칭할 수 없다. 내 차단을 해제하는 것이 상대의 거부까지 없애는 것은 아니다.

여기서 후보를 그냥 버리면 또 다른 문제가 생긴다. B는 A를 못 만날 뿐, 다른 사람과 통화할 기회까지 잃은 것은 아니다. 제외된 후보와 요청자는 다시 기다리는 경로로 이어진다. 후보를 꺼내는 데 성공했다는 것과 만남을 허용한다는 것은 별개의 판단이다.

차단 대상은 서버가 통화 기록에서 찾는다

그렇다면 API에 상대 ID만 보내면 될까? 이 기능은 통화 기록에서 상대를 차단하거나 신고하는 기능이다. 요청자가 아무 사용자를 지정하게 두기보다, 실제로 참여한 통화에서 상대를 찾아야 규칙이 맞는다.

서버는 통화 기록에서 요청자가 참가자인지 확인한 뒤 상대를 찾는다. A와 B의 기록 번호를 C가 알아냈다는 설명용 상황에서도 C는 이 검사에서 거절된다. 기록 번호를 아는 것과 그 통화의 당사자인 것은 다르다.

차단과 신고의 중복 기준도 같은 방식으로 정해진다.

요청 기준이 되는 대상 같은 요청이 다시 오면
차단 요청자 → 통화 상대 이미 있으면 종료
차단 해제 요청자 → 통화 상대 해당 방향의 관계 삭제
신고 신고자와 통화 기록 이미 신고한 통화로 거절
매칭 가능 여부 두 사용자 사이 양방향 차단 한쪽이라도 있으면 제외

같은 사람을 두 번 차단한다고 관계 두 개가 필요하지는 않다. blockByCallHistoryIsIdempotent 테스트도 이미 차단한 관계라면 저장소의 save를 호출하지 않는지 확인한다. 반면 서로 다른 통화의 신고를 상대가 같다는 이유로 합치면 사건 하나가 사라진다. 접수된 신고는 운영자 검토용이며 자동 제재로 이어지지는 않는다.

조회 순간과 저장 순간 사이

두 요청이 동시에 “아직 차단하지 않았다”고 읽을 수 있다. 엔티티와 수동 DDL은 이를 막도록 user_blocks에 (blocker_user_id, blocked_user_id) 고유 제약을 정의한다. 신고 쪽은 (reporter_user_id, call_history_id)다. 서비스의 중복 기준을 실제 DB 스키마에도 적용해야 한다.

서비스는 저장 중 DataIntegrityViolationException을 잡아 중복 차단을 성공으로 취급한다. 다만 Mockito 테스트는 실제 트랜잭션을 열지 않는다. 중복 저장이 flush나 커밋에서 실패할 때도 응답이 성공하는지는 이 테스트만으로 증명되지 않는다.

매칭과 차단이 동시에 진행되면 더 까다롭다. 차단 없음 조회 → 차단 저장 → 통화 확정 순서라면 양방향 조회는 맞게 했어도 차단 뒤에 만남이 확정될 수 있다.

“한쪽이 차단하면 제외”와 “차단 저장 이후에는 진행 중인 매칭도 확정하지 않기”는 서로 다른 요구다. 양방향 조회는 누구의 차단을 볼지 해결하지만, 동시에 확정되는 매칭까지 막는 답은 아니다.

참고 자료

근거: 2026-10-12 VoiceLink 37109d5의 차단·신고 서비스와 엔티티, 후보 복구 코드, docs/user-safety.md를 대조했다. 10월 10일 1704b46에서 안전 서비스 9개·컨트롤러 4개 테스트가 통과했다. 동시 저장·매칭 경쟁의 실제 DB 시험은 포함하지 않았다.

Leave a Comment