@Repository
@RequiredArgsConstructor
public class ChatMessageQueryRepository {
// jpa의 EntityManager 의존 주입
private final EntityManager em;
// 특정 채팅방에 대한 채팅 메시지를 no-offset 방식으로 조회하는 메서드
public Page<ChatMessage> findChatMessagesByChatRoomIdUsingNoOffset(Pageable pageable, Long chatRoomId, @Nullable Long index) {
// JPAQueryFactory 생성 => QueryDSL 쿼리 작성 가능
JPAQueryFactory query = new JPAQueryFactory(em);
QChatRoom chatRoom = QChatRoom.chatRoom; // QChatRoom 타입 인스턴스 생성
QChatMessage chatMessage = QChatMessage.chatMessage; // QChatMessage 타입 인스턴스 생성
List<ChatMessage> results =
query.select(chatMessage)
.from(chatMessage)
.join(chatMessage.chatRoom, chatRoom)
.where(chatRoom.id.eq(chatRoomId) // 조인 조건
.and(ltChatMessageId(index))) // no-offset 조건
.orderBy(chatMessage.createdAt.desc())
.limit(20)
.fetch();
return new PageImpl<>(results, pageable, results.size());
}
// 주어진 index 보다 작은 chat_message_id에 대한 조건 생성 메서드
public BooleanExpression ltChatMessageId(@Nullable Long index) {
return index == null ? null : QChatMessage.chatMessage.id.lt(index);
}
}
3-4. api 호출로 성능 개선 결과 확인
마지막 페이지 조회 기준으로 기존 방식 대비 no-offset 방식으로 약 96.0% 성능 개선 확인!
기존 조회 api 호출: 2.84s 소요
no-offset 방식으로 변경한 조회 api 호출: 85ms 소요
3-5. 테스트 코드로 성능 개선 확인
동일하게 100만개의 채팅 메세지 삽입 후 테스트 진행함
ChatMessageRepositoryTest
@SpringBootTest
class ChatMessageRepositoryTest {
@Autowired
private ChatRoomRepository chatRoomRepository;
@Autowired
private ChatMessageRepository chatMessageRepository;
@Autowired
private ChatMessageQueryRepository chatMessageQueryRepository;
@Test
@DisplayName("기존의 방식대로 100만개 중 마지막 페이지를 조회하면 1초 이상이 걸린다.")
public void findChatMessagesLegacy() throws Exception {
//given
int limit = 20;
int offset = 1089900;
PageRequest pageRequest = PageRequest.of(offset / limit, limit,
Sort.by("createdAt").descending());
//when
long startTime = System.nanoTime(); // 시작 시간 기록
Page<ChatMessage> chatMessagesLegacy = chatMessageRepository.findChatMessagesByChatRoomId(pageRequest, 1L);
long endTime = System.nanoTime(); // 종료 시간 기록
System.out.println("성능 개선 전 실행 시간: " + (double) (endTime - startTime)/1000000000 + "s");
//then
assertThat(chatMessagesLegacy).hasSize(20);
List<ChatMessage> legacyMessages = chatMessagesLegacy.getContent();
for (ChatMessage legacyMessage : legacyMessages) {
System.out.println("legacyMessage.getContent() = " + legacyMessage.getContent());
}
}
@Test
@DisplayName("개선한 방식대로 100만개 중 마지막 페이지를 조회하면 1초 미만 (ms단위)으로 걸린다.")
public void findChatMessagesNoOffset() throws Exception {
//given
int limit = 20;
Pageable pageable = Pageable.ofSize(limit);
//when
long startTime = System.nanoTime(); // 시작 시간 기록
Page<ChatMessage> chatMessagesNoOffset = chatMessageQueryRepository.findChatMessagesByChatRoomIdUsingNoOffset(
pageable, 1L, 101L);
long endTime = System.nanoTime(); // 종료 시간 기록
System.out.println("성능 개선 후 실행 시간: " + (double) (endTime - startTime)/1000000000 + "s");
//then
assertThat(chatMessagesNoOffset).hasSize(20);
// 추가: 메시지 내용 비교
List<ChatMessage> noOffsetMessages = chatMessagesNoOffset.getContent();
for (ChatMessage noOffsetMessage : noOffsetMessages) {
System.out.println("noOffsetMessage.getContent() = " + noOffsetMessage.getContent());
}
}
}