[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개의 댓글

관련 채용 정보