본문 바로가기
카테고리 없음

[Spring] 대용량 테스트(offset, index 적용)

by 어떻게말이름이히힝 2025. 1. 23.

jpa 방식으로 saveAll 했다가 강제종료했다.

알고 보니 bulk insert로 더 괜찮게 할 수 있다는 것을 발견했다.

 

처음에는 과제의 의미를 제대로 이해하지 못해 이것저것 시도해보다가

init()으로 초기 데이터를 셋팅해주었다. 

셋팅 이후에는 주석처리 해놓았다.

데이터 셋팅

@Component
@RequiredArgsConstructor
public class InitData {

  JdbcTemplate jdbcTemplate;

  @PostConstruct
  @Transactional
  public void init() {
    final int BATCH_SIZE = 10_000;
    List<User> userList = new ArrayList<>();

    String sql = "INSERT INTO users (email, password, username, user_role) " +
        "VALUES (?, ?, ?, ?)";

    Faker faker = new Faker();
    for(int i = 0; i<1_000_000;i++){
      String fullName = faker.name().firstName();
      String email = fullName.toLowerCase()+ i + "@gmail.com";
      String password = "1234";
      UserRole userRole = UserRole.valueOf("USER");
      userList.add(new User(email,password,fullName,userRole));
    }

    jdbcTemplate.batchUpdate(sql,
        userList,
        BATCH_SIZE,
        (PreparedStatement ps, User user) -> {
          ps.setString(1, user.getEmail());
          ps.setString(2, user.getPassword());
          ps.setString(3,user.getUsername());
          ps.setString(4, user.getUserRole().toString());
        });

  }

}

 

localhost:8080/users/all?username=william 으로 get 요청

보통 350 - 400ms 정도 나오는 것 같다

혹시나해서 페이지 사이즈를 키워봣는데(30으로, 디폴트값은 10) 엇비슷하게 나왔다.

public Page<UserResponse> getAllUser(int page, int size, String username) {
      Pageable pageable = PageRequest.of(page - 1, size);
      Page<UserResponse> users = userRepository.findAllByUsername(pageable,username);
      return users;
  }

 

@Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
      + " FROM User u WHERE (:username IS NULL OR u.username = :username) ORDER BY u.id ASC")
  Page<UserResponse> findAllByUsername(Pageable pageable, String username);

 

 

 


 

처음에 짰던 코드

  @Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
      + " FROM User u WHERE (:username IS NULL OR u.username = :username)"
      + " ORDER BY u.id ASC")
  Page<UserResponse> findAllByUsername(Pageable pageable, String username);

편차가 있긴한데 350ms - 400ms 정도 나옴

 

두번째 짠 코드(no-offset만 적용)

@Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
      + " FROM User u"
      + " WHERE (:username IS NULL OR u.username = :username)"
      + " AND (:lastUserId IS NULL OR u.id < :lastUserId)"
      + " ORDER BY u.id ASC"
      + " LIMIT :size")
  List<UserResponse> findAllByUsername(Long lastUserId, int size, String username);

사이즈 기본 5개 > 13ms

사이즈 300개 > 340ms

사이즈 700개 > 482ms

 

세번째 짠 코드(인덱싱 적용)

@Table(name = "users", 
    indexes = @Index(name = "idx_user_username"
        , columnList = "username"))

이 이상 빨리나올 수는 없겠는데…

네번째 시도(인덱싱+nooffset)

아니 원래 이정도로 드라마틱하게 차이나나????

배수가 내가 들었던거랑 다른데 ㅠㅠ?….

 

 

 

닉네임 같은 사람의 표본이 너무 작다고 생각이 들었고 데이터베이스에서 

세팅 되어있는 값 중에 가장 겹치는 닉네임이 뭔지 검색했다.

select username, count(username) from users group by username order by count(username)

로 데이터베이스에 쿼리를 날렸고 Scottie라는 이름이 619명 있다는 것을 확인했고 해당 이름으로 조회 테스트를 진행했다.

 

1. 인덱싱 없이

localhost:8080/users/all?username=Scottie

처음 조회 448ms - 두번째 조회 332ms -세번째 조회 350ms

 

 

2. 인덱싱 추가

localhost:8080/users/all?username=Scottie

처음 조회 - 32ms 두번째 조회6ms -세번째 조회 5ms