트러블 슈팅 - IN 연산자를 활용하여 채팅 목록 조회 92% 성능 최적화

2024. 7. 2. 15:57·프로젝트/FitTrip
목차
  1. 서론
  2. 수정 전 로직
  3. 문제 원인
  4. 수정 후 로직
  5. 아쉬운 점

서론

채팅에는 이모지와 댓글이 달려 있을 수 있습니다.

그래서 채팅 목록을 조회하면 해당 채팅에 달려 있는 이모지와 댓글의 개수를 채팅 마다마다 달아서 프론트에 응답을 해야 했습니다. MongoDB 같은 경우는 조인 연산이 없어 MySQL처럼 table 조회 시 다른 table을 조인해서 데이터를 가져오는 방식으로 생각할 수 없었습니다.

처음 생각 한 수정 전 로직에서는 채팅 목록을 불러온 후 채팅 목록만큼 반복문을 돌면서 채팅에 달려있는 이모지와 댓글의 개수만큼 쿼리가 날아가는 문제가 발생했습니다.

그래서 이러한 쿼리 개수를 줄이기 위해 수정한 로직에 대해 글을 작성하려 합니다.

수정 전 로직

특정 channelId에 해당하는 채팅 목록을 불러오고 해당 값들을 DTO로 변환

불러온 채팅 개수만큼 반복문을 돌면서 채팅 id로 각 채팅에 달린 이모지와 댓글 개수를 조회

이러다 보니 채팅 목록 개수만큼 이모지와 댓글 개수를 조회하는 쿼리가 발생
채팅 하나에 이모지와 댓글 개수를 조회하는 쿼리가 발생해서 불러오는 채팅 목록이 N개면 총 2N + 1개의 쿼리문이 발생

public Page<CommunityMessageDto> getMessages(Long channelId, int page, int size) {
    Page<CommunityMessageDto> messageDtos = messagesToMessageDtos("message", channelId, page, size);
    for (CommunityMessageDto messageDto : messageDtos) {
        List<EmojiDto> emojiDtos = emojisToEmojiDtos(messageDto.getMessageId());
        messageDto.setEmojis(emojiDtos);
        messageDto.setCount(messageRepository.countByParentId(messageDto.getMessageId()));
    }
    return messageDtos;
}

private Page<CommunityMessageDto> messagesToMessageDtos(String type, Long id, int page, int size) {
   Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
   Page<CommunityMessage> messages = null;
   if (type.equals("message")) {
       messages = messageRepository.findByChannelIdAndDelCheckAndParentId(id, pageable);
    } else if (type.equals("comment")) {
        messages = messageRepository.findByParentIdAndDelCheckFalse(id, pageable);
    }

    return (messages != null) ? messages.map(CommunityMessageDto::new) : Page.empty(pageable);
}

private List<EmojiDto> emojisToEmojiDtos(Long messageId) {
    List<Emoji> emojis = emojiRepository.findAllByCommunityMessageId(messageId);
    return emojis.stream()
            .map(EmojiDto::new)
            .toList();
}

발생 쿼리

조회하는 채팅 목록 개수가 20개이면 총 41개의 쿼리문이 발생했습니다.

2024-07-02T17:05:00.000+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "channelId" : 4, "delCheck" : false, "parentId" : 0} fields: Document{{}} for class: class harmony.chatservice.domain.CommunityMessage in collection: communityMessages
2024-07-02T17:05:00.012+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 321} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.015+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 321} in collection: communityMessages
2024-07-02T17:05:00.019+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 320} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.021+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 320} in collection: communityMessages
2024-07-02T17:05:00.025+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 319} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.027+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 319} in collection: communityMessages
2024-07-02T17:05:00.030+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 318} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.033+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 318} in collection: communityMessages
2024-07-02T17:05:00.037+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 313} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.039+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 313} in collection: communityMessages
2024-07-02T17:05:00.043+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 312} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.045+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 312} in collection: communityMessages
2024-07-02T17:05:00.048+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 311} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.051+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 311} in collection: communityMessages
2024-07-02T17:05:00.054+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 310} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.056+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 310} in collection: communityMessages
2024-07-02T17:05:00.060+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 309} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.062+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 309} in collection: communityMessages
2024-07-02T17:05:00.065+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 308} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.069+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 308} in collection: communityMessages
2024-07-02T17:05:00.072+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 307} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.074+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 307} in collection: communityMessages
2024-07-02T17:05:00.078+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 306} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.081+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 306} in collection: communityMessages
2024-07-02T17:05:00.084+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 305} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.086+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 305} in collection: communityMessages
2024-07-02T17:05:00.090+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 304} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.092+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 304} in collection: communityMessages
2024-07-02T17:05:00.095+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 303} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.098+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 303} in collection: communityMessages
2024-07-02T17:05:00.101+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 302} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.104+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 302} in collection: communityMessages
2024-07-02T17:05:00.106+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 301} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.109+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 301} in collection: communityMessages
2024-07-02T17:05:00.113+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 300} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.115+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 300} in collection: communityMessages
2024-07-02T17:05:00.118+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 299} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.121+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 299} in collection: communityMessages
2024-07-02T17:05:00.123+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : find using query: { "communityMessageId" : 298} fields: Document{{}} for class: class harmony.chatservice.domain.Emoji in collection: emojis
2024-07-02T17:05:00.126+09:00 DEBUG 18288 --- [nio-7000-exec-1] o.s.data.mongodb.core.MongoTemplate      : Executing count: { "parentId" : 298} in collection: communityMessages

문제 원인

현재 쿼리가 많이 발생하는 이유는 각 채팅마다 이모지와 댓글 개수를 조회하는 쿼리가 발생하기 때문입니다.

그래서 이러한 쿼리를 줄이기 위해서 한 번에 이모지와 댓글 개수를 조회하는 방식에 대해서 생각을 했습니다.

이모지와 댓글 개수를 조회할 때 필요한 건 채팅 id 값이고 그러면 이 id 값들을 한 번에 보내서 IN 연산을 사용하여 이모지와 댓글 개수를 한 번에 조회하는 방식으로 로직을 수정했습니다. 

수정 후 로직

특정 channelId에 해당하는 채팅 메시지들을 불러오고 해당 값들을 DTO로 변환

getMessageIds() 메서드를 통해 채팅 목록들로부터 채팅의 id 값들을 뽑아서 List에 담음

 

이모지를 한 번에 조회하기 위해 IN 연산을 사용하여  id값들이 담긴 List를 보내 한번에 채팅에 달린 이모지들을 조회하는 메서드 구현

  • getEmojisForMessages(messageIds);

댓글 개수도 똑같이 IN 연산을 사용하여 채팅에 달린 댓글 개수들을 조회하는 메서드 구현

  • getCommentCountForMessages(messageIds);

채팅 목록만큼 반복하면서 채팅에 이모지와 댓글 개수를 달아서 프론트에 반환

    @Override
    public Page<ServerMessageDto> getMessages(Long channelId, int page, int size) {
        Page<ServerMessageDto> messageDtos = messagesToMessageDtos("message", channelId, page, size);
        List<Long> messageIds = getMessageIds(messageDtos);

        Map<Long, List<EmojiDto>> emojiMap = getEmojisForMessages(messageIds);
        Map<Long, Long> commentCount = getCommentCountForMessages(messageIds);
        for (ServerMessageDto messageDto : messageDtos) {
            messageDto.setEmojis(emojiMap.getOrDefault(messageDto.getMessageId(), Collections.emptyList()));
            messageDto.setCount(commentCount.getOrDefault(messageDto.getMessageId(), 0L));
        }

        return messageDtos;
    }

    private Page<ServerMessageDto> messagesToMessageDtos(String type, Long id, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        Page<ServerMessage> messages = null;
        if (type.equals("message")) {
            messages = messageRepository.findByChannelIdAndIsDeletedAndParentId(id, pageable);
        } else if (type.equals("comment")) {
            messages = messageRepository.findByParentIdAndIsDeleted(id, pageable);
        }

        return (messages != null) ? messages.map(ServerMessageDto::from) : Page.empty(pageable);
    }

    private Map<Long, List<EmojiDto>> getEmojisForMessages(List<Long> messageIds) {
        List<Emoji> emojis = emojiRepository.findEmojisByServerMessageIds(messageIds);
        Map<Long, List<EmojiDto>> emojiMap = new HashMap<>();

        for (Long messageId : messageIds) {
            List<EmojiDto> emojiDtos = new ArrayList<>();
            for (Emoji emoji : emojis) {
                if (messageId.equals(emoji.getServerMessageId())) {
                    EmojiDto emojiDto = EmojiDto.from(emoji);
                    emojiDtos.add(emojiDto);
                }
            }
            emojiMap.put(messageId, emojiDtos);
        }
        return emojiMap;
    }

    private Map<Long, Long> getCommentCountForMessages(List<Long> messageIds) {
        List<ServerMessage> messages = messageRepository.findCommentCountByParentIdsAndIsDeleted(messageIds);
        Map<Long, Long> commentCount = new HashMap<>();

        for (Long messageId : messageIds) {
            long count = 0L;
            for (ServerMessage message : messages) {
                if (message.getParentId().equals(messageId)) {
                    count += 1;
                }
            }
            commentCount.put(messageId, count);
        }

        return commentCount;
    }

    private List<Long> getMessageIds(Page<ServerMessageDto> messageDtos) {
        return messageDtos.getContent().stream()
                .map(ServerMessageDto::getMessageId)
                .collect(Collectors.toList());
    }

findCommentCountByParentIdsAndIsDeleted

  • @Query를 사용하여 Spring Data MongoDB의 커스텀 쿼리를 사용한 메서드입니다.
  • $in 연산자를 통해 parentIds 배열 안에 있는 모든 값을 검색하여 채팅 목록에 대한 댓글 수를 조회합니다.
@Repository
public interface ServerMessageRepository extends MongoRepository<ServerMessage, Long> {

    @Query(value = "{ 'parentId': { $in: ?0 }, 'isDeleted': false }")
    List<ServerMessage> findCommentCountByParentIdsAndIsDeleted(List<Long> parentIds);
}

findEmojisByServerMessageIds

  • @Query를 사용하여 Spring Data MongoDB의 커스텀 쿼리를 사용한 메서드입니다.
  • $in 연산자를 통해 messageIds 배열 안에 있는 모든 값을 검색하여 채팅 목록에 대한 이모지를 조회합니다.
@Repository
public interface EmojiRepository extends MongoRepository<Emoji, Long> {

    @Query("{'serverMessageId': {$in: ?0}}")
    List<Emoji> findEmojisByServerMessageIds(List<Long> messageIds);
}

발생 쿼리

조회하는 채팅 목록 개수에 상관없이 총 3개의 쿼리문이 발생했습니다.

  • 채팅 목록 조회
  • 채팅에 달린 이모지들 조회
  • 채팅에 달린 댓글 개수들 조회
2024-07-02T19:32:10.539+09:00 DEBUG 62488 --- [chat-service] [nio-7070-exec-2] o.s.data.mongodb.core.MongoTemplate      : find using query: { "channelId" : 1, "isDeleted" : false, "parentId" : 0} fields: Document{{}} for class: class capstone.chatservice.domain.server.domain.ServerMessage in collection: serverMessages
2024-07-02T19:32:10.685+09:00 DEBUG 62488 --- [chat-service] [nio-7070-exec-2] o.s.data.mongodb.core.MongoTemplate      : find using query: { "serverMessageId" : { "$in" : [58, 40, 39, 38, 37, 36, 35, 34, 33, 32]}} fields: Document{{}} for class: class capstone.chatservice.domain.emoji.domain.Emoji in collection: emojis
2024-07-02T19:32:10.699+09:00 DEBUG 62488 --- [chat-service] [nio-7070-exec-2] o.s.data.mongodb.core.MongoTemplate      : find using query: { "parentId" : { "$in" : [58, 40, 39, 38, 37, 36, 35, 34, 33, 32]}, "isDeleted" : false} fields: Document{{}} for class: class capstone.chatservice.domain.server.domain.ServerMessage in collection: serverMessages

아쉬운 점

쿼리 개수는 줄였으나 비즈니스 로직단에서 조회한 채팅 목록에 채팅 별로 이모지와 댓글 개수를 달아줘야 돼서 for문 연산이 늘어나게 되었습니다.  하지만 쿼리를 DB에 날리면 DB에 있는 데이터들에서 완탐을 통해 데이터를 불러와 여러 개의 쿼리를 날리기보다는 비즈니스 로직단에서 연산이 늘어나는 게 괜찮다고 생각합니다. 그래도 늘어난 for문 연산 부분은 아쉬워서 좀 더 줄일 수 있는 방법에 대해 생각하려 합니다.

 

 

'프로젝트 > FitTrip' 카테고리의 다른 글

개발 기록 - WebSocket & STOMP 개발 이슈  (0) 2024.07.05
개발 기록 - MongoDB auto-incremented sequence 적용  (0) 2024.07.03
트러블 슈팅 - 웹소켓 연결 요청 JWT 검증 문제  (0) 2024.06.30
개발 기록 - MongoDB 트랜잭션 도입  (0) 2024.06.29
개발 기록 - 채팅 서비스 몽고DB 데이터 모델링  (1) 2024.06.28
  1. 서론
  2. 수정 전 로직
  3. 문제 원인
  4. 수정 후 로직
  5. 아쉬운 점
'프로젝트/FitTrip' 카테고리의 다른 글
  • 개발 기록 - WebSocket & STOMP 개발 이슈
  • 개발 기록 - MongoDB auto-incremented sequence 적용
  • 트러블 슈팅 - 웹소켓 연결 요청 JWT 검증 문제
  • 개발 기록 - MongoDB 트랜잭션 도입
an_jjin
an_jjin
공부한 내용을 정리하는 개발 기록 블로그
an_jjin
An Devlog
an_jjin
전체
오늘
어제
  • 분류 전체보기
    • JAVA
      • 초급
      • 중급1
      • 중급2
      • 고급1
    • Spring
      • 핵심 원리 기본
      • MVC1
      • MVC2
      • DB1
      • 락
      • 디자인 패턴
      • AOP
    • JPA
      • JPA 기본
      • JPA 활용 1
      • JPA 활용 2
      • Spring Data Jpa
      • JPA 정리
    • 프로젝트
      • Filmeet
      • FitTrip
      • Kidsping
    • CS
      • 기술 면접 대비 CS 전공 핵심요약집
    • 네트워크
      • HTTP
      • WebSocket
    • 데이터베이스
    • 운영체제
      • 뇌를 자극하는 윈도우즈 시스템 프로그래밍
    • Git
    • Kafka
    • Docker
    • [LG유플러스] 유레카
    • 회고

블로그 메뉴

  • 홈
  • 태그
  • 방명록

링크

공지사항

인기 글

태그

Websocket
자바
spring websocket
객체지향
분산락
프록시 팩토리
lg 유레카
before_commit
이벤트 응모 시스템
트랜잭션 아웃박스 패턴
db부하
선착순 응모 시스템
redis
동시성
queryplan
캐시
after_commit
transactional outbox pattern
lg 유플러스 유레카 sw 교육 후기
Transactional Outbox
도커
redlock
lg 유플러스 유레카 sw 교육
스레드
STOMP
AOP
redis 분산락
빈 후처리기
lg 유플러스 유레카
lg 유플러스 유레카 후기

최근 댓글

최근 글

hELLO· Designed By정상우.v4.6.1
an_jjin
트러블 슈팅 - IN 연산자를 활용하여 채팅 목록 조회 92% 성능 최적화

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.