채팅방 입장메세지가 안 보내지는 문제

김영민·2022년 4월 14일
0
post-thumbnail

1. 문제 상황

  • database를 H2에서 MySql로 변경을 하자 채팅방 입장할 때 입장메세지가 안 보내지는 현상이 생김
  • 그런데 간헐적으로 (약 5% 확률로) 입장메세지가 나타나기도 함

2. 원인 분석

  • 처음에는 db문제 인줄 알았는데 간헐적으로는 동작하는 것으로 보아 동기/비동기 문제로 접근
  • EC2 instance가 t2.medium 일 때보다 t2.micro 일 때 더 빈번하게 입장메세지가 나타나는 것으로 보아 server와 client의 컴퓨터 성능도 어느정도 영향을 미치는 것으로 판단

3. 문제 확인

  • t2.micro 일 때 더 빈번한게 입장메세지가 나타난 것으로 보아 server 쪽 속도를 인위적으로 늦춤
public void sendMessage(String publishMessage) {	
	if (publishMessage.startsWith("ENTER", 9)) {
    	Thread.sleep(3000);
		ChatMessageEnterResponseDto chatMessageEnterResponseDto = objectMapper.readValue(publishMessage, ChatMessageEnterResponseDto.class);
		messagingTemplate.convertAndSend("/sub/chat/" + chatMessageEnterResponseDto.getRoomId(), chatMessageEnterResponseDto);
	}
}    

Thread.sleep(3000); : 구독 요청 후 3초 후에 입장메세지를 보내게끔 설정

  • 해당 설정 후 문제 없이 입장메세지가 전송된 것으로 보아 client에서 구독을 완료하기도 전에 server에서 입장메세지를 전송해 버그가 발생한 것으로 문제 확인

  • 하지만 인위적으로 delay를 주는 방식은 user의 컴퓨터 사양이 어떠냐에 따라 작동이 안될 수도 있기 때문에 근본적인 해결방안은 아님

4. 조치 방안

  • 기존 코드 (StompHandler)
public class StompHandler implements ChannelInterceptor {
	@Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        if (StompCommand.SUBSCRIBE == accessor.getCommand()) {
            String token = accessor.getFirstNativeHeader("Authorization").substring(7);
            Long userId = Long.parseLong(jwtDecoder.decodeUserId(token));
            User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException("해당 유저가 존재하지 않습니다."));

            String roomId = getRoomId(Optional.ofNullable((String) message.getHeaders()
                .get("simpDestination")).orElse("InvalidRoomId"));

            chatMessageService.accessChatMessage(ChatMessageRequestDto.builder()
                .type(ChatMessage.MessageType.Enter).roomId(roomId).userId(userId).build());
		}
	}
}
            

ChannelInterceptor : 해당 인터셉터는 user 정보를 불러온 후 user를 인증하는 역할을 함
StompCommand.SUBSCRIBE : 따라서 client에서 server로 구독 요청을 보낼 때는 구독을 완료하고 보내는 것이 아니라 구독을 하겠다고 요청을 보낸 것임.
chatMessageService.accessChatMessage : 그런데 해당 로직에서 구독을 완료 했는지 확인 없이 바로 입장메세지 전송 로직으로 넘어갔었음

  • 개선 코드(ChatMessageController)
@MessageMapping("/chat/enter")
@ApiOperation(value = "채팅방 입장")
public void enterMessage(@RequestBody ChatMessageRequestDto chatMessageRequestDto,
                         @Header("Authorization") String rawToken) {
	String token = rawToken.substring(7);
	Long userId = Long.parseLong(jwtDecoder.decodeUserId(token));
	chatMessageRequestDto.setUserId(userId);
	chatMessageService.accessChatMessage(chatMessageRequestDto);
}

@MessageMapping("/chat/enter") : pub destination을 하나 더 설정하여 client가 구독이 완료 되면 해당 destination으로 입장메세지 전송 요청(accessChatMessage())을 보내게끔 개선시킴

profile
Macro Developer

0개의 댓글