[Spring] WebSocket 채팅 데이터 캐싱 전략 With Redis 2편

한호성·2022년 10월 11일
1

Websocket With Redis

목록 보기
2/2

글의목적

  1. Spring Boot stomp-websocket & spring-boot-starter-data-redis 라이브러리를 활용하여 만든 채팅이 기반이 된 글입니다.
  2. Redis 쓰기 전략 & 읽기 전략 성능 향상을 위한 Test를 기록한 글입니다.

쓰기 성능향상

문제상황

Websocket으로 들어오는 채팅내역들을 들어올 때 마다, MYSQL에 Insert 를 해야함. 계속해서 MYSQL에 Query 요청을 통해 쓰기 작업을 하는것은 비효율적이라고 판단. Redis에 caching 시킨 후 MYSQL에 일괄 저장하는 방법을 고민하기로함. (Write back 방식사용)

선택사항

  1. Spring Data JPA SaveAll() with Entity (Primary key : Generation.Type.Identity) ->Batch Insert x : 단일 쿼리 사용

  2. Spring Data JPA SaveAll() with Entity (Primary key : Generation.Type.Table) ->Batch Insert O

  3. Spring Data JDBC batchUpdate() with Entity(Primary key : Generation.Type.Identity) -> Batch Insert O

Test 환경

  • Java Version : 11
  • Spring Boot : 2.7.3
  • Test Libaray : Junit5
  • RDBS : mysql 8.0

Test Code

public class InsertEffiencyTest {

 	 @Autowired
    private ChatRepository chatRepository;
    
    @Autowired
    private ChatJdbcRepositoryImpl chatJdbcRepository;

    public class ChatSaveTest{
        @Test
        @DisplayName("채팅 저장 성능 테스트 SPRING DATA JDBC  - Batch Insert")
        void saveChatsWithJDBC(){
            long start = System.currentTimeMillis();
            chatJdbcRepository.batchInsertRoomInventories(chatList);
            log.info("elapsed time : {}", System.currentTimeMillis() - start);
        }

        @Test
        @DisplayName("채팅 저장 성능 테스트 SPRING DATA JPA - SaveAll() Generation.TYPE.IDENTITY")
        void saveChatsWithHibernate(){
            long start = System.currentTimeMillis();
            chatRepository.saveAll(chatList);
            log.info("elapsed time : {}", System.currentTimeMillis() - start);
        }

    }

}

Test 결과

Test 결과 분석

  1. Spring Data JDBC batchUpdate()
    -> 한개의 쿼리를 통해 Batch Insert 진행 (Connection 한번으로 처리되기 때문에 상당히 빠름)

  1. Spring Data JPA SaveAll() - Generation.Type.Identity
    -> Entity 식별자 생성할 때 Generation.Type IDENTITY 방식을 사용하면 batch insert를
    비활성화한다.
    그 이유로는 IDENETITY를 사용하면, 새로 할당된 식별자 값을 미리 알 수 없기 때문에, Batch insert를 사용할 때, Hibernate의 flush 방식인 Transactional Write Behind와 충돌이 발생하기 때문에

  1. Spring Data JPA SaveAll() - Generation.Type.Table
    -> 새로 할당된 식별자 값을 알기 위해서 따로 생성된 Table의 값을 한번더 찾아오고 update해주는 한개의 쿼리가 더 발생하기 때문에 Batch Insert가 가능하지만,오히려 더 느린 특성을 갖는다.

cf) 추가적으로 왜 JDBC batchupdate() 함수는 spring data jpa와 다르게 어떻게 동작하기에 Batch Insert가 가능한지에 대해서도 추가 조사가 필요해보인다..

읽기 방식 변경 및 성능향상

문제상황

채팅 데이터를 Off-Set Paging을 통해 읽어들일 때 , 새로운 채팅 데이터가 들어오면, 이전 페이지에 있던 데이터들이 밀려서 중복으로 나타나는 문제가 존재함.

변경사항

Off-set Paging -> Cursor Paging (생성일자 & 채팅방 Id를 Cursor 로 사용)

실험환경

  • Java Version : 11
  • Spring Boot : 2.7.3
  • Test Libaray : Junit5
  • RDBS : mysql 8.0

Test Code

    @Nested
    public class ChatReadTest {
        @Test
        @DisplayName("채팅 불러오기 Cursor paging")
        void readChatCursorPaging() {

            String cursor = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"));
            long start = System.currentTimeMillis();
            Slice<Chat> chatSlice = 
       		chatRepository.findByCreatedAtBeforeAndWorkSpace_IdOrderByCreatedAtDesc(
                    cursor,
                    1L,
                    PageRequest.of(0, 10));
            log.info("elapsed time : {}", System.currentTimeMillis() - start);


            log.info("chat size {}", chatSlice.getContent().size());
            for (Chat chat : chatSlice.getContent()) {
                log.info("chat id {}", chat.getId());
            }

        }

        @Test
        @DisplayName("채팅 불러오기 offset paging")
        void readChatOffsetPaging() {
            int page = 0;
            long start = System.currentTimeMillis();
            Page<Chat> pages = chatRepository.findByWorkSpace_IdOrderByCreatedAtDesc(
                    1L, 
                    PageRequest.of(page, 10));
            log.info("elapsed time : {}", System.currentTimeMillis() - start);
            for (Chat chat : pages.getContent()) {
                log.info("chat id {}", chat.getId());
            }

        }

    }

Test 결과

Test 분석

  1. 데이터 중복문제는 해결되는 것을 알 수 있었다.

  1. 기존의 Off-set Paging 보다 Cursor Paging을 통해 더 나은 데이터 조회를 하는 것을 확인할 수 있지만, 정확한 CursorPaging과 Off-set Paging을 통한 Test가 맞는지 조금 더 조사가 필요한 것으로 보임.

#cf) 기존의 확인한 래퍼런스로 나온 그래프와 조금 다른 데이터 결과치..
데이터의 양을 방대하게 진행해보지 못한것 때문인지,
실제로 효율적인 Cursor Paging을 구현한것이 맞는지 확인이 필요함

profile
개발자 지망생입니다.

0개의 댓글